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 9fa84494aa67ab5b81f9fd07ec2197db74148cbc
Author: Andrus Adamchik <[email protected]>
AuthorDate: Mon May 25 10:54:15 2026 -0400

    CAY-2948 Modeler: use native FileDialog on Mac
    
    turning Swing-specific dir dialogs into standalone components classes
---
 .../modeler/platform/mac/MacFileChooser.java       |  19 +--
 .../modeler/toolkit/filechooser/CMFileChooser.java |   2 -
 .../toolkit/filechooser/SwingDirChooserDialog.java | 112 ++++++++++++++++++
 .../toolkit/filechooser/SwingFileChooser.java      | 128 +++------------------
 ...FileChooser.java => SwingFileOpenerDialog.java} |  51 +++++---
 ...MFileChooser.java => SwingFileSaverDialog.java} |  50 ++++----
 6 files changed, 197 insertions(+), 165 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 3faf7d3ee..c81e4533f 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
@@ -21,11 +21,10 @@ package org.apache.cayenne.modeler.platform.mac;
 
 import org.apache.cayenne.modeler.pref.adapters.FileChooserPrefs;
 import org.apache.cayenne.modeler.toolkit.filechooser.CMFileChooser;
-import org.apache.cayenne.modeler.toolkit.filechooser.SwingFileChooser;
+import org.apache.cayenne.modeler.toolkit.filechooser.SwingDirChooserDialog;
 
 import javax.swing.filechooser.FileFilter;
-import java.awt.FileDialog;
-import java.awt.Frame;
+import java.awt.*;
 import java.io.File;
 import java.io.FilenameFilter;
 import java.util.function.Consumer;
