From 91e8d04a9f4fe7215213aadd1241d1903504e1d4 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Mon, 6 Jan 2025 18:01:50 +0100 Subject: [PATCH] System File Chooser: introduced class `SystemFileChooser` as replacement for `JFileChooser` --- .../formdev/flatlaf/FlatSystemProperties.java | 13 + .../flatlaf/ui/FlatNativeLinuxLibrary.java | 5 + .../flatlaf/ui/FlatNativeWindowsLibrary.java | 3 +- .../flatlaf/util/SystemFileChooser.java | 559 +++++++++++++ .../com/formdev/flatlaf/demo/DemoFrame.java | 24 + .../com/formdev/flatlaf/demo/DemoFrame.jfd | 15 +- .../flatlaf-natives-linux/build.gradle.kts | 11 + .../src/main/cpp/GtkFileChooser.cpp | 8 +- .../src/main/cpp/WinFileChooser.cpp | 13 +- flatlaf-testing/build.gradle.kts | 1 + .../testing/FlatSystemFileChooserTest.java | 749 ++++++++++++++++++ .../testing/FlatSystemFileChooserTest.jfd | 321 ++++++++ .../FlatSystemFileChooserWindowsTest.java | 4 + .../FlatSystemFileChooserWindowsTest.jfd | 3 + .../flatlaf/testing/FlatTestFrame.java | 3 + gradle/libs.versions.toml | 1 + 16 files changed, 1721 insertions(+), 12 deletions(-) create mode 100644 flatlaf-core/src/main/java/com/formdev/flatlaf/util/SystemFileChooser.java create mode 100644 flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserTest.java create mode 100644 flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserTest.jfd diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatSystemProperties.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatSystemProperties.java index db7ccb13..1d333eaa 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatSystemProperties.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatSystemProperties.java @@ -16,7 +16,9 @@ package com.formdev.flatlaf; +import javax.swing.JFileChooser; import javax.swing.SwingUtilities; +import com.formdev.flatlaf.util.SystemFileChooser; import com.formdev.flatlaf.util.UIScale; /** @@ -226,6 +228,17 @@ public interface FlatSystemProperties */ String USE_SUB_MENU_SAFE_TRIANGLE = "flatlaf.useSubMenuSafeTriangle"; + /** + * Specifies whether {@link SystemFileChooser} uses operating system file dialogs. + * If set to {@code false}, the {@link JFileChooser} is used instead. + *

+ * Allowed Values {@code false} and {@code true}
+ * Default {@code true} + * + * @since 3.6 + */ + String USE_SYSTEM_FILE_CHOOSER = "flatlaf.useSystemFileChooser"; + /** * Checks whether a system property is set and returns {@code true} if its value * is {@code "true"} (case-insensitive), otherwise it returns {@code false}. diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeLinuxLibrary.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeLinuxLibrary.java index 9ebdda20..f1bed502 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeLinuxLibrary.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeLinuxLibrary.java @@ -134,6 +134,10 @@ public class FlatNativeLinuxLibrary * Shows the Linux system file dialog * GtkFileChooserDialog. *

+ * Uses {@code GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER} if {@link #FC_select_folder} is set in parameter {@code optionsSet}. + * Otherwise uses {@code GTK_FILE_CHOOSER_ACTION_OPEN} if parameter {@code open} is {@code true}, + * or {@code GTK_FILE_CHOOSER_ACTION_SAVE} if {@code false}. + *

* Note: This method blocks the current thread until the user closes * the file dialog. It is highly recommended to invoke it from a new thread * to avoid blocking the AWT event dispatching thread. @@ -142,6 +146,7 @@ public class FlatNativeLinuxLibrary * @param open if {@code true}, shows the open dialog; if {@code false}, shows the save dialog * @param title text displayed in dialog title; or {@code null} * @param okButtonLabel text displayed in default button; or {@code null}. Use '_' for mnemonics (e.g. "_Choose") + * Use '__' for '_' character (e.g. "Choose__and__Quit"). * @param currentName user-editable filename currently shown in the filename field in save dialog; or {@code null} * @param currentFolder current directory shown in the dialog; or {@code null} * @param optionsSet options to set; see {@code FOS_*} constants diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeWindowsLibrary.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeWindowsLibrary.java index 3e7c7b0c..f0a769aa 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeWindowsLibrary.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeWindowsLibrary.java @@ -204,7 +204,8 @@ public class FlatNativeWindowsLibrary * @param owner the owner of the file dialog; or {@code null} * @param open if {@code true}, shows the open dialog; if {@code false}, shows the save dialog * @param title text displayed in dialog title; or {@code null} - * @param okButtonLabel text displayed in default button; or {@code null}. Use '&' for mnemonics (e.g. "&Choose") + * @param okButtonLabel text displayed in default button; or {@code null}. Use '&' for mnemonics (e.g. "&Choose"). + * Use '&&' for '&' character (e.g. "Choose && Quit"). * @param fileNameLabel text displayed in front of the filename text field; or {@code null} * @param fileName user-editable filename currently shown in the filename field; or {@code null} * @param folder current directory shown in the dialog; or {@code null} diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/SystemFileChooser.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/SystemFileChooser.java new file mode 100644 index 00000000..d5d6d773 --- /dev/null +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/SystemFileChooser.java @@ -0,0 +1,559 @@ +/* + * 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 java.awt.Component; +import java.awt.SecondaryLoop; +import java.awt.Toolkit; +import java.awt.Window; +import java.io.File; +import java.util.ArrayList; +import java.util.Locale; +import java.util.concurrent.atomic.AtomicReference; +import javax.swing.JFileChooser; +import javax.swing.JOptionPane; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.filechooser.FileSystemView; +import com.formdev.flatlaf.FlatSystemProperties; +import com.formdev.flatlaf.ui.FlatNativeLinuxLibrary; +import com.formdev.flatlaf.ui.FlatNativeWindowsLibrary; + +/** + * Gives access to operating system file dialogs. + *

+ * The API is (mostly) compatible with {@link JFileChooser}. + * To use this class in existing code, do a string replace from {@code JFileChooser} to {@code SystemFileChooser}. + * If there are no compile errors, then there is a good chance that it works without further changes. + * If there are compile errors, then you're using a feature that {@code SystemFileChooser} does not support. + *

+ * Supported platforms are Windows 10+, macOS 10.14+ and Linux GTK 3. + * {@code JFileChooser} is used on unsupported platforms. + *

+ * {@code SystemFileChooser} requires FlatLaf native libraries (usually contained in flatlaf.jar). + * If not available or disabled (via {@link FlatSystemProperties#USE_NATIVE_LIBRARY} + * or {@link FlatSystemProperties#USE_SYSTEM_FILE_CHOOSER}), then {@code JFileChooser} is used. + *

+ *

+ *

Limitations/incompatibilities compared to JFileChooser

+ * + * + * + * @author Karl Tauber + * @since 3.6 + */ +public class SystemFileChooser +{ + /** @see JFileChooser#OPEN_DIALOG */ + public static final int OPEN_DIALOG = JFileChooser.OPEN_DIALOG; + + /** @see JFileChooser#SAVE_DIALOG */ + public static final int SAVE_DIALOG = JFileChooser.SAVE_DIALOG; + + /** @see JFileChooser#CANCEL_OPTION */ + public static final int CANCEL_OPTION = JFileChooser.CANCEL_OPTION; + + /** @see JFileChooser#APPROVE_OPTION */ + public static final int APPROVE_OPTION = JFileChooser.APPROVE_OPTION; + + /** @see JFileChooser#FILES_ONLY */ + public static final int FILES_ONLY = JFileChooser.FILES_ONLY; + + /** @see JFileChooser#DIRECTORIES_ONLY */ + public static final int DIRECTORIES_ONLY = JFileChooser.DIRECTORIES_ONLY; + + private int dialogType = OPEN_DIALOG; + private String dialogTitle; + private String approveButtonText; + private int approveButtonMnemonic = 0; + private int fileSelectionMode = FILES_ONLY; + private boolean multiSelection; + private boolean useFileHiding = true; + + private File currentDirectory; + private File selectedFile; + private File[] selectedFiles; + + /** @see JFileChooser#JFileChooser() */ + public SystemFileChooser() { + this( (File) null ); + } + + /** @see JFileChooser#JFileChooser(String) */ + public SystemFileChooser( String currentDirectoryPath ) { + setCurrentDirectory( (currentDirectoryPath != null) + ? FileSystemView.getFileSystemView().createFileObject( currentDirectoryPath ) + : null ); + } + + /** @see JFileChooser#JFileChooser(File) */ + public SystemFileChooser( File currentDirectory ) { + setCurrentDirectory( currentDirectory ); + } + + /** @see JFileChooser#showOpenDialog(Component) */ + public int showOpenDialog( Component parent ) { + setDialogType( OPEN_DIALOG ); + return showDialogImpl( parent ); + } + + /** @see JFileChooser#showSaveDialog(Component) */ + public int showSaveDialog( Component parent ) { + setDialogType( SAVE_DIALOG ); + return showDialogImpl( parent ); + } + + /** @see JFileChooser#showDialog(Component, String) */ + public int showDialog( Component parent, String approveButtonText ) { + if( approveButtonText != null ) + setApproveButtonText( approveButtonText ); + return showDialogImpl( parent ); + } + + /** @see JFileChooser#getDialogType() */ + public int getDialogType() { + return dialogType; + } + + /** @see JFileChooser#setDialogType(int) */ + public void setDialogType( int dialogType ) { + if( dialogType != OPEN_DIALOG && dialogType != SAVE_DIALOG ) + throw new IllegalArgumentException( "Invalid dialog type " + dialogType ); + + this.dialogType = dialogType; + } + + /** @see JFileChooser#getDialogTitle() */ + public String getDialogTitle() { + return dialogTitle; + } + + /** @see JFileChooser#setDialogTitle(String) */ + public void setDialogTitle( String dialogTitle ) { + this.dialogTitle = dialogTitle; + } + + /** @see JFileChooser#getApproveButtonText() */ + public String getApproveButtonText() { + return approveButtonText; + } + + /** @see JFileChooser#setApproveButtonText(String) */ + public void setApproveButtonText( String approveButtonText ) { + this.approveButtonText = approveButtonText; + } + + /** @see JFileChooser#getApproveButtonMnemonic() */ + public int getApproveButtonMnemonic() { + return approveButtonMnemonic; + } + + /** @see JFileChooser#setApproveButtonMnemonic(int) */ + public void setApproveButtonMnemonic( int mnemonic ) { + approveButtonMnemonic = mnemonic; + } + + /** @see JFileChooser#setApproveButtonMnemonic(char) */ + public void setApproveButtonMnemonic( char mnemonic ) { + int vk = mnemonic; + if( vk >= 'a' && vk <= 'z' ) + vk -= 'a' - 'A'; + setApproveButtonMnemonic( vk ); + } + + /** @see JFileChooser#getFileSelectionMode() */ + public int getFileSelectionMode() { + return fileSelectionMode; + } + + /** @see JFileChooser#setFileSelectionMode(int) */ + public void setFileSelectionMode( int fileSelectionMode ) { + if( fileSelectionMode != FILES_ONLY && fileSelectionMode != DIRECTORIES_ONLY ) + throw new IllegalArgumentException( "Invalid file selection mode " + fileSelectionMode ); + + this.fileSelectionMode = fileSelectionMode; + } + + /** @see JFileChooser#isFileSelectionEnabled() */ + public boolean isFileSelectionEnabled() { + return fileSelectionMode == FILES_ONLY; + } + + /** @see JFileChooser#isDirectorySelectionEnabled() */ + public boolean isDirectorySelectionEnabled() { + return fileSelectionMode == DIRECTORIES_ONLY; + } + + /** @see JFileChooser#isMultiSelectionEnabled() */ + public boolean isMultiSelectionEnabled() { + return multiSelection; + } + + /** @see JFileChooser#setMultiSelectionEnabled(boolean) */ + public void setMultiSelectionEnabled( boolean multiSelection ) { + this.multiSelection = multiSelection; + } + + /** @see JFileChooser#isFileHidingEnabled() */ + public boolean isFileHidingEnabled() { + return useFileHiding; + } + + /** @see JFileChooser#setFileHidingEnabled(boolean) */ + public void setFileHidingEnabled( boolean useFileHiding ) { + this.useFileHiding = useFileHiding; + } + + /** @see JFileChooser#getCurrentDirectory() */ + public File getCurrentDirectory() { + return currentDirectory; + } + + /** @see JFileChooser#setCurrentDirectory(File) */ + public void setCurrentDirectory( File dir ) { + // for compatibility with JFileChooser + if( dir != null && !dir.exists() ) + return; + if( dir == null ) + dir = FileSystemView.getFileSystemView().getDefaultDirectory(); + + currentDirectory = dir; + } + + /** @see JFileChooser#getSelectedFile() */ + public File getSelectedFile() { + return selectedFile; + } + + /** @see JFileChooser#setSelectedFile(File) */ + public void setSelectedFile( File file ) { + selectedFile = file; + + // for compatibility with JFileChooser + if( file != null && + file.isAbsolute() && + !FileSystemView.getFileSystemView().isParent( getCurrentDirectory(), file ) ) + setCurrentDirectory( file.getParentFile() ); + } + + /** @see JFileChooser#getSelectedFiles() */ + public File[] getSelectedFiles() { + return (selectedFiles != null) ? selectedFiles.clone() : new File[0]; + } + + /** @see JFileChooser#setSelectedFiles(File[]) */ + public void setSelectedFiles( File[] selectedFiles ) { + if( selectedFiles != null && selectedFiles.length > 0 ) { + this.selectedFiles = selectedFiles.clone(); + setSelectedFile( selectedFiles[0] ); + } else { + this.selectedFiles = null; + setSelectedFile( null ); + } + } + + private int showDialogImpl( Component parent ) { + File[] files = getProvider().showDialog( parent, this ); + setSelectedFiles( files ); + return (files != null) ? APPROVE_OPTION : CANCEL_OPTION; + } + + private FileChooserProvider getProvider() { + if( !FlatSystemProperties.getBoolean( FlatSystemProperties.USE_SYSTEM_FILE_CHOOSER, true ) ) + return new SwingFileChooserProvider(); + + if( SystemInfo.isWindows_10_orLater && FlatNativeWindowsLibrary.isLoaded() ) + return new WindowsFileChooserProvider(); + else if( SystemInfo.isLinux && FlatNativeLinuxLibrary.isLoaded() ) + return new LinuxFileChooserProvider(); + else // unknown platform or FlatLaf native library not loaded + return new SwingFileChooserProvider(); + } + + //---- interface FileChooserProvider -------------------------------------- + + private interface FileChooserProvider { + File[] showDialog( Component parent, SystemFileChooser fc ); + } + + //---- class SystemFileChooserProvider ------------------------------------ + + private static abstract class SystemFileChooserProvider + implements FileChooserProvider + { + @Override + public File[] showDialog( Component parent, SystemFileChooser fc ) { + Window owner = (parent instanceof Window) + ? (Window) parent + : (parent != null) ? SwingUtilities.windowForComponent( parent ) : null; + AtomicReference filenamesRef = new AtomicReference<>(); + + // create secondary event look and invoke system file dialog on a new thread + SecondaryLoop secondaryLoop = Toolkit.getDefaultToolkit().getSystemEventQueue().createSecondaryLoop(); + new Thread( () -> { + filenamesRef.set( showSystemDialog( owner, fc ) ); + secondaryLoop.exit(); + } ).start(); + secondaryLoop.enter(); + + String[] filenames = filenamesRef.get(); + + // fallback to Swing file chooser if system file dialog failed or is not available + if( filenames == null ) + return new SwingFileChooserProvider().showDialog( parent, fc ); + + // canceled? + if( filenames.length == 0 ) + return null; + + // convert file names to file objects + FileSystemView fsv = FileSystemView.getFileSystemView(); + File[] files = new File[filenames.length]; + for( int i = 0; i < filenames.length; i++ ) + files[i] = fsv.createFileObject( filenames[i] ); + return files; + } + + abstract String[] showSystemDialog( Window owner, SystemFileChooser fc ); + } + + //---- class WindowsFileChooserProvider ----------------------------------- + + private static class WindowsFileChooserProvider + extends SystemFileChooserProvider + { + @Override + String[] showSystemDialog( Window owner, SystemFileChooser fc ) { + boolean open = (fc.getDialogType() == OPEN_DIALOG); + String approveButtonText = fc.getApproveButtonText(); + int approveButtonMnemonic = fc.getApproveButtonMnemonic(); + String fileName = null; + String folder = null; + String saveAsItem = null; + + // approve button text and mnemonic + if( approveButtonText != null ) { + approveButtonText = approveButtonText.replace( "&", "&&" ); + if( approveButtonMnemonic > 0 ) { + int mnemonicIndex = approveButtonText.toUpperCase( Locale.ENGLISH ).indexOf( approveButtonMnemonic ); + if( mnemonicIndex >= 0 ) { + approveButtonText = approveButtonText.substring( 0, mnemonicIndex ) + + '&' + approveButtonText.substring( mnemonicIndex ); + } + } + } + + // paths + File currentDirectory = fc.getCurrentDirectory(); + File selectedFile = fc.getSelectedFile(); + if( selectedFile != null ) { + if( selectedFile.exists() && !open ) + saveAsItem = selectedFile.getAbsolutePath(); + else { + fileName = selectedFile.getName(); + folder = selectedFile.getParent(); + } + } else if( currentDirectory != null ) + folder = currentDirectory.getAbsolutePath(); + + // options + int optionsSet = FlatNativeWindowsLibrary.FOS_OVERWRITEPROMPT; + int optionsClear = 0; + if( fc.isDirectorySelectionEnabled() ) + optionsSet |= FlatNativeWindowsLibrary.FOS_PICKFOLDERS; + if( fc.isMultiSelectionEnabled() ) + optionsSet |= FlatNativeWindowsLibrary.FOS_ALLOWMULTISELECT; + if( !fc.isFileHidingEnabled() ) + optionsSet |= FlatNativeWindowsLibrary.FOS_FORCESHOWHIDDEN; + + // filter + int fileTypeIndex = 0; + ArrayList fileTypes = new ArrayList<>(); + // FOS_PICKFOLDERS does not support file types + if( !fc.isDirectorySelectionEnabled() ) { + // if there are no file types + // - for Save dialog add "All Files", otherwise Windows would show an empty "Save as type" combobox + // - for Open dialog, Windows hides the combobox + if( !open && fileTypes.isEmpty() ) { + fileTypes.add( UIManager.getString( "FileChooser.acceptAllFileFilterText" ) ); + fileTypes.add( "*.*" ); + } + } + + // show system file dialog + return FlatNativeWindowsLibrary.showFileChooser( owner, open, + fc.getDialogTitle(), approveButtonText, null, fileName, + folder, saveAsItem, null, null, optionsSet, optionsClear, + fileTypeIndex, fileTypes.toArray( new String[fileTypes.size()] ) ); + } + } + + //---- class LinuxFileChooserProvider -----------------------------------.. + + private static class LinuxFileChooserProvider + extends SystemFileChooserProvider + { + @Override + String[] showSystemDialog( Window owner, SystemFileChooser fc ) { + boolean open = (fc.getDialogType() == OPEN_DIALOG); + String approveButtonText = fc.getApproveButtonText(); + int approveButtonMnemonic = fc.getApproveButtonMnemonic(); + String currentName = null; + String currentFolder = null; + + // approve button text and mnemonic + if( approveButtonText != null ) { + approveButtonText = approveButtonText.replace( "_", "__" ); + if( approveButtonMnemonic > 0 ) { + int mnemonicIndex = approveButtonText.toUpperCase( Locale.ENGLISH ).indexOf( approveButtonMnemonic ); + if( mnemonicIndex >= 0 ) { + approveButtonText = approveButtonText.substring( 0, mnemonicIndex ) + + '_' + approveButtonText.substring( mnemonicIndex ); + } + } + } + + // paths + File currentDirectory = fc.getCurrentDirectory(); + File selectedFile = fc.getSelectedFile(); + if( selectedFile != null ) { + if( selectedFile.isDirectory() ) + currentFolder = selectedFile.getAbsolutePath(); + else { + currentName = selectedFile.getName(); + currentFolder = selectedFile.getParent(); + } + } else if( currentDirectory != null ) + currentFolder = currentDirectory.getAbsolutePath(); + + // options + int optionsSet = FlatNativeLinuxLibrary.FC_do_overwrite_confirmation; + int optionsClear = 0; + if( fc.isDirectorySelectionEnabled() ) + optionsSet |= FlatNativeLinuxLibrary.FC_select_folder; + if( fc.isMultiSelectionEnabled() ) + optionsSet |= FlatNativeLinuxLibrary.FC_select_multiple; + if( !fc.isFileHidingEnabled() ) + optionsSet |= FlatNativeLinuxLibrary.FC_show_hidden; + else // necessary because GTK seems to be remember last state and re-use it for new file dialogs + optionsClear |= FlatNativeLinuxLibrary.FC_show_hidden; + + // show system file dialog + return FlatNativeLinuxLibrary.showFileChooser( owner, open, + fc.getDialogTitle(), approveButtonText, currentName, currentFolder, + optionsSet, optionsClear, 0 ); + } + } + + //---- class SwingFileChooserProvider ------------------------------------- + + private static class SwingFileChooserProvider + implements FileChooserProvider + { + @Override + public File[] showDialog( Component parent, SystemFileChooser fc ) { + JFileChooser chooser = new JFileChooser() { + @Override + public void approveSelection() { + File[] files = isMultiSelectionEnabled() + ? getSelectedFiles() + : new File[] { getSelectedFile() }; + + if( getDialogType() == OPEN_DIALOG || isDirectorySelectionEnabled() ) { + if( !checkMustExist( this, files ) ) + return; + } else { + if( !checkOverwrite( this, files ) ) + return; + } + super.approveSelection(); + } + }; + + chooser.setDialogType( fc.getDialogType() ); + chooser.setDialogTitle( fc.getDialogTitle() ); + chooser.setApproveButtonText( fc.getApproveButtonText() ); + chooser.setApproveButtonMnemonic( fc.getApproveButtonMnemonic() ); + chooser.setFileSelectionMode( fc.getFileSelectionMode() ); + chooser.setMultiSelectionEnabled( fc.isMultiSelectionEnabled() ); + chooser.setFileHidingEnabled( fc.isFileHidingEnabled() ); + + // system file dialogs do not support multi-selection for Save File dialogs + if( chooser.isMultiSelectionEnabled() && + chooser.getDialogType() == JFileChooser.SAVE_DIALOG && + !chooser.isDirectorySelectionEnabled() ) + chooser.setMultiSelectionEnabled( false ); + + // paths + chooser.setCurrentDirectory( fc.getCurrentDirectory() ); + chooser.setSelectedFile( fc.getSelectedFile() ); + + if( chooser.showDialog( parent, null ) != JFileChooser.APPROVE_OPTION ) + return null; + + return chooser.isMultiSelectionEnabled() + ? chooser.getSelectedFiles() + : new File[] { chooser.getSelectedFile() }; + } + } + + private static boolean checkMustExist( JFileChooser chooser, File[] files ) { + for( File file : files ) { + if( !file.exists() ) { + String title = chooser.getDialogTitle(); + JOptionPane.showMessageDialog( chooser, + file.getName() + (chooser.isDirectorySelectionEnabled() + ? "\nPath does not exist.\nCheck the path and try again." + : "\nFile not found.\nCheck the file name and try again."), + (title != null) ? title : "Open", + JOptionPane.WARNING_MESSAGE ); + return false; + } + } + return true; + } + + private static boolean checkOverwrite( JFileChooser chooser, File[] files ) { + for( File file : files ) { + if( file.exists() ) { + String title = chooser.getDialogTitle(); + Locale l = chooser.getLocale(); + Object[] options = { + UIManager.getString( "OptionPane.yesButtonText", l ), + UIManager.getString( "OptionPane.noButtonText", l ), }; + int result = JOptionPane.showOptionDialog( chooser, + file.getName() + " already exists.\nDo you want to replace it?", + "Confirm " + (title != null ? title : "Save"), + JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, + null, options, options[1] ); + return (result == 0); + } + } + return true; + } +} 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 e9a9171a..9ec5e815 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 @@ -49,6 +49,7 @@ 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.SystemFileChooser; import com.formdev.flatlaf.util.SystemInfo; import net.miginfocom.layout.ConstraintParser; import net.miginfocom.layout.LC; @@ -172,6 +173,16 @@ class DemoFrame chooser.showSaveDialog( this ); } + private void openSystemActionPerformed() { + SystemFileChooser chooser = new SystemFileChooser(); + chooser.showOpenDialog( this ); + } + + private void saveAsSystemActionPerformed() { + SystemFileChooser chooser = new SystemFileChooser(); + chooser.showSaveDialog( this ); + } + private void exitActionPerformed() { dispose(); } @@ -496,6 +507,8 @@ class DemoFrame JMenuItem newMenuItem = new JMenuItem(); JMenuItem openMenuItem = new JMenuItem(); JMenuItem saveAsMenuItem = new JMenuItem(); + JMenuItem openSystemMenuItem = new JMenuItem(); + JMenuItem saveAsSystemMenuItem = new JMenuItem(); JMenuItem closeMenuItem = new JMenuItem(); exitMenuItem = new JMenuItem(); JMenu editMenu = new JMenu(); @@ -596,6 +609,17 @@ class DemoFrame fileMenu.add(saveAsMenuItem); fileMenu.addSeparator(); + //---- openSystemMenuItem ---- + openSystemMenuItem.setText("Open (System)..."); + openSystemMenuItem.addActionListener(e -> openSystemActionPerformed()); + fileMenu.add(openSystemMenuItem); + + //---- saveAsSystemMenuItem ---- + saveAsSystemMenuItem.setText("Save As (System)..."); + saveAsSystemMenuItem.addActionListener(e -> saveAsSystemActionPerformed()); + fileMenu.add(saveAsSystemMenuItem); + fileMenu.addSeparator(); + //---- closeMenuItem ---- closeMenuItem.setText("Close"); closeMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); 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..61243782 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" @@ -182,6 +182,19 @@ new FormModel { "mnemonic": 83 addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "saveAsActionPerformed", false ) ) } ) + add( new FormComponent( "javax.swing.JPopupMenu$Separator" ) { + name: "separator9" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "openSystemMenuItem" + "text": "Open (System)..." + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "openSystemActionPerformed", false ) ) + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "saveAsSystemMenuItem" + "text": "Save As (System)..." + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "saveAsSystemActionPerformed", false ) ) + } ) add( new FormComponent( "javax.swing.JPopupMenu$Separator" ) { name: "separator2" } ) diff --git a/flatlaf-natives/flatlaf-natives-linux/build.gradle.kts b/flatlaf-natives/flatlaf-natives-linux/build.gradle.kts index 1ae7abbc..e541d58e 100644 --- a/flatlaf-natives/flatlaf-natives-linux/build.gradle.kts +++ b/flatlaf-natives/flatlaf-natives-linux/build.gradle.kts @@ -71,6 +71,17 @@ tasks { else -> emptyList() } } ) + + doFirst { + // check required Java version + if( JavaVersion.current() < JavaVersion.VERSION_11 ) { + println() + println( "WARNING: Java 11 or later required to build Linux native library (running ${System.getProperty( "java.version" )})" ) + println( " Native library built with older Java versions throw following exception when running in Java 17+:" ) + println( " java.lang.UnsatisfiedLinkError: .../libjawt.so: version `SUNWprivate_1.1' not found" ) + println() + } + } } withType().configureEach { diff --git a/flatlaf-natives/flatlaf-natives-linux/src/main/cpp/GtkFileChooser.cpp b/flatlaf-natives/flatlaf-natives-linux/src/main/cpp/GtkFileChooser.cpp index 01017446..858ef1b0 100644 --- a/flatlaf-natives/flatlaf-natives-linux/src/main/cpp/GtkFileChooser.cpp +++ b/flatlaf-natives/flatlaf-natives-linux/src/main/cpp/GtkFileChooser.cpp @@ -177,11 +177,11 @@ JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrar bool multiSelect = isOptionSet( FC_select_multiple ); GtkWidget* dialog = gtk_file_chooser_dialog_new( (ctitle != NULL) ? ctitle - : (open ? (selectFolder ? (multiSelect ? _("Select Folders") : _("Select Folder")) - : (multiSelect ? _("Open Files") : _("Open File"))) : _("Save File")), + : (selectFolder ? (multiSelect ? _("Select Folders") : _("Select Folder")) + : (open ? ((multiSelect ? _("Open Files") : _("Open File"))) : _("Save File"))), NULL, // can not use AWT X11 window as parent because GtkWindow is required - open ? (selectFolder ? GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER : GTK_FILE_CHOOSER_ACTION_OPEN) - : GTK_FILE_CHOOSER_ACTION_SAVE, + selectFolder ? GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER + : (open ? GTK_FILE_CHOOSER_ACTION_OPEN : GTK_FILE_CHOOSER_ACTION_SAVE), _("_Cancel"), GTK_RESPONSE_CANCEL, (cokButtonLabel != NULL) ? cokButtonLabel : (open ? _("_Open") : _("_Save")), GTK_RESPONSE_ACCEPT, NULL ); // marks end of buttons diff --git a/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/WinFileChooser.cpp b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/WinFileChooser.cpp index 03bc2b5b..613cb072 100644 --- a/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/WinFileChooser.cpp +++ b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/WinFileChooser.cpp @@ -144,6 +144,7 @@ public: //---- helper ----------------------------------------------------------------- +#define isOptionSet( option ) ((optionsSet & com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_ ## option) != 0) #define CHECK_HRESULT( code ) { if( (code) != S_OK ) return NULL; } jobjectArray newJavaStringArray( JNIEnv* env, jsize count ) { @@ -170,12 +171,12 @@ JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibr return NULL; // handle limitations (without this, some Win32 method fails and this method returns NULL) - if( (optionsSet & FOS_PICKFOLDERS) != 0 ) { - if( open ) - fileTypes = NULL; // no filter allowed for picking folders - else - optionsSet &= ~FOS_PICKFOLDERS; // not allowed for save dialog + if( isOptionSet( FOS_PICKFOLDERS ) ) { + open = true; // always use IFileOpenDialog for picking folders + fileTypes = NULL; // no filter allowed for picking folders } + if( !open && isOptionSet( FOS_ALLOWMULTISELECT ) ) + optionsSet &= ~FOS_ALLOWMULTISELECT; // convert Java strings to C strings AutoReleaseString ctitle( env, title ); @@ -219,7 +220,7 @@ JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibr CHECK_HRESULT( dialog->SetOptions ( (existingOptions & ~optionsClear) | optionsSet ) ); // initialize filter - if( specs.count > 0 && (optionsSet & FOS_PICKFOLDERS) == 0 ) { + if( specs.count > 0 ) { CHECK_HRESULT( dialog->SetFileTypes( specs.count, specs.specs ) ); if( fileTypeIndex > 0 ) CHECK_HRESULT( dialog->SetFileTypeIndex( min( fileTypeIndex + 1, specs.count ) ) ); diff --git a/flatlaf-testing/build.gradle.kts b/flatlaf-testing/build.gradle.kts index 6fd1b64f..7215de8b 100644 --- a/flatlaf-testing/build.gradle.kts +++ b/flatlaf-testing/build.gradle.kts @@ -41,6 +41,7 @@ dependencies { implementation( libs.jide.oss ) implementation( libs.glazedlists ) implementation( libs.netbeans.api.awt ) + implementation( libs.nativejfilechooser ) components.all() } diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserTest.java new file mode 100644 index 00000000..cb66fa12 --- /dev/null +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserTest.java @@ -0,0 +1,749 @@ +/* + * 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.testing; + +import java.awt.Dialog; +import java.awt.FileDialog; +import java.awt.Frame; +import java.awt.Window; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.awt.event.WindowFocusListener; +import java.awt.event.WindowListener; +import java.awt.event.WindowStateListener; +import java.io.File; +import java.util.Arrays; +import java.util.function.Consumer; +import java.util.prefs.Preferences; +import java.util.stream.Stream; +import javax.swing.*; +import javax.swing.filechooser.FileFilter; +import javax.swing.filechooser.FileNameExtensionFilter; +import com.formdev.flatlaf.FlatSystemProperties; +import com.formdev.flatlaf.demo.DemoPrefs; +import com.formdev.flatlaf.util.SystemFileChooser; +import com.formdev.flatlaf.util.SystemInfo; +import li.flor.nativejfilechooser.NativeJFileChooser; +import net.miginfocom.swing.*; + +/** + * @author Karl Tauber + */ +public class FlatSystemFileChooserTest + extends FlatTestPanel +{ + public static void main( String[] args ) { + // macOS (see https://www.formdev.com/flatlaf/macos/) + if( SystemInfo.isMacOS ) { + // appearance of window title bars + // possible values: + // - "system": use current macOS appearance (light or dark) + // - "NSAppearanceNameAqua": use light appearance + // - "NSAppearanceNameDarkAqua": use dark appearance + // (needs to be set on main thread; setting it on AWT thread does not work) + System.setProperty( "apple.awt.application.appearance", "system" ); + } + + SwingUtilities.invokeLater( () -> { + FlatTestFrame frame = FlatTestFrame.create( args, "FlatSystemFileChooserTest" ); + frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); // necessary because of JavaFX + addListeners( frame ); + frame.showFrame( FlatSystemFileChooserTest::new ); + } ); + } + + FlatSystemFileChooserTest() { + initComponents(); + + if( !NativeJFileChooser.FX_AVAILABLE ) { + javafxOpenButton.setEnabled( false ); + javafxSaveButton.setEnabled( false ); + } + + Preferences state = DemoPrefs.getState(); + currentDirField.setText( state.get( "systemfilechooser.currentdir", "" ) ); + selectedFileField.setText( state.get( "systemfilechooser.selectedfile", "" ) ); + selectedFilesField.setText( state.get( "systemfilechooser.selectedfiles", "" ) ); + currentDirCheckBox.setSelected( state.getBoolean( "systemfilechooser.currentdir.enabled", false ) ); + selectedFileCheckBox.setSelected( state.getBoolean( "systemfilechooser.selectedfile.enabled", false ) ); + selectedFilesCheckBox.setSelected( state.getBoolean( "systemfilechooser.selectedfiles.enabled", false ) ); + + currentDirChanged(); + selectedFileChanged(); + selectedFilesChanged(); + } + + private void open() { + SystemFileChooser fc = new SystemFileChooser(); + configureSystemFileChooser( fc ); + showWithOwner( owner -> { + int result = fc.showOpenDialog( owner ); + outputSystemFileChooser( fc, result ); + } ); + } + + private void save() { + SystemFileChooser fc = new SystemFileChooser(); + configureSystemFileChooser( fc ); + showWithOwner( owner -> { + int result = fc.showSaveDialog( owner ); + outputSystemFileChooser( fc, result ); + } ); + } + + private void swingOpen() { + JFileChooser fc = new JFileChooser(); + configureSwingFileChooser( fc ); + showWithOwner( owner -> { + int result = fc.showOpenDialog( owner ); + outputSwingFileChooser( "Swing", fc, result ); + } ); + } + + private void swingSave() { + JFileChooser fc = new JFileChooser(); + configureSwingFileChooser( fc ); + showWithOwner( owner -> { + int result = fc.showSaveDialog( owner ); + outputSwingFileChooser( "Swing", fc, result ); + } ); + } + + private void awtOpen() { + showWithOwner( owner -> { + FileDialog fc = (owner instanceof Frame) + ? new FileDialog( (Frame) owner ) + : new FileDialog( (Dialog) owner ); + configureAWTFileChooser( fc, true ); + fc.setVisible( true ); + outputAWTFileChooser( fc ); + } ); + } + + private void awtSave() { + showWithOwner( owner -> { + FileDialog fc = (owner instanceof Frame) + ? new FileDialog( (Frame) owner ) + : new FileDialog( (Dialog) owner ); + configureAWTFileChooser( fc, false ); + fc.setVisible( true ); + outputAWTFileChooser( fc ); + } ); + } + + private void javafxOpen() { + JFileChooser fc = new NativeJFileChooser(); + configureSwingFileChooser( fc ); + showWithOwner( owner -> { + int result = fc.showOpenDialog( owner ); + outputSwingFileChooser( "JavaFX", fc, result ); + } ); + } + + private void javafxSave() { + JFileChooser fc = new NativeJFileChooser(); + configureSwingFileChooser( fc ); + showWithOwner( owner -> { + int result = fc.showSaveDialog( owner ); + outputSwingFileChooser( "JavaFX", fc, result ); + } ); + } + + private void configureSystemFileChooser( SystemFileChooser fc ) { + fc.setDialogTitle( n( dialogTitleField.getText() ) ); + fc.setApproveButtonText( n( approveButtonTextField.getText() ) ); + fc.setApproveButtonMnemonic( mnemonic( approveButtonMnemonicField.getText() ) ); + + // paths + if( currentDirCheckBox.isSelected() ) + fc.setCurrentDirectory( toFile( currentDirField.getText() ) ); + if( selectedFileCheckBox.isSelected() ) + fc.setSelectedFile( toFile( selectedFileField.getText() ) ); + if( selectedFilesCheckBox.isSelected() ) + fc.setSelectedFiles( toFiles( selectedFilesField.getText() ) ); + + // options + if( directorySelectionCheckBox.isSelected() ) + fc.setFileSelectionMode( SystemFileChooser.DIRECTORIES_ONLY ); + fc.setMultiSelectionEnabled( multiSelectionEnabledCheckBox.isSelected() ); + fc.setFileHidingEnabled( useFileHidingCheckBox.isSelected() ); + if( useSystemFileChooserCheckBox.isSelected() ) + System.clearProperty( FlatSystemProperties.USE_SYSTEM_FILE_CHOOSER ); + else + System.setProperty( FlatSystemProperties.USE_SYSTEM_FILE_CHOOSER, "false" ); + + //TODO filter + } + + private void configureSwingFileChooser( JFileChooser fc ) { + fc.setDialogTitle( n( dialogTitleField.getText() ) ); + fc.setApproveButtonText( n( approveButtonTextField.getText() ) ); + fc.setApproveButtonMnemonic( mnemonic( approveButtonMnemonicField.getText() ) ); + + // paths + if( currentDirCheckBox.isSelected() ) + fc.setCurrentDirectory( toFile( currentDirField.getText() ) ); + if( selectedFileCheckBox.isSelected() ) + fc.setSelectedFile( toFile( selectedFileField.getText() ) ); + if( selectedFilesCheckBox.isSelected() ) + fc.setSelectedFiles( toFiles( selectedFilesField.getText() ) ); + + // options + if( directorySelectionCheckBox.isSelected() ) + fc.setFileSelectionMode( JFileChooser.DIRECTORIES_ONLY ); + fc.setMultiSelectionEnabled( multiSelectionEnabledCheckBox.isSelected() ); + fc.setFileHidingEnabled( useFileHidingCheckBox.isSelected() ); + + // filter + String fileTypesStr = n( (String) fileTypesField.getSelectedItem() ); + String[] fileTypes = {}; + if( fileTypesStr != null ) + fileTypes = fileTypesStr.trim().split( "[,]+" ); + int fileTypeIndex = fileTypeIndexSlider.getValue(); + if( !useAcceptAllFileFilterCheckBox.isSelected() ) + fc.setAcceptAllFileFilterUsed( false ); + for( int i = 0; i < fileTypes.length; i += 2 ) { + fc.addChoosableFileFilter( "*".equals( fileTypes[i+1] ) + ? fc.getAcceptAllFileFilter() + : new FileNameExtensionFilter( fileTypes[i], fileTypes[i+1].split( ";" ) ) ); + } + FileFilter[] filters = fc.getChoosableFileFilters(); + if( filters.length > 0 ) + fc.setFileFilter( filters[Math.min( Math.max( fileTypeIndex, 0 ), filters.length - 1 )] ); + } + + private void configureAWTFileChooser( FileDialog fc, boolean open ) { + fc.setMode( open ? FileDialog.LOAD : FileDialog.SAVE ); + fc.setTitle( n( dialogTitleField.getText() ) ); + + // paths + if( currentDirCheckBox.isSelected() ) + fc.setDirectory( n( currentDirField.getText() ) ); + + // options + fc.setMultipleMode( multiSelectionEnabledCheckBox.isSelected() ); + } + + private void outputSystemFileChooser( SystemFileChooser fc, int result ) { + output( "System", fc.getDialogType() == SystemFileChooser.OPEN_DIALOG, + fc.isDirectorySelectionEnabled(), fc.isMultiSelectionEnabled(), + "result", result, + "currentDirectory", fc.getCurrentDirectory(), + "selectedFile", fc.getSelectedFile(), + "selectedFiles", fc.getSelectedFiles() ); + } + + private void outputSwingFileChooser( String type, JFileChooser fc, int result ) { + output( type, fc.getDialogType() == JFileChooser.OPEN_DIALOG, + fc.isDirectorySelectionEnabled(), fc.isMultiSelectionEnabled(), + "result", result, + "currentDirectory", fc.getCurrentDirectory(), + "selectedFile", fc.getSelectedFile(), + "selectedFiles", fc.getSelectedFiles() ); + } + + private void outputAWTFileChooser( FileDialog fc ) { + output( "AWT", fc.getMode() == FileDialog.LOAD, false, fc.isMultipleMode(), + "files", fc.getFiles(), + "directory", fc.getDirectory(), + "file", fc.getFile() ); + } + + private void output( String type, boolean open, boolean directorySelection, + boolean multiSelection, Object... values ) + { + outputField.append( "---- " + type + " " + (open ? "Open " : "Save ") + + (directorySelection ? " directory-sel " : "") + + (multiSelection ? " multi-sel " : "") + + "----\n" ); + + for( int i = 0; i < values.length; i += 2 ) { + outputField.append( values[i] + " = " ); + Object value = values[i+1]; + if( value instanceof File[] ) + outputField.append( Arrays.toString( (File[]) value ).replace( ",", "\n " ) ); + else + outputField.append( String.valueOf( value ) ); + outputField.append( "\n" ); + } + outputField.append( "\n" ); + outputField.setCaretPosition( outputField.getDocument().getLength() ); + } + + private static String n( String s ) { + return !s.isEmpty() ? s : null; + } + + private static char mnemonic( String s ) { + return !s.isEmpty() ? s.charAt( 0 ) : 0; + } + + private void showWithOwner( Consumer showConsumer ) { + Window frame = SwingUtilities.windowForComponent( this ); + if( ownerFrameRadioButton.isSelected() ) + showConsumer.accept( frame ); + else if( ownerDialogRadioButton.isSelected() ) { + JDialog dialog = new JDialog( frame, "Dummy Modal Dialog", Dialog.DEFAULT_MODALITY_TYPE ); + dialog.setDefaultCloseOperation( JDialog.DISPOSE_ON_CLOSE ); + dialog.addWindowListener( new WindowAdapter() { + @Override + public void windowOpened( WindowEvent e ) { + showConsumer.accept( dialog ); + } + } ); + dialog.setSize( 1200, 1000 ); + dialog.setLocationRelativeTo( this ); + dialog.setVisible( true ); + } else + showConsumer.accept( null ); + } + + private void currentDirChanged() { + boolean b = currentDirCheckBox.isSelected(); + currentDirField.setEditable( b ); + currentDirChooseButton.setEnabled( b ); + + DemoPrefs.getState().putBoolean( "systemfilechooser.currentdir.enabled", b ); + } + + private void selectedFileChanged() { + boolean b = selectedFileCheckBox.isSelected(); + selectedFileField.setEditable( b ); + selectedFileChooseButton.setEnabled( b ); + + DemoPrefs.getState().putBoolean( "systemfilechooser.selectedfile.enabled", b ); + } + + private void selectedFilesChanged() { + boolean b = selectedFilesCheckBox.isSelected(); + selectedFilesField.setEditable( b ); + selectedFilesChooseButton.setEnabled( b ); + + DemoPrefs.getState().putBoolean( "systemfilechooser.selectedfiles.enabled", b ); + } + + private void chooseCurrentDir() { + JFileChooser chooser = new JFileChooser(); + chooser.setDialogTitle( "Current Directory" ); + chooser.setSelectedFile( toFile( currentDirField.getText() ) ); + chooser.setFileSelectionMode( JFileChooser.DIRECTORIES_ONLY ); + if( chooser.showOpenDialog( this ) == JFileChooser.APPROVE_OPTION ) { + currentDirField.setText( toString( chooser.getSelectedFile() ) ); + putState( "systemfilechooser.currentdir", currentDirField.getText() ); + } + } + + private void chooseSelectedFile() { + JFileChooser chooser = new JFileChooser(); + chooser.setDialogTitle( "Selected File" ); + chooser.setSelectedFile( toFile( selectedFileField.getText() ) ); + chooser.setFileSelectionMode( JFileChooser.FILES_ONLY ); + if( chooser.showOpenDialog( this ) == JFileChooser.APPROVE_OPTION ) { + selectedFileField.setText( toString( chooser.getSelectedFile() ) ); + putState( "systemfilechooser.selectedfile", selectedFileField.getText() ); + } + } + + private void chooseSelectedFiles() { + JFileChooser chooser = new JFileChooser(); + chooser.setDialogTitle( "Selected Files" ); + chooser.setSelectedFiles( toFiles( selectedFilesField.getText() ) ); + chooser.setFileSelectionMode( JFileChooser.FILES_ONLY ); + chooser.setMultiSelectionEnabled( true ); + if( chooser.showOpenDialog( this ) == JFileChooser.APPROVE_OPTION ) { + selectedFilesField.setText( toString( chooser.getSelectedFiles() ) ); + putState( "systemfilechooser.selectedfiles", selectedFilesField.getText() ); + } + } + + private static File toFile( String s ) { + return !s.isEmpty() ? new File( s ) : null; + } + + private static String toString( File file ) { + return (file != null) ? file.getAbsolutePath() : null; + } + + private static File[] toFiles( String s ) { + return !s.isEmpty() + ? Stream.of( s.split( "," ) ).map( name -> new File( name ) ).toArray( File[]::new ) + : new File[0]; + } + + private static String toString( File[] files ) { + return (files != null && files.length > 0) + ? String.join( ",", Stream.of( files ).map( file -> file.getAbsolutePath() ).toArray( String[]::new ) ) + : ""; + } + + private static void putState( String key, String value ) { + if( value.isEmpty() ) + DemoPrefs.getState().remove( key ); + else + DemoPrefs.getState().put( key, value ); + } + + private static void addListeners( Window w ) { + w.addWindowListener( new WindowListener() { + @Override + public void windowOpened( WindowEvent e ) { + System.out.println( e ); + } + + @Override + public void windowIconified( WindowEvent e ) { + System.out.println( e ); + } + + @Override + public void windowDeiconified( WindowEvent e ) { + System.out.println( e ); + } + + @Override + public void windowDeactivated( WindowEvent e ) { + System.out.println( e ); + } + + @Override + public void windowClosing( WindowEvent e ) { + System.out.println( e ); + } + + @Override + public void windowClosed( WindowEvent e ) { + System.out.println( e ); + } + + @Override + public void windowActivated( WindowEvent e ) { + System.out.println( e ); + } + } ); + w.addWindowStateListener( new WindowStateListener() { + @Override + public void windowStateChanged( WindowEvent e ) { + System.out.println( e ); + } + } ); + w.addWindowFocusListener( new WindowFocusListener() { + @Override + public void windowLostFocus( WindowEvent e ) { + System.out.println( e ); + } + + @Override + public void windowGainedFocus( WindowEvent e ) { + System.out.println( e ); + } + } ); + } + + private void initComponents() { + // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents + ownerLabel = new JLabel(); + ownerFrameRadioButton = new JRadioButton(); + ownerDialogRadioButton = new JRadioButton(); + ownerNullRadioButton = new JRadioButton(); + ownerSpacer = new JPanel(null); + dialogTitleLabel = new JLabel(); + dialogTitleField = new JTextField(); + panel1 = new JPanel(); + directorySelectionCheckBox = new JCheckBox(); + multiSelectionEnabledCheckBox = new JCheckBox(); + useFileHidingCheckBox = new JCheckBox(); + useSystemFileChooserCheckBox = new JCheckBox(); + approveButtonTextLabel = new JLabel(); + approveButtonTextField = new JTextField(); + approveButtonMnemonicLabel = new JLabel(); + approveButtonMnemonicField = new JTextField(); + currentDirCheckBox = new JCheckBox(); + currentDirField = new JTextField(); + currentDirChooseButton = new JButton(); + selectedFileCheckBox = new JCheckBox(); + selectedFileField = new JTextField(); + selectedFileChooseButton = new JButton(); + selectedFilesCheckBox = new JCheckBox(); + selectedFilesField = new JTextField(); + selectedFilesChooseButton = new JButton(); + fileTypesLabel = new JLabel(); + fileTypesField = new JComboBox<>(); + fileTypeIndexLabel = new JLabel(); + fileTypeIndexSlider = new JSlider(); + useAcceptAllFileFilterCheckBox = new JCheckBox(); + openButton = new JButton(); + saveButton = new JButton(); + swingOpenButton = new JButton(); + swingSaveButton = new JButton(); + awtOpenButton = new JButton(); + awtSaveButton = new JButton(); + javafxOpenButton = new JButton(); + javafxSaveButton = new JButton(); + outputScrollPane = new JScrollPane(); + outputField = new JTextArea(); + + //======== this ======== + setLayout(new MigLayout( + "ltr,insets dialog,hidemode 3", + // columns + "[left]" + + "[grow,fill]" + + "[fill]", + // rows + "[]" + + "[]" + + "[]" + + "[]" + + "[]" + + "[]" + + "[]" + + "[]" + + "[]" + + "[grow,fill]")); + + //---- ownerLabel ---- + ownerLabel.setText("owner"); + add(ownerLabel, "cell 0 0"); + + //---- ownerFrameRadioButton ---- + ownerFrameRadioButton.setText("JFrame"); + ownerFrameRadioButton.setSelected(true); + add(ownerFrameRadioButton, "cell 1 0"); + + //---- ownerDialogRadioButton ---- + ownerDialogRadioButton.setText("JDialog"); + add(ownerDialogRadioButton, "cell 1 0"); + + //---- ownerNullRadioButton ---- + ownerNullRadioButton.setText("null"); + add(ownerNullRadioButton, "cell 1 0"); + add(ownerSpacer, "cell 1 0,growx"); + + //---- dialogTitleLabel ---- + dialogTitleLabel.setText("dialogTitle"); + add(dialogTitleLabel, "cell 0 1"); + add(dialogTitleField, "cell 1 1"); + + //======== panel1 ======== + { + panel1.setLayout(new MigLayout( + "insets 2,hidemode 3", + // columns + "[left]", + // rows + "[]0" + + "[]0" + + "[]" + + "[]")); + + //---- directorySelectionCheckBox ---- + directorySelectionCheckBox.setText("directorySelection"); + panel1.add(directorySelectionCheckBox, "cell 0 0"); + + //---- multiSelectionEnabledCheckBox ---- + multiSelectionEnabledCheckBox.setText("multiSelectionEnabled"); + panel1.add(multiSelectionEnabledCheckBox, "cell 0 1"); + + //---- useFileHidingCheckBox ---- + useFileHidingCheckBox.setText("useFileHiding"); + useFileHidingCheckBox.setSelected(true); + panel1.add(useFileHidingCheckBox, "cell 0 2"); + + //---- useSystemFileChooserCheckBox ---- + useSystemFileChooserCheckBox.setText("use SystemFileChooser"); + useSystemFileChooserCheckBox.setSelected(true); + panel1.add(useSystemFileChooserCheckBox, "cell 0 3"); + } + add(panel1, "cell 2 1 1 7,aligny top,growy 0"); + + //---- approveButtonTextLabel ---- + approveButtonTextLabel.setText("approveButtonText"); + add(approveButtonTextLabel, "cell 0 2"); + add(approveButtonTextField, "cell 1 2,growx"); + + //---- approveButtonMnemonicLabel ---- + approveButtonMnemonicLabel.setText("approveButtonMnemonic"); + add(approveButtonMnemonicLabel, "cell 1 2"); + + //---- approveButtonMnemonicField ---- + approveButtonMnemonicField.setColumns(3); + add(approveButtonMnemonicField, "cell 1 2"); + + //---- currentDirCheckBox ---- + currentDirCheckBox.setText("current directory"); + currentDirCheckBox.addActionListener(e -> currentDirChanged()); + add(currentDirCheckBox, "cell 0 3"); + add(currentDirField, "cell 1 3,growx"); + + //---- currentDirChooseButton ---- + currentDirChooseButton.setText("..."); + currentDirChooseButton.addActionListener(e -> chooseCurrentDir()); + add(currentDirChooseButton, "cell 1 3"); + + //---- selectedFileCheckBox ---- + selectedFileCheckBox.setText("selected file"); + selectedFileCheckBox.addActionListener(e -> selectedFileChanged()); + add(selectedFileCheckBox, "cell 0 4"); + add(selectedFileField, "cell 1 4,growx"); + + //---- selectedFileChooseButton ---- + selectedFileChooseButton.setText("..."); + selectedFileChooseButton.addActionListener(e -> chooseSelectedFile()); + add(selectedFileChooseButton, "cell 1 4"); + + //---- selectedFilesCheckBox ---- + selectedFilesCheckBox.setText("selected files"); + selectedFilesCheckBox.addActionListener(e -> selectedFilesChanged()); + add(selectedFilesCheckBox, "cell 0 5"); + add(selectedFilesField, "cell 1 5,growx"); + + //---- selectedFilesChooseButton ---- + selectedFilesChooseButton.setText("..."); + selectedFilesChooseButton.addActionListener(e -> chooseSelectedFiles()); + add(selectedFilesChooseButton, "cell 1 5"); + + //---- fileTypesLabel ---- + fileTypesLabel.setText("fileTypes"); + add(fileTypesLabel, "cell 0 6"); + + //---- fileTypesField ---- + fileTypesField.setEditable(true); + fileTypesField.setModel(new DefaultComboBoxModel<>(new String[] { + "Text Files,txt", + "All Files,*", + "Text Files,txt,PDF Files,pdf,All Files,*", + "Text and PDF Files,txt;pdf" + })); + add(fileTypesField, "cell 1 6"); + + //---- fileTypeIndexLabel ---- + fileTypeIndexLabel.setText("fileTypeIndex"); + add(fileTypeIndexLabel, "cell 0 7"); + + //---- fileTypeIndexSlider ---- + fileTypeIndexSlider.setMaximum(10); + fileTypeIndexSlider.setMajorTickSpacing(1); + fileTypeIndexSlider.setValue(0); + fileTypeIndexSlider.setPaintLabels(true); + fileTypeIndexSlider.setSnapToTicks(true); + add(fileTypeIndexSlider, "cell 1 7,growx"); + + //---- useAcceptAllFileFilterCheckBox ---- + useAcceptAllFileFilterCheckBox.setText("useAcceptAllFileFilter"); + useAcceptAllFileFilterCheckBox.setSelected(true); + add(useAcceptAllFileFilterCheckBox, "cell 1 7"); + + //---- openButton ---- + openButton.setText("Open..."); + openButton.addActionListener(e -> open()); + add(openButton, "cell 0 8 3 1"); + + //---- saveButton ---- + saveButton.setText("Save..."); + saveButton.addActionListener(e -> save()); + add(saveButton, "cell 0 8 3 1"); + + //---- swingOpenButton ---- + swingOpenButton.setText("Swing Open..."); + swingOpenButton.addActionListener(e -> swingOpen()); + add(swingOpenButton, "cell 0 8 3 1"); + + //---- swingSaveButton ---- + swingSaveButton.setText("Swing Save..."); + swingSaveButton.addActionListener(e -> swingSave()); + add(swingSaveButton, "cell 0 8 3 1"); + + //---- awtOpenButton ---- + awtOpenButton.setText("AWT Open..."); + awtOpenButton.addActionListener(e -> awtOpen()); + add(awtOpenButton, "cell 0 8 3 1"); + + //---- awtSaveButton ---- + awtSaveButton.setText("AWT Save..."); + awtSaveButton.addActionListener(e -> awtSave()); + add(awtSaveButton, "cell 0 8 3 1"); + + //---- javafxOpenButton ---- + javafxOpenButton.setText("JavaFX Open..."); + javafxOpenButton.addActionListener(e -> javafxOpen()); + add(javafxOpenButton, "cell 0 8 3 1"); + + //---- javafxSaveButton ---- + javafxSaveButton.setText("JavaFX Save..."); + javafxSaveButton.addActionListener(e -> javafxSave()); + add(javafxSaveButton, "cell 0 8 3 1"); + + //======== outputScrollPane ======== + { + + //---- outputField ---- + outputField.setRows(20); + outputScrollPane.setViewportView(outputField); + } + add(outputScrollPane, "cell 0 9 3 1,growx"); + + //---- ownerButtonGroup ---- + ButtonGroup ownerButtonGroup = new ButtonGroup(); + ownerButtonGroup.add(ownerFrameRadioButton); + ownerButtonGroup.add(ownerDialogRadioButton); + ownerButtonGroup.add(ownerNullRadioButton); + // JFormDesigner - End of component initialization //GEN-END:initComponents + } + + // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables + private JLabel ownerLabel; + private JRadioButton ownerFrameRadioButton; + private JRadioButton ownerDialogRadioButton; + private JRadioButton ownerNullRadioButton; + private JPanel ownerSpacer; + private JLabel dialogTitleLabel; + private JTextField dialogTitleField; + private JPanel panel1; + private JCheckBox directorySelectionCheckBox; + private JCheckBox multiSelectionEnabledCheckBox; + private JCheckBox useFileHidingCheckBox; + private JCheckBox useSystemFileChooserCheckBox; + private JLabel approveButtonTextLabel; + private JTextField approveButtonTextField; + private JLabel approveButtonMnemonicLabel; + private JTextField approveButtonMnemonicField; + private JCheckBox currentDirCheckBox; + private JTextField currentDirField; + private JButton currentDirChooseButton; + private JCheckBox selectedFileCheckBox; + private JTextField selectedFileField; + private JButton selectedFileChooseButton; + private JCheckBox selectedFilesCheckBox; + private JTextField selectedFilesField; + private JButton selectedFilesChooseButton; + private JLabel fileTypesLabel; + private JComboBox fileTypesField; + private JLabel fileTypeIndexLabel; + private JSlider fileTypeIndexSlider; + private JCheckBox useAcceptAllFileFilterCheckBox; + private JButton openButton; + private JButton saveButton; + private JButton swingOpenButton; + private JButton swingSaveButton; + private JButton awtOpenButton; + private JButton awtSaveButton; + private JButton javafxOpenButton; + private JButton javafxSaveButton; + private JScrollPane outputScrollPane; + private JTextArea outputField; + // JFormDesigner - End of variables declaration //GEN-END:variables +} diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserTest.jfd new file mode 100644 index 00000000..2aa7c66b --- /dev/null +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserTest.jfd @@ -0,0 +1,321 @@ +JFDML JFormDesigner: "8.3" encoding: "UTF-8" + +new FormModel { + contentType: "form/swing" + root: new FormRoot { + add( new FormContainer( "com.formdev.flatlaf.testing.FlatTestPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { + "$layoutConstraints": "ltr,insets dialog,hidemode 3" + "$columnConstraints": "[left][grow,fill][fill]" + "$rowConstraints": "[][][][][][][][][][grow,fill]" + } ) { + name: "this" + add( new FormComponent( "javax.swing.JLabel" ) { + name: "ownerLabel" + "text": "owner" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0" + } ) + add( new FormComponent( "javax.swing.JRadioButton" ) { + name: "ownerFrameRadioButton" + "text": "JFrame" + "selected": true + "$buttonGroup": new FormReference( "ownerButtonGroup" ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 0" + } ) + add( new FormComponent( "javax.swing.JRadioButton" ) { + name: "ownerDialogRadioButton" + "text": "JDialog" + "$buttonGroup": new FormReference( "ownerButtonGroup" ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 0" + } ) + add( new FormComponent( "javax.swing.JRadioButton" ) { + name: "ownerNullRadioButton" + "text": "null" + "$buttonGroup": new FormReference( "ownerButtonGroup" ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 0" + } ) + add( new FormComponent( "com.jformdesigner.designer.wrapper.HSpacer" ) { + name: "ownerSpacer" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 0,growx" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "dialogTitleLabel" + "text": "dialogTitle" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 1" + } ) + add( new FormComponent( "javax.swing.JTextField" ) { + name: "dialogTitleField" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 1" + } ) + add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { + "$layoutConstraints": "insets 2,hidemode 3" + "$columnConstraints": "[left]" + "$rowConstraints": "[]0[]0[][]" + } ) { + name: "panel1" + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "directorySelectionCheckBox" + "text": "directorySelection" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0" + } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "multiSelectionEnabledCheckBox" + "text": "multiSelectionEnabled" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 1" + } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "useFileHidingCheckBox" + "text": "useFileHiding" + "selected": true + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 2" + } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "useSystemFileChooserCheckBox" + "text": "use SystemFileChooser" + "selected": true + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 3" + } ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 2 1 1 7,aligny top,growy 0" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "approveButtonTextLabel" + "text": "approveButtonText" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 2" + } ) + add( new FormComponent( "javax.swing.JTextField" ) { + name: "approveButtonTextField" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 2,growx" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "approveButtonMnemonicLabel" + "text": "approveButtonMnemonic" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 2" + } ) + add( new FormComponent( "javax.swing.JTextField" ) { + name: "approveButtonMnemonicField" + "columns": 3 + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 2" + } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "currentDirCheckBox" + "text": "current directory" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "currentDirChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 3" + } ) + add( new FormComponent( "javax.swing.JTextField" ) { + name: "currentDirField" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 3,growx" + } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "currentDirChooseButton" + "text": "..." + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "chooseCurrentDir", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 3" + } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "selectedFileCheckBox" + "text": "selected file" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "selectedFileChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 4" + } ) + add( new FormComponent( "javax.swing.JTextField" ) { + name: "selectedFileField" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 4,growx" + } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "selectedFileChooseButton" + "text": "..." + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "chooseSelectedFile", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 4" + } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "selectedFilesCheckBox" + "text": "selected files" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "selectedFilesChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 5" + } ) + add( new FormComponent( "javax.swing.JTextField" ) { + name: "selectedFilesField" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 5,growx" + } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "selectedFilesChooseButton" + "text": "..." + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "chooseSelectedFiles", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 5" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "fileTypesLabel" + "text": "fileTypes" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 6" + } ) + add( new FormComponent( "javax.swing.JComboBox" ) { + name: "fileTypesField" + "editable": true + "model": new javax.swing.DefaultComboBoxModel { + selectedItem: "Text Files,txt" + addElement( "Text Files,txt" ) + addElement( "All Files,*" ) + addElement( "Text Files,txt,PDF Files,pdf,All Files,*" ) + addElement( "Text and PDF Files,txt;pdf" ) + } + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 6" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "fileTypeIndexLabel" + "text": "fileTypeIndex" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 7" + } ) + add( new FormComponent( "javax.swing.JSlider" ) { + name: "fileTypeIndexSlider" + "maximum": 10 + "majorTickSpacing": 1 + "value": 0 + "paintLabels": true + "snapToTicks": true + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 7,growx" + } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "useAcceptAllFileFilterCheckBox" + "text": "useAcceptAllFileFilter" + "selected": true + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 7" + } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "openButton" + "text": "Open..." + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "open", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 8 3 1" + } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "saveButton" + "text": "Save..." + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "save", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 8 3 1" + } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "swingOpenButton" + "text": "Swing Open..." + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "swingOpen", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 8 3 1" + } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "swingSaveButton" + "text": "Swing Save..." + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "swingSave", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 8 3 1" + } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "awtOpenButton" + "text": "AWT Open..." + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "awtOpen", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 8 3 1" + } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "awtSaveButton" + "text": "AWT Save..." + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "awtSave", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 8 3 1" + } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "javafxOpenButton" + "text": "JavaFX Open..." + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "javafxOpen", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 8 3 1" + } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "javafxSaveButton" + "text": "JavaFX Save..." + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "javafxSave", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 8 3 1" + } ) + add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) { + name: "outputScrollPane" + add( new FormComponent( "javax.swing.JTextArea" ) { + name: "outputField" + "rows": 20 + } ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 9 3 1,growx" + } ) + }, new FormLayoutConstraints( null ) { + "location": new java.awt.Point( 0, 0 ) + "size": new java.awt.Dimension( 825, 465 ) + } ) + add( new FormNonVisual( "javax.swing.ButtonGroup" ) { + name: "ownerButtonGroup" + }, new FormLayoutConstraints( null ) { + "location": new java.awt.Point( 0, 475 ) + } ) + } +} diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserWindowsTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserWindowsTest.java index 31a55aea..2aa9af1e 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserWindowsTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserWindowsTest.java @@ -19,6 +19,7 @@ package com.formdev.flatlaf.testing; import static com.formdev.flatlaf.ui.FlatNativeWindowsLibrary.*; import java.awt.Dialog; import java.awt.EventQueue; +import java.awt.Font; import java.awt.SecondaryLoop; import java.awt.Toolkit; import java.awt.Window; @@ -393,6 +394,7 @@ public class FlatSystemFileChooserWindowsTest //---- pickFoldersCheckBox ---- pickFoldersCheckBox.setText("pickFolders"); + pickFoldersCheckBox.setFont(pickFoldersCheckBox.getFont().deriveFont(pickFoldersCheckBox.getFont().getStyle() | Font.BOLD)); panel1.add(pickFoldersCheckBox, "cell 0 3"); //---- shareAwareCheckBox ---- @@ -401,6 +403,7 @@ public class FlatSystemFileChooserWindowsTest //---- forceShowHiddenCheckBox ---- forceShowHiddenCheckBox.setText("forceShowHidden"); + forceShowHiddenCheckBox.setFont(forceShowHiddenCheckBox.getFont().deriveFont(forceShowHiddenCheckBox.getFont().getStyle() | Font.BOLD)); panel1.add(forceShowHiddenCheckBox, "cell 2 3"); //---- forceFileSystemCheckBox ---- @@ -441,6 +444,7 @@ public class FlatSystemFileChooserWindowsTest //---- allowMultiSelectCheckBox ---- allowMultiSelectCheckBox.setText("allowMultiSelect"); + allowMultiSelectCheckBox.setFont(allowMultiSelectCheckBox.getFont().deriveFont(allowMultiSelectCheckBox.getFont().getStyle() | Font.BOLD)); panel1.add(allowMultiSelectCheckBox, "cell 0 7"); //---- hidePinnedPlacesCheckBox ---- diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserWindowsTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserWindowsTest.jfd index 66590a21..82828444 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserWindowsTest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserWindowsTest.jfd @@ -116,6 +116,7 @@ new FormModel { add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) { name: "pickFoldersCheckBox" "text": "pickFolders" + "font": new com.jformdesigner.model.SwingDerivedFont( null, 1, 0, false ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 3" } ) @@ -128,6 +129,7 @@ new FormModel { add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) { name: "forceShowHiddenCheckBox" "text": "forceShowHidden" + "font": new com.jformdesigner.model.SwingDerivedFont( null, 1, 0, false ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 2 3" } ) @@ -188,6 +190,7 @@ new FormModel { add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) { name: "allowMultiSelectCheckBox" "text": "allowMultiSelect" + "font": new com.jformdesigner.model.SwingDerivedFont( null, 1, 0, false ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 7" } ) diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTestFrame.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTestFrame.java index 520220a9..61b01c1c 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTestFrame.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTestFrame.java @@ -242,6 +242,9 @@ public class FlatTestFrame super.dispose(); FlatUIDefaultsInspector.hide(); + + if( getDefaultCloseOperation() == JFrame.EXIT_ON_CLOSE ) + System.exit( 0 ); } private void updateTitle() { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4d9d326c..6bd75ca2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -44,6 +44,7 @@ fifesoft-autocomplete = "com.fifesoft:autocomplete:3.3.1" # flatlaf-testing glazedlists = "com.glazedlists:glazedlists:1.11.0" netbeans-api-awt = "org.netbeans.api:org-openide-awt:RELEASE112" +nativejfilechooser = "li.flor:native-j-file-chooser:1.6.4" # flatlaf-natives-jna jna = "net.java.dev.jna:jna:5.15.0"