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; + } }
