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 cd25e182..ae08f63f 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 @@ -25,6 +25,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Locale; import java.util.concurrent.atomic.AtomicReference; +import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; @@ -81,6 +82,7 @@ import com.formdev.flatlaf.ui.FlatNativeWindowsLibrary; * as first item and selects it by default. * Use {@code chooser.addChoosableFileFilter( chooser.getAcceptAllFileFilter() )} * to place All Files filter somewhere else. + *
+ * The callback has two parameters: + *
{@code
+ * chooser.setApproveCallback( (selectedFiles, context) -> {
+ * // do something
+ * return SystemFileChooser.APPROVE_OPTION; // or SystemFileChooser.CANCEL_OPTION
+ * } );
+ * }
+ *
+ * or
+ *
+ * {@code
+ * chooser.setApproveCallback( this::approveCallback );
+ *
+ * ...
+ *
+ * private boolean approveCallback( File[] selectedFiles, ApproveContext context ) {
+ * // do something
+ * return SystemFileChooser.APPROVE_OPTION; // or SystemFileChooser.CANCEL_OPTION
+ * }
+ * }
+ *
+ * WARNING: Do not show a Swing dialog for the callback. This will not work!
+ * + * Instead use {@link ApproveContext#showMessageDialog(int, String, String, int, String...)}, + * which shows a modal system message dialog as child of the file dialog. + * + *
{@code
+ * chooser.setApproveCallback( (selectedFiles, context) -> {
+ * if( !selectedFiles[0].getName().startsWith( "blabla" ) ) {
+ * context.showMessageDialog( JOptionPane.WARNING_MESSAGE,
+ * "File name must start with 'blabla' :)", null, 0 );
+ * return SystemFileChooser.CANCEL_OPTION;
+ * }
+ * return SystemFileChooser.APPROVE_OPTION;
+ * } );
+ * }
+ *
+ * @see ApproveContext
+ * @see JFileChooser#approveSelection()
+ */
+ public void setApproveCallback( ApproveCallback approveCallback ) {
+ this.approveCallback = approveCallback;
+ }
+
private int showDialogImpl( Component parent ) {
+ approveResult = APPROVE_OPTION;
File[] files = getProvider().showDialog( parent, this );
setSelectedFiles( files );
- return (files != null) ? APPROVE_OPTION : CANCEL_OPTION;
+ return (files != null) ? approveResult : CANCEL_OPTION;
}
private FileChooserProvider getProvider() {
@@ -464,14 +530,31 @@ public class SystemFileChooser
return null;
// convert file names to file objects
+ return filenames2files( filenames );
+ }
+
+ abstract String[] showSystemDialog( Window owner, SystemFileChooser fc );
+
+ boolean invokeApproveCallback( SystemFileChooser fc, String[] files, ApproveContext context ) {
+ if( files == null || files.length == 0 )
+ return false; // should never happen
+
+ ApproveCallback approveCallback = fc.getApproveCallback();
+ int result = approveCallback.approve( filenames2files( files ), context );
+ if( result == CANCEL_OPTION )
+ return false;
+
+ fc.approveResult = result;
+ return true;
+ }
+
+ private static File[] filenames2files( String[] filenames ) {
FileSystemView fsv = FileSystemView.getFileSystemView();
File[] files = new File[filenames.length];
for( int i = 0; i < filenames.length; i++ )
files[i] = fsv.createFileObject( filenames[i] );
return files;
}
-
- abstract String[] showSystemDialog( Window owner, SystemFileChooser fc );
}
//---- class WindowsFileChooserProvider -----------------------------------
@@ -549,12 +632,50 @@ public class SystemFileChooser
}
}
+ // callback
+ FlatNativeWindowsLibrary.FileChooserCallback callback = (fc.getApproveCallback() != null)
+ ? (files, hwndFileDialog) -> {
+ return invokeApproveCallback( fc, files, new WindowsApproveContext( hwndFileDialog ) );
+ } : null;
+
// show system file dialog
return FlatNativeWindowsLibrary.showFileChooser( owner, open,
fc.getDialogTitle(), approveButtonText, null, fileName,
- folder, saveAsItem, null, null, optionsSet, optionsClear, null,
+ folder, saveAsItem, null, null, optionsSet, optionsClear, callback,
fileTypeIndex, fileTypes.toArray( new String[fileTypes.size()] ) );
}
+
+ //---- class WindowsApproveContext ----
+
+ private static class WindowsApproveContext
+ extends ApproveContext
+ {
+ private final long hwndFileDialog;
+
+ WindowsApproveContext( long hwndFileDialog ) {
+ this.hwndFileDialog = hwndFileDialog;
+ }
+
+ @Override
+ public int showMessageDialog( int messageType, String primaryText,
+ String secondaryText, int defaultButton, String... buttons )
+ {
+ // concat primary and secondary texts
+ if( secondaryText != null )
+ primaryText = primaryText + "\n\n" + secondaryText;
+
+ // button menmonics ("&" -> "&&", "__" -> "_", "_" -> "&")
+ for( int i = 0; i < buttons.length; i++ )
+ buttons[i] = buttons[i].replace( "&", "&&" ).replace( "__", "\u0001" ).replace( '_', '&' ).replace( '\u0001', '_' );
+
+ // use "OK" button if no buttons given
+ if( buttons.length == 0 )
+ buttons = new String[] { UIManager.getString( "OptionPane.okButtonText", Locale.getDefault() ) };
+
+ return FlatNativeWindowsLibrary.showMessageDialog( hwndFileDialog,
+ messageType, null, primaryText, defaultButton, buttons );
+ }
+ }
}
//---- class MacFileChooserProvider ---------------------------------------
@@ -613,12 +734,42 @@ public class SystemFileChooser
}
}
+ // callback
+ FlatNativeMacLibrary.FileChooserCallback callback = (fc.getApproveCallback() != null)
+ ? (files, hwndFileDialog) -> {
+ return invokeApproveCallback( fc, files, new MacApproveContext( hwndFileDialog ) );
+ } : null;
+
// show system file dialog
return FlatNativeMacLibrary.showFileChooser( open,
fc.getDialogTitle(), fc.getApproveButtonText(), null, null, null,
- nameFieldStringValue, directoryURL, optionsSet, optionsClear, null,
+ nameFieldStringValue, directoryURL, optionsSet, optionsClear, callback,
fileTypeIndex, fileTypes.toArray( new String[fileTypes.size()] ) );
}
+
+ //---- class MacApproveContext ----
+
+ private static class MacApproveContext
+ extends ApproveContext
+ {
+ private final long hwndFileDialog;
+
+ MacApproveContext( long hwndFileDialog ) {
+ this.hwndFileDialog = hwndFileDialog;
+ }
+
+ @Override
+ public int showMessageDialog( int messageType, String primaryText,
+ String secondaryText, int defaultButton, String... buttons )
+ {
+ // remove button menmonics ("__" -> "_", "_" -> "")
+ for( int i = 0; i < buttons.length; i++ )
+ buttons[i] = buttons[i].replace( "__", "\u0001" ).replace( "_", "" ).replace( "\u0001", "_" );
+
+ return FlatNativeMacLibrary.showMessageDialog( hwndFileDialog,
+ messageType, primaryText, secondaryText, defaultButton, buttons );
+ }
+ }
}
//---- class LinuxFileChooserProvider -------------------------------------
@@ -690,10 +841,17 @@ public class SystemFileChooser
}
}
+ // callback
+ FlatNativeLinuxLibrary.FileChooserCallback callback = (fc.getApproveCallback() != null)
+ ? (files, hwndFileDialog) -> {
+ return invokeApproveCallback( fc, files, new LinuxApproveContext( hwndFileDialog ) );
+ } : null;
+
// show system file dialog
return FlatNativeLinuxLibrary.showFileChooser( owner, open,
fc.getDialogTitle(), approveButtonText, currentName, currentFolder,
- optionsSet, optionsClear, null, fileTypeIndex, fileTypes.toArray( new String[fileTypes.size()] ) );
+ optionsSet, optionsClear, callback,
+ fileTypeIndex, fileTypes.toArray( new String[fileTypes.size()] ) );
}
private String caseInsensitiveGlobPattern( String ext ) {
@@ -712,6 +870,26 @@ public class SystemFileChooser
}
return buf.toString();
}
+
+ //---- class LinuxApproveContext ----
+
+ private static class LinuxApproveContext
+ extends ApproveContext
+ {
+ private final long hwndFileDialog;
+
+ LinuxApproveContext( long hwndFileDialog ) {
+ this.hwndFileDialog = hwndFileDialog;
+ }
+
+ @Override
+ public int showMessageDialog( int messageType, String primaryText,
+ String secondaryText, int defaultButton, String... buttons )
+ {
+ return FlatNativeLinuxLibrary.showMessageDialog( hwndFileDialog,
+ messageType, primaryText, secondaryText, defaultButton, buttons );
+ }
+ }
}
//---- class SwingFileChooserProvider -------------------------------------
@@ -727,6 +905,8 @@ public class SystemFileChooser
File[] files = isMultiSelectionEnabled()
? getSelectedFiles()
: new File[] { getSelectedFile() };
+ if( files == null || files.length == 0 )
+ return; // should never happen
if( getDialogType() == OPEN_DIALOG || isDirectorySelectionEnabled() ) {
if( !checkMustExist( this, files ) )
@@ -735,6 +915,17 @@ public class SystemFileChooser
if( !checkOverwrite( this, files ) )
return;
}
+
+ // callback
+ ApproveCallback approveCallback = fc.getApproveCallback();
+ if( approveCallback != null ) {
+ int result = approveCallback.approve( files, new SwingApproveContext( this ) );
+ if( result == CANCEL_OPTION )
+ return;
+
+ fc.approveResult = result;
+ }
+
super.approveSelection();
}
};
@@ -831,6 +1022,47 @@ public class SystemFileChooser
}
return true;
}
+
+ //---- class SwingApproveContext ----
+
+ private static class SwingApproveContext
+ extends ApproveContext
+ {
+ private final JFileChooser chooser;
+
+ SwingApproveContext( JFileChooser chooser ) {
+ this.chooser = chooser;
+ }
+
+ @Override
+ public int showMessageDialog( int messageType, String primaryText,
+ String secondaryText, int defaultButton, String... buttons )
+ {
+ // title
+ String title = chooser.getDialogTitle();
+ if( title == null ) {
+ Window window = SwingUtilities.windowForComponent( chooser );
+ if( window instanceof JDialog )
+ title = ((JDialog)window).getTitle();
+ }
+
+ // concat primary and secondary texts
+ if( secondaryText != null )
+ primaryText = primaryText + "\n\n" + secondaryText;
+
+ // remove button menmonics ("__" -> "_", "_" -> "")
+ for( int i = 0; i < buttons.length; i++ )
+ buttons[i] = buttons[i].replace( "__", "\u0001" ).replace( "_", "" ).replace( "\u0001", "_" );
+
+ // use "OK" button if no buttons given
+ if( buttons.length == 0 )
+ buttons = new String[] { UIManager.getString( "OptionPane.okButtonText", Locale.getDefault() ) };
+
+ return JOptionPane.showOptionDialog( chooser,
+ primaryText, title, JOptionPane.YES_NO_OPTION, messageType,
+ null, buttons, buttons[Math.min( Math.max( defaultButton, 0 ), buttons.length - 1 )] );
+ }
+ }
}
//---- class FileFilter ---------------------------------------------------
@@ -892,4 +1124,41 @@ public class SystemFileChooser
return UIManager.getString( "FileChooser.acceptAllFileFilterText" );
}
}
+
+ //---- class ApproveCallback ----------------------------------------------
+
+ public interface ApproveCallback {
+ /**
+ * @param selectedFiles one or more selected files
+ * @param context context object that provides additional methods
+ * @return If the callback returns {@link #CANCEL_OPTION}, then the file dialog stays open.
+ * If it returns {@link #APPROVE_OPTION} (or any other value other than {@link #CANCEL_OPTION}),
+ * the file dialog is closed and the {@code show...Dialog()} methods return that value.
+ */
+ int approve( File[] selectedFiles, ApproveContext context );
+ }
+
+ //---- class ApproveContext -----------------------------------------------
+
+ public static abstract class ApproveContext {
+ /**
+ * Shows a modal (operating system) message dialog as child of the system file chooser.
+ * + * Use this instead of {@link JOptionPane} in approve callbacks. + * + * @param messageType type of message being displayed: + * {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#INFORMATION_MESSAGE}, + * {@link JOptionPane#WARNING_MESSAGE}, {@link JOptionPane#QUESTION_MESSAGE} or + * {@link JOptionPane#PLAIN_MESSAGE} + * @param primaryText primary text + * @param secondaryText secondary text; shown below of primary text; or {@code null} + * @param defaultButton index of the default button, which can be pressed using ENTER key + * @param buttons texts of the buttons; if no buttons given the a default "OK" button is shown. + * Use '_' for mnemonics (e.g. "_Choose") + * Use '__' for '_' character (e.g. "Choose__and__Quit"). + * @return index of pressed button; or -1 for ESC key + */ + public abstract int showMessageDialog( int messageType, String primaryText, + String secondaryText, int defaultButton, String... buttons ); + } } diff --git a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeFileEditor.java b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeFileEditor.java index 9340627a..ab456671 100644 --- a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeFileEditor.java +++ b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeFileEditor.java @@ -73,6 +73,7 @@ import com.formdev.flatlaf.themes.FlatMacDarkLaf; import com.formdev.flatlaf.themes.FlatMacLightLaf; import com.formdev.flatlaf.ui.FlatUIUtils; import com.formdev.flatlaf.util.StringUtils; +import com.formdev.flatlaf.util.SystemFileChooser; import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.UIScale; @@ -96,6 +97,8 @@ class FlatThemeFileEditor private static final String KEY_SHOW_RGB_COLORS = "showRgbColors"; private static final String KEY_SHOW_COLOR_LUMA = "showColorLuma"; + private static final int NEW_PROPERTIES_FILE_OPTION = 100; + private File dir; private Preferences state; private boolean inLoadDirectory; @@ -227,48 +230,45 @@ class FlatThemeFileEditor return; // choose directory - JFileChooser chooser = new JFileChooser( dir ) { - @Override - public void approveSelection() { - if( !checkDirectory( this, getSelectedFile() ) ) - return; - - super.approveSelection(); - } - }; - chooser.setFileSelectionMode( JFileChooser.DIRECTORIES_ONLY ); - if( chooser.showOpenDialog( this ) != JFileChooser.APPROVE_OPTION ) + SystemFileChooser chooser = new SystemFileChooser( dir ); + chooser.setFileSelectionMode( SystemFileChooser.DIRECTORIES_ONLY ); + chooser.setApproveCallback( this::checkDirectory ); + int result = chooser.showOpenDialog( this ); + if( result == SystemFileChooser.CANCEL_OPTION ) return; File selectedFile = chooser.getSelectedFile(); if( selectedFile == null || selectedFile.equals( dir ) ) return; + if( result == NEW_PROPERTIES_FILE_OPTION ) { + if( !newPropertiesFile( selectedFile ) ) + return; + } + // open new directory loadDirectory( selectedFile ); } - private boolean checkDirectory( Component parentComponent, File dir ) { + private int checkDirectory( File[] selectedFiles, SystemFileChooser.ApproveContext context ) { + File dir = selectedFiles[0]; if( !dir.isDirectory() ) { - JOptionPane.showMessageDialog( parentComponent, - "Directory '" + dir + "' does not exist.", - getTitle(), JOptionPane.INFORMATION_MESSAGE ); - return false; + showMessageDialog( context, "Directory '" + dir + "' does not exist.", null ); + return SystemFileChooser.CANCEL_OPTION; } if( getPropertiesFiles( dir ).length == 0 ) { UIManager.put( "OptionPane.sameSizeButtons", false ); - int result = JOptionPane.showOptionDialog( parentComponent, - "Directory '" + dir + "' does not contain properties files.\n\n" - + "Do you want create a new theme in this directory?\n\n" + int result = showMessageDialog( context, + "Directory '" + dir + "' does not contain properties files.", + "Do you want create a new theme in this directory?\n\n" + "Or do you want modify/extend core themes and create empty" + " 'FlatLightLaf.properties' and 'FlatDarkLaf.properties' files in this directory?", - getTitle(), JOptionPane.DEFAULT_OPTION, JOptionPane.INFORMATION_MESSAGE, null, - new Object[] { "New Theme", "Modify Core Themes", "Cancel" }, null ); + "_New Theme", "_Modify Core Themes", "_Cancel" ); UIManager.put( "OptionPane.sameSizeButtons", null ); if( result == 0 ) - return newPropertiesFile( dir ); + return NEW_PROPERTIES_FILE_OPTION; else if( result == 1 ) { try { String content = @@ -280,18 +280,37 @@ class FlatThemeFileEditor "\n"; writeFile( new File( dir, "FlatLightLaf.properties" ), content ); writeFile( new File( dir, "FlatDarkLaf.properties" ), content ); - return true; + return SystemFileChooser.APPROVE_OPTION; } catch( IOException ex ) { ex.printStackTrace(); - JOptionPane.showMessageDialog( parentComponent, - "Failed to create 'FlatLightLaf.properties' or 'FlatDarkLaf.properties'." ); + showMessageDialog( context, + "Failed to create 'FlatLightLaf.properties' or 'FlatDarkLaf.properties'.", null ); } } - return false; + return SystemFileChooser.CANCEL_OPTION; } - return true; + return SystemFileChooser.APPROVE_OPTION; + } + + private int showMessageDialog( SystemFileChooser.ApproveContext context, + String primaryText, String secondaryText, String... buttons ) + { + if( context != null ) { + // invoked from SystemFileChooser + return context.showMessageDialog( JOptionPane.INFORMATION_MESSAGE, + primaryText, secondaryText, 0, buttons ); + } else { + // invoked from directoryChanged() + if( secondaryText != null ) + primaryText = primaryText + "\n\n" + secondaryText; + for( int i = 0; i < buttons.length; i++ ) + buttons[i] = buttons[i].replace( "_", "" ); + return JOptionPane.showOptionDialog( this, primaryText, getTitle(), + JOptionPane.DEFAULT_OPTION, JOptionPane.INFORMATION_MESSAGE, null, + (buttons.length > 0) ? buttons : null, null ); + } } private void directoryChanged() { @@ -302,7 +321,15 @@ class FlatThemeFileEditor if( dir == null ) return; - if( checkDirectory( this, dir ) ) + directoryField.hidePopup(); + + int result = checkDirectory( new File[] { dir }, null ); + if( result == NEW_PROPERTIES_FILE_OPTION ) { + if( !newPropertiesFile( dir ) ) + return; + } + + if( result != SystemFileChooser.CANCEL_OPTION ) loadDirectory( dir ); else { // remove from directories history @@ -390,6 +417,9 @@ class FlatThemeFileEditor File[] propertiesFiles = dir.listFiles( (d, name) -> { return name.endsWith( ".properties" ); } ); + if( propertiesFiles == null ) + propertiesFiles = new File[0]; + Arrays.sort( propertiesFiles, (f1, f2) -> { String n1 = toSortName( f1.getName() ); String n2 = toSortName( f2.getName() );