diff --git a/com.io7m.jwheatsheaf.examples/src/main/java/com/io7m/jwheatsheaf/examples/ExampleViewController.java b/com.io7m.jwheatsheaf.examples/src/main/java/com/io7m/jwheatsheaf/examples/ExampleViewController.java index 8050680..e9c39db 100644 --- a/com.io7m.jwheatsheaf.examples/src/main/java/com/io7m/jwheatsheaf/examples/ExampleViewController.java +++ b/com.io7m.jwheatsheaf.examples/src/main/java/com/io7m/jwheatsheaf/examples/ExampleViewController.java @@ -39,6 +39,7 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.net.URL; +import java.nio.file.Files; import java.nio.file.Path; import java.time.Duration; import java.time.temporal.ChronoUnit; @@ -49,6 +50,7 @@ import java.util.Objects; import java.util.ResourceBundle; import java.util.concurrent.Executors; +import java.util.function.Function; import java.util.stream.Collectors; /** @@ -88,6 +90,8 @@ public final class ExampleViewController implements Initializable @FXML private CheckBox unusualStrings; @FXML + private CheckBox onlyFiles; + @FXML private ChoiceBox action; @FXML private TextField title; @@ -219,6 +223,13 @@ private void onOpenSelected() stringOverrides = JWFileChooserStringOverridesEmpty.get(); } + final Function fileSelectionMode; + if (this.onlyFiles.isSelected()) { + fileSelectionMode = path -> Boolean.valueOf(Files.isRegularFile(path)); + } else { + fileSelectionMode = path -> Boolean.TRUE; + } + configurationBuilder .setAllowDirectoryCreation(this.allowDirectoryCreation.isSelected()) .setShowParentDirectory(this.parentDirectory.isSelected()) @@ -230,6 +241,7 @@ private void onOpenSelected() .setStringOverrides(stringOverrides) .addFileFilters(new ExampleFilterRejectAll()) .addFileFilters(new ExampleFilterXML()) + .setFileSelectionMode(fileSelectionMode) .addAllRecentFiles(recents); if (this.homeDirectory.isSelected()) { diff --git a/com.io7m.jwheatsheaf.examples/src/main/resources/com/io7m/jwheatsheaf/examples/example.fxml b/com.io7m.jwheatsheaf.examples/src/main/resources/com/io7m/jwheatsheaf/examples/example.fxml index a3e3fdf..920dd6f 100644 --- a/com.io7m.jwheatsheaf.examples/src/main/resources/com/io7m/jwheatsheaf/examples/example.fxml +++ b/com.io7m.jwheatsheaf.examples/src/main/resources/com/io7m/jwheatsheaf/examples/example.fxml @@ -4,6 +4,7 @@ + @@ -14,7 +15,6 @@ - @@ -51,6 +51,7 @@ + @@ -71,8 +72,7 @@ diff --git a/com.io7m.jwheatsheaf.tests/src/test/java/com/io7m/jwheatsheaf/tests/JWFileChooserBug34.java b/com.io7m.jwheatsheaf.tests/src/test/java/com/io7m/jwheatsheaf/tests/JWFileChooserBug34.java new file mode 100644 index 0000000..dbfb46e --- /dev/null +++ b/com.io7m.jwheatsheaf.tests/src/test/java/com/io7m/jwheatsheaf/tests/JWFileChooserBug34.java @@ -0,0 +1,132 @@ +/* + * Copyright © 2020 Mark Raynsford http://io7m.com + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package com.io7m.jwheatsheaf.tests; + +import com.io7m.jwheatsheaf.api.JWFileChooserAction; +import com.io7m.jwheatsheaf.api.JWFileChooserConfiguration; +import com.io7m.jwheatsheaf.api.JWFileChooserEventType; +import com.io7m.jwheatsheaf.api.JWFileChooserType; +import com.io7m.jwheatsheaf.api.JWFileChoosersType; +import com.io7m.jwheatsheaf.ui.JWFileChoosers; +import javafx.scene.input.KeyCode; +import javafx.stage.Stage; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.extension.ExtendWith; +import org.testfx.api.FxAssert; +import org.testfx.api.FxRobot; +import org.testfx.framework.junit5.ApplicationExtension; +import org.testfx.framework.junit5.Start; +import org.testfx.framework.junit5.Stop; +import org.testfx.matcher.base.NodeMatchers; + +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +@ExtendWith(ApplicationExtension.class) +public final class JWFileChooserBug34 +{ + private JWTestFilesystems filesystems; + private FileSystem dosFilesystem; + private JWFileChooserType chooser; + private List events; + private JWFileChoosersType choosers; + + @Start + public void start(final Stage stage) + throws Exception + { + this.events = Collections.synchronizedList(new ArrayList<>()); + + this.filesystems = JWTestFilesystems.create(); + final var systems = this.filesystems.filesystems(); + this.dosFilesystem = systems.get("ExampleDOS"); + + final var configuration = + JWFileChooserConfiguration.builder() + .setAction(JWFileChooserAction.CREATE) + .setFileSystem(this.dosFilesystem) + .setFileSelectionMode(path -> { + return Boolean.valueOf(Files.isRegularFile(path)); + }) + .build(); + + this.choosers = JWFileChoosers.create(); + this.chooser = this.choosers.create(stage, configuration); + this.chooser.setEventListener(event -> this.events.add(event)); + this.chooser.show(); + } + + @Stop + public void stop() + throws IOException + { + this.choosers.close(); + this.chooser.cancel(); + } + + /** + * In CREATE mode, specifying a nonexistent file if using a filter that + * checks if files are regular files, does not silently fail when the user + * clicks OK. + * + * @param robot The FX test robot + */ + + @Test + public void test_NameField_NonexistentFileDoesNotSilentlyFail( + final FxRobot robot, + final TestInfo info) + { + JWFileWindowTitles.setTitle(this.chooser, info); + + final var delegate = new JWRobotDelegate(robot); + + final var okButton = + robot.lookup("#fileChooserOKButton") + .queryButton(); + + robot.clickOn("#fileChooserNameField"); + + FxAssert.verifyThat(okButton, NodeMatchers.isDisabled()); + robot.write("THIS_DOES_NOT_EXIST.TXT"); + delegate.pauseBriefly(); + FxAssert.verifyThat(okButton, NodeMatchers.isEnabled()); + + robot.type(KeyCode.ENTER); + FxAssert.verifyThat(okButton, NodeMatchers.isEnabled()); + + FxAssert.verifyThat(okButton, NodeMatchers.isEnabled()); + robot.clickOn(okButton); + + Assertions.assertEquals( + List.of("Z:\\USERS\\GROUCH\\THIS_DOES_NOT_EXIST.TXT"), + this.chooser.result() + .stream() + .map(Path::toString) + .collect(Collectors.toList()) + ); + Assertions.assertEquals(0, this.events.size()); + } +} diff --git a/com.io7m.jwheatsheaf.ui/src/main/java/com/io7m/jwheatsheaf/ui/internal/JWFileChooserViewController.java b/com.io7m.jwheatsheaf.ui/src/main/java/com/io7m/jwheatsheaf/ui/internal/JWFileChooserViewController.java index eaed0ca..fc57afd 100644 --- a/com.io7m.jwheatsheaf.ui/src/main/java/com/io7m/jwheatsheaf/ui/internal/JWFileChooserViewController.java +++ b/com.io7m.jwheatsheaf.ui/src/main/java/com/io7m/jwheatsheaf/ui/internal/JWFileChooserViewController.java @@ -650,6 +650,8 @@ private void onCreateDirectoryButton() @FXML private void onOKSelected() { + LOG.debug("ok: selected"); + this.result = List.of(); var resultTarget = List.of(); @@ -669,25 +671,23 @@ private void onOKSelected() break; } - resultTarget = - resultTarget.stream() - .filter(this::filterSelectionMode) - .collect(Collectors.toList()); - - if (!resultTarget.isEmpty()) { - final boolean confirmed; - if (this.isFileSelectionConfirmationRequired()) { - confirmed = this.confirmFileSelection(resultTarget); - } else { - confirmed = true; - } + final boolean confirmed; + if (this.isFileSelectionConfirmationRequired()) { + confirmed = this.confirmFileSelection(resultTarget); + } else { + confirmed = true; + } - if (confirmed) { - this.result = resultTarget; - final var window = this.mainContent.getScene().getWindow(); - window.hide(); - } + if (!confirmed) { + LOG.trace("ok: confirmation failed"); + return; } + + this.result = resultTarget; + final var window = this.mainContent.getScene().getWindow(); + + LOG.trace("ok: hiding window"); + window.hide(); } /**