Skip to content

Commit bdb7438

Browse files
committed
Merge PR #988: System File Chooser
2 parents 1e2a75a + 299250a commit bdb7438

56 files changed

Lines changed: 8559 additions & 76 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/natives.yml

Lines changed: 73 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,30 @@ jobs:
3333

3434
- uses: gradle/actions/wrapper-validation@v4
3535

36-
- name: install libxt-dev
36+
- name: apt update (Linux)
3737
if: matrix.os == 'ubuntu-latest' || matrix.os == 'ubuntu-24.04-arm'
38-
run: sudo apt install libxt-dev
38+
run: sudo apt-get update
3939

40-
- name: install g++-aarch64-linux-gnu
41-
if: matrix.os == 'ubuntu-latest'
42-
run: sudo apt install g++-aarch64-linux-gnu
40+
- name: install libxt-dev and libgtk-3-dev (Linux)
41+
if: matrix.os == 'ubuntu-latest' || matrix.os == 'ubuntu-24.04-arm'
42+
run: sudo apt-get install libxt-dev libgtk-3-dev
43+
44+
# - name: Download libgtk-3.so for arm64 (Linux)
45+
# if: matrix.os == 'ubuntu-latest'
46+
# working-directory: flatlaf-natives/flatlaf-natives-linux/lib/aarch64
47+
# run: |
48+
# pwd
49+
# ls -l /usr/lib/x86_64-linux-gnu/libgtk*
50+
# wget --no-verbose https://ports.ubuntu.com/pool/main/g/gtk%2b3.0/libgtk-3-0_3.24.18-1ubuntu1_arm64.deb
51+
# ls -l
52+
# ar -x libgtk-3-0_3.24.18-1ubuntu1_arm64.deb data.tar.xz
53+
# tar -xvf data.tar.xz --wildcards --to-stdout "./usr/lib/aarch64-linux-gnu/libgtk-3.so.0.*" > libgtk-3.so
54+
# rm libgtk-3-0_3.24.18-1ubuntu1_arm64.deb data.tar.xz
55+
# ls -l
56+
57+
# - name: install g++-aarch64-linux-gnu (Linux)
58+
# if: matrix.os == 'ubuntu-latest'
59+
# run: sudo apt-get install g++-aarch64-linux-gnu
4360

4461
- name: Setup Java 11
4562
uses: actions/setup-java@v4
@@ -53,10 +70,60 @@ jobs:
5370
# tar.exe: Couldn't open ~/.gradle/caches/modules-2/modules-2.lock: Permission denied
5471
run: ./gradlew build-natives --no-daemon
5572

