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;
}