@@ -57,14 +56,16 @@ public class MacFileChooser implements CMFileChooser {
 
     @Override
     public File openDir(File initialDir) {
-        // must use custom JFileChooser, as MacOS native dialog doesn't allow 
to block file name selection
-        return SwingFileChooser.showOpenDir(parent, title, initialDir, c -> 
{});
+        // must use custom JFileChooser, as macOS native dialog doesn't allow 
to block file name selection
+        return new SwingDirChooserDialog(parent, title, initialDir).open();
     }
 
     @Override
     public File openDir(FileChooserPrefs prefs) {
-        // must use custom JFileChooser, as MacOS native dialog doesn't allow 
to block file name selection
-        return SwingFileChooser.showOpenDir(parent, title, prefs.getDir(), 
prefs::bind);
+        // must use custom JFileChooser, as macOS native dialog doesn't allow 
to block file name selection
+        SwingDirChooserDialog dialog = new SwingDirChooserDialog(parent, 
title, prefs.getDir());
+        prefs.bind(dialog);
+        return dialog.open();
     }
 
     @Override
@@ -74,8 +75,8 @@ public class MacFileChooser implements CMFileChooser {
 
     @Override
     public File saveDir(File initialDir) {
-        // must use custom JFileChooser, as MacOS native dialog doesn't allow 
to block file name selection
-        return SwingFileChooser.showOpenDir(parent, title, initialDir, c -> 
{});
+        // must use custom JFileChooser, as macOS native dialog doesn't allow 
to block file name selection
+        return new SwingDirChooserDialog(parent, title, initialDir).open();
     }
 
     private File showOpen(FilenameFilter fnFilter, File startDir, 
Consumer<FileDialog> init) {
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/CMFileChooser.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/CMFileChooser.java
index 4ca8db8cd..b4a779791 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/CMFileChooser.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/CMFileChooser.java
@@ -27,8 +27,6 @@ import java.io.File;
 /**
  * A file/directory chooser dialog bound to a specific parent component and 
title.
  * Obtained via {@code application.getFileChooser(parent, title)}.
- *
- * @see CMFileChooserFactory
  */
 public interface CMFileChooser {
 
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/SwingDirChooserDialog.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/SwingDirChooserDialog.java
new file mode 100644
index 000000000..8638c2a97
--- /dev/null
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/SwingDirChooserDialog.java
@@ -0,0 +1,112 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.apache.cayenne.modeler.toolkit.filechooser;
+
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JFileChooser;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.FlowLayout;
+import java.io.File;
+
+/**
+ * Modal directory-picker dialog built around {@link JFileChooser} with custom 
Select / Cancel buttons and an extra
+ * "New Folder" button (opening a directory may require creating a new one).
+ */
+public final class SwingDirChooserDialog extends JFileChooser {
+
+    private final Component parent;
+    private final JButton selectButton = new JButton("Select");
+    private final JButton cancelButton = new JButton("Cancel");
+    private final JButton newFolderButton = new JButton("New Folder");
+
+    public SwingDirChooserDialog(Component parent, String title, File 
startDir) {
+        super(startDir);
+        this.parent = parent;
+
+        setFileSelectionMode(DIRECTORIES_ONLY);
+
+        // Using setControlButtonsAreShown(false) because on macOS Aqua L&F 
the built-in approve button is
+        // continuously re-disabled when nothing is selected (e.g. after 
navigating into a directory).
+        setControlButtonsAreShown(false);
+
+        if (title != null) {
+            setDialogTitle(title);
+        }
+
+        initBindings();
+    }
+
+    /**
+     * Shows the dialog modally and returns the selected directory, or {@code 
null} if the user cancelled.
+     */
+    public File open() {
+        if (showOpenDialog(parent) != APPROVE_OPTION) {
+            return null;
+        }
+        File selected = getSelectedFile();
+        return selected != null ? selected : getCurrentDirectory();
+    }
+
+    @Override
+    protected JDialog createDialog(Component parent) {
+        JDialog dialog = super.createDialog(parent);
+
+        JPanel right = new JPanel(new FlowLayout(FlowLayout.TRAILING));
+        right.add(cancelButton);
+        right.add(selectButton);
+
+        JPanel left = new JPanel(new FlowLayout(FlowLayout.LEADING));
+        left.add(newFolderButton);
+
+        JPanel buttons = new JPanel(new BorderLayout());
+        buttons.add(left, BorderLayout.WEST);
+        buttons.add(right, BorderLayout.EAST);
+
+        dialog.add(buttons, BorderLayout.SOUTH);
+        dialog.getRootPane().setDefaultButton(selectButton);
+        dialog.pack();
+        dialog.setLocationRelativeTo(parent);
+        return dialog;
+    }
+
+    private void initBindings() {
+        selectButton.addActionListener(e -> approveSelection());
+        cancelButton.addActionListener(e -> cancelSelection());
+        newFolderButton.addActionListener(e -> promptNewFolder());
+    }
+
+    private void promptNewFolder() {
+        String name = JOptionPane.showInputDialog(this, "Folder name:", "New 
Folder", JOptionPane.PLAIN_MESSAGE);
+        if (name == null || name.isBlank()) {
+            return;
+        }
+        File newDir = new File(getCurrentDirectory(), name);
+        if (newDir.mkdir()) {
+            setCurrentDirectory(newDir);
+        } else {
+            JOptionPane.showMessageDialog(this,
+                    "Could not create folder \"" + name + "\".", "Error", 
JOptionPane.ERROR_MESSAGE);
+        }
+    }
+}
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/SwingFileChooser.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/SwingFileChooser.java
index 433f1d11f..b4d4f46eb 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/SwingFileChooser.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/SwingFileChooser.java
@@ -21,20 +21,9 @@ package org.apache.cayenne.modeler.toolkit.filechooser;
 
 import org.apache.cayenne.modeler.pref.adapters.FileChooserPrefs;
 
-import javax.swing.JButton;
-import javax.swing.JDialog;
-import javax.swing.JFileChooser;
-import javax.swing.JOptionPane;
-import javax.swing.JPanel;
-import javax.swing.SwingUtilities;
 import javax.swing.filechooser.FileFilter;
-import java.awt.BorderLayout;
-import java.awt.Component;
-import java.awt.Dialog;
-import java.awt.FlowLayout;
-import java.awt.Window;
+import java.awt.*;
 import java.io.File;
-import java.util.function.Consumer;
 
 public class SwingFileChooser implements CMFileChooser {
 
@@ -48,128 +37,37 @@ public class SwingFileChooser implements CMFileChooser {
 
     @Override
     public File openFile(File initialDir, FileFilter filter) {
-        return showOpen(JFileChooser.FILES_ONLY, initialDir, c -> {}, filter);
+        return new SwingFileOpenerDialog(parent, title, initialDir, 
filter).open();
     }
 
     @Override
     public File openFile(FileChooserPrefs prefs, FileFilter filter) {
-        return showOpen(JFileChooser.FILES_ONLY, prefs.getDir(), prefs::bind, 
filter);
+        SwingFileOpenerDialog dialog = new SwingFileOpenerDialog(parent, 
title, prefs.getDir(), filter);
+        prefs.bind(dialog);
+        return dialog.open();
     }
 
     @Override
     public File openDir(File initialDir) {
-        return showOpenDir(parent, title, initialDir, c -> {});
+        return new SwingDirChooserDialog(parent, title, initialDir).open();
     }
 
     @Override
     public File openDir(FileChooserPrefs prefs) {
-        return showOpenDir(parent, title, prefs.getDir(), prefs::bind);
+        SwingDirChooserDialog dialog = new SwingDirChooserDialog(parent, 
title, prefs.getDir());
+        prefs.bind(dialog);
+        return dialog.open();
     }
 
     @Override
     public File saveFile(FileChooserPrefs prefs, String defaultName) {
-        return showSave(prefs.getDir(), prefs::bind, defaultName);
+        SwingFileSaverDialog dialog = new SwingFileSaverDialog(parent, title, 
prefs.getDir(), defaultName);
+        prefs.bind(dialog);
+        return dialog.open();
     }
 
     @Override
     public File saveDir(File initialDir) {
-        return showOpenDir(parent, title, initialDir, c -> {});
-    }
-
-    // setControlButtonsAreShown(false) + custom buttons: on macOS Aqua L&F 
the built-in
-    // approve button is continuously re-disabled when nothing is selected 
(e.g. after
-    // navigating into a directory). The custom dialog owns its own 
always-enabled "Select"
-    // button and a "New Folder" button, and is used on all platforms for 
consistency.
-
-    public static File showOpenDir(Component parent, String title, File 
startDir, Consumer<JFileChooser> init) {
-        JFileChooser chooser = new JFileChooser(startDir);
-        chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
-        chooser.setControlButtonsAreShown(false);
-        init.accept(chooser);
-
-        boolean[] approved = {false};
-        JButton selectBtn = new JButton("Select");
-        JButton cancelBtn = new JButton("Cancel");
-        JButton newFolderBtn = new JButton("New Folder");
-
-        selectBtn.addActionListener(e -> {
-            approved[0] = true;
-            SwingUtilities.getWindowAncestor(chooser).dispose();
-        });
-        cancelBtn.addActionListener(e -> 
SwingUtilities.getWindowAncestor(chooser).dispose());
-        newFolderBtn.addActionListener(e -> {
-            String name = 
JOptionPane.showInputDialog(SwingUtilities.getWindowAncestor(chooser),
-                    "Folder name:", "New Folder", JOptionPane.PLAIN_MESSAGE);
-            if (name != null && !name.isBlank()) {
-                File newDir = new File(chooser.getCurrentDirectory(), name);
-                if (newDir.mkdir()) {
-                    chooser.setCurrentDirectory(newDir);
-                } else {
-                    
JOptionPane.showMessageDialog(SwingUtilities.getWindowAncestor(chooser),
-                            "Could not create folder \"" + name + "\".", 
"Error", JOptionPane.ERROR_MESSAGE);
-                }
-            }
-        });
-        chooser.addActionListener(e -> {
-            if (JFileChooser.APPROVE_SELECTION.equals(e.getActionCommand())) {
-                approved[0] = true;
-            }
-            SwingUtilities.getWindowAncestor(chooser).dispose();
-        });
-
-        JPanel left = new JPanel(new FlowLayout(FlowLayout.LEADING));
-        left.add(newFolderBtn);
-        JPanel right = new JPanel(new FlowLayout(FlowLayout.TRAILING));
-        right.add(cancelBtn);
-        right.add(selectBtn);
-        JPanel buttons = new JPanel(new BorderLayout());
-        buttons.add(left, BorderLayout.WEST);
-        buttons.add(right, BorderLayout.EAST);
-
-        Window owner = (parent instanceof Window w) ? w : (parent != null ? 
SwingUtilities.getWindowAncestor(parent) : null);
-        JDialog dialog = new JDialog(owner, title != null ? title : "", 
Dialog.ModalityType.APPLICATION_MODAL);
-        dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
-        dialog.add(chooser, BorderLayout.CENTER);
-        dialog.add(buttons, BorderLayout.SOUTH);
-        dialog.getRootPane().setDefaultButton(selectBtn);
-        dialog.pack();
-        dialog.setLocationRelativeTo(parent);
-        dialog.setVisible(true);
-
-        if (!approved[0]) {
-            return null;
-        }
-        File selected = chooser.getSelectedFile();
-        return selected != null ? selected : chooser.getCurrentDirectory();
-    }
-
-    private File showOpen(int mode, File startDir, Consumer<JFileChooser> 
init, FileFilter filter) {
-        JFileChooser chooser = new JFileChooser(startDir);
-        chooser.setFileSelectionMode(mode);
-        init.accept(chooser);
-        if (title != null) {
-            chooser.setDialogTitle(title);
-        }
-        if (filter != null) {
-            chooser.addChoosableFileFilter(filter);
-            chooser.setFileFilter(filter);
-        }
-        return chooser.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION
-                ? chooser.getSelectedFile()
-                : null;
-    }
-
-    private File showSave(File startDir, Consumer<JFileChooser> init, String 
defaultName) {
-        JFileChooser chooser = new JFileChooser(startDir);
-        init.accept(chooser);
-        if (title != null) {
-            chooser.setDialogTitle(title);
-        }
-        if (defaultName != null) {
-            chooser.setSelectedFile(new File(defaultName));
-        }
-        return chooser.showSaveDialog(parent) == JFileChooser.APPROVE_OPTION
-                ? chooser.getSelectedFile()
-                : null;
+        return new SwingDirChooserDialog(parent, title, initialDir).open();
     }
 }
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/CMFileChooser.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/SwingFileOpenerDialog.java
similarity index 54%
copy from 
modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/CMFileChooser.java
copy to 
modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/SwingFileOpenerDialog.java
index 4ca8db8cd..849b8b210 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/CMFileChooser.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/SwingFileOpenerDialog.java
@@ -19,28 +19,41 @@
 
 package org.apache.cayenne.modeler.toolkit.filechooser;
 
-import org.apache.cayenne.modeler.pref.adapters.FileChooserPrefs;
-
+import javax.swing.JFileChooser;
 import javax.swing.filechooser.FileFilter;
+import java.awt.Component;
 import java.io.File;
 
 /**
- * A file/directory chooser dialog bound to a specific parent component and 
title.
- * Obtained via {@code application.getFileChooser(parent, title)}.
- *
- * @see CMFileChooserFactory
+ * Modal open-file dialog backed by {@link JFileChooser#showOpenDialog}.
  */
-public interface CMFileChooser {
-
-    File openFile(File initialDir, FileFilter filter);
-
-    File openFile(FileChooserPrefs prefs, FileFilter filter);
-
-    File openDir(File initialDir);
-
-    File openDir(FileChooserPrefs prefs);
-
-    File saveFile(FileChooserPrefs prefs, String defaultName);
-
-    File saveDir(File initialDir);
+public final class SwingFileOpenerDialog extends JFileChooser {
+
+    private final Component parent;
+
+    public SwingFileOpenerDialog(
+            Component parent,
+            String title,
+            File startDir,
+            FileFilter filter) {
+
+        super(startDir);
+
+        this.parent = parent;
+        setFileSelectionMode(FILES_ONLY);
+        if (title != null) {
+            setDialogTitle(title);
+        }
+        if (filter != null) {
+            addChoosableFileFilter(filter);
+            setFileFilter(filter);
+        }
+    }
+
+    /**
+     * Shows the dialog modally and returns the selected file, or {@code null} 
if the user cancelled.
+     */
+    public File open() {
+        return showOpenDialog(parent) == APPROVE_OPTION ? getSelectedFile() : 
null;
+    }
 }
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/CMFileChooser.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/SwingFileSaverDialog.java
similarity index 55%
copy from 
modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/CMFileChooser.java
copy to 
modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/SwingFileSaverDialog.java
index 4ca8db8cd..661f4ef6d 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/CMFileChooser.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/SwingFileSaverDialog.java
@@ -19,28 +19,38 @@
 
 package org.apache.cayenne.modeler.toolkit.filechooser;
 
-import org.apache.cayenne.modeler.pref.adapters.FileChooserPrefs;
-
-import javax.swing.filechooser.FileFilter;
+import javax.swing.JFileChooser;
+import java.awt.Component;
 import java.io.File;
 
 /**
- * A file/directory chooser dialog bound to a specific parent component and 
title.
- * Obtained via {@code application.getFileChooser(parent, title)}.
- *
- * @see CMFileChooserFactory
+ * Modal save-file dialog backed by {@link JFileChooser#showSaveDialog}.
  */
-public interface CMFileChooser {
-
-    File openFile(File initialDir, FileFilter filter);
-
-    File openFile(FileChooserPrefs prefs, FileFilter filter);
-
-    File openDir(File initialDir);
-
-    File openDir(FileChooserPrefs prefs);
-
-    File saveFile(FileChooserPrefs prefs, String defaultName);
-
-    File saveDir(File initialDir);
+public final class SwingFileSaverDialog extends JFileChooser {
+
+    private final Component parent;
+
+    public SwingFileSaverDialog(
+            Component parent,
+            String title,
+            File startDir,
+            String defaultName) {
+
+        super(startDir);
+
+        this.parent = parent;
+        if (title != null) {
+            setDialogTitle(title);
+        }
+        if (defaultName != null) {
+            setSelectedFile(new File(defaultName));
+        }
+    }
+
+    /**
+     * Shows the dialog modally and returns the selected file, or {@code null} 
if the user cancelled.
+     */
+    public File open() {
+        return showSaveDialog(parent) == APPROVE_OPTION ? getSelectedFile() : 
null;
+    }
 }

Reply via email to