73+
- name: Sign Windows DLLs
74+
if: matrix.os == 'windows-latest'
75+
uses: skymatic/code-sign-action@v3
76+
with:
77+
certificate: '${{ secrets.CODE_SIGN_CERT_BASE64 }}'
78+
password: '${{ secrets.CODE_SIGN_CERT_PASSWORD }}'
79+
certificatesha1: '${{ secrets.CODE_SIGN_CERT_SHA1 }}'
80+
folder: 'flatlaf-core/src/main/resources/com/formdev/flatlaf/natives'
81+
82+
- name: Sign macOS natives
83+
if: matrix.os == 'DISABLED--macos-latest'
84+
env:
85+
CERT_BASE64: ${{ secrets.CODE_SIGN_CERT_BASE64 }}
86+
CERT_PASSWORD: ${{ secrets.CODE_SIGN_CERT_PASSWORD }}
87+
CERT_IDENTITY: ${{ secrets.CODE_SIGN_CERT_IDENTITY }}
88+
run: |
89+
# https://docs.github.com/en/actions/use-cases-and-examples/deploying/installing-an-apple-certificate-on-macos-runners-for-xcode-development
90+
# create variables
91+
CERTIFICATE_PATH=$RUNNER_TEMP/cert.p12
92+
KEYCHAIN_PATH=$RUNNER_TEMP/signing.keychain-db
93+
KEYCHAIN_PASSWORD=$CERT_PASSWORD
94+
# decode certificate
95+
printenv CERT_BASE64 | base64 --decode > $CERTIFICATE_PATH
96+
# create temporary keychain
97+
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
98+
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
99+
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
100+
# import certificate to keychain
101+
security import $CERTIFICATE_PATH -P "$CERT_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
102+
# set partition list (required for codesign)
103+
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
104+
# add keychain to keychain search list
105+
security list-keychains -d user -s $KEYCHAIN_PATH
106+
# sign code
107+
codesign --sign "$CERT_IDENTITY" --force --verbose=4 --timestamp \
108+
flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-macos-*.dylib
109+
codesign --display --verbose=4 flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-macos-*.dylib
110+
# cleanup
111+
security delete-keychain $KEYCHAIN_PATH
112+
113+
- name: Set artifacts pattern
114+
shell: bash
115+
run: |
116+
case ${{ matrix.os }} in
117+
windows-latest) echo "artifactPattern=flatlaf-windows-*.dll" >> $GITHUB_ENV ;;
118+
macos-latest) echo "artifactPattern=libflatlaf-macos-*.dylib" >> $GITHUB_ENV ;;
119+
ubuntu-latest) echo "artifactPattern=libflatlaf-linux-x86_64.so" >> $GITHUB_ENV ;;
120+
ubuntu-24.04-arm) echo "artifactPattern=libflatlaf-linux-arm64.so" >> $GITHUB_ENV ;;
121+
esac
122+
56123
- name: Upload artifacts
57124
uses: actions/upload-artifact@v4
58125
with:
59126
name: FlatLaf-natives-build-artifacts-${{ matrix.os }}
60127
path: |
61-
flatlaf-core/src/main/resources/com/formdev/flatlaf/natives
128+
flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/${{ env.artifactPattern }}
62129
flatlaf-natives/flatlaf-natives-*/build

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,21 @@ FlatLaf Change Log
33

44
## 3.7-SNAPSHOT
55

