From 49a0a83ecaa4439f4b63972ace63bdb5a7c6efdf Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Tue, 31 Dec 2024 17:15:37 +0100 Subject: [PATCH] System File Chooser: implemented native bindings for IFileOpenDialog and IFileSaveDialog on Windows --- .github/workflows/ci.yml | 2 +- .github/workflows/natives.yml | 2 +- .../flatlaf/ui/FlatNativeWindowsLibrary.java | 75 ++- .../flatlaf-natives-windows/build.gradle.kts | 4 +- .../src/main/cpp/ApiVersion.cpp | 2 +- .../src/main/cpp/WinFileChooser.cpp | 257 ++++++++ ...mdev_flatlaf_ui_FlatNativeWindowsLibrary.h | 54 ++ .../testing/FlatWindowsFileChooserTest.java | 551 ++++++++++++++++++ .../testing/FlatWindowsFileChooserTest.jfd | 324 ++++++++++ 9 files changed, 1265 insertions(+), 6 deletions(-) create mode 100644 flatlaf-natives/flatlaf-natives-windows/src/main/cpp/WinFileChooser.cpp create mode 100644 flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowsFileChooserTest.java create mode 100644 flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowsFileChooserTest.jfd diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d1f0e1e9..57acad88 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,7 +42,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: gradle/actions/wrapper-validation@v3 + - uses: gradle/actions/wrapper-validation@v4 if: matrix.java == '8' - name: Setup Java ${{ matrix.java }} diff --git a/.github/workflows/natives.yml b/.github/workflows/natives.yml index bae5cb2a..cf314dcc 100644 --- a/.github/workflows/natives.yml +++ b/.github/workflows/natives.yml @@ -30,7 +30,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: gradle/actions/wrapper-validation@v3 + - uses: gradle/actions/wrapper-validation@v4 - name: Setup Java 11 uses: actions/setup-java@v4 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 8a4ce3cf..991a9878 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 @@ -30,7 +30,7 @@ import com.formdev.flatlaf.util.SystemInfo; */ public class FlatNativeWindowsLibrary { - private static int API_VERSION_WINDOWS = 1001; + private static int API_VERSION_WINDOWS = 1002; private static long osBuildNumber = Long.MIN_VALUE; @@ -158,4 +158,77 @@ public class FlatNativeWindowsLibrary // DwmSetWindowAttribute() expects COLORREF as attribute value, which is defined as DWORD return dwmSetWindowAttributeDWORD( hwnd, attribute, rgb ); } + + + /** + * FILEOPENDIALOGOPTIONS + * see https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/ne-shobjidl_core-_fileopendialogoptions + * + * @since 3.6 + */ + public static final int + FOS_OVERWRITEPROMPT = 0x2, // default for Save + FOS_STRICTFILETYPES = 0x4, + FOS_NOCHANGEDIR = 0x8, // default + FOS_PICKFOLDERS = 0x20, + FOS_FORCEFILESYSTEM = 0x40, + FOS_ALLNONSTORAGEITEMS = 0x80, + FOS_NOVALIDATE = 0x100, + FOS_ALLOWMULTISELECT = 0x200, + FOS_PATHMUSTEXIST = 0x800, // default + FOS_FILEMUSTEXIST = 0x1000, // default for Open + FOS_CREATEPROMPT = 0x2000, + FOS_SHAREAWARE = 0x4000, + FOS_NOREADONLYRETURN = 0x8000, // default for Save + FOS_NOTESTFILECREATE = 0x10000, + FOS_HIDEMRUPLACES = 0x20000, + FOS_HIDEPINNEDPLACES = 0x40000, + FOS_NODEREFERENCELINKS = 0x100000, + FOS_OKBUTTONNEEDSINTERACTION = 0x200000, + FOS_DONTADDTORECENT = 0x2000000, + FOS_FORCESHOWHIDDEN = 0x10000000, + FOS_DEFAULTNOMINIMODE = 0x20000000, + FOS_FORCEPREVIEWPANEON = 0x40000000, + FOS_SUPPORTSTREAMABLEITEMS = 0x80000000; + + /** + * Shows the Windows system + * file dialogs + * IFileOpenDialog or + * IFileSaveDialog. + *

+ * 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 owner the owner of the file dialog + * @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 fileNameLabel text displayed in front of the filename text field; or {@code null} + * @param fileName user-editable filename currently shown in the filename field; or {@code null} + * @param folder current directory shown in the dialog; or {@code null} + * @param saveAsItem file to be used as the initial entry in a Save As dialog; or {@code null}. + * File name is shown in filename text field, folder is selected in view. + * To be used for saving files that already exist. For new files use {@code fileName}. + * @param defaultFolder folder used as a default if there is not a recently used folder value available; or {@code null}. + * Windows somewhere stores default folder on a per-app basis. + * So this is probably used only once when the app opens a file dialog for first time. + * @param defaultExtension default extension to be added to file name in save 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. + * Pairs of strings are required. + * 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 "*.*"). + * @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( Window owner, boolean open, + String title, String okButtonLabel, String fileNameLabel, String fileName, + String folder, String saveAsItem, String defaultFolder, String defaultExtension, + int optionsSet, int optionsClear, int fileTypeIndex, String... fileTypes ); } diff --git a/flatlaf-natives/flatlaf-natives-windows/build.gradle.kts b/flatlaf-natives/flatlaf-natives-windows/build.gradle.kts index e297703e..550179d1 100644 --- a/flatlaf-natives/flatlaf-natives-windows/build.gradle.kts +++ b/flatlaf-natives/flatlaf-natives-windows/build.gradle.kts @@ -80,8 +80,8 @@ tasks { linkerArgs.addAll( toolChain.map { when( it ) { - is Gcc, is Clang -> listOf( "-lUser32", "-lGdi32", "-lshell32", "-lAdvAPI32", "-lKernel32", "-lDwmapi" ) - is VisualCpp -> listOf( "User32.lib", "Gdi32.lib", "shell32.lib", "AdvAPI32.lib", "Kernel32.lib", "Dwmapi.lib", "/NODEFAULTLIB" ) + is Gcc, is Clang -> listOf( "-lUser32", "-lGdi32", "-lshell32", "-lAdvAPI32", "-lKernel32", "-lDwmapi", "-lOle32", "-luuid" ) + is VisualCpp -> listOf( "User32.lib", "Gdi32.lib", "shell32.lib", "AdvAPI32.lib", "Kernel32.lib", "Dwmapi.lib", "Ole32.lib", "uuid.lib", "/NODEFAULTLIB" ) else -> emptyList() } } ) 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 a32f0d6d..0f2e7a97 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 1001 +#define API_VERSION_WINDOWS 1002 //---- 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 new file mode 100644 index 00000000..b51b0d7f --- /dev/null +++ b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/WinFileChooser.cpp @@ -0,0 +1,257 @@ +/* + * Copyright 2024 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. + */ + +// avoid inlining of printf() +#define _NO_CRT_STDIO_INLINE + +#include +#include +#include "com_formdev_flatlaf_ui_FlatNativeWindowsLibrary.h" + +/** + * @author Karl Tauber + */ + +// see FlatWndProc.cpp +HWND getWindowHandle( JNIEnv* env, jobject window ); + +//---- class AutoReleasePtr --------------------------------------------------- + +template class AutoReleasePtr { + T* ptr; + +public: + AutoReleasePtr() { + ptr = NULL; + } + ~AutoReleasePtr() { + if( ptr != NULL ) + ptr->Release(); + } + T** operator&() { return &ptr; } + T* operator->() { return ptr; } + operator T*() { return ptr; } +}; + +//---- class AutoReleaseString ------------------------------------------------ + +class AutoReleaseString { + JNIEnv* env; + jstring javaString; + const jchar* chars; + +public: + AutoReleaseString( JNIEnv* _env, jstring _javaString ) { + env = _env; + javaString = _javaString; + chars = (javaString != NULL) ? env->GetStringChars( javaString, NULL ) : NULL; + } + ~AutoReleaseString() { + if( chars != NULL ) + env->ReleaseStringChars( javaString, chars ); + } + operator LPCWSTR() { return (LPCWSTR) chars; } +}; + +//---- class AutoReleaseIShellItem -------------------------------------------- + +class AutoReleaseIShellItem : public AutoReleasePtr { +public: + AutoReleaseIShellItem( JNIEnv* env, jstring path ) { + AutoReleaseString cpath( env, path ); + ::SHCreateItemFromParsingName( cpath, NULL, IID_IShellItem, reinterpret_cast( &*this ) ); + } +}; + +//---- class FilterSpec ------------------------------------------------------- + +class FilterSpec { + JNIEnv* env; + jstring* jnames = NULL; + jstring* jspecs = NULL; + +public: + UINT count = 0; + COMDLG_FILTERSPEC* specs = NULL; + +public: + FilterSpec( JNIEnv* _env, jobjectArray fileTypes ) { + env = _env; + count = env->GetArrayLength( fileTypes ) / 2; + if( count <= 0 ) + return; + + specs = new COMDLG_FILTERSPEC[count]; + jnames = new jstring[count]; + jspecs = new jstring[count]; + + for( int i = 0; i < count; i++ ) { + jnames[i] = (jstring) env->GetObjectArrayElement( fileTypes, i * 2 ); + jspecs[i] = (jstring) env->GetObjectArrayElement( fileTypes, (i * 2) + 1 ); + specs[i].pszName = (LPCWSTR) env->GetStringChars( jnames[i] , NULL ); + specs[i].pszSpec = (LPCWSTR) env->GetStringChars( jspecs[i], NULL ); + } + } + ~FilterSpec() { + if( specs == NULL ) + return; + + for( int i = 0; i < count; i++ ) { + env->ReleaseStringChars( jnames[i], (jchar *) specs[i].pszName ); + env->ReleaseStringChars( jspecs[i], (jchar *) specs[i].pszSpec ); + env->DeleteLocalRef( jnames[i] ); + env->DeleteLocalRef( jspecs[i] ); + } + + delete[] jnames; + delete[] jspecs; + delete[] specs; + } +}; + +//---- class CoInitializer ---------------------------------------------------- + +class CoInitializer { +public: + bool initialized; + + CoInitializer() { + HRESULT result = ::CoInitializeEx( NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE ); + initialized = SUCCEEDED( result ); + } + ~CoInitializer() { + if( initialized ) + ::CoUninitialize(); + } +}; + +//---- helper ----------------------------------------------------------------- + +#define CHECK_HRESULT( code ) { if( (code) != S_OK ) return NULL; } + +jobjectArray newJavaStringArray( JNIEnv* env, jsize count ) { + jclass stringClass = env->FindClass( "java/lang/String" ); + return env->NewObjectArray( count, stringClass, NULL ); +} + +jstring newJavaString( JNIEnv* env, LPWSTR str ) { + return env->NewString( reinterpret_cast( str ), static_cast( wcslen( str ) ) ); +} + +//---- JNI methods ------------------------------------------------------------ + +extern "C" +JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_showFileChooser + ( 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, jint fileTypeIndex, jobjectArray fileTypes ) +{ + // initialize COM library + CoInitializer coInitializer; + if( !coInitializer.initialized ) + return NULL; + + HWND hwndOwner = getWindowHandle( env, owner ); + + // convert Java strings to C strings + AutoReleaseString ctitle( env, title ); + AutoReleaseString cokButtonLabel( env, okButtonLabel ); + AutoReleaseString cfileNameLabel( env, fileNameLabel ); + AutoReleaseString cfileName( env, fileName ); + AutoReleaseIShellItem cfolder( env, folder ); + AutoReleaseIShellItem csaveAsItem( env, saveAsItem ); + AutoReleaseIShellItem cdefaultFolder( env, defaultFolder ); + AutoReleaseString cdefaultExtension( env, defaultExtension ); + FilterSpec specs( env, fileTypes ); + + // create IFileOpenDialog or IFileSaveDialog + // https://learn.microsoft.com/en-us/windows/win32/shell/common-file-dialog + AutoReleasePtr dialog; + CHECK_HRESULT( ::CoCreateInstance( open ? CLSID_FileOpenDialog : CLSID_FileSaveDialog, + NULL, CLSCTX_INPROC_SERVER, open ? IID_IFileOpenDialog : IID_IFileSaveDialog, + reinterpret_cast( &dialog ) ) ); + + if( ctitle != NULL ) + CHECK_HRESULT( dialog->SetTitle( ctitle ) ); + if( cokButtonLabel != NULL ) + CHECK_HRESULT( dialog->SetOkButtonLabel( cokButtonLabel ) ); + if( cfileNameLabel != NULL ) + CHECK_HRESULT( dialog->SetFileNameLabel( cfileNameLabel ) ); + if( cfileName != NULL ) + CHECK_HRESULT( dialog->SetFileName( cfileName ) ); + if( cfolder != NULL ) + CHECK_HRESULT( dialog->SetFolder( cfolder ) ); + if( !open && csaveAsItem != NULL ) + CHECK_HRESULT( ((IFileSaveDialog*)(IFileDialog*)dialog)->SetSaveAsItem( csaveAsItem ) ); + if( cdefaultFolder != NULL ) + CHECK_HRESULT( dialog->SetDefaultFolder( cdefaultFolder ) ); + if( cdefaultExtension != NULL ) + CHECK_HRESULT( dialog->SetDefaultExtension( cdefaultExtension ) ); + + FILEOPENDIALOGOPTIONS existingOptions; + CHECK_HRESULT( dialog->GetOptions( &existingOptions ) ); + CHECK_HRESULT( dialog->SetOptions ( (existingOptions & ~optionsClear) | optionsSet ) ); + + if( specs.count > 0 ) { + CHECK_HRESULT( dialog->SetFileTypes( specs.count, specs.specs ) ); + if( fileTypeIndex > 0 ) + CHECK_HRESULT( dialog->SetFileTypeIndex( min( fileTypeIndex + 1, specs.count ) ) ); + } + + // show dialog + HRESULT hr = dialog->Show( hwndOwner ); + if( hr == HRESULT_FROM_WIN32(ERROR_CANCELLED) ) + return newJavaStringArray( env, 0 ); + CHECK_HRESULT( hr ); + + // convert URLs to Java string array + if( open ) { + AutoReleasePtr shellItems; + DWORD count; + CHECK_HRESULT( ((IFileOpenDialog*)(IFileDialog*)dialog)->GetResults( &shellItems ) ); + CHECK_HRESULT( shellItems->GetCount( &count ) ); + + jobjectArray array = newJavaStringArray( env, count ); + for( int i = 0; i < count; i++ ) { + AutoReleasePtr shellItem; + LPWSTR path; + CHECK_HRESULT( shellItems->GetItemAt( i, &shellItem ) ); + CHECK_HRESULT( shellItem->GetDisplayName( SIGDN_FILESYSPATH, &path ) ); + + jstring jpath = newJavaString( env, path ); + CoTaskMemFree( path ); + + env->SetObjectArrayElement( array, 0, jpath ); + env->DeleteLocalRef( jpath ); + } + return array; + } else { + AutoReleasePtr shellItem; + LPWSTR path; + CHECK_HRESULT( dialog->GetResult( &shellItem ) ); + CHECK_HRESULT( shellItem->GetDisplayName( SIGDN_FILESYSPATH, &path ) ); + + jstring jpath = newJavaString( env, path ); + CoTaskMemFree( path ); + + jobjectArray array = newJavaStringArray( env, 1 ); + env->SetObjectArrayElement( array, 0, jpath ); + env->DeleteLocalRef( jpath ); + + return array; + } +} 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 1701cb3b..895b7267 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 @@ -27,6 +27,52 @@ extern "C" { #define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_DWMWA_COLOR_DEFAULT -1L #undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_DWMWA_COLOR_NONE #define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_DWMWA_COLOR_NONE -2L +#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_OVERWRITEPROMPT +#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_OVERWRITEPROMPT 2L +#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_STRICTFILETYPES +#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_STRICTFILETYPES 4L +#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_NOCHANGEDIR +#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_NOCHANGEDIR 8L +#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_PICKFOLDERS +#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_PICKFOLDERS 32L +#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_FORCEFILESYSTEM +#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_FORCEFILESYSTEM 64L +#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_ALLNONSTORAGEITEMS +#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_ALLNONSTORAGEITEMS 128L +#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_NOVALIDATE +#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_NOVALIDATE 256L +#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_ALLOWMULTISELECT +#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_ALLOWMULTISELECT 512L +#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_PATHMUSTEXIST +#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_PATHMUSTEXIST 2048L +#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_FILEMUSTEXIST +#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_FILEMUSTEXIST 4096L +#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_CREATEPROMPT +#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_CREATEPROMPT 8192L +#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_SHAREAWARE +#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_SHAREAWARE 16384L +#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_NOREADONLYRETURN +#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_NOREADONLYRETURN 32768L +#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_NOTESTFILECREATE +#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_NOTESTFILECREATE 65536L +#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_HIDEMRUPLACES +#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_HIDEMRUPLACES 131072L +#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_HIDEPINNEDPLACES +#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_HIDEPINNEDPLACES 262144L +#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_NODEREFERENCELINKS +#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_NODEREFERENCELINKS 1048576L +#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_OKBUTTONNEEDSINTERACTION +#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_OKBUTTONNEEDSINTERACTION 2097152L +#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_DONTADDTORECENT +#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_DONTADDTORECENT 33554432L +#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_FORCESHOWHIDDEN +#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_FORCESHOWHIDDEN 268435456L +#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_DEFAULTNOMINIMODE +#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_DEFAULTNOMINIMODE 536870912L +#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_FORCEPREVIEWPANEON +#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_FORCEPREVIEWPANEON 1073741824L +#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_SUPPORTSTREAMABLEITEMS +#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_SUPPORTSTREAMABLEITEMS -2147483648L /* * Class: com_formdev_flatlaf_ui_FlatNativeWindowsLibrary * Method: getOSBuildNumberImpl @@ -67,6 +113,14 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_dwmSetWindowAttributeDWORD (JNIEnv *, jclass, jlong, jint, jint); +/* + * 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;III[Ljava/lang/String;)[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, jint, jobjectArray); + #ifdef __cplusplus } #endif diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowsFileChooserTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowsFileChooserTest.java new file mode 100644 index 00000000..c15f1e63 --- /dev/null +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowsFileChooserTest.java @@ -0,0 +1,551 @@ +/* + * Copyright 2024 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.FlatNativeWindowsLibrary.*; +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.FlatNativeWindowsLibrary; +import net.miginfocom.swing.*; + +/** + * @author Karl Tauber + */ +public class FlatWindowsFileChooserTest + extends FlatTestPanel +{ + public static void main( String[] args ) { + SwingUtilities.invokeLater( () -> { + if( !FlatNativeWindowsLibrary.isLoaded() ) { + JOptionPane.showMessageDialog( null, "FlatLaf native library not loaded" ); + return; + } + + FlatTestFrame frame = FlatTestFrame.create( args, "FlatWindowsFileChooserTest" ); + addListeners( frame ); + frame.showFrame( FlatWindowsFileChooserTest::new ); + } ); + } + + FlatWindowsFileChooserTest() { + 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 ) { + Window owner = SwingUtilities.windowForComponent( this ); + String title = n( titleField.getText() ); + String okButtonLabel = n( okButtonLabelField.getText() ); + String fileNameLabel = n( fileNameLabelField.getText() ); + String fileName = n( fileNameField.getText() ); + String folder = n( folderField.getText() ); + String saveAsItem = n( saveAsItemField.getText() ); + String defaultFolder = n( defaultFolderField.getText() ); + String defaultExtension = n( defaultExtensionField.getText() ); + AtomicInteger optionsSet = new AtomicInteger(); + AtomicInteger optionsClear = new AtomicInteger(); + + o( FOS_OVERWRITEPROMPT, overwritePromptCheckBox, optionsSet, optionsClear ); + o( FOS_STRICTFILETYPES, strictFileTypesCheckBox, optionsSet, optionsClear ); + o( FOS_NOCHANGEDIR, noChangeDirCheckBox, optionsSet, optionsClear ); + o( FOS_PICKFOLDERS, pickFoldersCheckBox, optionsSet, optionsClear ); + o( FOS_FORCEFILESYSTEM, forceFileSystemCheckBox, optionsSet, optionsClear ); + o( FOS_ALLNONSTORAGEITEMS, allNonStorageItemsCheckBox, optionsSet, optionsClear ); + o( FOS_NOVALIDATE, noValidateCheckBox, optionsSet, optionsClear ); + o( FOS_ALLOWMULTISELECT, allowMultiSelectCheckBox, optionsSet, optionsClear ); + o( FOS_PATHMUSTEXIST, pathMustExistCheckBox, optionsSet, optionsClear ); + o( FOS_FILEMUSTEXIST, fileMustExistCheckBox, optionsSet, optionsClear ); + o( FOS_CREATEPROMPT, createPromptCheckBox, optionsSet, optionsClear ); + o( FOS_SHAREAWARE, shareAwareCheckBox, optionsSet, optionsClear ); + o( FOS_NOREADONLYRETURN, noReadOnlyReturnCheckBox, optionsSet, optionsClear ); + o( FOS_NOTESTFILECREATE, noTestFileCreateCheckBox, optionsSet, optionsClear ); + o( FOS_HIDEMRUPLACES, hideMruPlacesCheckBox, optionsSet, optionsClear ); + o( FOS_HIDEPINNEDPLACES, hidePinnedPlacesCheckBox, optionsSet, optionsClear ); + o( FOS_NODEREFERENCELINKS, noDereferenceLinksCheckBox, optionsSet, optionsClear ); + o( FOS_OKBUTTONNEEDSINTERACTION, okButtonNeedsInteractionCheckBox, optionsSet, optionsClear ); + o( FOS_DONTADDTORECENT, dontAddToRecentCheckBox, optionsSet, optionsClear ); + o( FOS_FORCESHOWHIDDEN, forceShowHiddenCheckBox, optionsSet, optionsClear ); + o( FOS_DEFAULTNOMINIMODE, defaultNoMiniModeCheckBox, optionsSet, optionsClear ); + o( FOS_FORCEPREVIEWPANEON, forcePreviewPaneonCheckBox, optionsSet, optionsClear ); + o( FOS_SUPPORTSTREAMABLEITEMS, supportStreamableItemsCheckBox, optionsSet, optionsClear ); + + String fileTypesStr = n( (String) fileTypesField.getSelectedItem() ); + String[] fileTypes = {}; + if( fileTypesStr != null ) + fileTypes = fileTypesStr.trim().split( "[,]+" ); + int fileTypeIndex = fileTypeIndexSlider.getValue(); + + if( direct ) { + String[] files = FlatNativeWindowsLibrary.showFileChooser( owner, open, + title, okButtonLabel, fileNameLabel, fileName, + folder, saveAsItem, defaultFolder, defaultExtension, + 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 = FlatNativeWindowsLibrary.showFileChooser( owner, open, + title, okButtonLabel, fileNameLabel, fileName, + folder, saveAsItem, defaultFolder, defaultExtension, + 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(); + overwritePromptCheckBox = new FlatTriStateCheckBox(); + pathMustExistCheckBox = new FlatTriStateCheckBox(); + noDereferenceLinksCheckBox = new FlatTriStateCheckBox(); + strictFileTypesCheckBox = new FlatTriStateCheckBox(); + fileMustExistCheckBox = new FlatTriStateCheckBox(); + okButtonNeedsInteractionCheckBox = new FlatTriStateCheckBox(); + noChangeDirCheckBox = new FlatTriStateCheckBox(); + createPromptCheckBox = new FlatTriStateCheckBox(); + dontAddToRecentCheckBox = new FlatTriStateCheckBox(); + pickFoldersCheckBox = new FlatTriStateCheckBox(); + shareAwareCheckBox = new FlatTriStateCheckBox(); + forceShowHiddenCheckBox = new FlatTriStateCheckBox(); + forceFileSystemCheckBox = new FlatTriStateCheckBox(); + noReadOnlyReturnCheckBox = new FlatTriStateCheckBox(); + defaultNoMiniModeCheckBox = new FlatTriStateCheckBox(); + allNonStorageItemsCheckBox = new FlatTriStateCheckBox(); + noTestFileCreateCheckBox = new FlatTriStateCheckBox(); + forcePreviewPaneonCheckBox = new FlatTriStateCheckBox(); + noValidateCheckBox = new FlatTriStateCheckBox(); + hideMruPlacesCheckBox = new FlatTriStateCheckBox(); + supportStreamableItemsCheckBox = new FlatTriStateCheckBox(); + allowMultiSelectCheckBox = new FlatTriStateCheckBox(); + hidePinnedPlacesCheckBox = new FlatTriStateCheckBox(); + okButtonLabelLabel = new JLabel(); + okButtonLabelField = new JTextField(); + fileNameLabelLabel = new JLabel(); + fileNameLabelField = new JTextField(); + fileNameLabel = new JLabel(); + fileNameField = new JTextField(); + folderLabel = new JLabel(); + folderField = new JTextField(); + saveAsItemLabel = new JLabel(); + saveAsItemField = new JTextField(); + defaultFolderLabel = new JLabel(); + defaultFolderField = new JTextField(); + defaultExtensionLabel = new JLabel(); + defaultExtensionField = 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]para" + + "[left]para" + + "[left]", + // rows + "[]0" + + "[]0" + + "[]0" + + "[]" + + "[]0" + + "[]0" + + "[]0" + + "[]0")); + + //---- overwritePromptCheckBox ---- + overwritePromptCheckBox.setText("overwritePrompt"); + panel1.add(overwritePromptCheckBox, "cell 0 0"); + + //---- pathMustExistCheckBox ---- + pathMustExistCheckBox.setText("pathMustExist"); + panel1.add(pathMustExistCheckBox, "cell 1 0"); + + //---- noDereferenceLinksCheckBox ---- + noDereferenceLinksCheckBox.setText("noDereferenceLinks"); + panel1.add(noDereferenceLinksCheckBox, "cell 2 0"); + + //---- strictFileTypesCheckBox ---- + strictFileTypesCheckBox.setText("strictFileTypes"); + panel1.add(strictFileTypesCheckBox, "cell 0 1"); + + //---- fileMustExistCheckBox ---- + fileMustExistCheckBox.setText("fileMustExist"); + panel1.add(fileMustExistCheckBox, "cell 1 1"); + + //---- okButtonNeedsInteractionCheckBox ---- + okButtonNeedsInteractionCheckBox.setText("okButtonNeedsInteraction"); + panel1.add(okButtonNeedsInteractionCheckBox, "cell 2 1"); + + //---- noChangeDirCheckBox ---- + noChangeDirCheckBox.setText("noChangeDir"); + panel1.add(noChangeDirCheckBox, "cell 0 2"); + + //---- createPromptCheckBox ---- + createPromptCheckBox.setText("createPrompt"); + panel1.add(createPromptCheckBox, "cell 1 2"); + + //---- dontAddToRecentCheckBox ---- + dontAddToRecentCheckBox.setText("dontAddToRecent"); + panel1.add(dontAddToRecentCheckBox, "cell 2 2"); + + //---- pickFoldersCheckBox ---- + pickFoldersCheckBox.setText("pickFolders"); + panel1.add(pickFoldersCheckBox, "cell 0 3"); + + //---- shareAwareCheckBox ---- + shareAwareCheckBox.setText("shareAware"); + panel1.add(shareAwareCheckBox, "cell 1 3"); + + //---- forceShowHiddenCheckBox ---- + forceShowHiddenCheckBox.setText("forceShowHidden"); + panel1.add(forceShowHiddenCheckBox, "cell 2 3"); + + //---- forceFileSystemCheckBox ---- + forceFileSystemCheckBox.setText("forceFileSystem"); + panel1.add(forceFileSystemCheckBox, "cell 0 4"); + + //---- noReadOnlyReturnCheckBox ---- + noReadOnlyReturnCheckBox.setText("noReadOnlyReturn"); + panel1.add(noReadOnlyReturnCheckBox, "cell 1 4"); + + //---- defaultNoMiniModeCheckBox ---- + defaultNoMiniModeCheckBox.setText("defaultNoMiniMode"); + panel1.add(defaultNoMiniModeCheckBox, "cell 2 4"); + + //---- allNonStorageItemsCheckBox ---- + allNonStorageItemsCheckBox.setText("allNonStorageItems"); + panel1.add(allNonStorageItemsCheckBox, "cell 0 5"); + + //---- noTestFileCreateCheckBox ---- + noTestFileCreateCheckBox.setText("noTestFileCreate"); + panel1.add(noTestFileCreateCheckBox, "cell 1 5"); + + //---- forcePreviewPaneonCheckBox ---- + forcePreviewPaneonCheckBox.setText("forcePreviewPaneon"); + panel1.add(forcePreviewPaneonCheckBox, "cell 2 5"); + + //---- noValidateCheckBox ---- + noValidateCheckBox.setText("noValidate"); + panel1.add(noValidateCheckBox, "cell 0 6"); + + //---- hideMruPlacesCheckBox ---- + hideMruPlacesCheckBox.setText("hideMruPlaces"); + panel1.add(hideMruPlacesCheckBox, "cell 1 6"); + + //---- supportStreamableItemsCheckBox ---- + supportStreamableItemsCheckBox.setText("supportStreamableItems"); + panel1.add(supportStreamableItemsCheckBox, "cell 2 6"); + + //---- allowMultiSelectCheckBox ---- + allowMultiSelectCheckBox.setText("allowMultiSelect"); + panel1.add(allowMultiSelectCheckBox, "cell 0 7"); + + //---- hidePinnedPlacesCheckBox ---- + hidePinnedPlacesCheckBox.setText("hidePinnedPlaces"); + panel1.add(hidePinnedPlacesCheckBox, "cell 1 7"); + } + add(panel1, "cell 2 0 1 10,aligny top,growy 0"); + + //---- okButtonLabelLabel ---- + okButtonLabelLabel.setText("okButtonLabel"); + add(okButtonLabelLabel, "cell 0 1"); + add(okButtonLabelField, "cell 1 1"); + + //---- fileNameLabelLabel ---- + fileNameLabelLabel.setText("fileNameLabel"); + add(fileNameLabelLabel, "cell 0 2"); + add(fileNameLabelField, "cell 1 2"); + + //---- fileNameLabel ---- + fileNameLabel.setText("fileName"); + add(fileNameLabel, "cell 0 3"); + add(fileNameField, "cell 1 3"); + + //---- folderLabel ---- + folderLabel.setText("folder"); + add(folderLabel, "cell 0 4"); + add(folderField, "cell 1 4"); + + //---- saveAsItemLabel ---- + saveAsItemLabel.setText("saveAsItem"); + add(saveAsItemLabel, "cell 0 5"); + add(saveAsItemField, "cell 1 5"); + + //---- defaultFolderLabel ---- + defaultFolderLabel.setText("defaultFolder"); + add(defaultFolderLabel, "cell 0 6"); + add(defaultFolderField, "cell 1 6"); + + //---- defaultExtensionLabel ---- + defaultExtensionLabel.setText("defaultExtension"); + add(defaultExtensionLabel, "cell 0 7"); + add(defaultExtensionField, "cell 1 7"); + + //---- fileTypesLabel ---- + fileTypesLabel.setText("fileTypes"); + add(fileTypesLabel, "cell 0 8"); + + //---- fileTypesField ---- + fileTypesField.setEditable(true); + fileTypesField.setModel(new DefaultComboBoxModel<>(new String[] { + "Text Files,*.txt", + "All Files,*.*", + "Text Files,*.txt,PDF Files,*.pdf,All Files,*.*", + "Text and PDF Files,*.txt;*.pdf" + })); + add(fileTypesField, "cell 1 8"); + + //---- fileTypeIndexLabel ---- + fileTypeIndexLabel.setText("fileTypeIndex"); + add(fileTypeIndexLabel, "cell 0 9"); + + //---- fileTypeIndexSlider ---- + fileTypeIndexSlider.setMaximum(10); + fileTypeIndexSlider.setMajorTickSpacing(1); + fileTypeIndexSlider.setValue(0); + fileTypeIndexSlider.setPaintLabels(true); + fileTypeIndexSlider.setSnapToTicks(true); + add(fileTypeIndexSlider, "cell 1 9"); + + //---- openButton ---- + openButton.setText("Open..."); + openButton.addActionListener(e -> open()); + add(openButton, "cell 0 10 3 1"); + + //---- saveButton ---- + saveButton.setText("Save..."); + saveButton.addActionListener(e -> save()); + add(saveButton, "cell 0 10 3 1"); + + //---- openDirectButton ---- + openDirectButton.setText("Open (no-thread)..."); + openDirectButton.addActionListener(e -> openDirect()); + add(openDirectButton, "cell 0 10 3 1"); + + //---- saveDirectButton ---- + saveDirectButton.setText("Save (no-thread)..."); + saveDirectButton.addActionListener(e -> saveDirect()); + add(saveDirectButton, "cell 0 10 3 1"); + + //======== filesScrollPane ======== + { + + //---- filesField ---- + filesField.setRows(8); + filesScrollPane.setViewportView(filesField); + } + add(filesScrollPane, "cell 0 11 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 overwritePromptCheckBox; + private FlatTriStateCheckBox pathMustExistCheckBox; + private FlatTriStateCheckBox noDereferenceLinksCheckBox; + private FlatTriStateCheckBox strictFileTypesCheckBox; + private FlatTriStateCheckBox fileMustExistCheckBox; + private FlatTriStateCheckBox okButtonNeedsInteractionCheckBox; + private FlatTriStateCheckBox noChangeDirCheckBox; + private FlatTriStateCheckBox createPromptCheckBox; + private FlatTriStateCheckBox dontAddToRecentCheckBox; + private FlatTriStateCheckBox pickFoldersCheckBox; + private FlatTriStateCheckBox shareAwareCheckBox; + private FlatTriStateCheckBox forceShowHiddenCheckBox; + private FlatTriStateCheckBox forceFileSystemCheckBox; + private FlatTriStateCheckBox noReadOnlyReturnCheckBox; + private FlatTriStateCheckBox defaultNoMiniModeCheckBox; + private FlatTriStateCheckBox allNonStorageItemsCheckBox; + private FlatTriStateCheckBox noTestFileCreateCheckBox; + private FlatTriStateCheckBox forcePreviewPaneonCheckBox; + private FlatTriStateCheckBox noValidateCheckBox; + private FlatTriStateCheckBox hideMruPlacesCheckBox; + private FlatTriStateCheckBox supportStreamableItemsCheckBox; + private FlatTriStateCheckBox allowMultiSelectCheckBox; + private FlatTriStateCheckBox hidePinnedPlacesCheckBox; + private JLabel okButtonLabelLabel; + private JTextField okButtonLabelField; + private JLabel fileNameLabelLabel; + private JTextField fileNameLabelField; + private JLabel fileNameLabel; + private JTextField fileNameField; + private JLabel folderLabel; + private JTextField folderField; + private JLabel saveAsItemLabel; + private JTextField saveAsItemField; + private JLabel defaultFolderLabel; + private JTextField defaultFolderField; + private JLabel defaultExtensionLabel; + private JTextField defaultExtensionField; + 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/FlatWindowsFileChooserTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowsFileChooserTest.jfd new file mode 100644 index 00000000..f26815de --- /dev/null +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowsFileChooserTest.jfd @@ -0,0 +1,324 @@ +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]para[left]para[left]" + "$rowConstraints": "[]0[]0[]0[][]0[]0[]0[]0" + } ) { + name: "panel1" + add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) { + name: "overwritePromptCheckBox" + "text": "overwritePrompt" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0" + } ) + add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) { + name: "pathMustExistCheckBox" + "text": "pathMustExist" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 0" + } ) + add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) { + name: "noDereferenceLinksCheckBox" + "text": "noDereferenceLinks" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 2 0" + } ) + add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) { + name: "strictFileTypesCheckBox" + "text": "strictFileTypes" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 1" + } ) + add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) { + name: "fileMustExistCheckBox" + "text": "fileMustExist" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 1" + } ) + add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) { + name: "okButtonNeedsInteractionCheckBox" + "text": "okButtonNeedsInteraction" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 2 1" + } ) + add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) { + name: "noChangeDirCheckBox" + "text": "noChangeDir" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 2" + } ) + add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) { + name: "createPromptCheckBox" + "text": "createPrompt" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 2" + } ) + add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) { + name: "dontAddToRecentCheckBox" + "text": "dontAddToRecent" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 2 2" + } ) + add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) { + name: "pickFoldersCheckBox" + "text": "pickFolders" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 3" + } ) + add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) { + name: "shareAwareCheckBox" + "text": "shareAware" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 3" + } ) + add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) { + name: "forceShowHiddenCheckBox" + "text": "forceShowHidden" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 2 3" + } ) + add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) { + name: "forceFileSystemCheckBox" + "text": "forceFileSystem" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 4" + } ) + add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) { + name: "noReadOnlyReturnCheckBox" + "text": "noReadOnlyReturn" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 4" + } ) + add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) { + name: "defaultNoMiniModeCheckBox" + "text": "defaultNoMiniMode" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 2 4" + } ) + add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) { + name: "allNonStorageItemsCheckBox" + "text": "allNonStorageItems" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 5" + } ) + add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) { + name: "noTestFileCreateCheckBox" + "text": "noTestFileCreate" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 5" + } ) + add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) { + name: "forcePreviewPaneonCheckBox" + "text": "forcePreviewPaneon" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 2 5" + } ) + add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) { + name: "noValidateCheckBox" + "text": "noValidate" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 6" + } ) + add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) { + name: "hideMruPlacesCheckBox" + "text": "hideMruPlaces" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 6" + } ) + add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) { + name: "supportStreamableItemsCheckBox" + "text": "supportStreamableItems" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 2 6" + } ) + add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) { + name: "allowMultiSelectCheckBox" + "text": "allowMultiSelect" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 7" + } ) + add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) { + name: "hidePinnedPlacesCheckBox" + "text": "hidePinnedPlaces" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 7" + } ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 2 0 1 10,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: "fileNameLabelLabel" + "text": "fileNameLabel" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 2" + } ) + add( new FormComponent( "javax.swing.JTextField" ) { + name: "fileNameLabelField" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 2" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "fileNameLabel" + "text": "fileName" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 3" + } ) + add( new FormComponent( "javax.swing.JTextField" ) { + name: "fileNameField" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 3" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "folderLabel" + "text": "folder" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 4" + } ) + add( new FormComponent( "javax.swing.JTextField" ) { + name: "folderField" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 4" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "saveAsItemLabel" + "text": "saveAsItem" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 5" + } ) + add( new FormComponent( "javax.swing.JTextField" ) { + name: "saveAsItemField" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 5" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "defaultFolderLabel" + "text": "defaultFolder" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 6" + } ) + add( new FormComponent( "javax.swing.JTextField" ) { + name: "defaultFolderField" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 6" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "defaultExtensionLabel" + "text": "defaultExtension" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 7" + } ) + add( new FormComponent( "javax.swing.JTextField" ) { + name: "defaultExtensionField" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 7" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "fileTypesLabel" + "text": "fileTypes" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 8" + } ) + add( new FormComponent( "javax.swing.JComboBox" ) { + name: "fileTypesField" + "editable": true + "model": new javax.swing.DefaultComboBoxModel { + selectedItem: "Text Files,*.txt" + addElement( "Text Files,*.txt" ) + addElement( "All Files,*.*" ) + addElement( "Text Files,*.txt,PDF Files,*.pdf,All Files,*.*" ) + addElement( "Text and PDF Files,*.txt;*.pdf" ) + } + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 8" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "fileTypeIndexLabel" + "text": "fileTypeIndex" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 9" + } ) + 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 9" + } ) + 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 10 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 10 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 10 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 10 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 11 3 1,growx" + } ) + }, new FormLayoutConstraints( null ) { + "location": new java.awt.Point( 0, 0 ) + "size": new java.awt.Dimension( 690, 630 ) + } ) + } +}