This is an automated email from the ASF dual-hosted git repository. asf-gitbox-commits pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/cayenne.git
commit 398add93ab20e86bf31a4693068031b8b104725f Author: Andrus Adamchik <[email protected]> AuthorDate: Sun May 24 09:28:46 2026 -0400 CAY-2948 Modeler: use native FileDialog on Mac reverting to JFileChooser for Save on Mac --- .../modeler/platform/mac/MacFileChooser.java | 83 ++++++++++++++++------ 1 file changed, 63 insertions(+), 20 deletions(-) diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/platform/mac/MacFileChooser.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/platform/mac/MacFileChooser.java index 8c36723bd..d03eeb20e 100644 --- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/platform/mac/MacFileChooser.java +++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/platform/mac/MacFileChooser.java @@ -22,9 +22,15 @@ package org.apache.cayenne.modeler.platform.mac; import org.apache.cayenne.modeler.pref.adapters.FileChooserPrefs; import org.apache.cayenne.modeler.toolkit.filechooser.CMFileChooser; +import javax.swing.JButton; +import javax.swing.JFileChooser; +import javax.swing.SwingUtilities; import javax.swing.filechooser.FileFilter; +import java.awt.Component; +import java.awt.Container; import java.awt.FileDialog; import java.awt.Frame; +import java.awt.event.HierarchyEvent; import java.io.File; import java.io.FilenameFilter; import java.util.function.Consumer; @@ -36,8 +42,6 @@ import java.util.function.Consumer; */ public class MacFileChooser implements CMFileChooser { - private static final String DIRS_DIALOG_PROPERTY = "apple.awt.fileDialogForDirectories"; - private final Frame parent; private final String title; @@ -59,7 +63,7 @@ public class MacFileChooser implements CMFileChooser { @Override public File openDir(File initialDir) { - return showOpenDir(fd -> { if (initialDir != null) fd.setDirectory(initialDir.getAbsolutePath()); }); + return showOpenDir(c -> { if (initialDir != null) c.setCurrentDirectory(initialDir); }); } @Override @@ -74,7 +78,7 @@ public class MacFileChooser implements CMFileChooser { @Override public File saveDir(File initialDir) { - return showOpenDir(fd -> { if (initialDir != null) fd.setDirectory(initialDir.getAbsolutePath()); }); + return showOpenDir(c -> { if (initialDir != null) c.setCurrentDirectory(initialDir); }); } private File showOpen(FilenameFilter fnFilter, Consumer<FileDialog> init) { @@ -99,25 +103,64 @@ public class MacFileChooser implements CMFileChooser { return file != null ? new File(fd.getDirectory(), file) : null; } - private File showOpenDir(Consumer<FileDialog> init) { - // Toggle the property only for the duration of this modal dialog. - // FileDialog is EDT-blocking, so no other dialog can run concurrently. - System.setProperty(DIRS_DIALOG_PROPERTY, "true"); - try { - - // TODO: FileDialog.LOAD results in the "Save" button being called "Load". This is unfortunate, but the - // alternative allows the user to override the file name - FileDialog fd = new FileDialog(parent, title != null ? title : "", FileDialog.LOAD); - init.accept(fd); - fd.setVisible(true); - String file = fd.getFile(); - // getFile() returns the selected directory name; getDirectory() is its parent. - return file != null ? new File(fd.getDirectory(), file) : null; - } finally { - System.clearProperty(DIRS_DIALOG_PROPERTY); + private File showOpenDir(Consumer<JFileChooser> init) { + + // JFileChooser instead of FileDialog: apple.awt.fileDialogForDirectories makes + // FileDialog pick directories, but the button is labeled "Load" with no way to + // override it. JFileChooser shows "Open" and lets us set a dialog title. + + // FILES_AND_DIRECTORIES + directory filter instead of DIRECTORIES_ONLY: on macOS + // Aqua L&F the approval button stays disabled in DIRECTORIES_ONLY mode. + JFileChooser chooser = new JFileChooser(); + chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); + chooser.setFileFilter(DIRS_ONLY_FILTER); + + // AquaFileChooserUI disables the approve button when the user navigates into a + // directory with nothing selected. Directly enabling buttons in the component tree + // (via invokeLater, after Aqua's own listener runs) keeps it always active so the + // user can confirm the current directory without selecting a child item. + // No state mutations — setSelectedFile(dir) makes Aqua navigate into that dir, + // firing DIRECTORY_CHANGED again and causing an infinite loop. + chooser.addPropertyChangeListener(e -> { + String prop = e.getPropertyName(); + if (JFileChooser.DIRECTORY_CHANGED_PROPERTY.equals(prop) + || JFileChooser.SELECTED_FILE_CHANGED_PROPERTY.equals(prop)) { + SwingUtilities.invokeLater(() -> enableButtons(chooser)); + } + }); + + chooser.addHierarchyListener(e -> { + if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0 && chooser.isShowing()) { + enableButtons(chooser); + } + }); + + init.accept(chooser); + if (title != null) { + chooser.setDialogTitle(title); + } + if (chooser.showOpenDialog(parent) != JFileChooser.APPROVE_OPTION) { + return null; + } + File selected = chooser.getSelectedFile(); + return selected != null ? selected : chooser.getCurrentDirectory(); + } + + private static void enableButtons(Container container) { + for (Component c : container.getComponents()) { + if (c instanceof JButton btn) { + btn.setEnabled(true); + } else if (c instanceof Container cont) { + enableButtons(cont); + } } } + private static final FileFilter DIRS_ONLY_FILTER = new FileFilter() { + @Override public boolean accept(File f) { return f.isDirectory(); } + @Override public String getDescription() { return "Directories"; } + }; + private static FilenameFilter toFilenameFilter(FileFilter filter) { return filter != null ? (dir, name) -> filter.accept(new File(dir, name)) : null; }
