diff --git a/.github/workflows/natives.yml b/.github/workflows/natives.yml index cf314dcc..f908574b 100644 --- a/.github/workflows/natives.yml +++ b/.github/workflows/natives.yml @@ -32,6 +32,10 @@ jobs: - uses: gradle/actions/wrapper-validation@v4 + - name: install libgtk-3-dev + if: matrix.os == 'ubuntu' + run: sudo apt install libgtk-3-dev + - name: Setup Java 11 uses: actions/setup-java@v4 with: 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 952d0ece..353b10b0 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 @@ -34,9 +34,9 @@ import com.formdev.flatlaf.util.SystemInfo; * @author Karl Tauber * @since 2.5 */ -class FlatNativeLinuxLibrary +public class FlatNativeLinuxLibrary { - private static int API_VERSION_LINUX = 3001; + private static int API_VERSION_LINUX = 3002; /** * Checks whether native library is loaded/available. @@ -44,7 +44,7 @@ class FlatNativeLinuxLibrary * Note: It is required to invoke this method before invoking any other * method of this class. Otherwise, the native library may not be loaded. */ - static boolean isLoaded() { + public static boolean isLoaded() { return SystemInfo.isLinux && FlatNativeLibrary.isLoaded( API_VERSION_LINUX ); } @@ -115,4 +115,48 @@ class FlatNativeLinuxLibrary return (window instanceof JFrame && JFrame.isDefaultLookAndFeelDecorated() && ((JFrame)window).isUndecorated()) || (window instanceof JDialog && JDialog.isDefaultLookAndFeelDecorated() && ((JDialog)window).isUndecorated()); } + + + /** + * https://docs.gtk.org/gtk3/iface.FileChooser.html#properties + * + * @since 3.6 + */ + public static final int + FC_select_folder = 1 << 0, + FC_select_multiple = 1 << 1, + FC_show_hidden = 1 << 2, + FC_local_only = 1 << 3, // default + FC_do_overwrite_confirmation = 1 << 4, // GTK 3 only; removed and always-on in GTK 4 + FC_create_folders = 1 << 5; // default for Save + + /** + * Shows the Linux system file dialog + * GtkFileChooserDialog. + *

+ * Note: This method blocks the current thread until the user closes + * the file dialog. It is highly recommended to invoke it from a new thread + * to avoid blocking the AWT event dispatching thread. + * + * @param open if {@code true}, shows the open dialog; if {@code false}, shows the save dialog + * @param title text displayed in dialog title; or {@code null} + * @param okButtonLabel text displayed in default button; or {@code null} + * @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 fileTypeIndex the file type that appears as selected (zero-based) + * @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 "*"). + * {@code null} is required to mark end of filter. + * @return file path(s) that the user selected; an empty array if canceled; + * or {@code null} on failures (no dialog shown) + * + * @since 3.6 + */ + public native static String[] showFileChooser( boolean open, + String title, String okButtonLabel, String currentName, String currentFolder, + int optionsSet, int optionsClear, int fileTypeIndex, String... fileTypes ); } diff --git a/flatlaf-natives/flatlaf-natives-linux/README.md b/flatlaf-natives/flatlaf-natives-linux/README.md index 3dd00c68..0b1cf98b 100644 --- a/flatlaf-natives/flatlaf-natives-linux/README.md +++ b/flatlaf-natives/flatlaf-natives-linux/README.md @@ -24,16 +24,25 @@ To build the library on Linux, some packages needs to be installed. ### Ubuntu `build-essential` contains GCC and development tools. `libxt-dev` contains the -X11 toolkit development headers. +X11 toolkit development headers. `libgtk-3-dev` contains the GTK toolkit +development headers. ~~~ sudo apt update -sudo apt install build-essential libxt-dev +sudo apt install build-essential libxt-dev libgtk-3-dev +~~~ + + +### Fedora + +~~~ +sudo dnf group install c-development +sudo dnf install libXt-devel gtk3-devel ~~~ ### CentOS ~~~ -sudo yum install libXt-devel +sudo yum install libXt-devel gtk3-devel ~~~ diff --git a/flatlaf-natives/flatlaf-natives-linux/build.gradle.kts b/flatlaf-natives/flatlaf-natives-linux/build.gradle.kts index a81d6643..1ae7abbc 100644 --- a/flatlaf-natives/flatlaf-natives-linux/build.gradle.kts +++ b/flatlaf-natives/flatlaf-natives-linux/build.gradle.kts @@ -52,7 +52,17 @@ tasks { includes.from( "${javaHome}/include", - "${javaHome}/include/linux" + "${javaHome}/include/linux", + + // for GTK + "/usr/include/gtk-3.0", + "/usr/include/glib-2.0", + "/usr/lib/x86_64-linux-gnu/glib-2.0/include", + "/usr/include/gdk-pixbuf-2.0", + "/usr/include/atk-1.0", + "/usr/include/cairo", + "/usr/include/pango-1.0", + "/usr/include/harfbuzz", ) compilerArgs.addAll( toolChain.map { @@ -75,7 +85,7 @@ tasks { linkerArgs.addAll( toolChain.map { when( it ) { - is Gcc, is Clang -> listOf( "-L${jawtPath}", "-l${jawt}" ) + is Gcc, is Clang -> listOf( "-L${jawtPath}", "-l${jawt}", "-lgtk-3" ) else -> emptyList() } } ) 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 02454fb3..cdd701a9 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 3001 +#define API_VERSION_LINUX 3002 //---- 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 new file mode 100644 index 00000000..ca048af5 --- /dev/null +++ b/flatlaf-natives/flatlaf-natives-linux/src/main/cpp/GtkFileChooser.cpp @@ -0,0 +1,177 @@ +/* + * Copyright 2025 FormDev Software GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include "com_formdev_flatlaf_ui_FlatNativeLinuxLibrary.h" + +/** + * @author Karl Tauber + * @since 3.6 + */ + +//---- class AutoReleaseStringUTF8 -------------------------------------------- + +class AutoReleaseStringUTF8 { + JNIEnv* env; + jstring javaString; + const char* chars; + +public: + AutoReleaseStringUTF8( JNIEnv* _env, jstring _javaString ) { + env = _env; + javaString = _javaString; + chars = (javaString != NULL) ? env->GetStringUTFChars( javaString, NULL ) : NULL; + } + ~AutoReleaseStringUTF8() { + if( chars != NULL ) + env->ReleaseStringUTFChars( javaString, chars ); + } + operator const gchar*() { return chars; } +}; + +//---- helper ----------------------------------------------------------------- + +#define isOptionSet( option ) ((optionsSet & com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_ ## option) != 0) +#define isOptionClear( option ) ((optionsClear & com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_ ## option) != 0) +#define isOptionSetOrClear( option ) isOptionSet( option ) || isOptionClear( option ) + +jobjectArray newJavaStringArray( JNIEnv* env, jsize count ) { + jclass stringClass = env->FindClass( "java/lang/String" ); + return env->NewObjectArray( count, stringClass, NULL ); +} + +void initFilters( GtkFileChooser* chooser, JNIEnv* env, jint fileTypeIndex, jobjectArray fileTypes ) { + jint length = env->GetArrayLength( fileTypes ); + if( length <= 0 ) + return; + + GtkFileFilter* filter = NULL; + int filterIndex = 0; + for( int i = 0; i < length; i++ ) { + jstring jstr = (jstring) env->GetObjectArrayElement( fileTypes, i ); + if( jstr == NULL ) { + if( filter != NULL ) { + gtk_file_chooser_add_filter( chooser, filter ); + if( fileTypeIndex == filterIndex ) + gtk_file_chooser_set_filter( chooser, filter ); + filter = NULL; + filterIndex++; + } + continue; + } + + AutoReleaseStringUTF8 str( env, jstr ); + if( filter == NULL ) { + filter = gtk_file_filter_new(); + gtk_file_filter_set_name( filter, str ); + } else + gtk_file_filter_add_pattern( filter, str ); + } +} + +static void handle_response( GtkWidget* dialog, gint responseId, gpointer data ) { + if( responseId == GTK_RESPONSE_ACCEPT ) + *((GSList**)data) = gtk_file_chooser_get_filenames( GTK_FILE_CHOOSER( dialog ) ); + + gtk_widget_hide( dialog ); + gtk_widget_destroy( dialog ); + gtk_main_quit(); +} + +//---- JNI methods ------------------------------------------------------------ + +extern "C" +JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_showFileChooser + ( JNIEnv* env, jclass cls, jboolean open, + jstring title, jstring okButtonLabel, jstring currentName, jstring currentFolder, + jint optionsSet, jint optionsClear, jint fileTypeIndex, jobjectArray fileTypes ) +{ + // initialize GTK + if( !gtk_init_check( NULL, NULL ) ) + return NULL; + + // convert Java strings to C strings + AutoReleaseStringUTF8 ctitle( env, title ); + AutoReleaseStringUTF8 cokButtonLabel( env, okButtonLabel ); + AutoReleaseStringUTF8 ccurrentName( env, currentName ); + AutoReleaseStringUTF8 ccurrentFolder( env, currentFolder ); + + // create GTK file chooser dialog + // https://docs.gtk.org/gtk3/class.FileChooserDialog.html + bool selectFolder = isOptionSet( FC_select_folder ); + bool multiSelect = isOptionSet( FC_select_multiple ); + GtkWidget* dialog = gtk_file_chooser_dialog_new( + (ctitle != NULL) ? ctitle + : (open ? (selectFolder ? (multiSelect ? _("Select Folders") : _("Select Folder")) + : (multiSelect ? _("Open Files") : _("Open File"))) : _("Save File")), + NULL, // can not use AWT X11 window as parent because GtkWindow is required + open ? (selectFolder ? GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER : GTK_FILE_CHOOSER_ACTION_OPEN) + : GTK_FILE_CHOOSER_ACTION_SAVE, + _("_Cancel"), GTK_RESPONSE_CANCEL, + (cokButtonLabel != NULL) ? cokButtonLabel : (open ? _("_Open") : _("_Save")), GTK_RESPONSE_ACCEPT, + NULL ); // marks end of buttons + GtkFileChooser* chooser = GTK_FILE_CHOOSER( dialog ); + + if( !open && ccurrentName != NULL ) + gtk_file_chooser_set_current_name( chooser, ccurrentName ); + if( ccurrentFolder != NULL ) + gtk_file_chooser_set_current_folder( chooser, ccurrentFolder ); + + if( isOptionSetOrClear( FC_select_multiple ) ) + gtk_file_chooser_set_select_multiple( chooser, isOptionSet( FC_select_multiple ) ); + if( isOptionSetOrClear( FC_show_hidden ) ) + gtk_file_chooser_set_show_hidden( chooser, isOptionSet( FC_show_hidden ) ); + if( isOptionSetOrClear( FC_local_only ) ) + gtk_file_chooser_set_local_only( chooser, isOptionSet( FC_local_only ) ); + if( isOptionSetOrClear( FC_do_overwrite_confirmation ) ) + gtk_file_chooser_set_do_overwrite_confirmation( chooser, isOptionSet( FC_do_overwrite_confirmation ) ); + if( isOptionSetOrClear( FC_create_folders ) ) + gtk_file_chooser_set_create_folders( chooser, isOptionSet( FC_create_folders ) ); + + initFilters( chooser, env, fileTypeIndex, fileTypes ); + + gtk_window_set_modal( GTK_WINDOW( dialog ), true ); + + // show dialog + // (similar to what's done in sun_awt_X11_GtkFileDialogPeer.c) + GSList* fileList = NULL; + g_signal_connect( dialog, "response", G_CALLBACK( handle_response ), &fileList ); + gtk_widget_show( dialog ); + gtk_main(); + + // canceled? + if( fileList == NULL ) + return newJavaStringArray( env, 0 ); + + // convert GSList to Java string array + guint count = g_slist_length( fileList ); + jobjectArray array = newJavaStringArray( env, count ); + GSList* it = fileList; + for( int i = 0; i < count; i++, it = it->next ) { + gchar* path = (gchar*) it->data; + jstring jpath = env->NewStringUTF( path ); + g_free( path ); + + env->SetObjectArrayElement( array, i, jpath ); + env->DeleteLocalRef( jpath ); + } + g_slist_free( fileList ); + return array; +} diff --git a/flatlaf-natives/flatlaf-natives-linux/src/main/cpp/X11WmUtils.cpp b/flatlaf-natives/flatlaf-natives-linux/src/main/cpp/X11WmUtils.cpp index 8cbc57a9..0f01b693 100644 --- a/flatlaf-natives/flatlaf-natives-linux/src/main/cpp/X11WmUtils.cpp +++ b/flatlaf-natives/flatlaf-natives-linux/src/main/cpp/X11WmUtils.cpp @@ -36,7 +36,7 @@ Window getWindowHandle( JNIEnv* env, JAWT* awt, jobject window, Display** displa /** * Send _NET_WM_MOVERESIZE to window to initiate moving or resizing. * - * https://specifications.freedesktop.org/wm-spec/wm-spec-latest.html#idm45446104441728 + * https://specifications.freedesktop.org/wm-spec/latest/ar01s04.html#id-1.5.4 * https://gitlab.gnome.org/GNOME/gtk/-/blob/main/gdk/x11/gdksurface-x11.c#L3841-3881 */ extern "C" 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 c3b8c4b2..dd0436a0 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 @@ -9,6 +9,18 @@ extern "C" { #endif #undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_MOVE #define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_MOVE 8L +#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_select_folder +#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_select_folder 1L +#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_select_multiple +#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_select_multiple 2L +#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_show_hidden +#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_show_hidden 4L +#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_local_only +#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_local_only 8L +#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_do_overwrite_confirmation +#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_do_overwrite_confirmation 16L +#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_create_folders +#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_create_folders 32L /* * Class: com_formdev_flatlaf_ui_FlatNativeLinuxLibrary * Method: xMoveOrResizeWindow @@ -25,6 +37,14 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_xM JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_xShowWindowMenu (JNIEnv *, jclass, jobject, jint, jint); +/* + * Class: com_formdev_flatlaf_ui_FlatNativeLinuxLibrary + * Method: showFileChooser + * Signature: (ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;III[Ljava/lang/String;)[Ljava/lang/String; + */ +JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_showFileChooser + (JNIEnv *, jclass, jboolean, jstring, jstring, jstring, jstring, jint, jint, jint, jobjectArray); + #ifdef __cplusplus } #endif 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 new file mode 100644 index 00000000..ecb0e439 --- /dev/null +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserLinuxTest.java @@ -0,0 +1,392 @@ +/* + * Copyright 2025 FormDev Software GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.formdev.flatlaf.testing; + +import static com.formdev.flatlaf.ui.FlatNativeLinuxLibrary.*; +import java.awt.EventQueue; +import java.awt.SecondaryLoop; +import java.awt.Toolkit; +import java.awt.Window; +import java.awt.event.WindowEvent; +import java.awt.event.WindowFocusListener; +import java.awt.event.WindowListener; +import java.awt.event.WindowStateListener; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicInteger; +import javax.swing.*; +import com.formdev.flatlaf.extras.components.*; +import com.formdev.flatlaf.extras.components.FlatTriStateCheckBox.State; +import com.formdev.flatlaf.ui.FlatNativeLinuxLibrary; +import net.miginfocom.swing.*; + +/** + * @author Karl Tauber + */ +public class FlatSystemFileChooserLinuxTest + extends FlatTestPanel +{ + public static void main( String[] args ) { + SwingUtilities.invokeLater( () -> { + if( !FlatNativeLinuxLibrary.isLoaded() ) { + JOptionPane.showMessageDialog( null, "FlatLaf native library not loaded" ); + return; + } + + FlatTestFrame frame = FlatTestFrame.create( args, "FlatSystemFileChooserLinuxTest" ); + addListeners( frame ); + frame.showFrame( FlatSystemFileChooserLinuxTest::new ); + } ); + } + + FlatSystemFileChooserLinuxTest() { + initComponents(); + + fileTypesField.setSelectedItem( null ); + } + + private void open() { + openOrSave( true, false ); + } + + private void save() { + openOrSave( false, false ); + } + + private void openDirect() { + openOrSave( true, true ); + } + + private void saveDirect() { + openOrSave( false, true ); + } + + private void openOrSave( boolean open, boolean direct ) { + String title = n( titleField.getText() ); + String okButtonLabel = n( okButtonLabelField.getText() ); + String currentName = n( currentNameField.getText() ); + String currentFolder = n( currentFolderField.getText() ); + AtomicInteger optionsSet = new AtomicInteger(); + AtomicInteger optionsClear = new AtomicInteger(); + + o( FC_select_folder, select_folderCheckBox, optionsSet, optionsClear ); + o( FC_select_multiple, select_multipleCheckBox, optionsSet, optionsClear ); + o( FC_show_hidden, show_hiddenCheckBox, optionsSet, optionsClear ); + o( FC_local_only, local_onlyCheckBox, optionsSet, optionsClear ); + o( FC_do_overwrite_confirmation, do_overwrite_confirmationCheckBox, optionsSet, optionsClear ); + o( FC_create_folders, create_foldersCheckBox, optionsSet, optionsClear ); + + String fileTypesStr = n( (String) fileTypesField.getSelectedItem() ); + String[] fileTypes = {}; + if( fileTypesStr != null ) { + if( !fileTypesStr.endsWith( ",null" ) ) + fileTypesStr += ",null"; + fileTypes = fileTypesStr.trim().split( "[,]+" ); + for( int i = 0; i < fileTypes.length; i++ ) { + if( "null".equals( fileTypes[i] ) ) + fileTypes[i] = null; + } + } + int fileTypeIndex = fileTypeIndexSlider.getValue(); + + if( direct ) { + String[] files = FlatNativeLinuxLibrary.showFileChooser( open, + title, okButtonLabel, currentName, currentFolder, + optionsSet.get(), optionsClear.get(), fileTypeIndex, fileTypes ); + + filesField.setText( (files != null) ? Arrays.toString( files ).replace( ',', '\n' ) : "null" ); + } else { + SecondaryLoop secondaryLoop = Toolkit.getDefaultToolkit().getSystemEventQueue().createSecondaryLoop(); + + String[] fileTypes2 = fileTypes; + new Thread( () -> { + String[] files = FlatNativeLinuxLibrary.showFileChooser( open, + title, okButtonLabel, currentName, currentFolder, + optionsSet.get(), optionsClear.get(), fileTypeIndex, fileTypes2 ); + + System.out.println( " secondaryLoop.exit() returned " + secondaryLoop.exit() ); + + EventQueue.invokeLater( () -> { + filesField.setText( (files != null) ? Arrays.toString( files ).replace( ',', '\n' ) : "null" ); + } ); + } ).start(); + + System.out.println( "---- enter secondary loop ----" ); + System.out.println( "---- secondary loop exited (secondaryLoop.enter() returned " + secondaryLoop.enter() + ") ----" ); + } + } + + private static String n( String s ) { + return s != null && !s.isEmpty() ? s : null; + } + + private static void o( int option, FlatTriStateCheckBox checkBox, AtomicInteger optionsSet, AtomicInteger optionsClear ) { + if( checkBox.getState() == State.SELECTED ) + optionsSet.set( optionsSet.get() | option ); + else if( checkBox.getState() == State.UNSELECTED ) + optionsClear.set( optionsClear.get() | option ); + } + + private static void addListeners( Window w ) { + w.addWindowListener( new WindowListener() { + @Override + public void windowOpened( WindowEvent e ) { + System.out.println( e ); + } + + @Override + public void windowIconified( WindowEvent e ) { + System.out.println( e ); + } + + @Override + public void windowDeiconified( WindowEvent e ) { + System.out.println( e ); + } + + @Override + public void windowDeactivated( WindowEvent e ) { + System.out.println( e ); + } + + @Override + public void windowClosing( WindowEvent e ) { + System.out.println( e ); + } + + @Override + public void windowClosed( WindowEvent e ) { + System.out.println( e ); + } + + @Override + public void windowActivated( WindowEvent e ) { + System.out.println( e ); + } + } ); + w.addWindowStateListener( new WindowStateListener() { + @Override + public void windowStateChanged( WindowEvent e ) { + System.out.println( e ); + } + } ); + w.addWindowFocusListener( new WindowFocusListener() { + @Override + public void windowLostFocus( WindowEvent e ) { + System.out.println( e ); + } + + @Override + public void windowGainedFocus( WindowEvent e ) { + System.out.println( e ); + } + } ); + } + + private void initComponents() { + // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents + titleLabel = new JLabel(); + titleField = new JTextField(); + panel1 = new JPanel(); + select_folderCheckBox = new FlatTriStateCheckBox(); + select_multipleCheckBox = new FlatTriStateCheckBox(); + do_overwrite_confirmationCheckBox = new FlatTriStateCheckBox(); + create_foldersCheckBox = new FlatTriStateCheckBox(); + show_hiddenCheckBox = new FlatTriStateCheckBox(); + local_onlyCheckBox = new FlatTriStateCheckBox(); + okButtonLabelLabel = new JLabel(); + okButtonLabelField = new JTextField(); + currentNameLabel = new JLabel(); + currentNameField = new JTextField(); + currentFolderLabel = new JLabel(); + currentFolderField = new JTextField(); + fileTypesLabel = new JLabel(); + fileTypesField = new JComboBox<>(); + fileTypeIndexLabel = new JLabel(); + fileTypeIndexSlider = new JSlider(); + openButton = new JButton(); + saveButton = new JButton(); + openDirectButton = new JButton(); + saveDirectButton = new JButton(); + filesScrollPane = new JScrollPane(); + filesField = new JTextArea(); + + //======== this ======== + setLayout(new MigLayout( + "ltr,insets dialog,hidemode 3", + // columns + "[left]" + + "[grow,fill]" + + "[fill]", + // rows + "[]" + + "[]" + + "[]" + + "[]" + + "[]" + + "[]" + + "[]" + + "[grow,fill]")); + + //---- titleLabel ---- + titleLabel.setText("title"); + add(titleLabel, "cell 0 0"); + add(titleField, "cell 1 0"); + + //======== panel1 ======== + { + panel1.setLayout(new MigLayout( + "insets 2,hidemode 3", + // columns + "[left]", + // rows + "[]0" + + "[]0" + + "[]0" + + "[]0" + + "[]0" + + "[]")); + + //---- select_folderCheckBox ---- + select_folderCheckBox.setText("select_folder"); + select_folderCheckBox.setAllowIndeterminate(false); + select_folderCheckBox.setState(FlatTriStateCheckBox.State.UNSELECTED); + panel1.add(select_folderCheckBox, "cell 0 0"); + + //---- select_multipleCheckBox ---- + select_multipleCheckBox.setText("select_multiple"); + select_multipleCheckBox.setState(FlatTriStateCheckBox.State.UNSELECTED); + select_multipleCheckBox.setAllowIndeterminate(false); + panel1.add(select_multipleCheckBox, "cell 0 1"); + + //---- do_overwrite_confirmationCheckBox ---- + do_overwrite_confirmationCheckBox.setText("do_overwrite_confirmation"); + panel1.add(do_overwrite_confirmationCheckBox, "cell 0 2"); + + //---- create_foldersCheckBox ---- + create_foldersCheckBox.setText("create_folders"); + panel1.add(create_foldersCheckBox, "cell 0 3"); + + //---- show_hiddenCheckBox ---- + show_hiddenCheckBox.setText("show_hidden"); + panel1.add(show_hiddenCheckBox, "cell 0 4"); + + //---- local_onlyCheckBox ---- + local_onlyCheckBox.setText("local_only"); + panel1.add(local_onlyCheckBox, "cell 0 5"); + } + add(panel1, "cell 2 0 1 6,aligny top,growy 0"); + + //---- okButtonLabelLabel ---- + okButtonLabelLabel.setText("okButtonLabel"); + add(okButtonLabelLabel, "cell 0 1"); + add(okButtonLabelField, "cell 1 1"); + + //---- currentNameLabel ---- + currentNameLabel.setText("currentName"); + add(currentNameLabel, "cell 0 2"); + add(currentNameField, "cell 1 2"); + + //---- currentFolderLabel ---- + currentFolderLabel.setText("currentFolder"); + add(currentFolderLabel, "cell 0 3"); + add(currentFolderField, "cell 1 3"); + + //---- fileTypesLabel ---- + fileTypesLabel.setText("fileTypes"); + add(fileTypesLabel, "cell 0 4"); + + //---- fileTypesField ---- + fileTypesField.setEditable(true); + fileTypesField.setModel(new DefaultComboBoxModel<>(new String[] { + "Text Files,*.txt,null", + "All Files,*,null", + "Text Files,*.txt,null,PDF Files,*.pdf,null,All Files,*,null", + "Text and PDF Files,*.txt,*.pdf,null" + })); + add(fileTypesField, "cell 1 4"); + + //---- fileTypeIndexLabel ---- + fileTypeIndexLabel.setText("fileTypeIndex"); + add(fileTypeIndexLabel, "cell 0 5"); + + //---- fileTypeIndexSlider ---- + fileTypeIndexSlider.setMaximum(10); + fileTypeIndexSlider.setMajorTickSpacing(1); + fileTypeIndexSlider.setValue(0); + fileTypeIndexSlider.setPaintLabels(true); + fileTypeIndexSlider.setSnapToTicks(true); + add(fileTypeIndexSlider, "cell 1 5"); + + //---- openButton ---- + openButton.setText("Open..."); + openButton.addActionListener(e -> open()); + add(openButton, "cell 0 6 3 1"); + + //---- saveButton ---- + saveButton.setText("Save..."); + saveButton.addActionListener(e -> save()); + add(saveButton, "cell 0 6 3 1"); + + //---- openDirectButton ---- + openDirectButton.setText("Open (no-thread)..."); + openDirectButton.addActionListener(e -> openDirect()); + add(openDirectButton, "cell 0 6 3 1"); + + //---- saveDirectButton ---- + saveDirectButton.setText("Save (no-thread)..."); + saveDirectButton.addActionListener(e -> saveDirect()); + add(saveDirectButton, "cell 0 6 3 1"); + + //======== filesScrollPane ======== + { + + //---- filesField ---- + filesField.setRows(8); + filesScrollPane.setViewportView(filesField); + } + add(filesScrollPane, "cell 0 7 3 1,growx"); + // JFormDesigner - End of component initialization //GEN-END:initComponents + } + + // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables + private JLabel titleLabel; + private JTextField titleField; + private JPanel panel1; + private FlatTriStateCheckBox select_folderCheckBox; + private FlatTriStateCheckBox select_multipleCheckBox; + private FlatTriStateCheckBox do_overwrite_confirmationCheckBox; + private FlatTriStateCheckBox create_foldersCheckBox; + private FlatTriStateCheckBox show_hiddenCheckBox; + private FlatTriStateCheckBox local_onlyCheckBox; + private JLabel okButtonLabelLabel; + private JTextField okButtonLabelField; + private JLabel currentNameLabel; + private JTextField currentNameField; + private JLabel currentFolderLabel; + private JTextField currentFolderField; + private JLabel fileTypesLabel; + private JComboBox fileTypesField; + private JLabel fileTypeIndexLabel; + private JSlider fileTypeIndexSlider; + private JButton openButton; + private JButton saveButton; + private JButton openDirectButton; + private JButton saveDirectButton; + private JScrollPane filesScrollPane; + private JTextArea filesField; + // JFormDesigner - End of variables declaration //GEN-END:variables +} diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserLinuxTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserLinuxTest.jfd new file mode 100644 index 00000000..2c463484 --- /dev/null +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserLinuxTest.jfd @@ -0,0 +1,182 @@ +JFDML JFormDesigner: "8.3" encoding: "UTF-8" + +new FormModel { + contentType: "form/swing" + root: new FormRoot { + add( new FormContainer( "com.formdev.flatlaf.testing.FlatTestPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { + "$layoutConstraints": "ltr,insets dialog,hidemode 3" + "$columnConstraints": "[left][grow,fill][fill]" + "$rowConstraints": "[][][][][][][][grow,fill]" + } ) { + name: "this" + add( new FormComponent( "javax.swing.JLabel" ) { + name: "titleLabel" + "text": "title" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0" + } ) + add( new FormComponent( "javax.swing.JTextField" ) { + name: "titleField" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 0" + } ) + add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { + "$layoutConstraints": "insets 2,hidemode 3" + "$columnConstraints": "[left]" + "$rowConstraints": "[]0[]0[]0[]0[]0[]" + } ) { + name: "panel1" + add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) { + name: "select_folderCheckBox" + "text": "select_folder" + "allowIndeterminate": false + "state": enum com.formdev.flatlaf.extras.components.FlatTriStateCheckBox$State UNSELECTED + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0" + } ) + add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) { + name: "select_multipleCheckBox" + "text": "select_multiple" + "state": enum com.formdev.flatlaf.extras.components.FlatTriStateCheckBox$State UNSELECTED + "allowIndeterminate": false + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 1" + } ) + add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) { + name: "do_overwrite_confirmationCheckBox" + "text": "do_overwrite_confirmation" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 2" + } ) + add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) { + name: "create_foldersCheckBox" + "text": "create_folders" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 3" + } ) + add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) { + name: "show_hiddenCheckBox" + "text": "show_hidden" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 4" + } ) + add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) { + name: "local_onlyCheckBox" + "text": "local_only" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 5" + } ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 2 0 1 6,aligny top,growy 0" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "okButtonLabelLabel" + "text": "okButtonLabel" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 1" + } ) + add( new FormComponent( "javax.swing.JTextField" ) { + name: "okButtonLabelField" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 1" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "currentNameLabel" + "text": "currentName" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 2" + } ) + add( new FormComponent( "javax.swing.JTextField" ) { + name: "currentNameField" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 2" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "currentFolderLabel" + "text": "currentFolder" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 3" + } ) + add( new FormComponent( "javax.swing.JTextField" ) { + name: "currentFolderField" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 3" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "fileTypesLabel" + "text": "fileTypes" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 4" + } ) + add( new FormComponent( "javax.swing.JComboBox" ) { + name: "fileTypesField" + "editable": true + "model": new javax.swing.DefaultComboBoxModel { + selectedItem: "Text Files,*.txt,null" + addElement( "Text Files,*.txt,null" ) + addElement( "All Files,*,null" ) + addElement( "Text Files,*.txt,null,PDF Files,*.pdf,null,All Files,*,null" ) + addElement( "Text and PDF Files,*.txt,*.pdf,null" ) + } + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 4" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "fileTypeIndexLabel" + "text": "fileTypeIndex" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 5" + } ) + add( new FormComponent( "javax.swing.JSlider" ) { + name: "fileTypeIndexSlider" + "maximum": 10 + "majorTickSpacing": 1 + "value": 0 + "paintLabels": true + "snapToTicks": true + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 5" + } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "openButton" + "text": "Open..." + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "open", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 6 3 1" + } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "saveButton" + "text": "Save..." + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "save", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 6 3 1" + } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "openDirectButton" + "text": "Open (no-thread)..." + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "openDirect", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 6 3 1" + } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "saveDirectButton" + "text": "Save (no-thread)..." + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "saveDirect", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 6 3 1" + } ) + add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) { + name: "filesScrollPane" + add( new FormComponent( "javax.swing.JTextArea" ) { + name: "filesField" + "rows": 8 + } ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 7 3 1,growx" + } ) + }, new FormLayoutConstraints( null ) { + "location": new java.awt.Point( 0, 0 ) + "size": new java.awt.Dimension( 690, 630 ) + } ) + } +}