|
25 | 25 | import com.formdev.flatlaf.*; |
26 | 26 | import com.formdev.flatlaf.extras.FlatSVGIcon; |
27 | 27 | import com.formdev.flatlaf.icons.FlatClearIcon; |
| 28 | +import com.formdev.flatlaf.util.SystemFileChooser; |
28 | 29 | import com.formdev.flatlaf.util.UIScale; |
29 | 30 | import com.jidesoft.plaf.LookAndFeelFactory; |
30 | 31 | import com.jidesoft.popup.JidePopup; |
@@ -1264,12 +1265,10 @@ public void changedUpdate(final DocumentEvent e) { |
1264 | 1265 | } |
1265 | 1266 |
|
1266 | 1267 | public File getOpenFile(final String title, final File file, final String... allowedExtensions) { |
1267 | | - final JFileChooser chooser = fileChooser(title, file, JFileChooser.OPEN_DIALOG, JFileChooser.FILES_ONLY); |
1268 | | - if (allowedExtensions != null && allowedExtensions.length > 0) { |
1269 | | - chooser.addChoosableFileFilter(new FileNameExtensionFilter( |
1270 | | - "Reconstruction files (" + String.join(",", allowedExtensions) + ")", allowedExtensions)); |
1271 | | - } |
1272 | | - return (File) getOpenFileChooserResult(chooser); |
| 1268 | + final FileNameExtensionFilter filter = (allowedExtensions == null || allowedExtensions.length == 0) ? null |
| 1269 | + : new FileNameExtensionFilter( |
| 1270 | + "Reconstruction files (" + String.join(",", allowedExtensions) + ")", allowedExtensions); |
| 1271 | + return (File) showOpenDialog(title, JFileChooser.FILES_ONLY, false, file, false, filter); |
1273 | 1272 | } |
1274 | 1273 |
|
1275 | 1274 | public File getSaveFile(final String title, final File file, final String... allowedExtensions) { |
@@ -1300,76 +1299,135 @@ public File getSaveFile(final String title, final File file, final String... all |
1300 | 1299 | } |
1301 | 1300 |
|
1302 | 1301 | public File[] getReconstructionFiles(final File selectedFile) { |
1303 | | - final JFileChooser fileChooser = getReconstructionFileChooser(null); |
1304 | | - if (selectedFile != null) fileChooser.setSelectedFile(selectedFile); |
1305 | | - return (File[])getOpenFileChooserResult(fileChooser); |
| 1302 | + final FileNameExtensionFilter filter = new FileNameExtensionFilter( |
| 1303 | + "Reconstruction files (.traces, .swc, .json, .ndf, .xml)", "traces", "swc", "json", "ndf", "xml"); |
| 1304 | + return (File[]) showOpenDialog("Choose Reconstruction File(s)", JFileChooser.FILES_ONLY, true, |
| 1305 | + selectedFile, false, filter); |
1306 | 1306 | } |
1307 | 1307 |
|
1308 | 1308 | public File getFile(final File file, final String extensionWithoutPeriod) { |
1309 | | - final JFileChooser fileChooser = GuiUtils.getDnDFileChooser(); |
1310 | | - fileChooser.setDialogTitle("/".equals(extensionWithoutPeriod) ? "Choose Directory" : "Choose " + extensionWithoutPeriod.toUpperCase() + " File"); |
1311 | | - fileChooser.setFileSelectionMode("/".equals(extensionWithoutPeriod) ? JFileChooser.DIRECTORIES_ONLY : JFileChooser.FILES_ONLY); |
1312 | | - fileChooser.setDialogType(JFileChooser.OPEN_DIALOG); |
1313 | | - fileChooser.setMultiSelectionEnabled(false); |
1314 | | - fileChooser.addChoosableFileFilter( |
1315 | | - new FileNameExtensionFilter(extensionWithoutPeriod.toUpperCase(), extensionWithoutPeriod)); |
1316 | | - if (file != null) { |
1317 | | - if ("/".equals(extensionWithoutPeriod)) { |
1318 | | - fileChooser.setCurrentDirectory(file); |
1319 | | - } else { |
1320 | | - fileChooser.setSelectedFile(file); |
1321 | | - } |
1322 | | - } |
1323 | | - fileChooser.setMultiSelectionEnabled(false); |
1324 | | - return (File) getOpenFileChooserResult(fileChooser); |
| 1309 | + final boolean dir = "/".equals(extensionWithoutPeriod); |
| 1310 | + final String title = dir ? "Choose Directory" : "Choose " + extensionWithoutPeriod.toUpperCase() + " File"; |
| 1311 | + final int selectionMode = dir ? JFileChooser.DIRECTORIES_ONLY : JFileChooser.FILES_ONLY; |
| 1312 | + // NB: a "/"-named extension filter is meaningless in DIRECTORIES_ONLY mode (both classic |
| 1313 | + // and native choosers only ever list directories there), so it is only added for files |
| 1314 | + final FileNameExtensionFilter filter = dir ? null |
| 1315 | + : new FileNameExtensionFilter(extensionWithoutPeriod.toUpperCase(), extensionWithoutPeriod); |
| 1316 | + return (File) showOpenDialog(title, selectionMode, false, file, dir, filter); |
1325 | 1317 | } |
1326 | 1318 |
|
1327 | 1319 | public File getImageFile(final File file) { |
1328 | | - final JFileChooser fileChooser = GuiUtils.getDnDFileChooser(); |
1329 | | - fileChooser.setDialogTitle("Choose Image File"); |
1330 | | - fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); |
1331 | | - fileChooser.setDialogType(JFileChooser.OPEN_DIALOG); |
1332 | | - fileChooser.addChoosableFileFilter( |
1333 | | - new FileNameExtensionFilter("IJ 'native' formats (.tif, .png, .raw, .zip, etc.)", "tif", "tiff", "dcm", |
1334 | | - "avi", "fits", "pgm", "jpg", "gif", "bmp", "zip", "png", "raw")); |
1335 | | - fileChooser.setMultiSelectionEnabled(false); |
1336 | | - if (file != null) |
1337 | | - fileChooser.setSelectedFile(file); |
1338 | | - fileChooser.setMultiSelectionEnabled(false); |
1339 | | - return (File) getOpenFileChooserResult(fileChooser); |
| 1320 | + final FileNameExtensionFilter filter = new FileNameExtensionFilter( |
| 1321 | + "IJ 'native' formats (.tif, .png, .raw, .zip, etc.)", "tif", "tiff", "dcm", |
| 1322 | + "avi", "fits", "pgm", "jpg", "gif", "bmp", "zip", "png", "raw"); |
| 1323 | + return (File) showOpenDialog("Choose Image File", JFileChooser.FILES_ONLY, false, file, false, filter); |
1340 | 1324 | } |
1341 | 1325 |
|
1342 | 1326 | public File getReconstructionFile(final File file, final String extension) { |
1343 | | - FileNameExtensionFilter filter = switch (extension) { |
| 1327 | + final FileNameExtensionFilter filter = switch (extension) { |
1344 | 1328 | case "swc" -> new FileNameExtensionFilter("SWC files (.swc)", "swc"); |
1345 | 1329 | case "traces" -> new FileNameExtensionFilter("SNT TRACES files (.traces)", "traces"); |
1346 | 1330 | case "json" -> new FileNameExtensionFilter("JSON files (.json)", "json"); |
1347 | 1331 | case "ndf" -> new FileNameExtensionFilter("NeuronJ NDF files (.ndf)", "ndf"); |
1348 | 1332 | case "xml" -> new FileNameExtensionFilter("Neurolucida XML files (.xml)", "xml"); |
1349 | 1333 | case "labels" -> new FileNameExtensionFilter("AmiraMesh labels (.labels)", "labels"); |
1350 | | - case null, default -> null; |
| 1334 | + case null, default -> new FileNameExtensionFilter( |
| 1335 | + "Reconstruction files (.traces, .swc, .json, .ndf, .xml)", "traces", "swc", "json", "ndf", "xml"); |
1351 | 1336 | }; |
1352 | | - final JFileChooser fileChooser = getReconstructionFileChooser(filter); |
1353 | | - fileChooser.setSelectedFile(file); |
1354 | | - if (extension == null) |
1355 | | - fileChooser.setDialogTitle("Choose Reconstruction File"); |
1356 | | - else if ("labels".equals(extension)) |
1357 | | - fileChooser.setDialogTitle("Choose LABELS File"); |
1358 | | - else |
1359 | | - fileChooser.setDialogTitle("Choose Reconstruction (" + extension.toUpperCase() + ") File"); |
1360 | | - fileChooser.setMultiSelectionEnabled(false); |
1361 | | - return (File)getOpenFileChooserResult(fileChooser); |
| 1337 | + final String title; |
| 1338 | + if (extension == null) title = "Choose Reconstruction File"; |
| 1339 | + else if ("labels".equals(extension)) title = "Choose LABELS File"; |
| 1340 | + else title = "Choose Reconstruction (" + extension.toUpperCase() + ") File"; |
| 1341 | + return (File) showOpenDialog(title, JFileChooser.FILES_ONLY, false, file, false, filter); |
1362 | 1342 | } |
1363 | 1343 |
|
1364 | | - private JFileChooser getReconstructionFileChooser(final FileNameExtensionFilter filter) { |
| 1344 | + /** |
| 1345 | + * Shows an "Open" dialog, honoring {@link SNTPrefs#getUseNativeFileChooser()}: either SNT's |
| 1346 | + * classic {@link FileChooser} (with its accessory toolbar and drag-and-drop support) or |
| 1347 | + * FlatLaf's {@link SystemFileChooser} (native OS dialog). Only intended for the simple, |
| 1348 | + * extension-filter-based dialogs built by the {@code getXxxFile(s)} methods above - dialogs that |
| 1349 | + * need custom {@code FileFilter}s, {@code FILES_AND_DIRECTORIES} selection, or drag-and-drop |
| 1350 | + * (e.g. {@link #getSaveFile}, {@link #fileChooser}, {@link #getDnDFileChooser()} and their |
| 1351 | + * callers) always use the classic chooser and are not affected by this method or the preference. |
| 1352 | + * |
| 1353 | + * @param title the dialog title |
| 1354 | + * @param selectionMode {@code JFileChooser.FILES_ONLY} or {@code JFileChooser.DIRECTORIES_ONLY} |
| 1355 | + * @param multiSelection whether multiple files may be selected |
| 1356 | + * @param preset a file/directory to pre-select or pre-navigate to, or {@code null} |
| 1357 | + * @param presetIsDirectory whether {@code preset} should be treated as a directory to navigate |
| 1358 | + * to ({@code setCurrentDirectory}) rather than a file to pre-select |
| 1359 | + * ({@code setSelectedFile}) |
| 1360 | + * @param filter the (single) choosable extension filter to add, or {@code null} for none |
| 1361 | + * @return a {@code File} (single-selection) or {@code File[]} (multi-selection), or {@code null} |
| 1362 | + * if the dialog was cancelled |
| 1363 | + */ |
| 1364 | + private Object showOpenDialog(final String title, final int selectionMode, final boolean multiSelection, |
| 1365 | + final File preset, final boolean presetIsDirectory, final FileNameExtensionFilter filter) { |
| 1366 | + if (SNTPrefs.getUseNativeFileChooser()) { |
| 1367 | + return showNativeOpenDialog(title, selectionMode, multiSelection, preset, presetIsDirectory, filter); |
| 1368 | + } |
1365 | 1369 | final JFileChooser fileChooser = GuiUtils.getDnDFileChooser(); |
1366 | | - fileChooser.setDialogTitle("Choose Reconstruction File(s)"); |
1367 | | - fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); |
| 1370 | + fileChooser.setDialogTitle(title); |
| 1371 | + fileChooser.setFileSelectionMode(selectionMode); |
1368 | 1372 | fileChooser.setDialogType(JFileChooser.OPEN_DIALOG); |
1369 | | - fileChooser.addChoosableFileFilter(Objects.requireNonNullElseGet(filter, () -> new FileNameExtensionFilter( |
1370 | | - "Reconstruction files (.traces, .swc, .json, .ndf, .xml)", "traces", "swc", "json", "ndf", "xml"))); |
1371 | | - fileChooser.setMultiSelectionEnabled(true); |
1372 | | - return fileChooser; |
| 1373 | + fileChooser.setMultiSelectionEnabled(multiSelection); |
| 1374 | + if (filter != null) fileChooser.addChoosableFileFilter(filter); |
| 1375 | + if (preset != null) { |
| 1376 | + if (presetIsDirectory) fileChooser.setCurrentDirectory(preset); |
| 1377 | + else fileChooser.setSelectedFile(preset); |
| 1378 | + } |
| 1379 | + return getOpenFileChooserResult(fileChooser); |
| 1380 | + } |
| 1381 | + |
| 1382 | + private Object showNativeOpenDialog(final String title, final int selectionMode, final boolean multiSelection, |
| 1383 | + final File preset, final boolean presetIsDirectory, final FileNameExtensionFilter filter) { |
| 1384 | + // Unlike JFileChooser.showOpenDialog(), SystemFileChooser.showOpenDialog() throws |
| 1385 | + // IllegalStateException if not called on the EDT |
| 1386 | + if (!SwingUtilities.isEventDispatchThread()) { |
| 1387 | + final Object[] result = new Object[1]; |
| 1388 | + try { |
| 1389 | + SwingUtilities.invokeAndWait(() -> result[0] = showNativeOpenDialogOnEDT( |
| 1390 | + title, selectionMode, multiSelection, preset, presetIsDirectory, filter)); |
| 1391 | + } catch (final InterruptedException e) { |
| 1392 | + Thread.currentThread().interrupt(); |
| 1393 | + return null; |
| 1394 | + } catch (final java.lang.reflect.InvocationTargetException e) { |
| 1395 | + SNTUtils.error("Native file dialog failed", e.getCause()); |
| 1396 | + return null; |
| 1397 | + } |
| 1398 | + return result[0]; |
| 1399 | + } |
| 1400 | + return showNativeOpenDialogOnEDT(title, selectionMode, multiSelection, preset, presetIsDirectory, filter); |
| 1401 | + } |
| 1402 | + |
| 1403 | + private Object showNativeOpenDialogOnEDT(final String title, final int selectionMode, |
| 1404 | + final boolean multiSelection, final File preset, final boolean presetIsDirectory, |
| 1405 | + final FileNameExtensionFilter filter) { |
| 1406 | + final SystemFileChooser fileChooser = new SystemFileChooser(); |
| 1407 | + fileChooser.setDialogTitle(title); |
| 1408 | + fileChooser.setFileSelectionMode(selectionMode == JFileChooser.DIRECTORIES_ONLY |
| 1409 | + ? SystemFileChooser.DIRECTORIES_ONLY : SystemFileChooser.FILES_ONLY); |
| 1410 | + fileChooser.setMultiSelectionEnabled(multiSelection); |
| 1411 | + if (filter != null) { |
| 1412 | + fileChooser.addChoosableFileFilter( |
| 1413 | + new SystemFileChooser.FileNameExtensionFilter(filter.getDescription(), filter.getExtensions())); |
| 1414 | + } |
| 1415 | + if (preset != null) { |
| 1416 | + if (presetIsDirectory) fileChooser.setCurrentDirectory(preset); |
| 1417 | + else fileChooser.setSelectedFile(preset); |
| 1418 | + } |
| 1419 | + // HACK: On macOS this seems to help to ensure prompt is displayed as frontmost |
| 1420 | + final boolean focused = parent instanceof Window && parent.hasFocus(); |
| 1421 | + if (focused) ((Window) parent).toBack(); |
| 1422 | + if (fileChooser.showOpenDialog(parent) == SystemFileChooser.APPROVE_OPTION) { |
| 1423 | + // NB: unlike JFileChooser, getSelectedFiles() returns the selection in single-selection |
| 1424 | + // mode too - see FlatLaf's SystemFileChooser docs |
| 1425 | + final File[] result = fileChooser.getSelectedFiles(); |
| 1426 | + if (result.length > 0) SNTPrefs.setLastKnownDir(result[0]); |
| 1427 | + return multiSelection ? result : (result.length > 0 ? result[0] : null); |
| 1428 | + } |
| 1429 | + if (focused) ((Window) parent).toFront(); |
| 1430 | + return null; |
1373 | 1431 | } |
1374 | 1432 |
|
1375 | 1433 | private Object getOpenFileChooserResult(final JFileChooser fileChooser) { |
|
0 commit comments