Skip to content

Commit

Permalink
Optionally display parent directories
Browse files Browse the repository at this point in the history
This adds a configuration option to show ".." entries in directory
listings. This will work correctly on all platforms, but may look
out of place on non-Unix platforms, and is therefore not enabled
by default.

Fix: #23
  • Loading branch information
io7m committed May 1, 2021
1 parent c4b112f commit 19c8242
Show file tree
Hide file tree
Showing 9 changed files with 195 additions and 10 deletions.
9 changes: 7 additions & 2 deletions README-CHANGES.xml
Expand Up @@ -39,7 +39,7 @@
<c:change date="2020-04-10T00:00:00+00:00" summary="Enable CheckStyle to enforce code style"/>
</c:changes>
</c:release>
<c:release date="2021-04-29T13:21:57+00:00" is-open="true" ticket-system="com.github.io7m.jwheatsheaf" version="3.0.0">
<c:release date="2021-05-01T13:22:17+00:00" is-open="true" ticket-system="com.github.io7m.jwheatsheaf" version="3.0.0">
<c:changes>
<c:change date="2021-04-10T00:00:00+00:00" summary="Allow the escape key to close file choosers">
<c:tickets>
Expand Down Expand Up @@ -81,11 +81,16 @@
<c:ticket id="19"/>
</c:tickets>
</c:change>
<c:change date="2021-04-29T13:21:57+00:00" summary="Improve filename field behaviour">
<c:change date="2021-04-29T00:00:00+00:00" summary="Improve filename field behaviour">
<c:tickets>
<c:ticket id="28"/>
</c:tickets>
</c:change>
<c:change date="2021-05-01T13:22:17+00:00" summary="Allow for including &quot;..&quot; in directory listings">
<c:tickets>
<c:ticket id="23"/>
</c:tickets>
</c:change>
</c:changes>
</c:release>
</c:releases>
Expand Down
Expand Up @@ -133,6 +133,19 @@ default boolean allowDirectoryCreation()
return true;
}

/**
* Determine whether or not to show a link to the parent directory inside
* the file listing. This entry is always called "..".
*
* @return {@code true} if the directory listing will contain ".."
*/

@Value.Default
default boolean showParentDirectory()
{
return false;
}

/**
* @return The date/time formatter used to display file times
*/
Expand Down
Expand Up @@ -66,6 +66,8 @@ public final class ExampleViewController implements Initializable
@FXML
private CheckBox homeDirectory;
@FXML
private CheckBox parentDirectory;
@FXML
private ChoiceBox<JWFileChooserAction> action;
@FXML
private TextField title;
Expand Down Expand Up @@ -143,6 +145,12 @@ private void onHomeDirectoryChanged()

}

@FXML
private void onParentDirectoryChanged()
{

}

@FXML
private void onOpenSelected()
throws IOException
Expand All @@ -164,6 +172,7 @@ private void onOpenSelected()
final var configurationBuilder =
JWFileChooserConfiguration.builder()
.setAllowDirectoryCreation(this.allowDirectoryCreation.isSelected())
.setShowParentDirectory(this.parentDirectory.isSelected())
.setFileSystem(fileSystem)
.setCssStylesheet(ExampleViewController.class.getResource(this.cssSelection.getValue()))
.setFileImageSet(imageSet)
Expand Down
Expand Up @@ -48,6 +48,13 @@
</children>
</HBox>
<Region maxHeight="-Infinity" minHeight="-Infinity" prefHeight="8.0" />
<HBox alignment="CENTER_LEFT" layoutX="18.0" layoutY="178.0">
<children>
<Region maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="32.0" prefWidth="160.0" />
<CheckBox fx:id="parentDirectory" maxHeight="-Infinity" maxWidth="1.7976931348623157E308" minHeight="-Infinity" mnemonicParsing="false" onAction="#onParentDirectoryChanged" prefHeight="32.0" text="Parent directory" HBox.hgrow="ALWAYS" />
</children>
</HBox>
<Region layoutX="18.0" layoutY="210.0" maxHeight="-Infinity" minHeight="-Infinity" prefHeight="8.0" />
<HBox layoutX="18.0" layoutY="58.0">
<children>
<Label maxHeight="-Infinity" minHeight="-Infinity" prefHeight="32.0" prefWidth="160.0" text="CSS" />
Expand Down
@@ -0,0 +1,129 @@
/*
* Copyright © 2020 Mark Raynsford <code@io7m.com> 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.stage.Stage;
import org.junit.jupiter.api.AfterEach;
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.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

@ExtendWith(ApplicationExtension.class)
public final class JWFileChooserParentDirectoryTest
{
private JWFileChooserType chooser;
private List<JWFileChooserEventType> events;
private JWFileChoosersType choosers;

@Start
public void start(final Stage stage)
throws Exception
{
this.events = Collections.synchronizedList(new ArrayList<>());

final JWTestFilesystems filesystems = JWTestFilesystems.create();
final var systems = filesystems.filesystems();
final FileSystem dosFilesystem = systems.get("ExampleDOS");

final var configuration =
JWFileChooserConfiguration.builder()
.setAllowDirectoryCreation(true)
.setShowParentDirectory(true)
.setAction(JWFileChooserAction.OPEN_EXISTING_SINGLE)
.setFileSystem(dosFilesystem)
.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();
}

@AfterEach
public void afterEach()
{
Assertions.assertEquals(0, this.events.size());
}

/**
* Clicking the ".." directory entry allows for navigating to the parent
* directory.
*/