6+
#### New features and improvements
7+
8+
- System File Chooser allows using **operating system file dialogs** in Java
9+
Swing applications. (PR #988)
10+
11+
#### Fixed bugs
12+
613
- TextField: Fixed wrong leading/trailing icon placement if border is set to
714
`null`. (issue #1047)
815
- Extras: UI defaults inspector: Exclude inspector window from being blocked by
916
modal dialogs. (issue #1048)
1017
- JideButton, JideToggleButton, JideSplitButton and JideToggleSplitButton: Paint
1118
border in button style `TOOLBAR_STYLE` if in selected state. (issue #1045)
1219

20+
1321
## 3.6.2
1422

1523
#### New features and improvements

flatlaf-core/src/main/java/com/formdev/flatlaf/FlatSystemProperties.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616

1717
package com.formdev.flatlaf;
1818

19+
import javax.swing.JFileChooser;
1920
import javax.swing.JPopupMenu;
2021
import javax.swing.SwingUtilities;
22+
import com.formdev.flatlaf.util.SystemFileChooser;
2123
import com.formdev.flatlaf.util.UIScale;
2224

2325
/**
@@ -246,6 +248,17 @@ public interface FlatSystemProperties
246248
*/
247249
String USE_SUB_MENU_SAFE_TRIANGLE = "flatlaf.useSubMenuSafeTriangle";
248250

251+
/**
252+
* Specifies whether {@link SystemFileChooser} uses operating system file dialogs.
253+
* If set to {@code false}, the {@link JFileChooser} is used instead.
254+
* <p>
255+
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>
256+
* <strong>Default</strong> {@code true}
257+
*
258+
* @since 3.7
259+
*/
260+
String USE_SYSTEM_FILE_CHOOSER = "flatlaf.useSystemFileChooser";
261+
249262
/**
250263
* Checks whether a system property is set and returns {@code true} if its value
251264
* is {@code "true"} (case-insensitive), otherwise it returns {@code false}.

flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeLinuxLibrary.java

Lines changed: 112 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.awt.geom.AffineTransform;
2525
import javax.swing.JDialog;
2626
import javax.swing.JFrame;
27+
import javax.swing.JOptionPane;
2728
import com.formdev.flatlaf.util.SystemInfo;
2829

2930
/**
@@ -34,20 +35,23 @@
3435
* @author Karl Tauber
3536
* @since 2.5
3637
*/
37-
class FlatNativeLinuxLibrary
38+
public class FlatNativeLinuxLibrary
3839
{
39-
private static int API_VERSION_LINUX = 3001;
40+
private static int API_VERSION_LINUX = 3002;
4041

4142
/**
4243
* Checks whether native library is loaded/available.
4344
* <p>
4445
* <b>Note</b>: It is required to invoke this method before invoking any other
4546
* method of this class. Otherwise, the native library may not be loaded.
4647
*/
47-
static boolean isLoaded() {
48+
public static boolean isLoaded() {
4849
return SystemInfo.isLinux && FlatNativeLibrary.isLoaded( API_VERSION_LINUX );
4950
}
5051

52+
53+
//---- X Window System ----------------------------------------------------
54+
5155
// direction for _NET_WM_MOVERESIZE message
5256
// see https://specifications.freedesktop.org/wm-spec/latest/ar01s04.html
5357
static final int
@@ -124,4 +128,109 @@ private static boolean hasCustomDecoration( Window window ) {
124128
return (window instanceof JFrame && JFrame.isDefaultLookAndFeelDecorated() && ((JFrame)window).isUndecorated()) ||
125129
(window instanceof JDialog && JDialog.isDefaultLookAndFeelDecorated() && ((JDialog)window).isUndecorated());
126130
}
131+
132+
133+
//---- GTK ----------------------------------------------------------------
134+
135+
private static Boolean isGtk3Available;
136+
137+
/**
138+
* Checks whether GTK 3 is available.
139+
* Use this before invoking any native method that uses GTK.
140+
* Otherwise the app may terminate immediately if GTK is not installed.
141+
* <p>
142+
* This works because Java uses {@code dlopen(RTLD_LAZY)} to load JNI libraries,
143+
* which only resolves symbols as the code that references them is executed.
144+
*
145+
* @since 3.7
146+
*/
147+
public static boolean isGtk3Available() {
148+
if( isGtk3Available == null )
149+
isGtk3Available = isLibAvailable( "libgtk-3.so.0" ) || isLibAvailable( "libgtk-3.so" );
150+
return isGtk3Available;
151+
}
152+
153+
private native static boolean isLibAvailable( String libname );
154+
155+
/**
156+
* https://docs.gtk.org/gtk3/iface.FileChooser.html#properties
157+
*
158+
* @since 3.7
159+
*/
160+
public static final int
161+
FC_select_folder = 1 << 0,
162+
FC_select_multiple = 1 << 1,
163+
FC_show_hidden = 1 << 2,
164+
FC_local_only = 1 << 3, // default
165+
FC_do_overwrite_confirmation = 1 << 4, // GTK 3 only; removed and always-on in GTK 4
166+
FC_create_folders = 1 << 5; // default for Save
167+
168+
/**
169+
* Shows the Linux/GTK system file dialog
170+
* <a href="https://docs.gtk.org/gtk3/class.FileChooserDialog.html">GtkFileChooserDialog</a>.
171+
* <p>
172+
* Uses {@code GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER} if {@link #FC_select_folder} is set in parameter {@code optionsSet}.
173+
* Otherwise uses {@code GTK_FILE_CHOOSER_ACTION_OPEN} if parameter {@code open} is {@code true},
174+
* or {@code GTK_FILE_CHOOSER_ACTION_SAVE} if {@code false}.
175+
* <p>
176+
* <b>Note:</b> This method blocks the current thread until the user closes
177+
* the file dialog. It is highly recommended to invoke it from a new thread
178+
* to avoid blocking the AWT event dispatching thread.
179+
*
180+
* @param owner the owner of the file dialog; or {@code null}
181+
* @param open if {@code true}, shows the open dialog; if {@code false}, shows the save dialog
182+
* @param title text displayed in dialog title; or {@code null}
183+
* @param okButtonLabel text displayed in default button; or {@code null}.
184+
* Use '_' for mnemonics (e.g. "_Choose")
185+
* Use '__' for '_' character (e.g. "Choose__and__Quit").
186+
* @param currentName user-editable filename currently shown in the filename field in save dialog; or {@code null}
187+
* @param currentFolder current directory shown in the dialog; or {@code null}
188+
* @param optionsSet options to set; see {@code FOS_*} constants
189+
* @param optionsClear options to clear; see {@code FOS_*} constants
190+
* @param callback approve callback; or {@code null}
191+
* @param fileTypeIndex the file type that appears as selected (zero-based)
192+
* @param fileTypes file types that the dialog can open or save.
193+
* Two or more strings and {@code null} are required for each filter.
194+
* First string is the display name of the filter shown in the combobox (e.g. "Text Files").
195+
* Subsequent strings are the filter patterns (e.g. "*.txt" or "*").
196+
* {@code null} is required to mark end of filter.
197+
* @return file path(s) that the user selected; an empty array if canceled;
198+
* or {@code null} on failures (no dialog shown)
199+
*
200+
* @since 3.7
201+
*/
202+
public native static String[] showFileChooser( Window owner, boolean open,
203+
String title, String okButtonLabel, String currentName, String currentFolder,
204+
int optionsSet, int optionsClear, FileChooserCallback callback,
205+
int fileTypeIndex, String... fileTypes );
206+
207+
/** @since 3.7 */
208+
public interface FileChooserCallback {
209+
boolean approve( String[] files, long hwndFileDialog );
210+
}
211+
212+
/**
213+
* Shows a GTK message box
214+
* <a href="https://docs.gtk.org/gtk3/class.MessageDialog.html">GtkMessageDialog</a>.
215+
* <p>
216+
* For use in {@link FileChooserCallback} only.
217+
*
218+
* @param hwndParent the parent of the message box
219+
* @param messageType type of message being displayed:
220+
* {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#INFORMATION_MESSAGE},
221+
* {@link JOptionPane#WARNING_MESSAGE}, {@link JOptionPane#QUESTION_MESSAGE} or
222+
* {@link JOptionPane#PLAIN_MESSAGE}
223+
* @param primaryText primary text; if the dialog has a secondary text,
224+
* this will appear as title in a larger bold font
225+
* @param secondaryText secondary text; shown below of primary text; or {@code null}
226+
* @param defaultButton index of the default button, which can be pressed using ENTER key
227+
* @param buttons texts of the buttons; if no buttons given the a default "OK" button is shown.
228+
* Use '_' for mnemonics (e.g. "_Choose")
229+
* Use '__' for '_' character (e.g. "Choose__and__Quit").
230+
* @return index of pressed button; or -1 for ESC key
231+
*
232+
* @since 3.7
233+
*/
234+
public native static int showMessageDialog( long hwndParent, int messageType,
235+
String primaryText, String secondaryText, int defaultButton, String... buttons );
127236
}

flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.awt.Rectangle;
2020
import java.awt.Window;
21+
import javax.swing.JOptionPane;
2122
import com.formdev.flatlaf.util.SystemInfo;
2223

2324
/**
@@ -44,7 +45,7 @@
4445
*/
4546
public class FlatNativeMacLibrary
4647
{
47-
private static int API_VERSION_MACOS = 2001;
48+
private static int API_VERSION_MACOS = 2002;
4849

4950
/**
5051
* Checks whether native library is loaded/available.
@@ -68,4 +69,88 @@ public static boolean isLoaded() {
6869
/** @since 3.4 */ public native static Rectangle getWindowButtonsBounds( Window window );
6970
/** @since 3.4 */ public native static boolean isWindowFullScreen( Window window );
7071
/** @since 3.4 */ public native static boolean toggleWindowFullScreen( Window window );
72+
73+
74+
/** @since 3.7 */
75+
public static final int
76+
// NSOpenPanel (extends NSSavePanel)
77+
FC_canChooseFiles = 1 << 0, // default
78+
FC_canChooseDirectories = 1 << 1,
79+
FC_resolvesAliases = 1 << 2, // default
80+
FC_allowsMultipleSelection = 1 << 3,
81+
FC_accessoryViewDisclosed = 1 << 4,
82+
// NSSavePanel
83+
FC_showsTagField = 1 << 8, // default for Save
84+
FC_canCreateDirectories = 1 << 9, // default for Save
85+
FC_canSelectHiddenExtension = 1 << 10,
86+
FC_showsHiddenFiles = 1 << 11,
87+
FC_extensionHidden = 1 << 12,
88+
FC_allowsOtherFileTypes = 1 << 13,
89+
FC_treatsFilePackagesAsDirectories = 1 << 14,
90+
// custom
91+
FC_showSingleFilterField = 1 << 24;
92+
93+
/**
94+
* Shows the macOS system file dialogs
95+
* <a href="https://developer.apple.com/documentation/appkit/nsopenpanel?language=objc">NSOpenPanel</a> or
96+
* <a href="https://developer.apple.com/documentation/appkit/nssavepanel?language=objc">NSSavePanel</a>.
97+
* <p>
98+
* <b>Note:</b> This method blocks the current thread until the user closes
99+
* the file dialog. It is highly recommended to invoke it from a new thread
100+
* to avoid blocking the AWT event dispatching thread.
101+
*
102+
* @param owner the owner of the file dialog; or {@code null}
103+
* @param dark appearance of the file dialog: {@code 1} = dark, {@code 0} = light, {@code -1} = default
104+
* @param open if {@code true}, shows the open dialog; if {@code false}, shows the save dialog
105+
* @param title text displayed at top of save dialog (not used in open dialog); or {@code null}
106+
* @param prompt text displayed in default button; or {@code null}
107+
* @param message text displayed at top of open/save dialogs; or {@code null}
108+
* @param filterFieldLabel text displayed in front of the filter combobox; or {@code null}
109+
* @param nameFieldLabel text displayed in front of the filename text field in save dialog (not used in open dialog); or {@code null}
110+
* @param nameFieldStringValue user-editable filename currently shown in the name field in save dialog (not used in open dialog); or {@code null}
111+
* @param directoryURL current directory shown in the dialog; or {@code null}
112+
* @param optionsSet options to set; see {@code FC_*} constants
113+
* @param optionsClear options to clear; see {@code FC_*} constants
114+
* @param fileTypeIndex the file type that appears as selected (zero-based)
115+
* @param fileTypes file types that the dialog can open or save.
116+
* Two or more strings and {@code null} are required for each filter.
117+
* First string is the display name of the filter shown in the combobox (e.g. "Text Files").
118+
* Subsequent strings are the filter patterns (e.g. "txt" or "*").
119+
* {@code null} is required to mark end of filter.
120+
* @return file path(s) that the user selected; an empty array if canceled;
121+
* or {@code null} on failures (no dialog shown)
122+
*
123+
* @since 3.7
124+
*/
125+
public native static String[] showFileChooser( Window owner, int dark, boolean open,
126+
String title, String prompt, String message, String filterFieldLabel,
127+
String nameFieldLabel, String nameFieldStringValue, String directoryURL,
128+
int optionsSet, int optionsClear, FileChooserCallback callback,
129+
int fileTypeIndex, String... fileTypes );
130+
131+
/** @since 3.7 */
132+
public interface FileChooserCallback {
133+
boolean approve( String[] files, long hwndFileDialog );
134+
}
135+
136+
/**
137+
* Shows a macOS alert
138+
* <a href="https://developer.apple.com/documentation/appkit/nsalert?language=objc">NSAlert</a>.
139+
* <p>
140+
* For use in {@link FileChooserCallback} only.
141+
*
142+
* @param hwndParent the parent of the message box
143+
* @param alertStyle type of alert being displayed:
144+
* {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#INFORMATION_MESSAGE} or
145+
* {@link JOptionPane#WARNING_MESSAGE}
146+
* @param messageText main message of the alert
147+
* @param informativeText additional information about the alert; shown below of main message; or {@code null}
148+
* @param defaultButton index of the default button, which can be pressed using ENTER key
149+
* @param buttons texts of the buttons; if no buttons given the a default "OK" button is shown
150+
* @return index of pressed button
151+
*
152+
* @since 3.7
153+
*/
154+
public native static int showMessageDialog( long hwndParent, int alertStyle,
155+
String messageText, String informativeText, int defaultButton, String... buttons );
71156
}

0 commit comments

Comments
 (0)