diff --git a/app/src/main/java/com/oracle/javafx/scenebuilder/app/welcomedialog/WelcomeDialogFilesDropHandler.java b/app/src/main/java/com/oracle/javafx/scenebuilder/app/welcomedialog/WelcomeDialogFilesDropHandler.java new file mode 100644 index 000000000..7158bdc3f --- /dev/null +++ b/app/src/main/java/com/oracle/javafx/scenebuilder/app/welcomedialog/WelcomeDialogFilesDropHandler.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2024, Gluon and/or its affiliates. + * All rights reserved. Use is subject to license terms. + * + * This file is available and licensed under the following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the distribution. + * - Neither the name of Oracle Corporation and Gluon nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.oracle.javafx.scenebuilder.app.welcomedialog; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.logging.Level; +import java.util.logging.Logger; + +final class WelcomeDialogFilesDropHandler { + + private static final Logger LOGGER = Logger.getLogger(WelcomeDialogFilesDropHandler.class.getName()); + + private final List droppedFiles; + private final List toOpen; + private final List unsupportedItems; + private Consumer> openFiles; + private Consumer> handleUnsupported; + + WelcomeDialogFilesDropHandler(List droppedFiles) { + this.droppedFiles = Objects.requireNonNull(droppedFiles); + this.toOpen = new ArrayList<>(droppedFiles.size()); + this.unsupportedItems = new ArrayList<>(droppedFiles.size()); + } + + final WelcomeDialogFilesDropHandler withSupportedFiles(Consumer> handleOpen) { + this.openFiles = handleOpen; + return this; + } + + final WelcomeDialogFilesDropHandler withUnsupportedFiles(Consumer> unsupportedHandler) { + this.handleUnsupported = unsupportedHandler; + return this; + } + + final void run() { + analyzeDroppedItems(); + handleDropResult(); + } + + final void handleDropResult() { + if (this.openFiles == null) { + throw new IllegalStateException("Please configure a dropped file handling action using the withSupportedFiles(...) method."); + } + if (this.handleUnsupported == null) { + throw new IllegalStateException("Please configure an action for handling of unsupported files using withUnsupportedFiles(...) method."); + } + + if (!toOpen.isEmpty()) { + LOGGER.log(Level.INFO, "Received drop event to open files..."); + openFiles.accept(toOpen); + } else { + LOGGER.log(Level.INFO, "Dropped object does not contain any loadable FXML files."); + handleUnsupported.accept(unsupportedItems); + } + } + + final void analyzeDroppedItems() { + if (droppedFiles.isEmpty()) { + return; + } + + for (var file : droppedFiles) { + if (file.isDirectory()) { + File[] children = file.listFiles(); + List inDir = new ArrayList<>(children.length); + for (var child : children) { + if (isFxml(child)) { + inDir.add(child.getAbsolutePath()); + } + } + if (inDir.isEmpty()) { + unsupportedItems.add(file.getAbsolutePath()); + } else { + toOpen.addAll(inDir); + } + } else { + if (isFxml(file)) { + toOpen.add(file.getAbsolutePath()); + } else { + unsupportedItems.add(file.getAbsolutePath()); + } + } + } + } + + final boolean isFxml(File file) { + if (file.isDirectory()) { + return false; + } + return file.toString() + .toLowerCase() + .endsWith(".fxml"); + } +} diff --git a/app/src/main/java/com/oracle/javafx/scenebuilder/app/welcomedialog/WelcomeDialogWindowController.java b/app/src/main/java/com/oracle/javafx/scenebuilder/app/welcomedialog/WelcomeDialogWindowController.java index deee8cdb7..29146a563 100644 --- a/app/src/main/java/com/oracle/javafx/scenebuilder/app/welcomedialog/WelcomeDialogWindowController.java +++ b/app/src/main/java/com/oracle/javafx/scenebuilder/app/welcomedialog/WelcomeDialogWindowController.java @@ -32,27 +32,17 @@ package com.oracle.javafx.scenebuilder.app.welcomedialog; -import java.io.File; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.stream.Collectors; - import com.oracle.javafx.scenebuilder.app.SceneBuilderApp; import com.oracle.javafx.scenebuilder.app.i18n.I18N; import com.oracle.javafx.scenebuilder.app.preferences.PreferencesController; import com.oracle.javafx.scenebuilder.app.preferences.PreferencesRecordGlobal; import com.oracle.javafx.scenebuilder.app.util.AppSettings; import com.oracle.javafx.scenebuilder.kit.editor.EditorController; -import com.oracle.javafx.scenebuilder.kit.editor.panel.util.dialog.AlertDialog; import com.oracle.javafx.scenebuilder.kit.editor.panel.util.dialog.AbstractModalDialog.ButtonID; +import com.oracle.javafx.scenebuilder.kit.editor.panel.util.dialog.AlertDialog; +import com.oracle.javafx.scenebuilder.kit.editor.panel.util.dialog.ErrorDialog; import com.oracle.javafx.scenebuilder.kit.template.Template; import com.oracle.javafx.scenebuilder.kit.template.TemplatesBaseWindowController; - import javafx.application.Platform; import javafx.event.ActionEvent; import javafx.fxml.FXML; @@ -61,6 +51,8 @@ import javafx.scene.control.Label; import javafx.scene.control.ProgressIndicator; import javafx.scene.control.Tooltip; +import javafx.scene.input.DragEvent; +import javafx.scene.input.TransferMode; import javafx.scene.layout.BorderPane; import javafx.scene.layout.VBox; import javafx.stage.FileChooser; @@ -68,6 +60,16 @@ import javafx.stage.Stage; import javafx.stage.WindowEvent; +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + public class WelcomeDialogWindowController extends TemplatesBaseWindowController { private static final Logger LOGGER = Logger.getLogger(WelcomeDialogWindowController.class.getName()); @@ -116,6 +118,33 @@ protected void controllerDidCreateStage() { getStage().setTitle(I18N.getString("welcome.title")); getStage().initModality(Modality.APPLICATION_MODAL); } + + @FXML + void handleFileDraggedOver(DragEvent event) { + if (event.getDragboard().hasFiles()) { + event.acceptTransferModes(TransferMode.ANY); + } + } + + @FXML + void handleDroppedFiles(DragEvent event) { + if (event.getDragboard().hasFiles()) { + new WelcomeDialogFilesDropHandler(event.getDragboard().getFiles()) + .withSupportedFiles(fileNames->Platform.runLater(()->handleOpen(fileNames))) + .withUnsupportedFiles(unsupported->notifyUserWhenDroppedUnsupportedFiles(unsupported)) + .run(); + } + } + + private void notifyUserWhenDroppedUnsupportedFiles(List unsupported) { + ErrorDialog dialog = new ErrorDialog(getStage()); + dialog.setTitle(I18N.getString("welcome.loading.when.dropped.error.title")); + dialog.setMessage(I18N.getString("welcome.loading.when.dropped.error.message")); + String detail = unsupported.stream() + .collect(Collectors.joining(System.lineSeparator())); + dialog.setDetails(detail); + Platform.runLater(()->dialog.showAndWait()); + } @Override protected void controllerDidLoadFxml() { @@ -262,7 +291,7 @@ private void handleOpen(List filePaths) { private void askUserToRemoveMissingRecentFiles(List missingFiles) { if (!missingFiles.isEmpty()) { var questionDialog = questionMissingFilesCleanup(getStage(), missingFiles); - if (questionDialog.showAndWait() == AlertDialog.ButtonID.OK) { + if (questionDialog.showAndWait() == ButtonID.OK) { removeMissingFilesFromPrefs(missingFiles); loadAndPopulateRecentItemsInBackground(); } @@ -292,7 +321,7 @@ private void openFilesAndHideStage(List files) { void handleOpen(List filePaths, Consumer> missingFilesHandler, Consumer> fileLoader) { - + LOGGER.log(Level.INFO, "Attempting to open files: {0}", filePaths); if (filePaths.isEmpty()) { return; } diff --git a/app/src/main/resources/com/oracle/javafx/scenebuilder/app/i18n/SceneBuilderApp.properties b/app/src/main/resources/com/oracle/javafx/scenebuilder/app/i18n/SceneBuilderApp.properties index 90350b145..543a2cd43 100644 --- a/app/src/main/resources/com/oracle/javafx/scenebuilder/app/i18n/SceneBuilderApp.properties +++ b/app/src/main/resources/com/oracle/javafx/scenebuilder/app/i18n/SceneBuilderApp.properties @@ -511,6 +511,9 @@ welcome.recent.items.loading = Loading recent projects... welcome.recent.items.no.recent.items = no recent projects welcome.open.project.label = Open Project welcome.loading.label = Loading Components... +welcome.loading.when.dropped.error.title=Unsupported file format or empty directory +welcome.loading.when.dropped.error.message=The dropped object is either not a JavaFX FXML file or does not contain any FXML files to be loaded. + # -- Template (this keys are replicated from SceneBuilderKit.properties) template.new.project.label = New Project from Template template.title.header.desktop = Desktop diff --git a/app/src/main/resources/com/oracle/javafx/scenebuilder/app/welcomedialog/WelcomeWindow.fxml b/app/src/main/resources/com/oracle/javafx/scenebuilder/app/welcomedialog/WelcomeWindow.fxml index 76c629d24..d77276f79 100644 --- a/app/src/main/resources/com/oracle/javafx/scenebuilder/app/welcomedialog/WelcomeWindow.fxml +++ b/app/src/main/resources/com/oracle/javafx/scenebuilder/app/welcomedialog/WelcomeWindow.fxml @@ -1,7 +1,7 @@