diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a3499f0..fec8d763 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ FlatLaf Change Log #### Fixed bugs +- Native window decorations: Fixed double window title bar when first disposing + a window with `frame.dispose()` and then showing it again with + `frame.setVisible(true)`. (issue #277) - Custom window decorations: Fixed NPE in `FlatTitlePane.findHorizontalGlue()`. (issue #275) diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeWindowBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeWindowBorder.java index 459424bf..4a720492 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeWindowBorder.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeWindowBorder.java @@ -62,20 +62,20 @@ public class FlatNativeWindowBorder if( !isSupported() ) return null; - // Check whether root pane already has a window, which is the case when switching LaF. + // Check whether root pane already has a window, which is the case when + // switching from another LaF to FlatLaf. // Also check whether the window is displayable, which is required to install // FlatLaf native window border. // If the window is not displayable, then it was probably closed/disposed but not yet removed // from the list of windows that AWT maintains and returns with Window.getWindows(). // It could be also be a window that is currently hidden, but may be shown later. Window window = SwingUtilities.windowForComponent( rootPane ); - if( window != null && window.isDisplayable() ) { + if( window != null && window.isDisplayable() ) install( window, FlatSystemProperties.USE_WINDOW_DECORATIONS ); - return null; - } // Install FlatLaf native window border, which must be done late, // when the native window is already created, because it needs access to the window. + // Uninstall FlatLaf native window border when window is disposed (or root pane removed). // "ancestor" property change event is fired from JComponent.addNotify() and removeNotify(). PropertyChangeListener ancestorListener = e -> { Object newValue = e.getNewValue(); @@ -148,7 +148,11 @@ public class FlatNativeWindowBorder if( data instanceof PropertyChangeListener ) rootPane.removePropertyChangeListener( "ancestor", (PropertyChangeListener) data ); - // uninstall native window border, except when switching to another FlatLaf theme + // do not uninstall when switching to another FlatLaf theme + if( UIManager.getLookAndFeel() instanceof FlatLaf ) + return; + + // uninstall native window border Window window = SwingUtilities.windowForComponent( rootPane ); if( window != null ) uninstall( window ); @@ -158,10 +162,6 @@ public class FlatNativeWindowBorder if( !hasCustomDecoration( window ) ) return; - // do not uninstall when switching to another FlatLaf theme - if( UIManager.getLookAndFeel() instanceof FlatLaf ) - return; - // disable native window border for window setHasCustomDecoration( window, false ); diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatNativeWindowBorderTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatNativeWindowBorderTest.java index 0aae3173..307c6ba2 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatNativeWindowBorderTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatNativeWindowBorderTest.java @@ -26,10 +26,17 @@ import java.awt.event.WindowEvent; import java.awt.event.WindowListener; import java.util.WeakHashMap; import javax.swing.*; +import javax.swing.plaf.metal.MetalLookAndFeel; +import javax.swing.plaf.nimbus.NimbusLookAndFeel; +import com.formdev.flatlaf.FlatDarculaLaf; +import com.formdev.flatlaf.FlatDarkLaf; +import com.formdev.flatlaf.FlatIntelliJLaf; +import com.formdev.flatlaf.FlatLaf; import com.formdev.flatlaf.FlatLightLaf; import com.formdev.flatlaf.extras.FlatInspector; import com.formdev.flatlaf.ui.FlatLineBorder; import com.formdev.flatlaf.ui.FlatNativeWindowBorder; +import com.formdev.flatlaf.util.SystemInfo; import net.miginfocom.swing.*; /** @@ -144,6 +151,22 @@ public class FlatNativeWindowBorderTest System.out.println( windowId + " windowDeactivated" ); } } ); + + registerSwitchToLookAndFeel( "F1", FlatLightLaf.class.getName() ); + registerSwitchToLookAndFeel( "F2", FlatDarkLaf.class.getName() ); + registerSwitchToLookAndFeel( "F3", FlatIntelliJLaf.class.getName() ); + registerSwitchToLookAndFeel( "F4", FlatDarculaLaf.class.getName() ); + + registerSwitchToLookAndFeel( "F8", FlatTestLaf.class.getName() ); + + if( SystemInfo.isWindows ) + registerSwitchToLookAndFeel( "F9", "com.sun.java.swing.plaf.windows.WindowsLookAndFeel" ); + else if( SystemInfo.isMacOS ) + registerSwitchToLookAndFeel( "F9", "com.apple.laf.AquaLookAndFeel" ); + else if( SystemInfo.isLinux ) + registerSwitchToLookAndFeel( "F9", "com.sun.java.swing.plaf.gtk.GTKLookAndFeel" ); + registerSwitchToLookAndFeel( "F12", MetalLookAndFeel.class.getName() ); + registerSwitchToLookAndFeel( "F11", NimbusLookAndFeel.class.getName() ); } private void updateInfo() { @@ -220,6 +243,28 @@ public class FlatNativeWindowBorderTest (r1.y + r1.height) - (r2.y + r2.height) ); } + private void registerSwitchToLookAndFeel( String keyStrokeStr, String lafClassName ) { + KeyStroke keyStroke = KeyStroke.getKeyStroke( keyStrokeStr ); + if( keyStroke == null ) + throw new IllegalArgumentException( "Invalid key stroke '" + keyStrokeStr + "'" ); + + ((JComponent)((RootPaneContainer)window).getContentPane()).registerKeyboardAction( + e -> applyLookAndFeel( lafClassName ), + keyStroke, + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ); + } + + private void applyLookAndFeel( String lafClassName ) { + EventQueue.invokeLater( () -> { + try { + UIManager.setLookAndFeel( lafClassName ); + FlatLaf.updateUI(); + } catch( Exception ex ) { + ex.printStackTrace(); + } + } ); + } + private void resizableChanged() { if( window instanceof Frame ) ((Frame)window).setResizable( resizableCheckBox.isSelected() ); @@ -298,6 +343,23 @@ public class FlatNativeWindowBorderTest } } + private void reopen() { + window.dispose(); + window.setVisible( true ); + } + + private void reshow() { + window.setVisible( false ); + + try { + Thread.sleep( 100 ); + } catch( InterruptedException ex ) { + // ignore + } + + window.setVisible( true ); + } + private void close() { window.dispose(); } @@ -310,12 +372,14 @@ public class FlatNativeWindowBorderTest undecoratedCheckBox = new JCheckBox(); fullScreenCheckBox = new JCheckBox(); nativeCheckBox = new JCheckBox(); - revalidateButton = new JButton(); - replaceRootPaneButton = new JButton(); openDialogButton = new JButton(); - openFrameButton = new JButton(); hideWindowButton = new JButton(); + reopenButton = new JButton(); + replaceRootPaneButton = new JButton(); + openFrameButton = new JButton(); showHiddenWindowButton = new JButton(); + reshowButton = new JButton(); + revalidateButton = new JButton(); hSpacer1 = new JPanel(null); closeButton = new JButton(); @@ -331,6 +395,7 @@ public class FlatNativeWindowBorderTest "[]0" + "[]0" + "[]" + + "[]" + "[]")); //---- info ---- @@ -368,43 +433,53 @@ public class FlatNativeWindowBorderTest nativeCheckBox.addActionListener(e -> nativeChanged()); add(nativeCheckBox, "cell 0 3 3 1"); - //---- revalidateButton ---- - revalidateButton.setText("revalidate"); - revalidateButton.addActionListener(e -> revalidateLayout()); - add(revalidateButton, "cell 0 3 3 1"); - - //---- replaceRootPaneButton ---- - replaceRootPaneButton.setText("replace rootpane"); - replaceRootPaneButton.addActionListener(e -> replaceRootPane()); - add(replaceRootPaneButton, "cell 0 3 3 1"); - //---- openDialogButton ---- openDialogButton.setText("Open Dialog"); openDialogButton.setMnemonic('D'); openDialogButton.addActionListener(e -> openDialog()); add(openDialogButton, "cell 0 4 3 1"); - //---- openFrameButton ---- - openFrameButton.setText("Open Frame"); - openFrameButton.setMnemonic('A'); - openFrameButton.addActionListener(e -> openFrame()); - add(openFrameButton, "cell 0 4 3 1"); - //---- hideWindowButton ---- hideWindowButton.setText("Hide"); hideWindowButton.addActionListener(e -> hideWindow()); add(hideWindowButton, "cell 0 4 3 1"); + //---- reopenButton ---- + reopenButton.setText("Dispose and Reopen"); + reopenButton.addActionListener(e -> reopen()); + add(reopenButton, "cell 0 4 3 1"); + + //---- replaceRootPaneButton ---- + replaceRootPaneButton.setText("replace rootpane"); + replaceRootPaneButton.addActionListener(e -> replaceRootPane()); + add(replaceRootPaneButton, "cell 0 4 3 1"); + + //---- openFrameButton ---- + openFrameButton.setText("Open Frame"); + openFrameButton.setMnemonic('A'); + openFrameButton.addActionListener(e -> openFrame()); + add(openFrameButton, "cell 0 5 3 1"); + //---- showHiddenWindowButton ---- showHiddenWindowButton.setText("Show hidden"); showHiddenWindowButton.addActionListener(e -> showHiddenWindow()); - add(showHiddenWindowButton, "cell 0 4 3 1"); - add(hSpacer1, "cell 0 4 3 1,growx"); + add(showHiddenWindowButton, "cell 0 5 3 1"); + + //---- reshowButton ---- + reshowButton.setText("Hide and Show"); + reshowButton.addActionListener(e -> reshow()); + add(reshowButton, "cell 0 5 3 1"); + + //---- revalidateButton ---- + revalidateButton.setText("revalidate"); + revalidateButton.addActionListener(e -> revalidateLayout()); + add(revalidateButton, "cell 0 5 3 1"); + add(hSpacer1, "cell 0 5 3 1,growx"); //---- closeButton ---- closeButton.setText("Close"); closeButton.addActionListener(e -> close()); - add(closeButton, "cell 0 4 3 1"); + add(closeButton, "cell 0 5 3 1"); // JFormDesigner - End of component initialization //GEN-END:initComponents } @@ -415,12 +490,14 @@ public class FlatNativeWindowBorderTest private JCheckBox undecoratedCheckBox; private JCheckBox fullScreenCheckBox; private JCheckBox nativeCheckBox; - private JButton revalidateButton; - private JButton replaceRootPaneButton; private JButton openDialogButton; - private JButton openFrameButton; private JButton hideWindowButton; + private JButton reopenButton; + private JButton replaceRootPaneButton; + private JButton openFrameButton; private JButton showHiddenWindowButton; + private JButton reshowButton; + private JButton revalidateButton; private JPanel hSpacer1; private JButton closeButton; // JFormDesigner - End of variables declaration //GEN-END:variables diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatNativeWindowBorderTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatNativeWindowBorderTest.jfd index 7b0512bd..dcf543c0 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatNativeWindowBorderTest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatNativeWindowBorderTest.jfd @@ -1,4 +1,4 @@ -JFDML JFormDesigner: "7.0.3.1.342" Java: "15" encoding: "UTF-8" +JFDML JFormDesigner: "7.0.3.1.342" Java: "16" encoding: "UTF-8" new FormModel { contentType: "form/swing" @@ -6,7 +6,7 @@ new FormModel { add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { "$layoutConstraints": "insets dialog,hidemode 3" "$columnConstraints": "[][][grow,fill]" - "$rowConstraints": "[grow,top]para[]0[]0[][]" + "$rowConstraints": "[grow,top]para[]0[]0[][][]" } ) { name: "this" add( new FormComponent( "javax.swing.JLabel" ) { @@ -65,20 +65,6 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 3 3 1" } ) - add( new FormComponent( "javax.swing.JButton" ) { - name: "revalidateButton" - "text": "revalidate" - addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "revalidateLayout", false ) ) - }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 3 3 1" - } ) - add( new FormComponent( "javax.swing.JButton" ) { - name: "replaceRootPaneButton" - "text": "replace rootpane" - addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "replaceRootPane", false ) ) - }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 3 3 1" - } ) add( new FormComponent( "javax.swing.JButton" ) { name: "openDialogButton" "text": "Open Dialog" @@ -87,14 +73,6 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 4 3 1" } ) - add( new FormComponent( "javax.swing.JButton" ) { - name: "openFrameButton" - "text": "Open Frame" - "mnemonic": 65 - addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "openFrame", false ) ) - }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 4 3 1" - } ) add( new FormComponent( "javax.swing.JButton" ) { name: "hideWindowButton" "text": "Hide" @@ -102,28 +80,64 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 4 3 1" } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "reopenButton" + "text": "Dispose and Reopen" + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "reopen", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 4 3 1" + } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "replaceRootPaneButton" + "text": "replace rootpane" + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "replaceRootPane", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 4 3 1" + } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "openFrameButton" + "text": "Open Frame" + "mnemonic": 65 + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "openFrame", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 5 3 1" + } ) add( new FormComponent( "javax.swing.JButton" ) { name: "showHiddenWindowButton" "text": "Show hidden" addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "showHiddenWindow", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 4 3 1" + "value": "cell 0 5 3 1" + } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "reshowButton" + "text": "Hide and Show" + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "reshow", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 5 3 1" + } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "revalidateButton" + "text": "revalidate" + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "revalidateLayout", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 5 3 1" } ) add( new FormComponent( "com.jformdesigner.designer.wrapper.HSpacer" ) { name: "hSpacer1" }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 4 3 1,growx" + "value": "cell 0 5 3 1,growx" } ) add( new FormComponent( "javax.swing.JButton" ) { name: "closeButton" "text": "Close" addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "close", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 4 3 1" + "value": "cell 0 5 3 1" } ) }, new FormLayoutConstraints( null ) { "location": new java.awt.Point( 0, 0 ) - "size": new java.awt.Dimension( 500, 300 ) + "size": new java.awt.Dimension( 520, 300 ) } ) } }