diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 60a2ce19..1f2e6e13 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -73,7 +73,7 @@ jobs: needs: build if: | github.event_name == 'push' && - github.ref == 'refs/heads/main' && + (github.ref == 'refs/heads/main' || startsWith( github.ref, 'refs/heads/develop-' )) && github.repository == 'JFormDesigner/FlatLaf' steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index c4e46b71..95156014 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,6 +75,25 @@ FlatLaf Change Log - `FlatSVGUtils`: Support loading SVG from `URL` (for JPMS). (issue #325) +## 1.6.5 + +#### Fixed bugs + +- Linux: Fixed font problems when running on Oracle Java (OpenJDK is not + affected): + - oversized text if system font is "Inter" (issue #427) + - missing text if system font is "Cantarell" (on Fedora) +- MenuItem: Changed accelerator delimiter from `-` to `+`. (Windows and Linux). +- ComboBox: Fixed occasional `StackOverflowError` when modifying combo box not + on AWT thread. (issue #432) +- macOS: Fixed `NullPointerException` when using AWT component + `java.awt.Choice`. (issue #439) +- Native window decorations: Do not exit application with `UnsatisfiedLinkError` + in case that FlatLaf DLL cannot be executed because of restrictions on + temporary directory. Instead, continue with default window decorations. (issue + #436) + + ## 1.6.4 #### Fixed bugs diff --git a/build.gradle.kts b/build.gradle.kts index 5857ac40..00f33f6a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -14,7 +14,7 @@ * limitations under the License. */ -val releaseVersion = "1.6.4" +val releaseVersion = "1.6.5" val developmentVersion = "2.0-SNAPSHOT" version = if( java.lang.Boolean.getBoolean( "release" ) ) releaseVersion else developmentVersion 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 af3ef041..592d1aa2 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java @@ -52,6 +52,7 @@ import javax.swing.ImageIcon; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JFrame; +import javax.swing.JMenuBar; import javax.swing.LookAndFeel; import javax.swing.PopupFactory; import javax.swing.RootPaneContainer; @@ -1036,12 +1037,23 @@ public abstract class FlatLaf /** * Revalidate and repaint all displayable frames and dialogs. + *

+ * Useful to update UI after changing {@code TitlePane.menuBarEmbedded}. * * @since 1.1.2 */ public static void revalidateAndRepaintAllFramesAndDialogs() { for( Window w : Window.getWindows() ) { if( isDisplayableFrameOrDialog( w ) ) { + // revalidate menu bar + JMenuBar menuBar = (w instanceof JFrame) + ? ((JFrame)w).getJMenuBar() + : (w instanceof JDialog + ? ((JDialog)w).getJMenuBar() + : null); + if( menuBar != null ) + menuBar.revalidate(); + w.revalidate(); w.repaint(); } @@ -1050,6 +1062,9 @@ public abstract class FlatLaf /** * Repaint all displayable frames and dialogs. + *

+ * Useful to update UI after changing {@code TitlePane.unifiedBackground}, + * {@code MenuItem.selectionType} or {@code Component.hideMnemonics}. * * @since 1.1.2 */ diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/LinuxFontPolicy.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/LinuxFontPolicy.java index b20fba54..837f5052 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/LinuxFontPolicy.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/LinuxFontPolicy.java @@ -17,6 +17,7 @@ package com.formdev.flatlaf; import java.awt.Font; +import java.awt.FontMetrics; import java.awt.GraphicsConfiguration; import java.awt.GraphicsEnvironment; import java.awt.Toolkit; @@ -28,7 +29,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.StringTokenizer; - +import javax.swing.text.StyleContext; import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.StringUtils; import com.formdev.flatlaf.util.SystemInfo; @@ -121,14 +122,25 @@ class LinuxFontPolicy for(;;) { Font font = createFont( family, style, size, dsize ); - // if the font family does not match any font on the system, "Dialog" family is returned - if( !"Dialog".equals( font.getFamily() ) || "Dialog".equals( family ) ) + if( Font.DIALOG.equals( family ) ) return font; + // if the font family does not match any font on the system, "Dialog" family is returned + if( !Font.DIALOG.equals( font.getFamily() ) ) { + // check for font problems + // - font height much larger than expected (e.g. font Inter; Oracle Java 8) + // - character width is zero (e.g. font Cantarell; Fedora; Oracle Java 8) + FontMetrics fm = StyleContext.getDefaultStyleContext().getFontMetrics( font ); + if( fm.getHeight() > size * 2 || fm.stringWidth( "a" ) == 0 ) + return createFont( Font.DIALOG, style, size, dsize ); + + return font; + } + // find last word in family int index = family.lastIndexOf( ' ' ); if( index < 0 ) - return createFont( "Dialog", style, size, dsize ); + return createFont( Font.DIALOG, style, size, dsize ); // check whether last work contains some font weight (e.g. Ultra-Bold or Heavy) String lastWord = family.substring( index + 1 ).toLowerCase(); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatComboBoxUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatComboBoxUI.java index 37639d94..d2e160ac 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatComboBoxUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatComboBoxUI.java @@ -281,7 +281,10 @@ public class FlatComboBoxUI public void layoutContainer( Container parent ) { super.layoutContainer( parent ); - if( arrowButton != null ) { + // on macOS, a Swing combo box is used for AWT component java.awt.Choice + // and the font may be (temporary) null + + if( arrowButton != null && comboBox.getFont() != null ) { // limit button width to height of a raw combobox (without insets) FontMetrics fm = comboBox.getFontMetrics( comboBox.getFont() ); int maxButtonWidth = fm.getHeight() + scale( padding.top ) + scale( padding.bottom ); @@ -908,7 +911,9 @@ public class FlatComboBoxUI this.padding = padding; } - void install( Component c ) { + // using synchronized to avoid problems with code that modifies combo box + // (model, selection, etc) not on AWT thread (which should be not done) + synchronized void install( Component c ) { if( !(c instanceof JComponent) ) return; @@ -940,7 +945,7 @@ public class FlatComboBoxUI * there is no single place to uninstall it. * This is the reason why this method is called from various places. */ - void uninstall() { + synchronized void uninstall() { if( rendererComponent == null ) return; @@ -951,9 +956,9 @@ public class FlatComboBoxUI } @Override - public Insets getBorderInsets( Component c, Insets insets ) { + synchronized public Insets getBorderInsets( Component c, Insets insets ) { Insets padding = scale( this.padding ); - if( rendererBorder != null ) { + if( rendererBorder != null && !(rendererBorder instanceof CellPaddingBorder) ) { Insets insideInsets = rendererBorder.getBorderInsets( c ); insets.top = Math.max( padding.top, insideInsets.top ); insets.left = Math.max( padding.left, insideInsets.left ); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPopupFactory.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPopupFactory.java index 94c65fa9..a7178ea5 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPopupFactory.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPopupFactory.java @@ -121,6 +121,10 @@ public class FlatPopupFactory popupWindow.getGraphicsConfiguration() == owner.getGraphicsConfiguration() ) return popup; + // avoid endless loop (should newer happen; PopupFactory cache size is 5) + if( ++count > 10 ) + return popup; + // remove contents component from popup window if( popupWindow instanceof JWindow ) ((JWindow)popupWindow).getContentPane().removeAll(); @@ -128,10 +132,6 @@ public class FlatPopupFactory // dispose unused popup // (do not invoke popup.hide() because this would cache the popup window) popupWindow.dispose(); - - // avoid endless loop (should newer happen; PopupFactory cache size is 5) - if( ++count > 10 ) - return popup; } } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatWindowsNativeWindowBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatWindowsNativeWindowBorder.java index 2f76e6f5..62f21452 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatWindowsNativeWindowBorder.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatWindowsNativeWindowBorder.java @@ -167,11 +167,18 @@ class FlatWindowsNativeWindowBorder return; // install - WndProc wndProc = new WndProc( window ); - if( wndProc.hwnd == 0 ) - return; + try { + WndProc wndProc = new WndProc( window ); + if( wndProc.hwnd == 0 ) + return; - windowsMap.put( window, wndProc ); + windowsMap.put( window, wndProc ); + } catch( UnsatisfiedLinkError ex ) { + // catch for the case that the operating system prevents execution of DLL + // (e.g. if DLLs in temp folder are restricted) + // --> continue application without custom decorations + LoggingFacade.INSTANCE.logSevere( null, ex ); + } } private void uninstall( Window window ) { diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties index 3392a4f9..b8e79354 100644 --- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties +++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties @@ -439,7 +439,7 @@ MenuItem.iconTextGap = 6 MenuItem.textAcceleratorGap = 24 MenuItem.textNoAcceleratorGap = 6 MenuItem.acceleratorArrowGap = 2 -MenuItem.acceleratorDelimiter = - +MenuItem.acceleratorDelimiter = + [mac]MenuItem.acceleratorDelimiter = # for MenuItem.selectionType = underline diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAWTTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAWTTest.java new file mode 100644 index 00000000..2a9691f1 --- /dev/null +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAWTTest.java @@ -0,0 +1,76 @@ +/* + * Copyright 2021 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.testing; + +import java.awt.*; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import com.formdev.flatlaf.FlatLightLaf; + +/** + * Used to test AWT components on macOS, which internally use Swing. + * + * @author Karl Tauber + */ +public class FlatAWTTest +{ + public static void main( String[] args ) { + EventQueue.invokeLater( () -> { + FlatLightLaf.setup(); + + Frame frame = new Frame( "FlatAWTTest" ); + frame.addWindowListener( new WindowAdapter() { + @Override + public void windowClosing( WindowEvent e ) { + System.exit( 0 ); + } + } ); + frame.setLayout( new FlowLayout() ); + + frame.add( new Label( "text" ) ); + frame.add( new Button( "text" ) ); + frame.add( new Checkbox( "text" ) ); + + CheckboxGroup checkboxGroup = new CheckboxGroup(); + frame.add( new Checkbox( "radio 1", true, checkboxGroup ) ); + frame.add( new Checkbox( "radio 2", false, checkboxGroup ) ); + frame.add( new Checkbox( "radio 3", false, checkboxGroup ) ); + + Choice choice = new Choice(); + choice.add( "item 1" ); + choice.add( "item 2" ); + choice.add( "item 3" ); + frame.add( choice ); + + frame.add( new TextField( "text" ) ); + frame.add( new TextArea( "text" ) ); + + List list = new List(); + list.add( "item 1" ); + list.add( "item 2" ); + frame.add( list ); + + frame.add( new Scrollbar() ); + frame.add( new ScrollPane() ); + frame.add( new Panel() ); + frame.add( new Canvas() ); + + frame.setSize( 800, 600 ); + frame.setVisible( true ); + }); + } +} diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatStressTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatStressTest.java new file mode 100644 index 00000000..58b39a48 --- /dev/null +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatStressTest.java @@ -0,0 +1,83 @@ +/* + * Copyright 2021 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.testing; + +import java.awt.Container; +import java.awt.FlowLayout; +import java.util.Random; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JFrame; +import javax.swing.SwingUtilities; +import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.FlatLightLaf; + +/** + * @author Karl Tauber + */ +public class FlatStressTest +{ + public static void main( String[] args ) { + SwingUtilities.invokeLater( () -> { + FlatLightLaf.setup(); + new FlatStressTest(); + } ); + } + + protected FlatStressTest() { + JFrame frame = new JFrame( "FlatStressTest" ); + frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); + + Container contentPane = frame.getContentPane(); + contentPane.setLayout( new FlowLayout() ); + + contentPane.add( createStressTest() ); + + frame.setSize( 800, 600 ); + frame.setLocationRelativeTo( null ); + frame.setVisible( true ); + } + + private JComponent createStressTest() { + return createComboBoxStressTest(); + } + + // for https://github.com/JFormDesigner/FlatLaf/issues/432 + // simulates StackOverflowError in FlatComboBoxUI when doing stuff in various threads + // + // requires adding `Thread.sleep( 1 );` to `FlatComboBoxUI.CellPaddingBorder.install()` + // after invocation of `uninstall()` + private JComponent createComboBoxStressTest() { + Random random = new Random(); + + JComboBox comboBox = new JComboBox<>(); + comboBox.putClientProperty( FlatClientProperties.MINIMUM_WIDTH, 0 ); + for( int i = 0; i < 100; i++ ) + comboBox.addItem( Integer.toString( random.nextInt() ) ); + + Thread thread = new Thread( () -> { + for(;;) { + comboBox.setSelectedIndex( random.nextInt( comboBox.getItemCount() ) ); + comboBox.putClientProperty( FlatClientProperties.MINIMUM_WIDTH, random.nextInt( 500 ) ); + } + }); + thread.setDaemon( true ); + thread.start(); + + return comboBox; + } +}