From d513ec497b80e07c36d7b825af8814b443b51845 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Wed, 15 Jan 2025 18:51:37 +0100 Subject: [PATCH] System File Chooser: support system message dialog with custom buttons on Windows (not yet used in `SystemFileChooser` --- .../flatlaf/ui/FlatNativeLinuxLibrary.java | 7 +- .../flatlaf/ui/FlatNativeWindowsLibrary.java | 31 +- .../src/main/cpp/Runtime.cpp | 3 + .../src/main/cpp/WinMessageDialog.cpp | 344 +++++++++++++++++- ...mdev_flatlaf_ui_FlatNativeWindowsLibrary.h | 10 +- .../FlatSystemFileChooserWindowsTest.java | 102 +++++- .../FlatSystemFileChooserWindowsTest.jfd | 64 +++- 7 files changed, 543 insertions(+), 18 deletions(-) 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 5ea7d178..1cbefc97 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 @@ -146,7 +146,8 @@ public class FlatNativeLinuxLibrary * @param owner the owner of the file dialog; or {@code null} * @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}. Use '_' for mnemonics (e.g. "_Choose") + * @param okButtonLabel text displayed in default button; or {@code null}. + * Use '_' for mnemonics (e.g. "_Choose") * Use '__' for '_' character (e.g. "Choose__and__Quit"). * @param currentName user-editable filename currently shown in the filename field in save dialog; or {@code null} * @param currentFolder current directory shown in the dialog; or {@code null} @@ -189,7 +190,9 @@ public class FlatNativeLinuxLibrary * this will appear as title in a larger bold font * @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 + * @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 * * @since 3.6 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 ec2bed8f..65326049 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 @@ -18,6 +18,7 @@ package com.formdev.flatlaf.ui; import java.awt.Color; import java.awt.Window; +import javax.swing.JOptionPane; import com.formdev.flatlaf.util.SystemInfo; /** @@ -204,7 +205,8 @@ public class FlatNativeWindowsLibrary * @param owner the owner of the file dialog; or {@code null} * @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}. Use '&' for mnemonics (e.g. "&Choose"). + * @param okButtonLabel text displayed in default button; or {@code null}. + * Use '&' for mnemonics (e.g. "&Choose"). * Use '&&' for '&' character (e.g. "Choose && Quit"). * @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} @@ -240,6 +242,29 @@ public class FlatNativeWindowsLibrary boolean approve( String[] files, long hwndFileDialog ); } + /** + * Shows a modal Windows message dialog. + *

+ * For use in {@link FileChooserCallback} only. + * + * @param hwndParent the parent of the message box + * @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 title dialog box title; or {@code null} to use title from parent window + * @param text message to be displayed + * @param defaultButton index of the default button, which can be pressed using ENTER key + * @param buttons texts of the buttons. + * Use '&' for mnemonics (e.g. "&Choose"). + * Use '&&' for '&' character (e.g. "Choose && Quit"). + * @return index of pressed button; or -1 for ESC key + * + * @since 3.6 + */ + public native static int showMessageDialog( long hwndParent, int messageType, + String title, String text, int defaultButton, String... buttons ); + /** * Shows a Windows message box * MessageBox. @@ -251,10 +276,8 @@ public class FlatNativeWindowsLibrary * @param caption dialog box title * @param type see MessageBox parameter uType * @return see MessageBox Return value - * @return index of pressed button; or -1 for ESC key * * @since 3.6 */ - public native static int showMessageDialog( long hwndParent, - String text, String caption, int type ); + public native static int showMessageBox( long hwndParent, String text, String caption, int type ); } diff --git a/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/Runtime.cpp b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/Runtime.cpp index cbd5163d..cea43d13 100644 --- a/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/Runtime.cpp +++ b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/Runtime.cpp @@ -36,8 +36,11 @@ * @author Karl Tauber */ +HINSTANCE _instance; + extern "C" BOOL WINAPI _DllMainCRTStartup( HINSTANCE instance, DWORD reason, LPVOID reserved ) { + _instance = instance; return TRUE; } diff --git a/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/WinMessageDialog.cpp b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/WinMessageDialog.cpp index 38b9dd3a..7a087fc8 100644 --- a/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/WinMessageDialog.cpp +++ b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/WinMessageDialog.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2024 FormDev Software GmbH + * 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. @@ -18,6 +18,7 @@ #define _NO_CRT_STDIO_INLINE #include +#include #include "JNIUtils.h" #include "com_formdev_flatlaf_ui_FlatNativeWindowsLibrary.h" @@ -26,8 +27,24 @@ * @since 3.6 */ +#define ID_BUTTON1 101 + +// declare external fields +extern HINSTANCE _instance; + +// declare internal methods +static byte* createInMemoryTemplate( HWND owner, int messageType, LPCWSTR title, LPCWSTR text, + int defaultButton, int buttonCount, LPCWSTR* buttons ); +static INT_PTR CALLBACK messageDialogProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ); +static int textLengthAsDLUs( HDC hdc, LPCWSTR str, int strLen ); +static LONG pixel2dluX( LONG px ); +static LONG pixel2dluY( LONG px ); +static LONG dluX2pixel( LONG dluX ); +static LPWORD lpwAlign( LPWORD lpIn ); + + extern "C" -JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_showMessageDialog +JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_showMessageBox ( JNIEnv* env, jclass cls, jlong hwndParent, jstring text, jstring caption, jint type ) { // convert Java strings to C strings @@ -36,3 +53,326 @@ JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_show return ::MessageBox( reinterpret_cast( hwndParent ), ctext, ccaption, type ); } + +extern "C" +JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_showMessageDialog + ( JNIEnv* env, jclass cls, jlong hwndParent, jint messageType, jstring title, + jstring text, jint defaultButton, jobjectArray buttons ) +{ + HWND owner = reinterpret_cast( hwndParent ); + + // convert Java strings to C strings + AutoReleaseString ctitle( env, title ); + AutoReleaseString ctext( env, text ); + AutoReleaseStringArray cbuttons( env, buttons ); + + // get title from parent window if necessary + WCHAR parentTitle[100]; + if( ctitle == NULL ) + ::GetWindowText( owner, parentTitle, 100 ); + + byte* templ = createInMemoryTemplate( owner, messageType, (ctitle != NULL) ? ctitle : parentTitle, + ctext, defaultButton, cbuttons.count, cbuttons ); + if( templ == NULL ) + return -1; + + LRESULT ret = ::DialogBoxIndirect( _instance, (LPDLGTEMPLATE) templ, owner, messageDialogProc ); + delete templ; + return (ret >= ID_BUTTON1) ? ret - ID_BUTTON1 : -1; +} + + +// all values in DLUs + +#define INSETS_TOP 16 +#define INSETS_LEFT 12 +#define INSETS_RIGHT 12 +#define INSETS_BOTTOM 8 + +#define ICON_TEXT_GAP 8 + +#define LABEL_MIN_WIDTH 100 +#define LABEL_MAX_WIDTH 250 +#define LABEL_HEIGHT 8 + +#define BUTTON_WIDTH 50 +#define BUTTON_HEIGHT 14 +#define BUTTON_GAP 5 +#define BUTTON_TOP_GAP 16 +#define BUTTON_LEFT_RIGHT_GAP 8 + +// based on https://learn.microsoft.com/en-us/windows/win32/dlgbox/using-dialog-boxes#creating-a-template-in-memory +static byte* createInMemoryTemplate( HWND owner, int messageType, LPCWSTR title, LPCWSTR text, + int defaultButton, int buttonCount, LPCWSTR* buttons ) +{ + //---- calculate layout (in DLUs) ---- + + HDC hdc = GetDC( owner ); + + // layout icon + LPWSTR icon; + switch( messageType ) { + case /* JOptionPane.ERROR_MESSAGE */ 0: icon = IDI_ERROR; break; + case /* JOptionPane.INFORMATION_MESSAGE */ 1: icon = IDI_INFORMATION; break; + case /* JOptionPane.WARNING_MESSAGE */ 2: icon = IDI_WARNING; break; + case /* JOptionPane.QUESTION_MESSAGE */ 3: icon = IDI_QUESTION; break; + default: + case /* JOptionPane.PLAIN_MESSAGE */ -1: icon = NULL; break; + } + int ix = INSETS_LEFT; + int iy = INSETS_TOP; + int iw = pixel2dluX( ::GetSystemMetrics( SM_CXICON ) ); + int ih = pixel2dluY( ::GetSystemMetrics( SM_CYICON ) ); + + // layout text + int tx = ix + (icon != NULL ? iw + ICON_TEXT_GAP : 0); + int ty = iy; + int tw = 0; + int th = 0; + if( text == NULL ) + text = L""; + LPWSTR wrappedText = new WCHAR[wcslen( text ) + 1]; + wcscpy( wrappedText, text ); + LPWSTR lineStart = wrappedText; + for( LPWSTR t = wrappedText; ; t++ ) { + if( *t != '\n' && *t != 0 ) + continue; + + // calculate line width (in pixels) and number of charaters that fit into LABEL_MAX_WIDTH + int lineLen = t - lineStart; + int fit = 0; + SIZE size{ 0 }; + if( !::GetTextExtentExPoint( hdc, lineStart, lineLen, dluX2pixel( LABEL_MAX_WIDTH ), &fit, NULL, &size ) ) + break; + + if( fit < lineLen ) { + // wrap too long line --> try to wrap at space character + bool wrapped = false; + for( LPWSTR t2 = lineStart + fit - 1; t2 > lineStart; t2-- ) { + if( *t2 == ' ' || *t2 == '\t' ) { + *t2 = '\n'; + int w = textLengthAsDLUs( hdc, lineStart, t2 - lineStart ); + tw = max( tw, w ); + th += LABEL_HEIGHT; + + // continue wrapping after inserted line break + t = t2; + lineStart = t + 1; + wrapped = true; + break; + } + } + if( !wrapped ) { + // not able to wrap at word --> break long word + int breakIndex = (lineStart + fit) - wrappedText; + int w = textLengthAsDLUs( hdc, lineStart, breakIndex ); + tw = max( tw, w ); + th += LABEL_HEIGHT; + + // duplicate string + LPWSTR wrappedText2 = new WCHAR[wcslen( wrappedText ) + 1 + 1]; + // use wcscpy(), instead of wcsncpy(), because this method is inlined and does not require linking to runtime lib + wcscpy( wrappedText2, wrappedText ); + wrappedText2[breakIndex] = '\n'; + wcscpy( wrappedText2 + breakIndex + 1, wrappedText + breakIndex ); + + // delete old text + delete[] wrappedText; + wrappedText = wrappedText2; + + // continue wrapping after inserted line break + t = wrappedText + breakIndex; + lineStart = t + 1; + } + } else { + // line fits into LABEL_MAX_WIDTH + int w = pixel2dluX( size.cx ); + tw = max( tw, w ); + th += LABEL_HEIGHT; + lineStart = t + 1; + } + + if( *t == 0 ) + break; + } + tw = min( max( tw, LABEL_MIN_WIDTH ), LABEL_MAX_WIDTH ); + th = max( th, LABEL_HEIGHT ); + if( icon != NULL && th < ih ) + ty += (ih - th) / 2; // vertically center text + + // layout buttons + int* bw = new int[buttonCount]; + int buttonTotalWidth = BUTTON_GAP * (buttonCount - 1); + for( int i = 0; i < buttonCount; i++ ) { + int w = textLengthAsDLUs( hdc, buttons[i], -1 ) + 16; + bw[i] = max( BUTTON_WIDTH, w ); + buttonTotalWidth += bw[i]; + } + + // layout dialog + int dx = 0; + int dy = 0; + int dw = max( tx + tw + INSETS_RIGHT, BUTTON_LEFT_RIGHT_GAP + buttonTotalWidth + BUTTON_LEFT_RIGHT_GAP ); + int dh = max( iy + ih, ty + th ) + BUTTON_TOP_GAP + BUTTON_HEIGHT + INSETS_BOTTOM; + + // center dialog in owner + RECT ownerRect{ 0 }; + if( ::GetClientRect( owner, &ownerRect ) ) { + dx = (pixel2dluX( ownerRect.right - ownerRect.left ) - dw) / 2; + dy = (pixel2dluY( ownerRect.bottom - ownerRect.top ) - dh) / 2; + } + + // layout button area + int bx = dw - buttonTotalWidth - BUTTON_LEFT_RIGHT_GAP; + int by = dh - BUTTON_HEIGHT - INSETS_BOTTOM; + + // (approximately) calculate memory size needed for in-memory template + int templSize = (sizeof(DLGTEMPLATE) + /*menu*/ 2 + /*class*/ 2 + /*title*/ 2) + + ((sizeof(DLGITEMTEMPLATE) + /*class*/ 4 + /*title/icon*/ 4 + /*creation data*/ 2) * (/*icon+text*/2 + buttonCount)) + + (title != NULL ? wcslen( title ) * sizeof(wchar_t) : 0) + + (wcslen( wrappedText ) * sizeof(wchar_t)); + for( int i = 0; i < buttonCount; i++ ) + templSize += (wcslen( buttons[i] ) * sizeof(wchar_t)); + + templSize += (2 * (1 + 1 + buttonCount)); // necessary for DWORD alignment + templSize += 100; // some reserve + + // allocate memory for in-memory template + byte* templ = new byte[templSize]; + if( templ == NULL ) + return NULL; + + + //---- define dialog box ---- + + LPDLGTEMPLATE lpdt = (LPDLGTEMPLATE) templ; + lpdt->style = WS_POPUP | WS_BORDER | WS_SYSMENU | DS_MODALFRAME | WS_CAPTION; + lpdt->cdit = /*text*/ 1 + buttonCount; // number of controls + lpdt->x = dx; + lpdt->y = dy; + lpdt->cx = dw; + lpdt->cy = dh; + + LPWORD lpw = (LPWORD) (lpdt + 1); + *lpw++ = 0; // no menu + *lpw++ = 0; // predefined dialog box class (by default) + if( title != NULL ) { + wcscpy( (LPWSTR) lpw, title ); + lpw += wcslen( title ) + 1; + } else + *lpw++ = 0; // no title + + + //---- define icon ---- + + if( icon != NULL ) { + lpdt->cdit++; + + lpw = lpwAlign( lpw ); + LPDLGITEMTEMPLATE lpdit = (LPDLGITEMTEMPLATE) lpw; + lpdit->x = ix; + lpdit->y = iy; + lpdit->cx = iw; + lpdit->cy = ih; + lpdit->id = ID_BUTTON1 - 1; + lpdit->style = WS_CHILD | WS_VISIBLE | SS_ICON; + + lpw = (LPWORD) (lpdit + 1); + *lpw++ = 0xffff; *lpw++ = 0x0082; // Static class + *lpw++ = 0xffff; *lpw++ = (WORD) icon; // icon + *lpw++ = 0; // creation data + } + + + //---- define text ---- + + lpw = lpwAlign( lpw ); + LPDLGITEMTEMPLATE lpdit = (LPDLGITEMTEMPLATE) lpw; + lpdit->x = tx; + lpdit->y = ty; + lpdit->cx = tw; + lpdit->cy = th; + lpdit->id = ID_BUTTON1 - 2; + lpdit->style = WS_CHILD | WS_VISIBLE | SS_LEFT | SS_NOPREFIX | SS_EDITCONTROL; + + lpw = (LPWORD) (lpdit + 1); + *lpw++ = 0xffff; *lpw++ = 0x0082; // Static class + wcscpy( (LPWSTR) lpw, wrappedText ); lpw += wcslen( wrappedText ) + 1; // text + *lpw++ = 0; // creation data + + + //---- define buttons ---- + + defaultButton = min( max( defaultButton, 0 ), buttonCount - 1 ); + int buttonId = ID_BUTTON1; + for( int i = 0; i < buttonCount; i++ ) { + lpw = lpwAlign( lpw ); + LPDLGITEMTEMPLATE lpdit = (LPDLGITEMTEMPLATE) lpw; + lpdit->x = bx; + lpdit->y = by; + lpdit->cx = bw[i]; + lpdit->cy = BUTTON_HEIGHT; + lpdit->id = buttonId++; + lpdit->style = WS_CHILD | WS_VISIBLE | WS_TABSTOP | (i == 0 ? WS_GROUP : 0) + | BS_TEXT | (i == defaultButton ? BS_DEFPUSHBUTTON : BS_PUSHBUTTON); + + lpw = (LPWORD) (lpdit + 1); + *lpw++ = 0xffff; *lpw++ = 0x0080; // Button class + wcscpy( (LPWSTR) lpw, buttons[i] ); lpw += wcslen( buttons[i] ) + 1; // text + *lpw++ = 0; // creation data + + bx += bw[i] + BUTTON_GAP; + } + + delete[] wrappedText; + delete[] bw; + + return templ; +} + +static BOOL CALLBACK focusDefaultButtonProc( HWND hwnd, LPARAM lParam ) { + if( ::GetWindowLong( hwnd, GWL_ID ) >= ID_BUTTON1 ) { + LONG style = ::GetWindowLong( hwnd, GWL_STYLE ); + if( (style & BS_DEFPUSHBUTTON) != 0 ) { + ::SetFocus( hwnd ); + return FALSE; + } + } + return TRUE; +} + +static INT_PTR CALLBACK messageDialogProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { + if( uMsg == WM_INITDIALOG ) + ::EnumChildWindows( hwnd, focusDefaultButtonProc, 0 ); + else if( uMsg == WM_COMMAND ) { + ::EndDialog( hwnd, wParam ); + return TRUE; + } + return FALSE; +} + +static int textLengthAsDLUs( HDC hdc, LPCWSTR str, int strLen ) { + SIZE size{ 0 }; + ::GetTextExtentPoint32( hdc, str, (strLen >= 0) ? strLen : wcslen( str ), &size ); + return pixel2dluX( size.cx ); +} + +static LONG pixel2dluX( LONG px ) { + return MulDiv( px, 4, LOWORD( ::GetDialogBaseUnits() ) ); +} + +static LONG pixel2dluY( LONG py ) { + return MulDiv( py, 8, HIWORD( ::GetDialogBaseUnits() ) ); +} + +static LONG dluX2pixel( LONG dluX ) { + return MulDiv( dluX, LOWORD( ::GetDialogBaseUnits() ), 4 ); +} + +static LPWORD lpwAlign( LPWORD lpIn ) { + ULONG_PTR ul = (ULONG_PTR) lpIn; + ul += 3; + ul >>= 2; + ul <<= 2; + return (LPWORD) ul; +} 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 18b9f377..b5b33245 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 @@ -124,9 +124,17 @@ JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibr /* * Class: com_formdev_flatlaf_ui_FlatNativeWindowsLibrary * Method: showMessageDialog - * Signature: (JLjava/lang/String;Ljava/lang/String;I)I + * Signature: (JILjava/lang/String;Ljava/lang/String;I[Ljava/lang/String;)I */ JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_showMessageDialog + (JNIEnv *, jclass, jlong, jint, jstring, jstring, jint, jobjectArray); + +/* + * Class: com_formdev_flatlaf_ui_FlatNativeWindowsLibrary + * Method: showMessageBox + * Signature: (JLjava/lang/String;Ljava/lang/String;I)I + */ +JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_showMessageBox (JNIEnv *, jclass, jlong, jstring, jstring, jint); #ifdef __cplusplus diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserWindowsTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserWindowsTest.java index 621d00d8..cc36e77b 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserWindowsTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserWindowsTest.java @@ -30,7 +30,10 @@ import java.awt.event.WindowListener; import java.awt.event.WindowStateListener; import java.util.Arrays; import java.util.concurrent.atomic.AtomicInteger; +import java.util.prefs.Preferences; import javax.swing.*; +import javax.swing.border.TitledBorder; +import com.formdev.flatlaf.demo.DemoPrefs; import com.formdev.flatlaf.extras.components.*; import com.formdev.flatlaf.extras.components.FlatTriStateCheckBox.State; import com.formdev.flatlaf.ui.FlatNativeWindowsLibrary; @@ -50,7 +53,7 @@ public class FlatSystemFileChooserWindowsTest } FlatTestFrame frame = FlatTestFrame.create( args, "FlatSystemFileChooserWindowsTest" ); - addListeners( frame ); +// addListeners( frame ); frame.showFrame( FlatSystemFileChooserWindowsTest::new ); } ); } @@ -59,6 +62,10 @@ public class FlatSystemFileChooserWindowsTest initComponents(); fileTypesField.setSelectedItem( null ); + + Preferences state = DemoPrefs.getState(); + messageField.setText( state.get( "systemfilechooser.windows.message", "some message" ) ); + buttonsField.setText( state.get( "systemfilechooser.windows.buttons", "OK" ) ); } private void open() { @@ -143,8 +150,8 @@ public class FlatSystemFileChooserWindowsTest System.out.println( " -- callback " + hwndFileDialog + " " + Arrays.toString( files ) ); if( showMessageDialogOnOKCheckBox.isSelected() ) { System.out.println( FlatNativeWindowsLibrary.showMessageDialog( hwndFileDialog, - "some text", "title", - /* MB_ICONINFORMATION */ 0x00000040 | /* MB_YESNO */ 0x00000004 ) ); + JOptionPane.INFORMATION_MESSAGE, + null, "some text", 1, "Yes", "No" ) ); } return true; }; @@ -189,6 +196,28 @@ public class FlatSystemFileChooserWindowsTest optionsClear.set( optionsClear.get() | option ); } + private void messageDialog() { + long hwnd = getHWND( SwingUtilities.windowForComponent( this ) ); + String message = messageField.getText(); + String[] buttons = buttonsField.getText().trim().split( "[,]+" ); + + Preferences state = DemoPrefs.getState(); + state.put( "systemfilechooser.windows.message", message ); + state.put( "systemfilechooser.windows.buttons", buttonsField.getText() ); + + System.out.println( FlatNativeWindowsLibrary.showMessageDialog( hwnd, + JOptionPane.WARNING_MESSAGE, null, message, 1, buttons ) ); + } + + private void messageBox() { + long hwnd = getHWND( SwingUtilities.windowForComponent( this ) ); + String message = messageField.getText(); + + System.out.println( FlatNativeWindowsLibrary.showMessageBox( hwnd, message, null, + /* MB_ICONINFORMATION */ 0x00000040 | /* MB_YESNO */ 0x00000004 ) ); + } + + @SuppressWarnings( "unused" ) private static void addListeners( Window w ) { w.addWindowListener( new WindowListener() { @Override @@ -278,6 +307,12 @@ public class FlatSystemFileChooserWindowsTest supportStreamableItemsCheckBox = new FlatTriStateCheckBox(); allowMultiSelectCheckBox = new FlatTriStateCheckBox(); hidePinnedPlacesCheckBox = new FlatTriStateCheckBox(); + messageDialogPanel = new JPanel(); + messageLabel = new JLabel(); + messageScrollPane = new JScrollPane(); + messageField = new JTextArea(); + buttonsLabel = new JLabel(); + buttonsField = new JTextField(); okButtonLabelLabel = new JLabel(); okButtonLabelField = new JTextField(); fileNameLabelLabel = new JLabel(); @@ -301,6 +336,9 @@ public class FlatSystemFileChooserWindowsTest openDirectButton = new JButton(); saveDirectButton = new JButton(); showMessageDialogOnOKCheckBox = new JCheckBox(); + hSpacer1 = new JPanel(null); + messageDialogButton = new JButton(); + messageBoxButton = new JButton(); filesScrollPane = new JScrollPane(); filesField = new JTextArea(); @@ -365,7 +403,8 @@ public class FlatSystemFileChooserWindowsTest "[]0" + "[]0" + "[]0" + - "[]0")); + "[]0" + + "[grow]")); //---- overwritePromptCheckBox ---- overwritePromptCheckBox.setText("overwritePrompt"); @@ -461,8 +500,41 @@ public class FlatSystemFileChooserWindowsTest //---- hidePinnedPlacesCheckBox ---- hidePinnedPlacesCheckBox.setText("hidePinnedPlaces"); panel1.add(hidePinnedPlacesCheckBox, "cell 1 7"); + + //======== messageDialogPanel ======== + { + messageDialogPanel.setBorder(new TitledBorder("MessageDialog")); + messageDialogPanel.setLayout(new MigLayout( + "hidemode 3", + // columns + "[fill]" + + "[grow,fill]", + // rows + "[grow,fill]" + + "[]")); + + //---- messageLabel ---- + messageLabel.setText("Message"); + messageDialogPanel.add(messageLabel, "cell 0 0,aligny top,growy 0"); + + //======== messageScrollPane ======== + { + + //---- messageField ---- + messageField.setColumns(40); + messageField.setRows(4); + messageScrollPane.setViewportView(messageField); + } + messageDialogPanel.add(messageScrollPane, "cell 1 0"); + + //---- buttonsLabel ---- + buttonsLabel.setText("Buttons:"); + messageDialogPanel.add(buttonsLabel, "cell 0 1"); + messageDialogPanel.add(buttonsField, "cell 1 1"); + } + panel1.add(messageDialogPanel, "cell 0 8 3 1,grow"); } - add(panel1, "cell 2 1 1 10,aligny top,growy 0"); + add(panel1, "cell 2 1 1 10,growy"); //---- okButtonLabelLabel ---- okButtonLabelLabel.setText("okButtonLabel"); @@ -548,6 +620,17 @@ public class FlatSystemFileChooserWindowsTest //---- showMessageDialogOnOKCheckBox ---- showMessageDialogOnOKCheckBox.setText("show message dialog on OK"); add(showMessageDialogOnOKCheckBox, "cell 0 11 3 1"); + add(hSpacer1, "cell 0 11 3 1,growx"); + + //---- messageDialogButton ---- + messageDialogButton.setText("MessageDialog..."); + messageDialogButton.addActionListener(e -> messageDialog()); + add(messageDialogButton, "cell 0 11 3 1,alignx right,growx 0"); + + //---- messageBoxButton ---- + messageBoxButton.setText("MessageBox..."); + messageBoxButton.addActionListener(e -> messageBox()); + add(messageBoxButton, "cell 0 11 3 1"); //======== filesScrollPane ======== { @@ -598,6 +681,12 @@ public class FlatSystemFileChooserWindowsTest private FlatTriStateCheckBox supportStreamableItemsCheckBox; private FlatTriStateCheckBox allowMultiSelectCheckBox; private FlatTriStateCheckBox hidePinnedPlacesCheckBox; + private JPanel messageDialogPanel; + private JLabel messageLabel; + private JScrollPane messageScrollPane; + private JTextArea messageField; + private JLabel buttonsLabel; + private JTextField buttonsField; private JLabel okButtonLabelLabel; private JTextField okButtonLabelField; private JLabel fileNameLabelLabel; @@ -621,6 +710,9 @@ public class FlatSystemFileChooserWindowsTest private JButton openDirectButton; private JButton saveDirectButton; private JCheckBox showMessageDialogOnOKCheckBox; + private JPanel hSpacer1; + private JButton messageDialogButton; + private JButton messageBoxButton; 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/FlatSystemFileChooserWindowsTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserWindowsTest.jfd index 74e22930..040c5c82 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserWindowsTest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserWindowsTest.jfd @@ -56,7 +56,7 @@ new FormModel { 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" + "$rowConstraints": "[]0[]0[]0[][]0[]0[]0[]0[grow]" } ) { name: "panel1" add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) { @@ -200,8 +200,45 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 1 7" } ) + add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { + "$layoutConstraints": "hidemode 3" + "$columnConstraints": "[fill][grow,fill]" + "$rowConstraints": "[grow,fill][]" + } ) { + name: "messageDialogPanel" + "border": new javax.swing.border.TitledBorder( "MessageDialog" ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "messageLabel" + "text": "Message" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0,aligny top,growy 0" + } ) + add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) { + name: "messageScrollPane" + add( new FormComponent( "javax.swing.JTextArea" ) { + name: "messageField" + "columns": 40 + "rows": 4 + } ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 0" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "buttonsLabel" + "text": "Buttons:" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 1" + } ) + add( new FormComponent( "javax.swing.JTextField" ) { + name: "buttonsField" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 1" + } ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 8 3 1,grow" + } ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 2 1 1 10,aligny top,growy 0" + "value": "cell 2 1 1 10,growy" } ) add( new FormComponent( "javax.swing.JLabel" ) { name: "okButtonLabelLabel" @@ -344,11 +381,30 @@ new FormModel { "value": "cell 0 11 3 1" } ) add( new FormComponent( "javax.swing.JCheckBox" ) { - name: "showMessageDialogsOnOKCheckBox" + name: "showMessageDialogOnOKCheckBox" "text": "show message dialog on OK" }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 11 3 1" } ) + add( new FormComponent( "com.jformdesigner.designer.wrapper.HSpacer" ) { + name: "hSpacer1" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 11 3 1,growx" + } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "messageDialogButton" + "text": "MessageDialog..." + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "messageDialog", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 11 3 1,alignx right,growx 0" + } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "messageBoxButton" + "text": "MessageBox..." + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "messageBox", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 11 3 1" + } ) add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) { name: "filesScrollPane" add( new FormComponent( "javax.swing.JTextArea" ) { @@ -360,7 +416,7 @@ new FormModel { } ) }, new FormLayoutConstraints( null ) { "location": new java.awt.Point( 0, 0 ) - "size": new java.awt.Dimension( 845, 630 ) + "size": new java.awt.Dimension( 890, 630 ) } ) add( new FormNonVisual( "javax.swing.ButtonGroup" ) { name: "ownerButtonGroup"