mirror of
https://github.com/JFormDesigner/FlatLaf.git
synced 2026-02-11 06:27:13 -06:00
System File Chooser: added PatternFilter to support glob file filter on Windows and on Linux, but not on macOS (issue #1076)
This commit is contained in:
@@ -6,6 +6,8 @@ FlatLaf Change Log
|
|||||||
- System File Chooser:
|
- System File Chooser:
|
||||||
- Update current filter before invoking approve callback and after closing
|
- Update current filter before invoking approve callback and after closing
|
||||||
dialog. (issue #1065)
|
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
|
- Fixed: System and Swing file dialogs were shown at the same time if
|
||||||
application has no other displayable window. (issue #1078)
|
application has no other displayable window. (issue #1078)
|
||||||
- On Linux: Check whether required GSettings schemas are installed to avoid
|
- On Linux: Check whether required GSettings schemas are installed to avoid
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ public class FlatNativeMacLibrary
|
|||||||
* @param fileTypes file types that the dialog can open or save.
|
* @param fileTypes file types that the dialog can open or save.
|
||||||
* Two or more strings and {@code null} are required for each filter.
|
* 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").
|
* 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.
|
* {@code null} is required to mark end of filter.
|
||||||
* @param retFileTypeIndex returns selected file type (zero-based); array must be have one element
|
* @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;
|
* @return file path(s) that the user selected; an empty array if canceled;
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import java.util.Locale;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
import javax.swing.JDialog;
|
import javax.swing.JDialog;
|
||||||
import javax.swing.JFileChooser;
|
import javax.swing.JFileChooser;
|
||||||
import javax.swing.JOptionPane;
|
import javax.swing.JOptionPane;
|
||||||
@@ -90,7 +91,8 @@ import com.formdev.flatlaf.ui.FlatNativeWindowsLibrary;
|
|||||||
* <li>{@link JFileChooser#FILES_AND_DIRECTORIES} is not supported.
|
* <li>{@link JFileChooser#FILES_AND_DIRECTORIES} is not supported.
|
||||||
* <li>{@link #getSelectedFiles()} returns selected file also in single selection mode.
|
* <li>{@link #getSelectedFiles()} returns selected file also in single selection mode.
|
||||||
* {@link JFileChooser#getSelectedFiles()} only in multi selection mode.
|
* {@link JFileChooser#getSelectedFiles()} only in multi selection mode.
|
||||||
* <li>Only file name extension filters (see {@link FileNameExtensionFilter}) are supported.
|
* <li>Only file name extension filters (see {@link FileNameExtensionFilter}) are supported on all platforms.
|
||||||
|
* <li>Pattern filters (see {@link PatternFilter}) are only supported on Windows and Linux, but not on macOS.
|
||||||
* <li>If adding choosable file filters and {@link #isAcceptAllFileFilterUsed()} is {@code true},
|
* <li>If adding choosable file filters and {@link #isAcceptAllFileFilterUsed()} is {@code true},
|
||||||
* then the <b>All Files</b> filter is placed at the end of the combobox list
|
* then the <b>All Files</b> 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.
|
* (as usual in current operating systems) and the first choosable filter is selected by default.
|
||||||
@@ -587,6 +589,7 @@ public class SystemFileChooser
|
|||||||
private void checkSupportedFileFilter( FileFilter filter ) throws IllegalArgumentException {
|
private void checkSupportedFileFilter( FileFilter filter ) throws IllegalArgumentException {
|
||||||
if( filter == null ||
|
if( filter == null ||
|
||||||
filter instanceof FileNameExtensionFilter ||
|
filter instanceof FileNameExtensionFilter ||
|
||||||
|
filter instanceof PatternFilter ||
|
||||||
filter instanceof AcceptAllFileFilter )
|
filter instanceof AcceptAllFileFilter )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -936,6 +939,10 @@ public class SystemFileChooser
|
|||||||
fileTypes.add( filter.getDescription() );
|
fileTypes.add( filter.getDescription() );
|
||||||
fileTypes.add( "*." + String.join( ";*.", ((FileNameExtensionFilter)filter).getExtensions() ) );
|
fileTypes.add( "*." + String.join( ";*.", ((FileNameExtensionFilter)filter).getExtensions() ) );
|
||||||
fileTypeFilters.add( filter );
|
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 ) {
|
} else if( filter instanceof AcceptAllFileFilter ) {
|
||||||
fileTypes.add( filter.getDescription() );
|
fileTypes.add( filter.getDescription() );
|
||||||
fileTypes.add( "*.*" );
|
fileTypes.add( "*.*" );
|
||||||
@@ -1013,6 +1020,26 @@ public class SystemFileChooser
|
|||||||
private static class MacFileChooserProvider
|
private static class MacFileChooserProvider
|
||||||
extends SystemFileChooserProvider
|
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
|
@Override
|
||||||
String[] showSystemDialog( Window owner, SystemFileChooser fc ) {
|
String[] showSystemDialog( Window owner, SystemFileChooser fc ) {
|
||||||
int dark = FlatLaf.isLafDark() ? 1 : 0;
|
int dark = FlatLaf.isLafDark() ? 1 : 0;
|
||||||
@@ -1202,9 +1229,14 @@ public class SystemFileChooser
|
|||||||
if( filter instanceof FileNameExtensionFilter ) {
|
if( filter instanceof FileNameExtensionFilter ) {
|
||||||
fileTypes.add( filter.getDescription() );
|
fileTypes.add( filter.getDescription() );
|
||||||
for( String ext : ((FileNameExtensionFilter)filter).getExtensions() )
|
for( String ext : ((FileNameExtensionFilter)filter).getExtensions() )
|
||||||
fileTypes.add( caseInsensitiveGlobPattern( ext ) );
|
fileTypes.add( "*." + caseInsensitiveGlobPattern( ext ) );
|
||||||
fileTypes.add( null );
|
fileTypes.add( null );
|
||||||
fileTypeFilters.add( filter );
|
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 ) {
|
} else if( filter instanceof AcceptAllFileFilter ) {
|
||||||
fileTypes.add( filter.getDescription() );
|
fileTypes.add( filter.getDescription() );
|
||||||
fileTypes.add( "*" );
|
fileTypes.add( "*" );
|
||||||
@@ -1235,7 +1267,6 @@ public class SystemFileChooser
|
|||||||
|
|
||||||
private String caseInsensitiveGlobPattern( String ext ) {
|
private String caseInsensitiveGlobPattern( String ext ) {
|
||||||
StringBuilder buf = new StringBuilder();
|
StringBuilder buf = new StringBuilder();
|
||||||
buf.append( "*." );
|
|
||||||
int len = ext.length();
|
int len = ext.length();
|
||||||
for( int i = 0; i < len; i++ ) {
|
for( int i = 0; i < len; i++ ) {
|
||||||
char ch = ext.charAt( i );
|
char ch = ext.charAt( i );
|
||||||
@@ -1426,6 +1457,10 @@ public class SystemFileChooser
|
|||||||
return new javax.swing.filechooser.FileNameExtensionFilter(
|
return new javax.swing.filechooser.FileNameExtensionFilter(
|
||||||
((FileNameExtensionFilter)filter).getDescription(),
|
((FileNameExtensionFilter)filter).getDescription(),
|
||||||
((FileNameExtensionFilter)filter).getExtensions() );
|
((FileNameExtensionFilter)filter).getExtensions() );
|
||||||
|
} else if( filter instanceof PatternFilter ) {
|
||||||
|
return new SwingGlobFilter(
|
||||||
|
((PatternFilter)filter).getDescription(),
|
||||||
|
((PatternFilter)filter).getPatterns() );
|
||||||
} else if( filter instanceof AcceptAllFileFilter )
|
} else if( filter instanceof AcceptAllFileFilter )
|
||||||
return chooser.getAcceptAllFileFilter();
|
return chooser.getAcceptAllFileFilter();
|
||||||
else
|
else
|
||||||
@@ -1515,6 +1550,86 @@ public class SystemFileChooser
|
|||||||
null, buttons, buttons[Math.min( Math.max( defaultButton, 0 ), buttons.length - 1 )] );
|
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 ---------------------------------------------------
|
//---- class FileFilter ---------------------------------------------------
|
||||||
@@ -1566,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.
|
||||||
|
* <ul>
|
||||||
|
* <li>{@code '*'} matches any sequence of characters.
|
||||||
|
* <li>{@code '?'} matches any single character.
|
||||||
|
* </ul>
|
||||||
|
* Sample filters: {@code *.tar.gz} or {@code *_copy.txt}
|
||||||
|
* <p>
|
||||||
|
* <b>Warning</b>: This filter is <b>not supported on macOS</b>.
|
||||||
|
* 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.
|
||||||
|
* <p>
|
||||||
|
* E.g.:
|
||||||
|
* <pre>{@code
|
||||||
|
* if( SystemInfo.isMacOS )
|
||||||
|
* chooser.addChoosableFileFilter( new FileNameExtensionFilter( "Compressed TAR", "tgz" ) );
|
||||||
|
* else
|
||||||
|
* chooser.addChoosableFileFilter( new PatternFilter( "Compressed TAR", "*.tar.gz" ) );
|
||||||
|
* } );
|
||||||
|
* }</pre>
|
||||||
|
*
|
||||||
|
* @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 ------------------------------------------
|
//---- class AcceptAllFileFilter ------------------------------------------
|
||||||
|
|
||||||
private static final class AcceptAllFileFilter
|
private static final class AcceptAllFileFilter
|
||||||
|
|||||||
@@ -234,7 +234,9 @@ public class FlatSystemFileChooserTest
|
|||||||
for( int i = 0; i < fileTypes.length; i += 2 ) {
|
for( int i = 0; i < fileTypes.length; i += 2 ) {
|
||||||
fc.addChoosableFileFilter( "*".equals( fileTypes[i+1] )
|
fc.addChoosableFileFilter( "*".equals( fileTypes[i+1] )
|
||||||
? fc.getAcceptAllFileFilter()
|
? 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();
|
SystemFileChooser.FileFilter[] filters = fc.getChoosableFileFilters();
|
||||||
if( filters.length > 0 )
|
if( filters.length > 0 )
|
||||||
|
|||||||
Reference in New Issue
Block a user