@Test
public void test_ParentDirectory_DoubleClickParent_NavigatesToParent(
final FxRobot robot,
final TestInfo info)
{
JWFileWindowTitles.setTitle(this.chooser, info);

final var delegate = new JWRobotDelegate(robot);
final var okButton = delegate.getOkButton();

FxAssert.verifyThat(okButton, NodeMatchers.isDisabled());
robot.doubleClickOn(delegate.getTableCellFileName(".."));
delegate.pauseBriefly();

robot.clickOn(delegate.getTableCellFileName("."));
delegate.pauseBriefly();

FxAssert.verifyThat(okButton, NodeMatchers.isEnabled());
robot.clickOn(okButton);

this.assertSelected("Z:\\USERS");
}

private void assertSelected(final String... selectedItems)
{
Assertions.assertEquals(
List.of(selectedItems),
this.chooser.result()
.stream()
.map(Path::toString)
.collect(Collectors.toList())
);
}
}
Expand Up @@ -48,7 +48,8 @@ public void testListDOS()
throws IOException
{
final var items =
JWFileItems.listDirectory(this.dosFilesystem.getPath(""));
JWFileItems.listDirectory(
this.dosFilesystem.getPath(""), false);

Assertions.assertEquals(5, items.size());

Expand All @@ -73,7 +74,8 @@ public void testListBroken()
throws IOException
{
Assertions.assertThrows(IOException.class, () -> {
JWFileItems.listDirectory(this.brokenFilesystem.getPath(""));
JWFileItems.listDirectory(
this.brokenFilesystem.getPath(""), false);
});
}

Expand All @@ -82,7 +84,8 @@ public void testListBrokenFiles()
throws IOException
{
final var items =
JWFileItems.listDirectory(this.brokenFilesFilesystem.getPath(""));
JWFileItems.listDirectory(
this.brokenFilesFilesystem.getPath(""), false);

Assertions.assertEquals(2, items.size());
Assertions.assertEquals(JWFileKind.UNKNOWN, items.get(0).kind());
Expand Down
Expand Up @@ -270,7 +270,9 @@ private void configureSourceList(
final var sources = new ArrayList<JWFileSourceEntryType>();
sources.add(new JWFileSourceEntryRecentItems(this.configuration));
for (final var root : fileSystem.getRootDirectories()) {
sources.add(new JWFileSourceEntryFilesystemRoot(root));
sources.add(new JWFileSourceEntryFilesystemRoot(
this.configuration.showParentDirectory(), root)
);
}

this.sourcesList.setItems(FXCollections.observableList(sources));
Expand Down Expand Up @@ -359,7 +361,11 @@ private void rebuildPathMenu(
private void populateDirectoryTable(
final Path directory)
{
this.populateDirectoryTableWith(() -> JWFileItems.listDirectory(directory));
this.populateDirectoryTableWith(
() -> JWFileItems.listDirectory(
directory,
this.configuration.showParentDirectory())
);
}

private void populateDirectoryTableWith(
Expand Down
Expand Up @@ -46,19 +46,28 @@ private JWFileItems()
* List the given directory, resolving each entry into a file item.
*
* @param directory The directory
* @param withParent {@code true} if the parent directory entry should be included
*
* @return A list of items
*
* @throws IOException On I/O errors
*/

public static List<JWFileItem> listDirectory(
final Path directory)
final Path directory,
final boolean withParent)
throws IOException
{
final var items = new ArrayList<JWFileItem>(32);
items.add(resolveFileItem(directory).withDisplayName("."));

if (withParent) {
final var directoryParent = directory.getParent();
if (directoryParent != null) {
items.add(resolveFileItem(directoryParent).withDisplayName(".."));
}
}

try (var stream = Files.list(directory)) {
stream.sorted().forEach(path -> items.add(resolveFileItem(path)));
}
Expand Down
Expand Up @@ -33,17 +33,21 @@
public final class JWFileSourceEntryFilesystemRoot
implements JWFileSourceEntryType
{
private final boolean withParent;
private final Path root;

/**
* Construct a source entry.
*
* @param inRoot The root directory
* @param inRoot The root directory
* @param inWithParent {@code true} if a parent directory should be shown
*/

public JWFileSourceEntryFilesystemRoot(
final boolean inWithParent,
final Path inRoot)
{
this.withParent = inWithParent;
this.root = Objects.requireNonNull(inRoot, "root");
}

Expand Down Expand Up @@ -75,6 +79,6 @@ public List<JWFileItem> onFileItemsRequested()
!Platform.isFxApplicationThread(),
"Must not be FX application thread");

return JWFileItems.listDirectory(this.root);
return JWFileItems.listDirectory(this.root, this.withParent);
}
}

0 comments on commit 19c8242

Please sign in to comment.