diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b9f9e81..7c26a87e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,15 @@ FlatLaf Change Log ## 3.7.1-SNAPSHOT +- System File Chooser: + - Update current filter before invoking approve callback and after closing + dialog. (issue #1065) + - Added `PatternFilter` to support glob file filter (e.g. `*.tar.gz`) on + Windows and on Linux, but not on macOS. (issue #1076) + - Fixed: System and Swing file dialogs were shown at the same time if + application has no other displayable window. (issue #1078) + - On Linux: Check whether required GSettings schemas are installed to avoid + application crash (occurred on NixOS with Plasma/KDE desktop). (issue #1069) - ComboBox: Added UI property `ComboBox.buttonFocusedEditableBackground`. (issue #1068) - Dialog: Some client properties (e.g. `JRootPane.titleBarShowTitle`) did not 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 14e58039..63fc9264 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 @@ -37,7 +37,7 @@ import com.formdev.flatlaf.util.SystemInfo; */ public class FlatNativeLinuxLibrary { - private static int API_VERSION_LINUX = 3003; + private static int API_VERSION_LINUX = 3004; /** * Checks whether native library is loaded/available. @@ -186,8 +186,8 @@ public class FlatNativeLinuxLibrary * 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 - * @param optionsClear options to clear; see {@code FOS_*} constants + * @param optionsSet options to set; see {@code FC_*} constants + * @param optionsClear options to clear; see {@code FC_*} constants * @param callback approve callback; or {@code null} * @param fileTypeIndex the file type that appears as selected (zero-based) * @param fileTypes file types that the dialog can open or save. @@ -195,19 +195,20 @@ public class FlatNativeLinuxLibrary * First string is the display name of the filter shown in the combobox (e.g. "Text Files"). * Subsequent strings are the filter patterns (e.g. "*.txt" or "*"). * {@code null} is required to mark end of filter. + * @param retFileTypeIndex returns selected file type (zero-based); array must be have one element * @return file path(s) that the user selected; an empty array if canceled; * or {@code null} on failures (no dialog shown) * - * @since 3.7 + * @since 3.7.1 */ public native static String[] showFileChooser( Window owner, int dark, boolean open, String title, String okButtonLabel, String currentName, String currentFolder, int optionsSet, int optionsClear, FileChooserCallback callback, - int fileTypeIndex, String... fileTypes ); + int fileTypeIndex, String[] fileTypes, int[] retFileTypeIndex ); - /** @since 3.7 */ + /** @since 3.7.1 */ public interface FileChooserCallback { - boolean approve( String[] files, long hwndFileDialog ); + boolean approve( String[] files, int fileTypeIndex, long hwndFileDialog ); } /** diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java index 460ab899..d4b1aae5 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java @@ -45,7 +45,7 @@ import com.formdev.flatlaf.util.SystemInfo; */ public class FlatNativeMacLibrary { - private static int API_VERSION_MACOS = 2002; + private static int API_VERSION_MACOS = 2003; /** * Checks whether native library is loaded/available. @@ -115,22 +115,23 @@ public class FlatNativeMacLibrary * @param fileTypes file types that the dialog can open or save. * Two or more strings and {@code null} are required for each filter. * First string is the display name of the filter shown in the combobox (e.g. "Text Files"). - * Subsequent strings are the filter patterns (e.g. "txt" or "*"). + * Subsequent strings are the file name extensions (e.g. "txt" or "*"; '.' is not supported). * {@code null} is required to mark end of filter. + * @param retFileTypeIndex returns selected file type (zero-based); array must be have one element * @return file path(s) that the user selected; an empty array if canceled; * or {@code null} on failures (no dialog shown) * - * @since 3.7 + * @since 3.7.1 */ public native static String[] showFileChooser( Window owner, int dark, boolean open, String title, String prompt, String message, String filterFieldLabel, String nameFieldLabel, String nameFieldStringValue, String directoryURL, int optionsSet, int optionsClear, FileChooserCallback callback, - int fileTypeIndex, String... fileTypes ); + int fileTypeIndex, String[] fileTypes, int[] retFileTypeIndex ); - /** @since 3.7 */ + /** @since 3.7.1 */ public interface FileChooserCallback { - boolean approve( String[] files, long hwndFileDialog ); + boolean approve( String[] files, int fileTypeIndex, long hwndFileDialog ); } /** 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 a5caaef7..83690fee 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 @@ -31,7 +31,7 @@ import com.formdev.flatlaf.util.SystemInfo; */ public class FlatNativeWindowsLibrary { - private static int API_VERSION_WINDOWS = 1002; + private static int API_VERSION_WINDOWS = 1003; private static long osBuildNumber = Long.MIN_VALUE; @@ -226,20 +226,21 @@ public class FlatNativeWindowsLibrary * Pairs of strings are required for each filter. * First string is the display name of the filter shown in the combobox (e.g. "Text Files"). * Second string is the filter pattern (e.g. "*.txt", "*.exe;*.dll" or "*.*"). + * @param retFileTypeIndex returns selected file type (zero-based); array must be have one element * @return file path(s) that the user selected; an empty array if canceled; * or {@code null} on failures (no dialog shown) * - * @since 3.7 + * @since 3.7.1 */ public native static String[] showFileChooser( Window owner, boolean open, String title, String okButtonLabel, String fileNameLabel, String fileName, String folder, String saveAsItem, String defaultFolder, String defaultExtension, int optionsSet, int optionsClear, FileChooserCallback callback, - int fileTypeIndex, String... fileTypes ); + int fileTypeIndex, String[] fileTypes, int[] retFileTypeIndex ); - /** @since 3.7 */ + /** @since 3.7.1 */ public interface FileChooserCallback { - boolean approve( String[] files, long hwndFileDialog ); + boolean approve( String[] files, int fileTypeIndex, long hwndFileDialog ); } /** 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 index 86057afe..9d6b4edb 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/SystemFileChooser.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/SystemFileChooser.java @@ -18,6 +18,8 @@ package com.formdev.flatlaf.util; import java.awt.Component; import java.awt.Dimension; +import java.awt.EventQueue; +import java.awt.Frame; import java.awt.KeyboardFocusManager; import java.awt.SecondaryLoop; import java.awt.Toolkit; @@ -28,11 +30,13 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.IdentityHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Scanner; import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Pattern; import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JOptionPane; @@ -87,7 +91,8 @@ import com.formdev.flatlaf.ui.FlatNativeWindowsLibrary; *
  • {@link JFileChooser#FILES_AND_DIRECTORIES} is not supported. *
  • {@link #getSelectedFiles()} returns selected file also in single selection mode. * {@link JFileChooser#getSelectedFiles()} only in multi selection mode. - *
  • Only file name extension filters (see {@link FileNameExtensionFilter}) are supported. + *
  • Only file name extension filters (see {@link FileNameExtensionFilter}) are supported on all platforms. + *
  • Pattern filters (see {@link PatternFilter}) are only supported on Windows and Linux, but not on macOS. *
  • If adding choosable file filters and {@link #isAcceptAllFileFilterUsed()} is {@code true}, * then the All Files filter is placed at the end of the combobox list * (as usual in current operating systems) and the first choosable filter is selected by default. @@ -584,6 +589,7 @@ public class SystemFileChooser private void checkSupportedFileFilter( FileFilter filter ) throws IllegalArgumentException { if( filter == null || filter instanceof FileNameExtensionFilter || + filter instanceof PatternFilter || filter instanceof AcceptAllFileFilter ) return; @@ -607,6 +613,11 @@ public class SystemFileChooser return filters2; } + private void updateFileFilter( List filters, int index ) { + if( index >= 0 && index < filters.size() ) + setFileFilter( filters.get( index ) ); + } + public ApproveCallback getApproveCallback() { return approveCallback; } @@ -737,6 +748,9 @@ public class SystemFileChooser } private int showDialogImpl( Component parent ) { + if( !EventQueue.isDispatchThread() ) + throw new IllegalStateException( "Must be invoked from the AWT/Swing event dispatch thread" ); + Window owner = (parent instanceof Window) ? (Window) parent : (parent != null) ? SwingUtilities.windowForComponent( parent ) : null; @@ -785,6 +799,16 @@ public class SystemFileChooser { @Override public File[] showDialog( Window owner, SystemFileChooser fc ) { + // if there is no displayable window, then AWT's auto-shutdown feature + // quits our secondary event loop (see below) immediately + // https://docs.oracle.com/en/java/javase/25/docs/api/java.desktop/java/awt/doc-files/AWTThreadIssues.html#Autoshutdown + Window dummyWindow = null; + if( !hasDisplayableWindow( owner ) ) { + // create a (not visible) displayable window to avoid AWT auto-shutdown + dummyWindow = new Window( (Frame) null ); + dummyWindow.addNotify(); + } + AtomicReference filenamesRef = new AtomicReference<>(); // create secondary event look and invoke system file dialog on a new thread @@ -795,6 +819,10 @@ public class SystemFileChooser }, "FlatLaf SystemFileChooser" ).start(); secondaryLoop.enter(); + // dispose dummy window to allow AWT to auto-shutdown + if( dummyWindow != null ) + dummyWindow.dispose(); + String[] filenames = filenamesRef.get(); // fallback to Swing file chooser if system file dialog failed or is not available @@ -831,6 +859,17 @@ public class SystemFileChooser files[i] = fsv.createFileObject( filenames[i] ); return files; } + + private static boolean hasDisplayableWindow( Window owner ) { + if( owner != null && owner.isDisplayable() ) + return true; + + for( Window window : Window.getWindows() ) { + if( window.isDisplayable() ) + return true; + } + return false; + } } //---- class WindowsFileChooserProvider ----------------------------------- @@ -890,6 +929,7 @@ public class SystemFileChooser // filter int fileTypeIndex = 0; ArrayList fileTypes = new ArrayList<>(); + ArrayList fileTypeFilters = new ArrayList<>(); if( !fc.isDirectorySelectionEnabled() ) { List filters = fc.getFiltersForDialog(); if( !filters.isEmpty() ) { @@ -898,9 +938,15 @@ public class SystemFileChooser if( filter instanceof FileNameExtensionFilter ) { fileTypes.add( filter.getDescription() ); fileTypes.add( "*." + String.join( ";*.", ((FileNameExtensionFilter)filter).getExtensions() ) ); + fileTypeFilters.add( filter ); + } else if( filter instanceof PatternFilter ) { + fileTypes.add( filter.getDescription() ); + fileTypes.add( String.join( ";", ((PatternFilter)filter).getPatterns() ) ); + fileTypeFilters.add( filter ); } else if( filter instanceof AcceptAllFileFilter ) { fileTypes.add( filter.getDescription() ); fileTypes.add( "*.*" ); + fileTypeFilters.add( filter ); } } } @@ -916,19 +962,24 @@ public class SystemFileChooser // callback FlatNativeWindowsLibrary.FileChooserCallback callback = (fc.getApproveCallback() != null) - ? (files, hwndFileDialog) -> { + ? (files, fileTypeIndex2, hwndFileDialog) -> { + fc.updateFileFilter( fileTypeFilters, fileTypeIndex2 ); return invokeApproveCallback( fc, files, new WindowsApproveContext( hwndFileDialog ) ); } : null; // show system file dialog - return FlatNativeWindowsLibrary.showFileChooser( owner, open, + int[] retFileTypeIndex = { -1 }; + String[] result = FlatNativeWindowsLibrary.showFileChooser( owner, open, fc.getDialogTitle(), approveButtonText, fc.getPlatformProperty( WINDOWS_FILE_NAME_LABEL ), fileName, folder, saveAsItem, fc.getPlatformProperty( WINDOWS_DEFAULT_FOLDER ), fc.getPlatformProperty( WINDOWS_DEFAULT_EXTENSION ), optionsSet, optionsClear, callback, - fileTypeIndex, fileTypes.toArray( new String[fileTypes.size()] ) ); + fileTypeIndex, fileTypes.toArray( new String[fileTypes.size()] ), retFileTypeIndex ); + if( result != null ) + fc.updateFileFilter( fileTypeFilters, retFileTypeIndex[0] ); + return result; } //---- class WindowsApproveContext ---- @@ -969,6 +1020,26 @@ public class SystemFileChooser private static class MacFileChooserProvider extends SystemFileChooserProvider { + @Override + public File[] showDialog( Window owner, SystemFileChooser fc ) { + // fallback to Swing file chooser if PatternFilter is used + boolean usesPatternFilter = (fc.getFileFilter() instanceof PatternFilter); + if( !usesPatternFilter ) { + for( FileFilter filter : fc.getChoosableFileFilters() ) { + if( filter instanceof PatternFilter ) { + usesPatternFilter = true; + break; + } + } + } + if( usesPatternFilter ) { + LoggingFacade.INSTANCE.logSevere( "FlatLaf: SystemFileChooser.PatternFilter is not supported on macOS. Using Swing JFileChooser.", null ); + return new SwingFileChooserProvider().showDialog( owner, fc ); + } + + return super.showDialog( owner, fc ); + } + @Override String[] showSystemDialog( Window owner, SystemFileChooser fc ) { int dark = FlatLaf.isLafDark() ? 1 : 0; @@ -1013,6 +1084,7 @@ public class SystemFileChooser // filter int fileTypeIndex = 0; ArrayList fileTypes = new ArrayList<>(); + ArrayList fileTypeFilters = new ArrayList<>(); if( !fc.isDirectorySelectionEnabled() ) { List filters = fc.getFiltersForDialog(); if( !filters.isEmpty() ) { @@ -1023,10 +1095,12 @@ public class SystemFileChooser for( String ext : ((FileNameExtensionFilter)filter).getExtensions() ) fileTypes.add( ext ); fileTypes.add( null ); + fileTypeFilters.add( filter ); } else if( filter instanceof AcceptAllFileFilter ) { fileTypes.add( filter.getDescription() ); fileTypes.add( "*" ); fileTypes.add( null ); + fileTypeFilters.add( filter ); } } } @@ -1034,18 +1108,23 @@ public class SystemFileChooser // callback FlatNativeMacLibrary.FileChooserCallback callback = (fc.getApproveCallback() != null) - ? (files, hwndFileDialog) -> { + ? (files, fileTypeIndex2, hwndFileDialog) -> { + fc.updateFileFilter( fileTypeFilters, fileTypeIndex2 ); return invokeApproveCallback( fc, files, new MacApproveContext( hwndFileDialog ) ); } : null; // show system file dialog - return FlatNativeMacLibrary.showFileChooser( owner, dark, open, + int[] retFileTypeIndex = { -1 }; + String[] result = FlatNativeMacLibrary.showFileChooser( owner, dark, open, fc.getDialogTitle(), fc.getApproveButtonText(), fc.getPlatformProperty( MAC_MESSAGE ), fc.getPlatformProperty( MAC_FILTER_FIELD_LABEL ), fc.getPlatformProperty( MAC_NAME_FIELD_LABEL ), nameFieldStringValue, directoryURL, optionsSet, optionsClear, callback, - fileTypeIndex, fileTypes.toArray( new String[fileTypes.size()] ) ); + fileTypeIndex, fileTypes.toArray( new String[fileTypes.size()] ), retFileTypeIndex ); + if( result != null ) + fc.updateFileFilter( fileTypeFilters, retFileTypeIndex[0] ); + return result; } //---- class MacApproveContext ---- @@ -1141,6 +1220,7 @@ public class SystemFileChooser // filter int fileTypeIndex = 0; ArrayList fileTypes = new ArrayList<>(); + ArrayList fileTypeFilters = new ArrayList<>(); if( !fc.isDirectorySelectionEnabled() ) { List filters = fc.getFiltersForDialog(); if( !filters.isEmpty() ) { @@ -1149,12 +1229,19 @@ public class SystemFileChooser if( filter instanceof FileNameExtensionFilter ) { fileTypes.add( filter.getDescription() ); for( String ext : ((FileNameExtensionFilter)filter).getExtensions() ) - fileTypes.add( caseInsensitiveGlobPattern( ext ) ); + fileTypes.add( "*." + caseInsensitiveGlobPattern( ext ) ); fileTypes.add( null ); + fileTypeFilters.add( filter ); + } else if( filter instanceof PatternFilter ) { + fileTypes.add( filter.getDescription() ); + for( String pattern : ((PatternFilter)filter).getPatterns() ) + fileTypes.add( caseInsensitiveGlobPattern( pattern ) ); + fileTypeFilters.add( filter ); } else if( filter instanceof AcceptAllFileFilter ) { fileTypes.add( filter.getDescription() ); fileTypes.add( "*" ); fileTypes.add( null ); + fileTypeFilters.add( filter ); } } } @@ -1162,20 +1249,24 @@ public class SystemFileChooser // callback FlatNativeLinuxLibrary.FileChooserCallback callback = (fc.getApproveCallback() != null) - ? (files, hwndFileDialog) -> { + ? (files, fileTypeIndex2, hwndFileDialog) -> { + fc.updateFileFilter( fileTypeFilters, fileTypeIndex2 ); return invokeApproveCallback( fc, files, new LinuxApproveContext( hwndFileDialog ) ); } : null; // show system file dialog - return FlatNativeLinuxLibrary.showFileChooser( owner, dark, open, + int[] retFileTypeIndex = { -1 }; + String[] result = FlatNativeLinuxLibrary.showFileChooser( owner, dark, open, fc.getDialogTitle(), approveButtonText, currentName, currentFolder, optionsSet, optionsClear, callback, - fileTypeIndex, fileTypes.toArray( new String[fileTypes.size()] ) ); + fileTypeIndex, fileTypes.toArray( new String[fileTypes.size()] ), retFileTypeIndex ); + if( result != null ) + fc.updateFileFilter( fileTypeFilters, retFileTypeIndex[0] ); + return result; } private String caseInsensitiveGlobPattern( String ext ) { StringBuilder buf = new StringBuilder(); - buf.append( "*." ); int len = ext.length(); for( int i = 0; i < len; i++ ) { char ch = ext.charAt( i ); @@ -1253,6 +1344,8 @@ public class SystemFileChooser { @Override public File[] showDialog( Window owner, SystemFileChooser fc ) { + IdentityHashMap filterMap = new IdentityHashMap<>(); + JFileChooser chooser = new JFileChooser() { @Override public void approveSelection() { @@ -1273,6 +1366,7 @@ public class SystemFileChooser // callback ApproveCallback approveCallback = fc.getApproveCallback(); if( approveCallback != null ) { + updateFileFilter( fc, this, filterMap ); int result = approveCallback.approve( files, new SwingApproveContext( this ) ); if( result == CANCEL_OPTION ) return; @@ -1313,6 +1407,7 @@ public class SystemFileChooser chooser.addChoosableFileFilter( jfilter ); if( filter == currentFilter ) chooser.setFileFilter( jfilter ); + filterMap.put( jfilter, filter ); } } } @@ -1340,6 +1435,7 @@ public class SystemFileChooser // show dialog int result = chooser.showDialog( owner, null ); + updateFileFilter( fc, chooser, filterMap ); // save window size Dimension windowSize = chooser.getSize(); @@ -1361,12 +1457,24 @@ public class SystemFileChooser return new javax.swing.filechooser.FileNameExtensionFilter( ((FileNameExtensionFilter)filter).getDescription(), ((FileNameExtensionFilter)filter).getExtensions() ); + } else if( filter instanceof PatternFilter ) { + return new SwingGlobFilter( + ((PatternFilter)filter).getDescription(), + ((PatternFilter)filter).getPatterns() ); } else if( filter instanceof AcceptAllFileFilter ) return chooser.getAcceptAllFileFilter(); else return null; } + private void updateFileFilter( SystemFileChooser fc, JFileChooser chooser, + IdentityHashMap filterMap ) + { + FileFilter fileFilter = filterMap.get( chooser.getFileFilter() ); + if( fileFilter != null ) + fc.setFileFilter( fileFilter ); + } + private static boolean checkMustExist( JFileChooser chooser, File[] files ) { for( File file : files ) { if( !file.exists() ) { @@ -1442,6 +1550,86 @@ public class SystemFileChooser null, buttons, buttons[Math.min( Math.max( defaultButton, 0 ), buttons.length - 1 )] ); } } + + //---- class SwingGlobFilter ------------------------------------------ + + private static class SwingGlobFilter + extends javax.swing.filechooser.FileFilter + { + private final String description; + private final String[] patterns; + private Pattern regexPattern; + + SwingGlobFilter( String description, String... patterns ) { + this.description = description; + this.patterns = patterns; + } + + @Override + public String getDescription() { + return description; + } + + @Override + public boolean accept( File f ) { + if( f == null ) + return false; + + if( f.isDirectory() ) + return true; + + initRegexPattern(); + return regexPattern.matcher( f.getName() ).matches(); + } + + private void initRegexPattern() { + if( regexPattern != null ) + return; + + StringBuilder buf = new StringBuilder(); + for( String pattern : patterns ) { + if( buf.length() > 0 ) + buf.append( '|' ); + glob2regexPattern( pattern, buf ); + } + regexPattern = Pattern.compile( buf.toString(), Pattern.CASE_INSENSITIVE ); + } + + private static void glob2regexPattern( String globPattern, StringBuilder buf ) { + int globLength = globPattern.length(); + + // on windows, a pattern ending with "*.*" is equal to ending with "*" + if( SystemInfo.isWindows && globPattern.endsWith( "*.*" ) ) + globLength -= 2; + + for( int i = 0; i < globLength; i++ ) { + char ch = globPattern.charAt( i ); + switch( ch ) { + // glob pattern + case '*': buf.append( ".*" ); break; + case '?': buf.append( '.' ); break; + + // escape special regex characters + case '\\': + case '.': + case '+': + case '^': + case '$': + case '(': + case ')': + case '{': + case '}': + case '[': + case ']': + case '|': + buf.append( '\\' ).append( ch ); + break; + + default: buf.append( ch ); break; + } + } + } + } } //---- class FileFilter --------------------------------------------------- @@ -1493,6 +1681,67 @@ public class SystemFileChooser } } + //---- class PatternFilter ------------------------------------------------ + + /** + * A case-insensitive file filter which accepts file patterns containing + * the wildcard characters {@code *?} on Windows and Linux. + *
      + *
    • {@code '*'} matches any sequence of characters. + *
    • {@code '?'} matches any single character. + *
    + * Sample filters: {@code *.tar.gz} or {@code *_copy.txt} + *

    + * Warning: This filter is not supported on macOS. + * If used on macOS, the Swing file chooser {@link JFileChooser} is shown + * (instead of macOS file dialog) and a warning is logged. + * To avoid this, do not use this filter on macOS. + *

    + * E.g.: + *

    {@code
    +	 * if( SystemInfo.isMacOS )
    +	 *     chooser.addChoosableFileFilter( new FileNameExtensionFilter( "Compressed TAR", "tgz" ) );
    +	 * else
    +	 *     chooser.addChoosableFileFilter( new PatternFilter( "Compressed TAR", "*.tar.gz" ) );
    +	 * } );
    +	 * }
    + * + * @see FileNameExtensionFilter + * @since 3.7.1 + */ + public static final class PatternFilter + extends FileFilter + { + private final String description; + private final String[] patterns; + + public PatternFilter( String description, String... patterns ) { + if( patterns == null || patterns.length == 0 ) + throw new IllegalArgumentException( "Missing patterns" ); + for( String extension : patterns ) { + if( extension == null || extension.isEmpty() ) + throw new IllegalArgumentException( "Pattern is null or empty string" ); + } + + this.description = description; + this.patterns = patterns.clone(); + } + + @Override + public String getDescription() { + return description; + } + + public String[] getPatterns() { + return patterns.clone(); + } + + @Override + public String toString() { + return super.toString() + "[description=" + description + " patterns=" + Arrays.toString( patterns ) + "]"; + } + } + //---- class AcceptAllFileFilter ------------------------------------------ private static final class AcceptAllFileFilter diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/flatlaf-windows-arm64.dll b/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/flatlaf-windows-arm64.dll index 9fbd790a..8afc51fc 100644 Binary files a/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/flatlaf-windows-arm64.dll and b/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/flatlaf-windows-arm64.dll differ diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/flatlaf-windows-x86.dll b/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/flatlaf-windows-x86.dll index 96e41e99..d7055846 100644 Binary files a/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/flatlaf-windows-x86.dll and b/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/flatlaf-windows-x86.dll differ diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/flatlaf-windows-x86_64.dll b/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/flatlaf-windows-x86_64.dll index 6b3d15c5..473cf1f6 100644 Binary files a/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/flatlaf-windows-x86_64.dll and b/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/flatlaf-windows-x86_64.dll differ diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-linux-arm64.so b/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-linux-arm64.so index 82c645a6..51348e78 100755 Binary files a/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-linux-arm64.so and b/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-linux-arm64.so differ diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-linux-x86_64.so b/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-linux-x86_64.so index 9e54cc55..7af83bcd 100644 Binary files a/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-linux-x86_64.so and b/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-linux-x86_64.so differ diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-macos-arm64.dylib b/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-macos-arm64.dylib index 5dc29eb0..933bfb9a 100755 Binary files a/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-macos-arm64.dylib and b/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-macos-arm64.dylib differ diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-macos-x86_64.dylib b/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-macos-x86_64.dylib index 88c46142..199cff3c 100755 Binary files a/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-macos-x86_64.dylib and b/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-macos-x86_64.dylib differ diff --git a/flatlaf-natives/flatlaf-natives-linux/src/main/cpp/ApiVersion.cpp b/flatlaf-natives/flatlaf-natives-linux/src/main/cpp/ApiVersion.cpp index ae0a7d73..6957df8d 100644 --- a/flatlaf-natives/flatlaf-natives-linux/src/main/cpp/ApiVersion.cpp +++ b/flatlaf-natives/flatlaf-natives-linux/src/main/cpp/ApiVersion.cpp @@ -24,7 +24,7 @@ // increase this version if changing API or functionality of native library // also update version in Java class com.formdev.flatlaf.ui.FlatNativeLinuxLibrary -#define API_VERSION_LINUX 3003 +#define API_VERSION_LINUX 3004 //---- JNI methods ------------------------------------------------------------ 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 828ba66e..98105772 100644 --- a/flatlaf-natives/flatlaf-natives-linux/src/main/cpp/GtkFileChooser.cpp +++ b/flatlaf-natives/flatlaf-natives-linux/src/main/cpp/GtkFileChooser.cpp @@ -33,6 +33,9 @@ extern Window getWindowHandle( JNIEnv* env, JAWT* awt, jobject window, Display** // declare internal methods static jobjectArray fileListToStringArray( JNIEnv* env, GSList* fileList ); +// fields +static int settingsSchemaInstalled = -1; + //---- helper ----------------------------------------------------------------- #define isOptionSet( option ) ((optionsSet & com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_ ## option) != 0) @@ -58,6 +61,7 @@ static void initFilters( GtkFileChooser* chooser, JNIEnv* env, jint fileTypeInde gtk_file_chooser_add_filter( chooser, filter ); if( fileTypeIndex == filterIndex ) gtk_file_chooser_set_filter( chooser, filter ); + g_object_set_data( G_OBJECT( filter ), "flatlaf-filter-index", GINT_TO_POINTER( filterIndex + 1 ) ); filter = NULL; filterIndex++; } @@ -130,32 +134,42 @@ struct ResponseData { JNIEnv* env; jobject callback; GSList* fileList; + int filterIndex; ResponseData( JNIEnv* _env, jobject _callback ) { env = _env; callback = _callback; fileList = NULL; + filterIndex = -1; } }; static void handle_response( GtkWidget* dialog, gint responseId, gpointer data ) { + GtkFileChooser* chooser = GTK_FILE_CHOOSER( dialog ); + ResponseData *response = static_cast( data ); + + // get selected filter (even if user cancels dialog) + GtkFileFilter* filter = gtk_file_chooser_get_filter( chooser ); + response->filterIndex = (filter != NULL) + ? GPOINTER_TO_INT( g_object_get_data( G_OBJECT( filter ), "flatlaf-filter-index" ) ) - 1 + : -1; + // get filenames if user pressed OK if( responseId == GTK_RESPONSE_ACCEPT ) { - ResponseData *response = static_cast( data ); if( response->callback != NULL ) { - GSList* fileList = gtk_file_chooser_get_filenames( GTK_FILE_CHOOSER( dialog ) ); + GSList* fileList = gtk_file_chooser_get_filenames( chooser ); jobjectArray files = fileListToStringArray( response->env, fileList ); - + jint filterIndex = response->filterIndex; GtkWindow* window = GTK_WINDOW( dialog ); // invoke callback: boolean approve( String[] files, long hwnd ); jclass cls = response->env->GetObjectClass( response->callback ); - jmethodID approveID = response->env->GetMethodID( cls, "approve", "([Ljava/lang/String;J)Z" ); - if( approveID != NULL && !response->env->CallBooleanMethod( response->callback, approveID, files, window ) ) + jmethodID approveID = response->env->GetMethodID( cls, "approve", "([Ljava/lang/String;IJ)Z" ); + if( approveID != NULL && !response->env->CallBooleanMethod( response->callback, approveID, files, filterIndex, window ) ) return; // keep dialog open } - response->fileList = gtk_file_chooser_get_filenames( GTK_FILE_CHOOSER( dialog ) ); + response->fileList = gtk_file_chooser_get_filenames( chooser ); } // hide/destroy file dialog and quit loop @@ -170,12 +184,29 @@ extern "C" JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_showFileChooser ( JNIEnv* env, jclass cls, jobject owner, jint dark, jboolean open, jstring title, jstring okButtonLabel, jstring currentName, jstring currentFolder, - jint optionsSet, jint optionsClear, jobject callback, jint fileTypeIndex, jobjectArray fileTypes ) + jint optionsSet, jint optionsClear, jobject callback, + jint fileTypeIndex, jobjectArray fileTypes, jintArray retFileTypeIndex ) { // initialize GTK if( !gtk_init_check( NULL, NULL ) ) return NULL; + // check whether required GSettings schemas are installed (e.g. on NixOS) + // this avoids output of following message on console, followed by an application crash: + // GLib-GIO-ERROR: No GSettings schemas are installed on the system + if( settingsSchemaInstalled < 0 ) { + GSettingsSchemaSource* schemaSource = g_settings_schema_source_get_default(); + GSettingsSchema* schema = (schemaSource != NULL) + ? g_settings_schema_source_lookup( schemaSource, "org.gtk.Settings.FileChooser", FALSE ) + : NULL; + if( schema != NULL ) + g_settings_schema_unref( schema ); + + settingsSchemaInstalled = (schema != NULL); + } + if( settingsSchemaInstalled <= 0 ) + return NULL; + // convert Java strings to C strings AutoReleaseStringUTF8 ctitle( env, title ); AutoReleaseStringUTF8 cokButtonLabel( env, okButtonLabel ); @@ -282,7 +313,11 @@ JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrar // start event loop (will be quit in respone handler) gtk_main(); - // canceled? + // return selected filter + jint selectedFilterIndex = responseData.filterIndex; + env->SetIntArrayRegion( retFileTypeIndex, 0, 1, &selectedFilterIndex ); + + // return empty array if canceled if( responseData.fileList == NULL ) return newJavaStringArray( env, 0 ); diff --git a/flatlaf-natives/flatlaf-natives-linux/src/main/headers/com_formdev_flatlaf_ui_FlatNativeLinuxLibrary.h b/flatlaf-natives/flatlaf-natives-linux/src/main/headers/com_formdev_flatlaf_ui_FlatNativeLinuxLibrary.h index 2d855b67..1194917a 100644 --- a/flatlaf-natives/flatlaf-natives-linux/src/main/headers/com_formdev_flatlaf_ui_FlatNativeLinuxLibrary.h +++ b/flatlaf-natives/flatlaf-natives-linux/src/main/headers/com_formdev_flatlaf_ui_FlatNativeLinuxLibrary.h @@ -64,10 +64,10 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_is /* * Class: com_formdev_flatlaf_ui_FlatNativeLinuxLibrary * Method: showFileChooser - * Signature: (Ljava/awt/Window;IZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IILcom/formdev/flatlaf/ui/FlatNativeLinuxLibrary/FileChooserCallback;I[Ljava/lang/String;)[Ljava/lang/String; + * Signature: (Ljava/awt/Window;IZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IILcom/formdev/flatlaf/ui/FlatNativeLinuxLibrary/FileChooserCallback;I[Ljava/lang/String;[I)[Ljava/lang/String; */ JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_showFileChooser - (JNIEnv *, jclass, jobject, jint, jboolean, jstring, jstring, jstring, jstring, jint, jint, jobject, jint, jobjectArray); + (JNIEnv *, jclass, jobject, jint, jboolean, jstring, jstring, jstring, jstring, jint, jint, jobject, jint, jobjectArray, jintArray); /* * Class: com_formdev_flatlaf_ui_FlatNativeLinuxLibrary diff --git a/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h b/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h index 301039b2..9138a85d 100644 --- a/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h +++ b/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h @@ -82,10 +82,10 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_togg /* * Class: com_formdev_flatlaf_ui_FlatNativeMacLibrary * Method: showFileChooser - * Signature: (Ljava/awt/Window;IZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IILcom/formdev/flatlaf/ui/FlatNativeMacLibrary/FileChooserCallback;I[Ljava/lang/String;)[Ljava/lang/String; + * Signature: (Ljava/awt/Window;IZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IILcom/formdev/flatlaf/ui/FlatNativeMacLibrary/FileChooserCallback;I[Ljava/lang/String;[I)[Ljava/lang/String; */ JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_showFileChooser - (JNIEnv *, jclass, jobject, jint, jboolean, jstring, jstring, jstring, jstring, jstring, jstring, jstring, jint, jint, jobject, jint, jobjectArray); + (JNIEnv *, jclass, jobject, jint, jboolean, jstring, jstring, jstring, jstring, jstring, jstring, jstring, jint, jint, jobject, jint, jobjectArray, jintArray); /* * Class: com_formdev_flatlaf_ui_FlatNativeMacLibrary diff --git a/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/ApiVersion.mm b/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/ApiVersion.mm index b9b9a9b4..3f44bf0b 100644 --- a/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/ApiVersion.mm +++ b/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/ApiVersion.mm @@ -24,7 +24,7 @@ // increase this version if changing API or functionality of native library // also update version in Java class com.formdev.flatlaf.ui.FlatNativeMacLibrary -#define API_VERSION_MACOS 2002 +#define API_VERSION_MACOS 2003 //---- JNI methods ------------------------------------------------------------ diff --git a/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacFileChooser.mm b/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacFileChooser.mm index 52c2b28a..729df865 100644 --- a/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacFileChooser.mm +++ b/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacFileChooser.mm @@ -35,6 +35,7 @@ static NSArray* getDialogURLs( NSSavePanel* dialog ); @interface FileChooserDelegate : NSObject { NSArray* _filters; + int _selectedFormatIndex; JavaVM* _jvm; jobject _callback; @@ -43,18 +44,26 @@ static NSArray* getDialogURLs( NSSavePanel* dialog ); @property (nonatomic, assign) NSSavePanel* dialog; + - (id) init; - (void) initFilterAccessoryView: (NSMutableArray*)filters :(int)filterIndex :(NSString*)filterFieldLabel :(bool)showSingleFilterField; - (void) selectFormat: (id)sender; - (void) selectFormatAtIndex: (int)index; + - (int) selectedFormatIndex; @end @implementation FileChooserDelegate + - (id) init { + _selectedFormatIndex = -1; + return self; + } + - (void) initFilterAccessoryView: (NSMutableArray*)filters :(int)filterIndex :(NSString*)filterFieldLabel :(bool)showSingleFilterField { _filters = filters; + _selectedFormatIndex = filterIndex; // get filter names NSArray* filterNames = filters.lastObject; @@ -125,6 +134,12 @@ static NSArray* getDialogURLs( NSSavePanel* dialog ); // to support older macOS versions 10.14+ and because of some problems with allowedContentTypes: // https://github.com/chromium/chromium/blob/d8e0032963b7ca4728ff4117933c0feb3e479b7a/components/remote_cocoa/app_shim/select_file_dialog_bridge.mm#L209-232 _dialog.allowedFileTypes = [fileTypes containsObject:@"*"] ? nil : fileTypes; + + _selectedFormatIndex = index; + } + + - (int) selectedFormatIndex { + return _selectedFormatIndex; } //---- NSOpenSavePanelDelegate ---- @@ -161,12 +176,13 @@ static NSArray* getDialogURLs( NSSavePanel* dialog ); JNI_THREAD_ENTER( _jvm, true ) jobjectArray files = urlsToStringArray( env, urls ); + jint selectedFormatIndex = ((FileChooserDelegate*)((NSSavePanel*)sender).delegate).selectedFormatIndex; jlong window = (jlong) sender; // invoke callback: boolean approve( String[] files, long hwnd ); jclass cls = env->GetObjectClass( _callback ); - jmethodID approveID = env->GetMethodID( cls, "approve", "([Ljava/lang/String;J)Z" ); - if( approveID != NULL && !env->CallBooleanMethod( _callback, approveID, files, window ) ) { + jmethodID approveID = env->GetMethodID( cls, "approve", "([Ljava/lang/String;IJ)Z" ); + if( approveID != NULL && !env->CallBooleanMethod( _callback, approveID, files, selectedFormatIndex, window ) ) { _urlsSet = NULL; return false; // keep dialog open } @@ -265,7 +281,8 @@ JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_ ( JNIEnv* env, jclass cls, jobject owner, jint dark, jboolean open, jstring title, jstring prompt, jstring message, jstring filterFieldLabel, jstring nameFieldLabel, jstring nameFieldStringValue, jstring directoryURL, - jint optionsSet, jint optionsClear, jobject callback, jint fileTypeIndex, jobjectArray fileTypes ) + jint optionsSet, jint optionsClear, jobject callback, + jint fileTypeIndex, jobjectArray fileTypes, jintArray retFileTypeIndex ) { JNI_COCOA_ENTER() @@ -365,17 +382,26 @@ JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_ // show dialog NSModalResponse response = [dialog runModal]; + + // return selected filter + jint selectedFormatIndex = delegate.selectedFormatIndex; + env->SetIntArrayRegion( retFileTypeIndex, 0, 1, &selectedFormatIndex ); + [delegate release]; + + // return empty array if canceled if( response != NSModalResponseOK ) { *purls = @[]; return; } + // get selected file(s) *purls = getDialogURLs( dialog ); JNI_COCOA_CATCH() }]; + // return null on failures if( urls == NULL ) return NULL; diff --git a/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/ApiVersion.cpp b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/ApiVersion.cpp index 0f2e7a97..c8c204b8 100644 --- a/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/ApiVersion.cpp +++ b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/ApiVersion.cpp @@ -24,7 +24,7 @@ // increase this version if changing API or functionality of native library // also update version in Java class com.formdev.flatlaf.ui.FlatNativeWindowsLibrary -#define API_VERSION_WINDOWS 1002 +#define API_VERSION_WINDOWS 1003 //---- JNI methods ------------------------------------------------------------ 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 85a7b719..0a9a97d5 100644 --- a/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/WinFileChooser.cpp +++ b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/WinFileChooser.cpp @@ -128,6 +128,11 @@ public: } else files = getFiles( env, false, dialog ); + // get selected filter + UINT selectedFileTypeIndex = 0; + dialog->GetFileTypeIndex( &selectedFileTypeIndex ); + jint jselectedFileTypeIndex = selectedFileTypeIndex - 1; + // get hwnd of file dialog HWND hwndFileDialog = 0; AutoReleasePtr window; @@ -136,10 +141,10 @@ public: // invoke callback: boolean approve( String[] files, long hwnd ); jclass cls = env->GetObjectClass( callback ); - jmethodID approveID = env->GetMethodID( cls, "approve", "([Ljava/lang/String;J)Z" ); + jmethodID approveID = env->GetMethodID( cls, "approve", "([Ljava/lang/String;IJ)Z" ); if( approveID == NULL ) return S_OK; - return env->CallBooleanMethod( callback, approveID, files, hwndFileDialog ) ? S_OK : S_FALSE; + return env->CallBooleanMethod( callback, approveID, files, jselectedFileTypeIndex, hwndFileDialog ) ? S_OK : S_FALSE; } IFACEMETHODIMP OnFolderChange( IFileDialog* ) { return S_OK; } @@ -147,7 +152,7 @@ public: IFACEMETHODIMP OnHelp( IFileDialog* ) { return S_OK; } IFACEMETHODIMP OnSelectionChange( IFileDialog* ) { return S_OK; } IFACEMETHODIMP OnShareViolation( IFileDialog*, IShellItem*, FDE_SHAREVIOLATION_RESPONSE* ) { return S_OK; } - IFACEMETHODIMP OnTypeChange( IFileDialog*pfd ) { return S_OK; } + IFACEMETHODIMP OnTypeChange( IFileDialog* ) { return S_OK; } IFACEMETHODIMP OnOverwrite( IFileDialog*, IShellItem*, FDE_OVERWRITE_RESPONSE* ) { return S_OK; } //---- IUnknown methods ---- @@ -213,7 +218,8 @@ JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibr ( JNIEnv* env, jclass cls, jobject owner, jboolean open, jstring title, jstring okButtonLabel, jstring fileNameLabel, jstring fileName, jstring folder, jstring saveAsItem, jstring defaultFolder, jstring defaultExtension, - jint optionsSet, jint optionsClear, jobject callback, jint fileTypeIndex, jobjectArray fileTypes ) + jint optionsSet, jint optionsClear, jobject callback, + jint fileTypeIndex, jobjectArray fileTypes, jintArray retFileTypeIndex ) { // initialize COM library CoInitializer coInitializer; @@ -285,6 +291,14 @@ JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibr HWND hwndOwner = (owner != NULL) ? getWindowHandle( env, owner ) : NULL; HRESULT hr = dialog->Show( hwndOwner ); dialog->Unadvise( dwCookie ); + + // return selected filter + UINT selectedFileTypeIndex = 0; + CHECK_HRESULT( dialog->GetFileTypeIndex( &selectedFileTypeIndex ) ); + jint jselectedFileTypeIndex = selectedFileTypeIndex - 1; + env->SetIntArrayRegion( retFileTypeIndex, 0, 1, &jselectedFileTypeIndex ); + + // return empty array if canceled if( hr == HRESULT_FROM_WIN32(ERROR_CANCELLED) ) return newJavaStringArray( env, 0 ); CHECK_HRESULT( hr ); diff --git a/flatlaf-natives/flatlaf-natives-windows/src/main/headers/com_formdev_flatlaf_ui_FlatNativeWindowsLibrary.h b/flatlaf-natives/flatlaf-natives-windows/src/main/headers/com_formdev_flatlaf_ui_FlatNativeWindowsLibrary.h index b5b33245..05acfbae 100644 --- a/flatlaf-natives/flatlaf-natives-windows/src/main/headers/com_formdev_flatlaf_ui_FlatNativeWindowsLibrary.h +++ b/flatlaf-natives/flatlaf-natives-windows/src/main/headers/com_formdev_flatlaf_ui_FlatNativeWindowsLibrary.h @@ -116,10 +116,10 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_ /* * Class: com_formdev_flatlaf_ui_FlatNativeWindowsLibrary * Method: showFileChooser - * Signature: (Ljava/awt/Window;ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IILcom/formdev/flatlaf/ui/FlatNativeWindowsLibrary/FileChooserCallback;I[Ljava/lang/String;)[Ljava/lang/String; + * Signature: (Ljava/awt/Window;ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IILcom/formdev/flatlaf/ui/FlatNativeWindowsLibrary/FileChooserCallback;I[Ljava/lang/String;[I)[Ljava/lang/String; */ JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_showFileChooser - (JNIEnv *, jclass, jobject, jboolean, jstring, jstring, jstring, jstring, jstring, jstring, jstring, jstring, jint, jint, jobject, jint, jobjectArray); + (JNIEnv *, jclass, jobject, jboolean, jstring, jstring, jstring, jstring, jstring, jstring, jstring, jstring, jint, jint, jobject, jint, jobjectArray, jintArray); /* * Class: com_formdev_flatlaf_ui_FlatNativeWindowsLibrary diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserLinuxTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserLinuxTest.java index 52af4b9a..95b35125 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserLinuxTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserLinuxTest.java @@ -110,8 +110,8 @@ public class FlatSystemFileChooserLinuxTest } int fileTypeIndex = fileTypeIndexSlider.getValue(); - FlatNativeLinuxLibrary.FileChooserCallback callback = (files, hwndFileDialog) -> { - System.out.println( " -- callback " + hwndFileDialog + " " + Arrays.toString( files ) ); + FlatNativeLinuxLibrary.FileChooserCallback callback = (files, fileTypeIndex2, hwndFileDialog) -> { + System.out.println( " -- callback " + hwndFileDialog + " " + Arrays.toString( files ) + " " + fileTypeIndex2 ); if( showMessageDialogOnOKCheckBox.isSelected() ) { System.out.println( FlatNativeLinuxLibrary.showMessageDialog( hwndFileDialog, JOptionPane.INFORMATION_MESSAGE, @@ -124,24 +124,28 @@ public class FlatSystemFileChooserLinuxTest int dark = FlatLaf.isLafDark() ? 1 : 0; if( direct ) { + int[] retFileTypeIndex = { -1 }; String[] files = FlatNativeLinuxLibrary.showFileChooser( owner, dark, open, title, okButtonLabel, currentName, currentFolder, - optionsSet.get(), optionsClear.get(), callback, fileTypeIndex, fileTypes ); + optionsSet.get(), optionsClear.get(), callback, + fileTypeIndex, fileTypes, retFileTypeIndex ); - filesField.setText( (files != null) ? Arrays.toString( files ).replace( ',', '\n' ) : "null" ); + outputResult( files, retFileTypeIndex[0] ); } else { SecondaryLoop secondaryLoop = Toolkit.getDefaultToolkit().getSystemEventQueue().createSecondaryLoop(); String[] fileTypes2 = fileTypes; new Thread( () -> { + int[] retFileTypeIndex = { -1 }; String[] files = FlatNativeLinuxLibrary.showFileChooser( owner, dark, open, title, okButtonLabel, currentName, currentFolder, - optionsSet.get(), optionsClear.get(), callback, fileTypeIndex, fileTypes2 ); + optionsSet.get(), optionsClear.get(), callback, + fileTypeIndex, fileTypes2, retFileTypeIndex ); System.out.println( " secondaryLoop.exit() returned " + secondaryLoop.exit() ); EventQueue.invokeLater( () -> { - filesField.setText( (files != null) ? Arrays.toString( files ).replace( ',', '\n' ) : "null" ); + outputResult( files, retFileTypeIndex[0] ); } ); } ).start(); @@ -150,6 +154,11 @@ public class FlatSystemFileChooserLinuxTest } } + private void outputResult( String[] files, int retFileTypeIndex ) { + filesField.setText( (files != null) ? Arrays.toString( files ).replace( ',', '\n' ) : "null" ); + filesField.append( "\n\nretFileTypeIndex " + retFileTypeIndex ); + } + private static String n( String s ) { return s != null && !s.isEmpty() ? s : null; } diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserMacTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserMacTest.java index 5c6c2a99..1af07ef9 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserMacTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserMacTest.java @@ -145,8 +145,8 @@ public class FlatSystemFileChooserMacTest } int fileTypeIndex = fileTypeIndexSlider.getValue(); - FlatNativeMacLibrary.FileChooserCallback callback = (files, hwndFileDialog) -> { - System.out.println( " -- callback " + hwndFileDialog + " " + Arrays.toString( files ) ); + FlatNativeMacLibrary.FileChooserCallback callback = (files, fileTypeIndex2, hwndFileDialog) -> { + System.out.println( " -- callback " + hwndFileDialog + " " + Arrays.toString( files ) + " " + fileTypeIndex2 ); if( showMessageDialogOnOKCheckBox.isSelected() ) { int result = FlatNativeMacLibrary.showMessageDialog( hwndFileDialog, JOptionPane.INFORMATION_MESSAGE, @@ -160,26 +160,30 @@ public class FlatSystemFileChooserMacTest int dark = FlatLaf.isLafDark() ? 1 : 0; if( direct ) { + int[] retFileTypeIndex = { -1 }; String[] files = FlatNativeMacLibrary.showFileChooser( owner, dark, open, title, prompt, message, filterFieldLabel, nameFieldLabel, nameFieldStringValue, directoryURL, - optionsSet.get(), optionsClear.get(), callback, fileTypeIndex, fileTypes ); + optionsSet.get(), optionsClear.get(), callback, + fileTypeIndex, fileTypes, retFileTypeIndex ); - filesField.setText( (files != null) ? Arrays.toString( files ).replace( ',', '\n' ) : "null" ); + outputResult( files, retFileTypeIndex[0] ); } else { SecondaryLoop secondaryLoop = Toolkit.getDefaultToolkit().getSystemEventQueue().createSecondaryLoop(); String[] fileTypes2 = fileTypes; new Thread( () -> { + int[] retFileTypeIndex = { -1 }; String[] files = FlatNativeMacLibrary.showFileChooser( owner, dark, open, title, prompt, message, filterFieldLabel, nameFieldLabel, nameFieldStringValue, directoryURL, - optionsSet.get(), optionsClear.get(), callback, fileTypeIndex, fileTypes2 ); + optionsSet.get(), optionsClear.get(), callback, + fileTypeIndex, fileTypes2, retFileTypeIndex ); System.out.println( " secondaryLoop.exit() returned " + secondaryLoop.exit() ); SwingUtilities.invokeLater( () -> { - filesField.setText( (files != null) ? Arrays.toString( files ).replace( ',', '\n' ) : "null" ); + outputResult( files, retFileTypeIndex[0] ); } ); } ).start(); @@ -188,6 +192,11 @@ public class FlatSystemFileChooserMacTest } } + private void outputResult( String[] files, int retFileTypeIndex ) { + filesField.setText( (files != null) ? Arrays.toString( files ).replace( ',', '\n' ) : "null" ); + filesField.append( "\n\nretFileTypeIndex " + retFileTypeIndex ); + } + private static String n( String s ) { return s != null && !s.isEmpty() ? s : null; } 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 index 2fc682a3..03751ec1 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserTest.java @@ -234,7 +234,9 @@ public class FlatSystemFileChooserTest for( int i = 0; i < fileTypes.length; i += 2 ) { fc.addChoosableFileFilter( "*".equals( fileTypes[i+1] ) ? fc.getAcceptAllFileFilter() - : new SystemFileChooser.FileNameExtensionFilter( fileTypes[i], fileTypes[i+1].split( ";" ) ) ); + : ((fileTypes[i+1].indexOf( '*' ) >= 0 || fileTypes[i+1].indexOf( '?' ) >= 0) + ? new SystemFileChooser.PatternFilter( fileTypes[i], fileTypes[i+1].split( ";" ) ) + : new SystemFileChooser.FileNameExtensionFilter( fileTypes[i], fileTypes[i+1].split( ";" ) )) ); } SystemFileChooser.FileFilter[] filters = fc.getChoosableFileFilters(); if( filters.length > 0 ) @@ -310,7 +312,8 @@ public class FlatSystemFileChooserTest "result", result, "currentDirectory", fc.getCurrentDirectory(), "selectedFile", fc.getSelectedFile(), - "selectedFiles", fc.getSelectedFiles() ); + "selectedFiles", fc.getSelectedFiles(), + "fileFilter", fc.getFileFilter() ); } private void outputSwingFileChooser( String type, JFileChooser fc, int result ) { @@ -319,7 +322,8 @@ public class FlatSystemFileChooserTest "result", result, "currentDirectory", fc.getCurrentDirectory(), "selectedFile", fc.getSelectedFile(), - "selectedFiles", fc.getSelectedFiles() ); + "selectedFiles", fc.getSelectedFiles(), + "fileFilter", fc.getFileFilter() ); } private void outputAWTFileChooser( FileDialog fc ) { 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 b6ddbc4e..d00ada85 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 @@ -131,8 +131,8 @@ public class FlatSystemFileChooserWindowsTest fileTypes = fileTypesStr.trim().split( "[,]+" ); int fileTypeIndex = fileTypeIndexSlider.getValue(); - FlatNativeWindowsLibrary.FileChooserCallback callback = (files, hwndFileDialog) -> { - System.out.println( " -- callback " + hwndFileDialog + " " + Arrays.toString( files ) ); + FlatNativeWindowsLibrary.FileChooserCallback callback = (files, fileTypeIndex2, hwndFileDialog) -> { + System.out.println( " -- callback " + hwndFileDialog + " " + Arrays.toString( files ) + " " + fileTypeIndex2 ); if( showMessageDialogOnOKCheckBox.isSelected() ) { System.out.println( FlatNativeWindowsLibrary.showMessageDialog( hwndFileDialog, JOptionPane.INFORMATION_MESSAGE, @@ -142,26 +142,30 @@ public class FlatSystemFileChooserWindowsTest }; if( direct ) { + int[] retFileTypeIndex = { -1 }; String[] files = FlatNativeWindowsLibrary.showFileChooser( owner, open, title, okButtonLabel, fileNameLabel, fileName, folder, saveAsItem, defaultFolder, defaultExtension, - optionsSet.get(), optionsClear.get(), callback, fileTypeIndex, fileTypes ); + optionsSet.get(), optionsClear.get(), callback, + fileTypeIndex, fileTypes, retFileTypeIndex ); - filesField.setText( (files != null) ? Arrays.toString( files ).replace( ',', '\n' ) : "null" ); + outputResult( files, retFileTypeIndex[0] ); } else { SecondaryLoop secondaryLoop = Toolkit.getDefaultToolkit().getSystemEventQueue().createSecondaryLoop(); String[] fileTypes2 = fileTypes; new Thread( () -> { + int[] retFileTypeIndex = { -1 }; String[] files = FlatNativeWindowsLibrary.showFileChooser( owner, open, title, okButtonLabel, fileNameLabel, fileName, folder, saveAsItem, defaultFolder, defaultExtension, - optionsSet.get(), optionsClear.get(), callback, fileTypeIndex, fileTypes2 ); + optionsSet.get(), optionsClear.get(), callback, + fileTypeIndex, fileTypes2, retFileTypeIndex ); System.out.println( " secondaryLoop.exit() returned " + secondaryLoop.exit() ); EventQueue.invokeLater( () -> { - filesField.setText( (files != null) ? Arrays.toString( files ).replace( ',', '\n' ) : "null" ); + outputResult( files, retFileTypeIndex[0] ); } ); } ).start(); @@ -170,6 +174,11 @@ public class FlatSystemFileChooserWindowsTest } } + private void outputResult( String[] files, int retFileTypeIndex ) { + filesField.setText( (files != null) ? Arrays.toString( files ).replace( ',', '\n' ) : "null" ); + filesField.append( "\n\nretFileTypeIndex " + retFileTypeIndex ); + } + private static String n( String s ) { return s != null && !s.isEmpty() ? s : null; }