diff --git a/src/main/java/com/github/mfl28/boundingboxeditor/ui/MainView.java b/src/main/java/com/github/mfl28/boundingboxeditor/ui/MainView.java index ca40587..8b7ad52 100644 --- a/src/main/java/com/github/mfl28/boundingboxeditor/ui/MainView.java +++ b/src/main/java/com/github/mfl28/boundingboxeditor/ui/MainView.java @@ -35,6 +35,7 @@ import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.image.Image; import javafx.scene.image.ImageView; +import javafx.scene.input.TransferMode; import javafx.scene.layout.BorderPane; import javafx.scene.layout.GridPane; import javafx.scene.layout.Priority; @@ -306,6 +307,15 @@ public void connectToController(final Controller controller) { header.connectToController(controller); workspaceSplitPane.connectToController(controller); inferenceSettingsView.connectToController(controller); + + setOnDragDropped(event -> { + if(event.getDragboard().hasFiles()) { + controller.initiateImageFolderLoading(event.getDragboard().getFiles().get(0)); + event.setDropCompleted(true); + } + + event.consume(); + }); } @Override @@ -613,6 +623,16 @@ private void setUpInternalListeners() { .bind(editorSettingsConfig.autoSimplifyPolygonsProperty()); workspaceSplitPane.getEditor().getEditorImagePane().simplifyRelativeDistanceToleranceProperty() .bind(editorSettingsConfig.simplifyRelativeDistanceToleranceProperty()); + + setOnDragOver(event -> { + if(event.getDragboard().hasFiles() + && event.getDragboard().getFiles().size() == 1 + && event.getDragboard().getFiles().get(0).isDirectory()) { + event.acceptTransferModes(TransferMode.LINK); + } + + event.consume(); + }); } private static void displayInfoAlert(String title, String header, String content, Node additionalInfoNode, diff --git a/src/test/java/com/github/mfl28/boundingboxeditor/BoundingBoxEditorTestBase.java b/src/test/java/com/github/mfl28/boundingboxeditor/BoundingBoxEditorTestBase.java index dcc6934..9ec8960 100644 --- a/src/test/java/com/github/mfl28/boundingboxeditor/BoundingBoxEditorTestBase.java +++ b/src/test/java/com/github/mfl28/boundingboxeditor/BoundingBoxEditorTestBase.java @@ -128,8 +128,8 @@ protected static Matcher ratioListCloseTo(Double[] list) { } protected void moveRelativeToImageView(FxRobot robot, Point2D startPointRatios, Point2D endPointRatios) { - Point2D startPoint = getScreenPointFromImageViewRatios(startPointRatios); - Point2D endPoint = getScreenPointFromImageViewRatios(endPointRatios); + Point2D startPoint = getScreenPointFromRatios(mainView.getEditorImageView(), startPointRatios); + Point2D endPoint = getScreenPointFromRatios(mainView.getEditorImageView(), endPointRatios); robot.moveTo(startPoint) .press(MouseButton.PRIMARY) @@ -139,7 +139,7 @@ protected void moveRelativeToImageView(FxRobot robot, Point2D startPointRatios, protected void moveAndClickRelativeToImageView(FxRobot robot, MouseButton mousebutton, Point2D... points) { for(Point2D point : points) { - robot.moveTo(getScreenPointFromImageViewRatios(point)).clickOn(mousebutton); + robot.moveTo(getScreenPointFromRatios(mainView.getEditorImageView(), point)).clickOn(mousebutton); } } @@ -223,11 +223,8 @@ protected void tearDown() throws TimeoutException { .forEach(Thread::interrupt); } - protected Point2D getScreenPointFromImageViewRatios(Point2D ratios) { - final ImageView imageView = mainView.getEditorImageView(); - final Bounds imageViewScreenBounds = imageView.localToScreen(imageView.getBoundsInLocal()); - - return PointQueryUtils.atPositionFactors(imageViewScreenBounds, ratios); + protected Point2D getScreenPointFromRatios(Node node, Point2D ratios) { + return PointQueryUtils.atPositionFactors(node.localToScreen(node.getBoundsInLocal()), ratios); } protected Point2D getParentPointFromImageViewRatios(Point2D ratios) { diff --git a/src/test/java/com/github/mfl28/boundingboxeditor/ui/BoundingPolygonDrawingTests.java b/src/test/java/com/github/mfl28/boundingboxeditor/ui/BoundingPolygonDrawingTests.java index eb42ae3..1560e44 100644 --- a/src/test/java/com/github/mfl28/boundingboxeditor/ui/BoundingPolygonDrawingTests.java +++ b/src/test/java/com/github/mfl28/boundingboxeditor/ui/BoundingPolygonDrawingTests.java @@ -385,7 +385,8 @@ void onFreehandDrawing_WhenImageFolderLoaded_ShouldCorrectlyCreatePolygons(FxRob Double[] targetImageViewPointRatios = {0.25, 0.25, 0.1, 0.6, 0.4, 0.75, 0.75, 0.3}; List screenPoints = IntStream.range(0, targetImageViewPointRatios.length) .filter(i -> i % 2 == 0) - .mapToObj(i -> getScreenPointFromImageViewRatios(new Point2D(targetImageViewPointRatios[i], targetImageViewPointRatios[i+1]))) + .mapToObj(i -> getScreenPointFromRatios(mainView.getEditorImageView(), + new Point2D(targetImageViewPointRatios[i], targetImageViewPointRatios[i+1]))) .toList(); robot.moveTo(screenPoints.get(0)).press(MouseButton.PRIMARY); diff --git a/src/test/java/com/github/mfl28/boundingboxeditor/ui/NoImageFolderOpenedBasicTests.java b/src/test/java/com/github/mfl28/boundingboxeditor/ui/NoImageFolderOpenedBasicTests.java index 183e490..1627265 100644 --- a/src/test/java/com/github/mfl28/boundingboxeditor/ui/NoImageFolderOpenedBasicTests.java +++ b/src/test/java/com/github/mfl28/boundingboxeditor/ui/NoImageFolderOpenedBasicTests.java @@ -19,8 +19,10 @@ package com.github.mfl28.boundingboxeditor.ui; import com.github.mfl28.boundingboxeditor.BoundingBoxEditorTestBase; +import javafx.event.EventHandler; +import javafx.geometry.Point2D; import javafx.scene.control.MenuItem; -import javafx.scene.input.KeyCode; +import javafx.scene.input.*; import javafx.stage.Stage; import org.hamcrest.Matchers; import org.junit.jupiter.api.Tag; @@ -31,6 +33,11 @@ import org.testfx.matcher.base.NodeMatchers; import org.testfx.util.WaitForAsyncUtils; +import java.io.File; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.testfx.api.FxAssert.verifyThat; @@ -48,6 +55,95 @@ void onMenuItemsClicked_ShouldCorrectlyApplyVisibilityAndShowDialogueWindows(FxR verifyMenuBarFunctionality(robot, testinfo); } + @Test + void onDragFolderIntoView_ShouldCorrectlyImportImageFiles(FxRobot robot, TestInfo testInfo) { + EventHandler dragDetectedHandler = mainView.getOnDragDetected(); + try { + Map dataMap = new HashMap<>(); + dataMap.put(DataFormat.FILES, List.of(new File(getClass().getResource(TEST_IMAGE_FOLDER_PATH_3).getFile()))); + + setDummyMainViewDragDetector(dataMap); + + robot.moveTo(getScreenPointFromRatios(mainView, new Point2D(0.5, 0.25))) + .press(MouseButton.PRIMARY).drag(mainView, MouseButton.PRIMARY).dropTo(mainView).release(MouseButton.PRIMARY); + WaitForAsyncUtils.waitForFxEvents(); + + waitUntilCurrentImageIsLoaded(testInfo); + + verifyThat(model.getImageFileNameSet(), Matchers.containsInAnyOrder( + "rachel-hisko-rEM3cK8F1pk-unsplash.jpg", + "wexor-tmg-L-2p8fapOA8-unsplash.jpg")); + } finally { + mainView.setOnDragDetected(dragDetectedHandler); + } + } + + @Test + void onDragMultipleFoldersIntoView_ShouldDoNothing(FxRobot robot) { + EventHandler dragDetectedHandler = mainView.getOnDragDetected(); + try { + Map dataMap = new HashMap<>(); + dataMap.put(DataFormat.FILES, List.of(new File(getClass().getResource(TEST_IMAGE_FOLDER_PATH_1).getFile()), + new File(getClass().getResource(TEST_IMAGE_FOLDER_PATH_2).getFile()))); + + setDummyMainViewDragDetector(dataMap); + + robot.moveTo(getScreenPointFromRatios(mainView, new Point2D(0.5, 0.25))) + .press(MouseButton.PRIMARY).drag(mainView, MouseButton.PRIMARY).dropTo(mainView).release(MouseButton.PRIMARY); + WaitForAsyncUtils.waitForFxEvents(); + + verifyThat(model.getImageFileNameSet(), Matchers.empty()); + } finally { + mainView.setOnDragDetected(dragDetectedHandler); + } + } + + + @Test + void onDragNonFolderFileIntoView_ShouldDoNothing(FxRobot robot) { + EventHandler dragDetectedHandler = mainView.getOnDragDetected(); + try { + Map dataMap = new HashMap<>(); + dataMap.put(DataFormat.FILES, List.of( + new File(getClass().getResource(TEST_IMAGE_FOLDER_PATH_1 + "/austin-neill-685084-unsplash.jpg").getFile()))); + + setDummyMainViewDragDetector(dataMap); + + robot.moveTo(getScreenPointFromRatios(mainView, new Point2D(0.5, 0.25))) + .press(MouseButton.PRIMARY).drag(mainView, MouseButton.PRIMARY).dropTo(mainView).release(MouseButton.PRIMARY); + WaitForAsyncUtils.waitForFxEvents(); + + verifyThat(model.getImageFileNameSet(), Matchers.empty()); + } finally { + mainView.setOnDragDetected(dragDetectedHandler); + } + } + + @Test + void onEmptyDragIntoView_ShouldDoNothing(FxRobot robot) { + EventHandler dragDetectedHandler = mainView.getOnDragDetected(); + try { + Map dataMap = new HashMap<>(); + + setDummyMainViewDragDetector(dataMap); + + robot.moveTo(getScreenPointFromRatios(mainView, new Point2D(0.5, 0.25))) + .press(MouseButton.PRIMARY).drag(mainView, MouseButton.PRIMARY).dropTo(mainView).release(MouseButton.PRIMARY); + WaitForAsyncUtils.waitForFxEvents(); + + verifyThat(model.getImageFileNameSet(), Matchers.empty()); + } finally { + mainView.setOnDragDetected(dragDetectedHandler); + } + } + + private void setDummyMainViewDragDetector(Map content) { + mainView.setOnDragDetected(event -> { + Dragboard dragboard = mainView.startDragAndDrop(TransferMode.LINK); + dragboard.setContent(content); + }); + } + private void verifyMenuBarFunctionality(FxRobot robot, TestInfo testinfo) { timeOutClickOn(robot, "#file-menu", testinfo); @@ -109,19 +205,19 @@ private void verifyMenuBarFunctionality(FxRobot robot, TestInfo testinfo) { MenuItem fitWindowItem = getSubMenuItem(robot, "View", "Maximize Images"); assertTrue(fitWindowItem.isVisible(), - () -> saveScreenshotAndReturnMessage(testinfo, "Maximize images item not " + - "visible")); + () -> saveScreenshotAndReturnMessage(testinfo, "Maximize images item not " + + "visible")); assertTrue(fitWindowItem.isDisable(), - () -> saveScreenshotAndReturnMessage(testinfo, "Maximize images item not " + - "disabled")); + () -> saveScreenshotAndReturnMessage(testinfo, "Maximize images item not " + + "disabled")); MenuItem imageExplorerItem = getSubMenuItem(robot, "View", "Show Images Panel"); assertTrue(imageExplorerItem.isVisible(), - () -> saveScreenshotAndReturnMessage(testinfo, "Image explorer item not " + - "visible")); + () -> saveScreenshotAndReturnMessage(testinfo, "Image explorer item not " + + "visible")); assertTrue(imageExplorerItem.isDisable(), - () -> saveScreenshotAndReturnMessage(testinfo, "Image explorer item not " + - "disabled")); + () -> saveScreenshotAndReturnMessage(testinfo, "Image explorer item not " + + "disabled")); } private void verifyNodeVisibilities(TestInfo testinfo) {