From 1641ee4e31152f716c9d3d2a12f48fcfe65dd37c Mon Sep 17 00:00:00 2001 From: Oliver-Loeffler Date: Mon, 10 Oct 2022 23:42:02 +0200 Subject: [PATCH 01/37] fix: Scene Builder will ask to remove missing files from list of recent projects. #582 --- .../WelcomeDialogWindowController.java | 122 ++++++++++++++++-- .../app/i18n/SceneBuilderApp.properties | 9 +- .../scenebuilder/app/JfxInitializer.java | 64 +++++++++ .../WelcomeDialogWindowControllerTest.java | 114 ++++++++++++++++ 4 files changed, 295 insertions(+), 14 deletions(-) create mode 100644 app/src/test/java/com/oracle/javafx/scenebuilder/app/JfxInitializer.java create mode 100644 app/src/test/java/com/oracle/javafx/scenebuilder/app/welcomedialog/WelcomeDialogWindowControllerTest.java 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 86f1c72b4..1461feaaa 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,14 +32,24 @@ 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.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.template.Template; import com.oracle.javafx.scenebuilder.kit.template.TemplatesBaseWindowController; + import javafx.application.Platform; import javafx.event.ActionEvent; import javafx.fxml.FXML; @@ -52,13 +62,9 @@ import javafx.scene.layout.VBox; import javafx.stage.FileChooser; import javafx.stage.Modality; +import javafx.stage.Stage; import javafx.stage.WindowEvent; -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - public class WelcomeDialogWindowController extends TemplatesBaseWindowController { @FXML @@ -80,7 +86,7 @@ public class WelcomeDialogWindowController extends TemplatesBaseWindowController private final SceneBuilderApp sceneBuilderApp; - private WelcomeDialogWindowController() { + WelcomeDialogWindowController() { super(WelcomeDialogWindowController.class.getResource("WelcomeWindow.fxml"), //NOI18N I18N.getBundle(), null); // We want it to be a top level window so we're setting the owner to null. @@ -214,17 +220,107 @@ private void openDocument() { handleOpen(paths); } - - private void handleOpen(List paths) { - if (sceneBuilderApp.startupTasksFinishedBinding().get()) { - sceneBuilderApp.handleOpenFilesAction(paths); - getStage().hide(); + + protected static AlertDialog questionMissingFilesCleanup(Stage stage, List missingFiles) { + String withPath = missingFiles.stream() + .collect(Collectors.joining(System.lineSeparator())); + + AlertDialog question = new AlertDialog(stage); + StringBuilder shortMessage = new StringBuilder(); + if (missingFiles.size() > 1) { + shortMessage.append(I18N.getString("alert.welcome.files.not.found.question")); + question.setTitle(I18N.getString("alert.welcome.files.not.found.title")); + question.setOKButtonTitle(I18N.getString("alert.welcome.files.not.found.okay")); } else { - showMasker(() -> { + shortMessage.append(I18N.getString("alert.welcome.file.not.found.question")); + question.setTitle(I18N.getString("alert.welcome.file.not.found.title")); + question.setOKButtonTitle(I18N.getString("alert.welcome.file.not.found.okay")); + } + question.setCancelButtonTitle(I18N.getString("alert.welcome.file.not.found.no")); + question.setMessage(shortMessage.toString()); + question.setDetails(withPath); + return question; + } + + boolean filePathExists(String filePath) { + return Files.exists(Path.of(filePath)); + } + + /** + * Attempts to open files in filePaths. Scene Builder will only attempt to load + * files which exist. If a file does not exist, Scene Builder will ask the user + * to remove this file from recent files. + * + * @param filePaths List of file paths to project files to be opened by Scene + * Builder. + */ + private void handleOpen(List filePaths) { + Consumer> missingFilesHandler = missingFiles->{ + if (!missingFiles.isEmpty()) { + var questionDialog = questionMissingFilesCleanup(getStage(), missingFiles); + var x = getInstance().getStage().getX(); + var y = getInstance().getStage().getY(); + var width = getInstance().getStage().getWidth(); + var height = getInstance().getStage().getHeight(); + questionDialog.getStage().setX(x+width/3); + questionDialog.getStage().setY(y+height/3); + if (questionDialog.showAndWait() == AlertDialog.ButtonID.OK) { + removeMissingFilesFromPrefs(missingFiles); + loadAndPopulateRecentItemsInBackground(); + } + } + }; + + Consumer> existingFilesHandler = paths->{ + if (sceneBuilderApp.startupTasksFinishedBinding().get()) { sceneBuilderApp.handleOpenFilesAction(paths); getStage().hide(); - }); + } else { + showMasker(() -> { + sceneBuilderApp.handleOpenFilesAction(paths); + getStage().hide(); + }); + } + }; + + handleOpen(filePaths, missingFilesHandler, existingFilesHandler); + } + + /** + * Attempts to open files in filePaths. + * In case of files are missing, a special procedure is applied to handle missing files. + * + * @param filePaths List of file paths to project files to be opened by Scene Builder. + * @param missingFilesHandler Determines how missing files are handled. + * @param fileLoader Determines how files are loaded. + */ + void handleOpen(List filePaths, + Consumer> missingFilesHandler, + Consumer> fileLoader) { + if (filePaths.isEmpty()) { + return; } + + var candidates = filePaths.stream() + .collect(Collectors.groupingBy(this::filePathExists)); + + List missingFiles = candidates.getOrDefault(Boolean.FALSE, new ArrayList<>()); + missingFilesHandler.accept(missingFiles); + + List paths = candidates.getOrDefault(Boolean.TRUE, new ArrayList<>()) + .stream() + .toList(); + + if (paths.isEmpty()) { + return; + } + + fileLoader.accept(paths); + } + + private void removeMissingFilesFromPrefs(List missingFiles) { + PreferencesRecordGlobal preferencesRecordGlobal = PreferencesController.getSingleton().getRecordGlobal(); + preferencesRecordGlobal.removeRecentItems(missingFiles); } private void showMasker(Runnable onEndAction) { 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 b77ee6359..31dddd7d3 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 @@ -435,7 +435,14 @@ alert.save.noextension.savewith = Save with '.fxml' alert.save.noextension.savewithout = Save without '.fxml' alert.open.failure.charset.not.found = The given charset could not be set. alert.open.failure.charset.not.found.details = It may be due to the encoding in your document. - +alert.welcome.files.not.found.question = Some files were not found. Do you want Scene Builder to remove these files from list of recent projects?\n +alert.welcome.files.not.found.title = Project files not found +alert.welcome.files.not.found.okay = Yes, remove missing files from recent projects. +alert.welcome.file.not.found.question = The selected project file was not found. Do you want Scene Builder to remove this file from list of recent projects?\n +alert.welcome.file.not.found.title = Project file not found +alert.welcome.file.not.found.okay = Yes, remove missing file from recent projects. +alert.welcome.file.not.found.no = No, keep file(s) in recent projects. + # ----------------------------------------------------------------------------- # Log Messages # ----------------------------------------------------------------------------- diff --git a/app/src/test/java/com/oracle/javafx/scenebuilder/app/JfxInitializer.java b/app/src/test/java/com/oracle/javafx/scenebuilder/app/JfxInitializer.java new file mode 100644 index 000000000..44bd21724 --- /dev/null +++ b/app/src/test/java/com/oracle/javafx/scenebuilder/app/JfxInitializer.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2021, 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; + +import javafx.application.Application; +import javafx.stage.Stage; + +import java.util.concurrent.atomic.AtomicBoolean; + +public class JfxInitializer { + + private static final AtomicBoolean initialized = new AtomicBoolean(false); + + public static void initialize() { + if (initialized.compareAndSet(false, true)) { + Thread t = new Thread("JavaFX Init Thread") { + + @Override + public void run() { + Application.launch(DummyApp.class); + } + }; + t.setDaemon(true); + t.start(); + } + } + + public static class DummyApp extends Application { + + @Override + public void start(Stage primaryStage) { + // noop + } + } +} diff --git a/app/src/test/java/com/oracle/javafx/scenebuilder/app/welcomedialog/WelcomeDialogWindowControllerTest.java b/app/src/test/java/com/oracle/javafx/scenebuilder/app/welcomedialog/WelcomeDialogWindowControllerTest.java new file mode 100644 index 000000000..de70fced8 --- /dev/null +++ b/app/src/test/java/com/oracle/javafx/scenebuilder/app/welcomedialog/WelcomeDialogWindowControllerTest.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2022, 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 static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; + +import com.oracle.javafx.scenebuilder.app.JfxInitializer; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +class WelcomeDialogWindowControllerTest { + + private final WelcomeDialogWindowController classUnderTest = new WelcomeDialogWindowController(); + + @BeforeAll + public static void initialize() { + JfxInitializer.initialize(); + } + + @Test + void that_missing_files_are_detected_and_handled_and_existing_files_are_loaded() throws Exception { + String expectedExistingFile = getResource("WelcomeWindow.fxml").toString(); + List filesToLoad = List.of( + "k:/folder/test/notExisting.fxml", + expectedExistingFile); + + List filesMissing = new ArrayList<>(); + List filesLoaded = new ArrayList<>(); + + Consumer> missingFilesHandler = missing->filesMissing.addAll(missing); + Consumer> existingFilesHandler = existing->filesLoaded.addAll(existing); + + assertDoesNotThrow(()->classUnderTest.handleOpen(filesToLoad, + missingFilesHandler, + existingFilesHandler)); + + assertEquals(1, filesMissing.size()); + assertEquals(1, filesLoaded.size()); + assertTrue(filesLoaded.contains(expectedExistingFile)); + } + + @Test + void that_no_actions_are_performed_on_empty_list() { + List filesToLoad = Collections.emptyList(); + + Set actionsPerformed = new HashSet<>(); + Consumer> filesHandler = listOffiles->actionsPerformed.add("some action performed"); + classUnderTest.handleOpen(filesToLoad, filesHandler, filesHandler); + + assertTrue(actionsPerformed.isEmpty()); + } + + @Test + void that_file_loader_is_not_called_when_all_files_are_missing() { + List filesToLoad = List.of( + "k:/folder/test/notExisting.fxml", + "o:\\otherLocation\\another_missing.fxml"); + + List filesMissing = new ArrayList<>(); + List filesLoaded = new ArrayList<>(); + + Consumer> missingFilesHandler = missing->filesMissing.addAll(missing); + Consumer> existingFilesHandler = existing->filesLoaded.addAll(existing); + classUnderTest.handleOpen(filesToLoad, missingFilesHandler, existingFilesHandler); + + assertTrue(filesLoaded.isEmpty()); + assertEquals(2, filesMissing.size()); + } + + private Path getResource(String resourceName) throws Exception { + return Path.of(getClass().getResource(resourceName).toURI()); + } + +} From b303f9d43433c843125c24082e3ec8313fe7f6db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20L=C3=B6ffler?= <22102800+Oliver-Loeffler@users.noreply.github.com> Date: Mon, 31 Oct 2022 11:38:27 +0100 Subject: [PATCH 02/37] fix: incremented version to 20.0.0-SNAPSHOT. (#598) Co-authored-by: Oliver-Loeffler --- app/pom.xml | 2 +- kit/pom.xml | 2 +- pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/pom.xml b/app/pom.xml index 5bc1fb32b..7bbbc69c4 100644 --- a/app/pom.xml +++ b/app/pom.xml @@ -7,7 +7,7 @@ com.gluonhq.scenebuilder parent - 19.0.0-SNAPSHOT + 20.0.0-SNAPSHOT diff --git a/kit/pom.xml b/kit/pom.xml index 973811c7b..411d1972f 100644 --- a/kit/pom.xml +++ b/kit/pom.xml @@ -7,7 +7,7 @@ com.gluonhq.scenebuilder parent - 19.0.0-SNAPSHOT + 20.0.0-SNAPSHOT diff --git a/pom.xml b/pom.xml index ae01df2ba..434c52bc2 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.gluonhq.scenebuilder parent pom - 19.0.0-SNAPSHOT + 20.0.0-SNAPSHOT Scene Builder Scene Builder is a visual, drag n drop, layout tool for designing JavaFX application user interfaces 2012 From 484c4dd0987f02428221db44cc9fdc5d4529c9ac Mon Sep 17 00:00:00 2001 From: Oliver-Loeffler Date: Tue, 8 Nov 2022 17:02:07 +0100 Subject: [PATCH 03/37] build: Moved maven surefire plugin from kit-pom to parent-pom so that eventually tests are also picked up for app module. --- kit/pom.xml | 9 --------- pom.xml | 11 +++++++++++ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/kit/pom.xml b/kit/pom.xml index 411d1972f..754d9ba19 100644 --- a/kit/pom.xml +++ b/kit/pom.xml @@ -117,15 +117,6 @@ - - org.apache.maven.plugins - maven-surefire-plugin - 3.0.0-M5 - - false - 1 - - diff --git a/pom.xml b/pom.xml index 434c52bc2..00bb65a19 100644 --- a/pom.xml +++ b/pom.xml @@ -133,6 +133,17 @@ maven-resources-plugin 3.2.0 + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M5 + + false + 1 + + From 4cd777531acf0e1a83bb385742013c7c13c3a78a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20L=C3=B6ffler?= <22102800+Oliver-Loeffler@users.noreply.github.com> Date: Wed, 2 Nov 2022 11:52:25 +0100 Subject: [PATCH 04/37] build: Add Maven Wrapper to the project (#588) Co-authored-by: Oliver-Loeffler --- .mvn/wrapper/maven-wrapper.jar | Bin 0 -> 59925 bytes .mvn/wrapper/maven-wrapper.properties | 18 ++ README.md | 6 + mvnw | 287 ++++++++++++++++++++++++++ mvnw.cmd | 187 +++++++++++++++++ 5 files changed, 498 insertions(+) create mode 100644 .mvn/wrapper/maven-wrapper.jar create mode 100644 .mvn/wrapper/maven-wrapper.properties create mode 100644 mvnw create mode 100644 mvnw.cmd diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..bf82ff01c6cdae4a1bb754a6e062954d77ac5c11 GIT binary patch literal 59925 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

dy!05UMhSKsK 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 VCNKd3JGtOvEm(5t0lFyE{{i--k}m)N literal 0 HcmV?d00001 diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 000000000..30052152b --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar diff --git a/README.md b/README.md index be1b3b567..80c68e553 100644 --- a/README.md +++ b/README.md @@ -40,10 +40,16 @@ These are the requisites: ### How to build Scene Builder ### +Scene Builder makes use of the Maven Wrapper to build and run the project. So there is no need to install Maven on the developers machine. To utilize Maven Wrapper, instead of calling `mvn`, one can run `./mvnw` on Linux or macOS or `mvnw` on Windows instead. + To build the Scene Builder services, on the project's root, run: `mvn clean package` +Alternatively, utilizing the Maven Wrapper, one can run: + +`./mvnw clean package` + It will create a partial shadow cross-platform jar under `app/target/lib/scenebuilder-$version.jar`, that doesn't include the JavaFX dependencies. ### How to run Scene Builder ### diff --git a/mvnw b/mvnw new file mode 100644 index 000000000..b7f064624 --- /dev/null +++ b/mvnw @@ -0,0 +1,287 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.1.1 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="`/usr/libexec/java_home`"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`\\unset -f command; \\command -v java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + printf '%s' "$(cd "$basedir"; pwd)" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname $0)") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) wrapperUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $wrapperUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + QUIET="--quiet" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + QUIET="" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" + fi + [ $? -eq 0 ] || rm -f "$wrapperJarPath" + elif command -v curl > /dev/null; then + QUIET="--silent" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + QUIET="" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L + fi + [ $? -eq 0 ] || rm -f "$wrapperJarPath" + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaSource="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=`cygpath --path --windows "$javaSource"` + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 000000000..474c9d6b7 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,187 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.1.1 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% From 484a1fe09d2cbcf2043d0b89bda3368fff9c5fe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20L=C3=B6ffler?= <22102800+Oliver-Loeffler@users.noreply.github.com> Date: Wed, 2 Nov 2022 13:06:49 +0100 Subject: [PATCH 05/37] fix: Removed FX8 qualifier in BuiltInLibrary (#592) Co-authored-by: Oliver-Loeffler --- .../editor/panel/library/LibraryListCell.java | 6 +-- .../kit/library/BuiltinLibrary.java | 44 ++++++++----------- 2 files changed, 21 insertions(+), 29 deletions(-) diff --git a/kit/src/main/java/com/oracle/javafx/scenebuilder/kit/editor/panel/library/LibraryListCell.java b/kit/src/main/java/com/oracle/javafx/scenebuilder/kit/editor/panel/library/LibraryListCell.java index 4593d9fc0..dff61ae45 100644 --- a/kit/src/main/java/com/oracle/javafx/scenebuilder/kit/editor/panel/library/LibraryListCell.java +++ b/kit/src/main/java/com/oracle/javafx/scenebuilder/kit/editor/panel/library/LibraryListCell.java @@ -1,4 +1,5 @@ /* + * Copyright (c) 2022, Gluon and/or its affiliates. * Copyright (c) 2012, 2014, Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * @@ -127,11 +128,8 @@ public void updateItem(LibraryListItem item, boolean empty) { if (item.getLibItem() != null) { // A qualifier needed to discriminate items is kept in the ID: // this applies to orientation as well as empty qualifiers. - // FX8 qualifier is not kept as there's no ambiguity there. String id = item.getLibItem().getName(); - if (id.contains(BuiltinLibrary.getFX8Qualifier())) { - id = id.substring(0, id.indexOf(BuiltinLibrary.getFX8Qualifier())); - } + // If QE were about to test a localized version the ID should // remain unchanged. if (id.contains(BuiltinLibrary.getEmptyQualifier())) { diff --git a/kit/src/main/java/com/oracle/javafx/scenebuilder/kit/library/BuiltinLibrary.java b/kit/src/main/java/com/oracle/javafx/scenebuilder/kit/library/BuiltinLibrary.java index f4fcb7df7..ca61ffce7 100644 --- a/kit/src/main/java/com/oracle/javafx/scenebuilder/kit/library/BuiltinLibrary.java +++ b/kit/src/main/java/com/oracle/javafx/scenebuilder/kit/library/BuiltinLibrary.java @@ -66,8 +66,6 @@ public class BuiltinLibrary extends Library { private final BuiltinSectionComparator sectionComparator = new BuiltinSectionComparator(); - private static final String FX8_QUALIFIER = " (FX8)"; //NOI18N - // This qualifier is for use to provide a flavor of a component that has no // children, in addition to the one having children. Typical use is for // Accordion, ScrollPane, SplitPane, TabPane, TitledPane (see DTL-6274). @@ -89,11 +87,7 @@ public static synchronized BuiltinLibrary getLibrary() { } return library; } - - public static String getFX8Qualifier() { - return FX8_QUALIFIER; - } - + public static String getEmptyQualifier() { return EMPTY_QUALIFIER; } @@ -186,9 +180,9 @@ private BuiltinLibrary() { "AccordionEmpty", "Accordion", EMPTY_QUALIFIER); //NOI18N addRegionItem200x200(javafx.scene.layout.AnchorPane.class, TAG_CONTAINERS); addRegionItem200x200(javafx.scene.layout.BorderPane.class, TAG_CONTAINERS); - addCustomizedItem(javafx.scene.control.ButtonBar.class, TAG_CONTAINERS, FX8_QUALIFIER); - addCustomizedItem(javafx.scene.control.DialogPane.class, TAG_CONTAINERS, FX8_QUALIFIER); - addDefaultItem(javafx.scene.control.DialogPane.class, TAG_CONTAINERS, EMPTY_QUALIFIER, FX8_QUALIFIER); + addCustomizedItem(javafx.scene.control.ButtonBar.class, TAG_CONTAINERS); + addCustomizedItem(javafx.scene.control.DialogPane.class, TAG_CONTAINERS); + addDefaultItem(javafx.scene.control.DialogPane.class, TAG_CONTAINERS, EMPTY_QUALIFIER); addRegionItem200x200(javafx.scene.layout.FlowPane.class, TAG_CONTAINERS); addCustomizedItem(javafx.scene.layout.GridPane.class, TAG_CONTAINERS); addRegionItem200x100(javafx.scene.layout.HBox.class, TAG_CONTAINERS); @@ -206,7 +200,7 @@ private BuiltinLibrary() { addCustomizedItem(javafx.scene.control.TabPane.class, TAG_CONTAINERS); addCustomizedItem(javafx.scene.control.TabPane.class, TAG_CONTAINERS, "TabPaneEmpty", "TabPane", EMPTY_QUALIFIER); //NOI18N - addRegionItem200x200(javafx.scene.text.TextFlow.class, TAG_CONTAINERS, FX8_QUALIFIER); + addRegionItem200x200(javafx.scene.text.TextFlow.class, TAG_CONTAINERS); addRegionItem200x200(javafx.scene.layout.TilePane.class, TAG_CONTAINERS); addCustomizedItem(javafx.scene.control.TitledPane.class, TAG_CONTAINERS); addRegionItem200x200(javafx.scene.control.TitledPane.class, TAG_CONTAINERS, EMPTY_QUALIFIER); @@ -219,7 +213,7 @@ private BuiltinLibrary() { addCustomizedItem(javafx.scene.control.ChoiceBox.class, TAG_CONTROLS); addDefaultItem(javafx.scene.control.ColorPicker.class, TAG_CONTROLS); addCustomizedItem(javafx.scene.control.ComboBox.class, TAG_CONTROLS); - addDefaultItem(javafx.scene.control.DatePicker.class, TAG_CONTROLS, FX8_QUALIFIER); + addDefaultItem(javafx.scene.control.DatePicker.class, TAG_CONTROLS); addCustomizedItem(javafx.scene.web.HTMLEditor.class, TAG_CONTROLS); addCustomizedItem(javafx.scene.control.Hyperlink.class, TAG_CONTROLS); addCustomizedItem(javafx.scene.image.ImageView.class, TAG_CONTROLS); @@ -245,15 +239,15 @@ private BuiltinLibrary() { "SliderH", "Slider-h", HORIZONTAL_QUALIFIER); //NOI18N addCustomizedItem(javafx.scene.control.Slider.class, TAG_CONTROLS, "SliderV", "Slider-v", VERTICAL_QUALIFIER); //NOI18N - addDefaultItem(javafx.scene.control.Spinner.class, TAG_CONTROLS, FX8_QUALIFIER); + addDefaultItem(javafx.scene.control.Spinner.class, TAG_CONTROLS); addCustomizedItem(javafx.scene.control.SplitMenuButton.class, TAG_CONTROLS); addCustomizedItem(javafx.scene.control.TableColumn.class, TAG_CONTROLS); addCustomizedItem(javafx.scene.control.TableView.class, TAG_CONTROLS); addRegionItem200x200(javafx.scene.control.TextArea.class, TAG_CONTROLS); addDefaultItem(javafx.scene.control.TextField.class, TAG_CONTROLS); addCustomizedItem(javafx.scene.control.ToggleButton.class, TAG_CONTROLS); - addCustomizedItem(javafx.scene.control.TreeTableColumn.class, TAG_CONTROLS, FX8_QUALIFIER); - addCustomizedItem(javafx.scene.control.TreeTableView.class, TAG_CONTROLS, FX8_QUALIFIER); + addCustomizedItem(javafx.scene.control.TreeTableColumn.class, TAG_CONTROLS); + addCustomizedItem(javafx.scene.control.TreeTableView.class, TAG_CONTROLS); addRegionItem200x200(javafx.scene.control.TreeView.class, TAG_CONTROLS); addCustomizedItem(javafx.scene.web.WebView.class, TAG_CONTROLS); @@ -272,24 +266,24 @@ private BuiltinLibrary() { addRegionItem200x200(javafx.scene.layout.Region.class, TAG_MISCELLANEOUS); addCustomizedItem(javafx.scene.Scene.class, TAG_MISCELLANEOUS); addCustomizedItem(javafx.stage.Stage.class, TAG_MISCELLANEOUS); - addCustomizedItem(javafx.scene.SubScene.class, TAG_MISCELLANEOUS, FX8_QUALIFIER); - addDefaultItem(javafx.embed.swing.SwingNode.class, TAG_MISCELLANEOUS, FX8_QUALIFIER); + addCustomizedItem(javafx.scene.SubScene.class, TAG_MISCELLANEOUS); + addDefaultItem(javafx.embed.swing.SwingNode.class, TAG_MISCELLANEOUS); addCustomizedItem(javafx.scene.control.Tooltip.class, TAG_MISCELLANEOUS); // Shapes addCustomizedItem(javafx.scene.shape.Arc.class, TAG_SHAPES); addDefaultItem(javafx.scene.shape.ArcTo.class, TAG_SHAPES); - addCustomizedItem(javafx.scene.shape.Box.class, TAG_SHAPES, FX8_QUALIFIER); + addCustomizedItem(javafx.scene.shape.Box.class, TAG_SHAPES); addCustomizedItem(javafx.scene.shape.Circle.class, TAG_SHAPES); addDefaultItem(javafx.scene.shape.ClosePath.class, TAG_SHAPES); addCustomizedItem(javafx.scene.shape.CubicCurve.class, TAG_SHAPES); addDefaultItem(javafx.scene.shape.CubicCurveTo.class, TAG_SHAPES); - addCustomizedItem(javafx.scene.shape.Cylinder.class, TAG_SHAPES, FX8_QUALIFIER); + addCustomizedItem(javafx.scene.shape.Cylinder.class, TAG_SHAPES); addCustomizedItem(javafx.scene.shape.Ellipse.class, TAG_SHAPES); addDefaultItem(javafx.scene.shape.HLineTo.class, TAG_SHAPES); addCustomizedItem(javafx.scene.shape.Line.class, TAG_SHAPES); addDefaultItem(javafx.scene.shape.LineTo.class, TAG_SHAPES); - addDefaultItem(javafx.scene.shape.MeshView.class, TAG_SHAPES, FX8_QUALIFIER); + addDefaultItem(javafx.scene.shape.MeshView.class, TAG_SHAPES); addDefaultItem(javafx.scene.shape.MoveTo.class, TAG_SHAPES); addCustomizedItem(javafx.scene.shape.Path.class, TAG_SHAPES); addCustomizedItem(javafx.scene.shape.Polygon.class, TAG_SHAPES); @@ -297,7 +291,7 @@ private BuiltinLibrary() { addCustomizedItem(javafx.scene.shape.QuadCurve.class, TAG_SHAPES); addDefaultItem(javafx.scene.shape.QuadCurveTo.class, TAG_SHAPES); addCustomizedItem(javafx.scene.shape.Rectangle.class, TAG_SHAPES); - addCustomizedItem(javafx.scene.shape.Sphere.class, TAG_SHAPES, FX8_QUALIFIER); + addCustomizedItem(javafx.scene.shape.Sphere.class, TAG_SHAPES); addCustomizedItem(javafx.scene.shape.SVGPath.class, TAG_SHAPES); addCustomizedItem(javafx.scene.text.Text.class, TAG_SHAPES); addDefaultItem(javafx.scene.shape.VLineTo.class, TAG_SHAPES); @@ -313,10 +307,10 @@ private BuiltinLibrary() { addCustomizedItem(javafx.scene.chart.StackedBarChart.class, TAG_CHARTS); // 3D - addCustomizedItem(javafx.scene.AmbientLight.class, TAG_3D, FX8_QUALIFIER); - addDefaultItem(javafx.scene.ParallelCamera.class, TAG_3D, FX8_QUALIFIER); - addDefaultItem(javafx.scene.PerspectiveCamera.class, TAG_3D, FX8_QUALIFIER); - addCustomizedItem(javafx.scene.PointLight.class, TAG_3D, FX8_QUALIFIER); + addCustomizedItem(javafx.scene.AmbientLight.class, TAG_3D); + addDefaultItem(javafx.scene.ParallelCamera.class, TAG_3D); + addDefaultItem(javafx.scene.PerspectiveCamera.class, TAG_3D); + addCustomizedItem(javafx.scene.PointLight.class, TAG_3D); } From 437dadc2569aec8af95f77d8f381d5f9f5990810 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20L=C3=B6ffler?= <22102800+Oliver-Loeffler@users.noreply.github.com> Date: Wed, 2 Nov 2022 13:50:12 +0100 Subject: [PATCH 06/37] fix: Disabled mnemonic parsing on RecentDocumentsButton instances on welcome page in oder to have correct file name rendering for filenames with underscores. (#578) Co-authored-by: Oliver-Loeffler --- .../app/welcomedialog/WelcomeDialogWindowController.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 1461feaaa..64028b831 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 @@ -160,7 +160,8 @@ private void loadAndPopulateRecentItemsInBackground() { recentDocument.setAlignment(Pos.BASELINE_LEFT); recentDocument.setOnAction(event -> fireOpenRecentProject(event, recentItem)); recentDocument.setTooltip(new Tooltip(recentItem)); - + /* if MnemonicParsing is enabled, file names with underscores are displayed incorrectly */ + recentDocument.setMnemonicParsing(false); recentDocumentButtons.add(recentDocument); } From e676e1a780b746a8329d849cd31ec4fbd7fecd2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20L=C3=B6ffler?= Date: Mon, 7 Nov 2022 22:19:45 +0100 Subject: [PATCH 07/37] Modified error handling so that WelcomePage is no longer closed after confirming the error dialog. --- .../scenebuilder/app/SceneBuilderApp.java | 124 ++++++++++++------ .../WelcomeDialogWindowController.java | 57 ++++---- .../app/i18n/SceneBuilderApp.properties | 1 + 3 files changed, 112 insertions(+), 70 deletions(-) diff --git a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java index 25439a934..c0e6337a0 100644 --- a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java +++ b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java @@ -499,6 +499,10 @@ private void createEmptyDocumentWindow() { @Override public void handleOpenFilesAction(List files) { + handleOpenFilesAction(files, null); + } + + public void handleOpenFilesAction(List files, Runnable onSuccess) { assert files != null; assert files.isEmpty() == false; @@ -511,21 +515,76 @@ public void handleOpenFilesAction(List files) { // Fix for #45 if (userLibrary.isFirstExplorationCompleted()) { - performOpenFiles(fileObjs); + var openResult = performOpenFiles(fileObjs); + handleFileOpenResult(openResult, onSuccess); } else { // open files only after the first exploration has finished userLibrary.firstExplorationCompletedProperty().addListener(new InvalidationListener() { @Override public void invalidated(Observable observable) { if (userLibrary.isFirstExplorationCompleted()) { - performOpenFiles(fileObjs); + var openResult = performOpenFiles(fileObjs); userLibrary.firstExplorationCompletedProperty().removeListener(this); + handleFileOpenResult(openResult, onSuccess); } } }); } } + /** + * The onSuccess Runnable is only executed if the {@link FileOpenResult} has no + * errors at all. + * + * @param result {@link FileOpenResult} holding the list of files to be + * opened and a map of files with exceptions in case of an + * error. + * @param onSuccess A runnable holding the action to be performed on success. + */ + private void handleFileOpenResult(FileOpenResult result, Runnable onSuccess) { + if (result.hasErrors()) { + showFileOpenErrors(result); + } else { + if (onSuccess != null) { + onSuccess.run(); + } + } + } + + /** + * In case of errors (>= 1 exception) a message dialog is shown to the user. + * There are versions for exactly 1 error and for more than 1 errors. + * + * @param openResult {@link FileOpenResult} holding a map of files with + * exceptions. + */ + private void showFileOpenErrors(FileOpenResult openResult) { + if (openResult.errors.size() == 1) { + final File fxmlFile = openResult.errors().keySet().iterator().next(); + final Exception x = openResult.errors().get(fxmlFile); + final ErrorDialog errorDialog = new ErrorDialog(null); + errorDialog.setMessage(I18N.getString("alert.open.failure1.message", displayName(fxmlFile.getPath()))); + errorDialog.setDetails(I18N.getString("alert.open.failure1.details")); + errorDialog.setDebugInfoWithThrowable(x); + errorDialog.setTitle(I18N.getString("alert.open.failure.title")); + errorDialog.showAndWait(); + } else if (openResult.errors.size() > 1){ + final ErrorDialog errorDialog = new ErrorDialog(null); + if (openResult.errors().size() == openResult.filesToOpen.size()) { + // Open operation has failed for all the files + errorDialog.setMessage(I18N.getString("alert.open.failureN.message")); + errorDialog.setDetails(I18N.getString("alert.open.failureN.details")); + } else { + // Open operation has failed for some files + errorDialog.setMessage(I18N.getString("alert.open.failureMofN.message", + openResult.errors().size(), openResult.filesToOpen.size())); + errorDialog.setDetails(I18N.getString("alert.open.failureMofN.details")); + } + errorDialog.setTitle(I18N.getString("alert.open.failure.title")); + errorDialog.showAndWait(); + } + } + @Override public void handleMessageBoxFailure(Exception x) { final ErrorDialog errorDialog = new ErrorDialog(null); @@ -657,11 +716,12 @@ public DocumentWindowController getFrontDocumentWindow() { return null; } - private void performOpenFiles(List fxmlFiles) { + private FileOpenResult performOpenFiles(List fxmlFiles) { assert fxmlFiles != null; assert fxmlFiles.isEmpty() == false; final Map exceptions = new HashMap<>(); + final List openedFiles = new ArrayList<>(); for (File fxmlFile : fxmlFiles) { try { final DocumentWindowController dwc @@ -674,47 +734,20 @@ private void performOpenFiles(List fxmlFiles) { var hostWindow = findFirstUnusedDocumentWindowController().orElse(makeNewWindow()); hostWindow.loadFromFile(fxmlFile); hostWindow.openWindow(); + openedFiles.add(fxmlFile); } } catch (Exception xx) { exceptions.put(fxmlFile, xx); } } - - switch (exceptions.size()) { - case 0: { // Good - // Update recent items with opened files - final PreferencesController pc = PreferencesController.getSingleton(); - pc.getRecordGlobal().addRecentItems(fxmlFiles); - break; - } - case 1: { - final File fxmlFile = exceptions.keySet().iterator().next(); - final Exception x = exceptions.get(fxmlFile); - final ErrorDialog errorDialog = new ErrorDialog(null); - errorDialog.setMessage(I18N.getString("alert.open.failure1.message", displayName(fxmlFile.getPath()))); - errorDialog.setDetails(I18N.getString("alert.open.failure1.details")); - errorDialog.setDebugInfoWithThrowable(x); - errorDialog.setTitle(I18N.getString("alert.title.open")); - errorDialog.showAndWait(); - break; - } - default: { - final ErrorDialog errorDialog = new ErrorDialog(null); - if (exceptions.size() == fxmlFiles.size()) { - // Open operation has failed for all the files - errorDialog.setMessage(I18N.getString("alert.open.failureN.message")); - errorDialog.setDetails(I18N.getString("alert.open.failureN.details")); - } else { - // Open operation has failed for some files - errorDialog.setMessage(I18N.getString("alert.open.failureMofN.message", - exceptions.size(), fxmlFiles.size())); - errorDialog.setDetails(I18N.getString("alert.open.failureMofN.details")); - } - errorDialog.setTitle(I18N.getString("alert.title.open")); - errorDialog.showAndWait(); - break; - } + + // Update recent items with opened files + if (!openedFiles.isEmpty()) { + final PreferencesController pc = PreferencesController.getSingleton(); + pc.getRecordGlobal().addRecentItems(openedFiles); } + + return new FileOpenResult(fxmlFiles, exceptions); } private void performExit() { @@ -1083,4 +1116,19 @@ public static void applyToAllDocumentWindows(Consumer consumer.accept(dwc); } } + + /** + * Describes the result of FXML file loading process. As in general SceneBuilder + * can open multiple files at the same time. Hence this record can hold of the + * files supposed to be opened. In case of errors, the exceptions are stored in + * a map per file. + */ + record FileOpenResult(List filesToOpen, Map errors) { + public boolean isSuccess() { + return errors.isEmpty(); + } + public boolean hasErrors() { + return !errors.isEmpty(); + } + } } 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 64028b831..5ed1924c7 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 @@ -256,35 +256,31 @@ boolean filePathExists(String filePath) { * Builder. */ private void handleOpen(List filePaths) { - Consumer> missingFilesHandler = missingFiles->{ - if (!missingFiles.isEmpty()) { - var questionDialog = questionMissingFilesCleanup(getStage(), missingFiles); - var x = getInstance().getStage().getX(); - var y = getInstance().getStage().getY(); - var width = getInstance().getStage().getWidth(); - var height = getInstance().getStage().getHeight(); - questionDialog.getStage().setX(x+width/3); - questionDialog.getStage().setY(y+height/3); - if (questionDialog.showAndWait() == AlertDialog.ButtonID.OK) { - removeMissingFilesFromPrefs(missingFiles); - loadAndPopulateRecentItemsInBackground(); - } - } - }; + handleOpen(filePaths, + this::askUserToRemoveMissingRecentFiles, + this::attemptOpenExistingFiles); + } - Consumer> existingFilesHandler = paths->{ - if (sceneBuilderApp.startupTasksFinishedBinding().get()) { - sceneBuilderApp.handleOpenFilesAction(paths); - getStage().hide(); - } else { - showMasker(() -> { - sceneBuilderApp.handleOpenFilesAction(paths); - getStage().hide(); - }); + private void askUserToRemoveMissingRecentFiles(List missingFiles) { + if (!missingFiles.isEmpty()) { + var questionDialog = questionMissingFilesCleanup(getStage(), missingFiles); + if (questionDialog.showAndWait() == AlertDialog.ButtonID.OK) { + removeMissingFilesFromPrefs(missingFiles); + loadAndPopulateRecentItemsInBackground(); } - }; + } + } + + private void attemptOpenExistingFiles(List paths) { + if (sceneBuilderApp.startupTasksFinishedBinding().get()) { + openFilesAndHideStage(paths); + } else { + showMasker(() -> openFilesAndHideStage(paths)); + } + } - handleOpen(filePaths, missingFilesHandler, existingFilesHandler); + private void openFilesAndHideStage(List files) { + sceneBuilderApp.handleOpenFilesAction(files,()->getStage().hide()); } /** @@ -301,21 +297,20 @@ void handleOpen(List filePaths, if (filePaths.isEmpty()) { return; } - + var candidates = filePaths.stream() .collect(Collectors.groupingBy(this::filePathExists)); - + List missingFiles = candidates.getOrDefault(Boolean.FALSE, new ArrayList<>()); missingFilesHandler.accept(missingFiles); List paths = candidates.getOrDefault(Boolean.TRUE, new ArrayList<>()) .stream() .toList(); - + if (paths.isEmpty()) { return; } - fileLoader.accept(paths); } @@ -335,8 +330,6 @@ private void showMasker(Runnable onEndAction) { if (isFinished) { Platform.runLater(() -> { onEndAction.run(); - - // restore state in case welcome dialog is opened again contentPane.setDisable(false); masker.setVisible(false); }); 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 31dddd7d3..48fc94337 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 @@ -396,6 +396,7 @@ alert.title.copy = Copy alert.title.start = Startup alert.title.messagebox = External Open +alert.open.failure.title = File Open Error alert.open.failure1.message = Could not open ''{0}'' alert.open.failure1.details = Open operation has failed. Make sure that the chosen file is a valid FXML document. alert.open.failureN.message = Could not open the specified files From f11a8a125bc5bbcae21e671b450b22cf16e211e4 Mon Sep 17 00:00:00 2001 From: Oliver-Loeffler Date: Tue, 8 Nov 2022 17:57:39 +0100 Subject: [PATCH 08/37] Added warning and informative logging for cases of removing files from recent items and for cases where errors occured during FXML loading. --- .../scenebuilder/app/SceneBuilderApp.java | 28 ++++++++++++++++--- .../WelcomeDialogWindowController.java | 15 ++++++---- .../scenebuilder/app/JfxInitializer.java | 2 +- kit/pom.xml | 8 ------ pom.xml | 6 ++++ 5 files changed, 41 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java index c0e6337a0..d9d03bac7 100644 --- a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java +++ b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java @@ -99,6 +99,8 @@ */ public class SceneBuilderApp extends Application implements AppPlatform.AppNotificationHandler { + private static final Logger LOGGER = Logger.getLogger(SceneBuilderApp.class.getName()); + public enum ApplicationControlAction { ABOUT, CHECK_UPDATES, @@ -497,11 +499,24 @@ private void createEmptyDocumentWindow() { newWindow.updateWithDefaultContent(); } + /** + * By default all necessary actions to open a single file or a group of files take place in this method. + * If it is required to perform certain actions after successfully loading all files, please use {@code handleOpenFilesAction(List files, Runnable onSuccess)} instead. + * + * All error handling takes place here within, there is no way yet to access exceptional results and to work with them. + */ @Override public void handleOpenFilesAction(List files) { handleOpenFilesAction(files, null); } + /** + * As file loading errors are handled within this method (all exceptions are handled within), it can be helpful to be able to run a certain action after successful file loading (e.g. closing a certain stage). + * For this case this method offers the argument {@code Runnable onSuccess} which will be executed after successful file open activity. The {@code Runnable onSuccess} is only ran once, despite how many files have been loaded. + * + * @param files List of Strings denoting file paths to be opened + * @param onSuccess {@link Runnable} to be executed after all files have been opened successfully + */ public void handleOpenFilesAction(List files, Runnable onSuccess) { assert files != null; assert files.isEmpty() == false; @@ -720,9 +735,11 @@ private FileOpenResult performOpenFiles(List fxmlFiles) { assert fxmlFiles != null; assert fxmlFiles.isEmpty() == false; + LOGGER.log(Level.FINE, "Opening {0} files...", fxmlFiles.size()); final Map exceptions = new HashMap<>(); final List openedFiles = new ArrayList<>(); for (File fxmlFile : fxmlFiles) { + LOGGER.log(Level.FINE, "Attempting to open file {0}", fxmlFile); try { final DocumentWindowController dwc = lookupDocumentWindowControllers(fxmlFile.toURI().toURL()); @@ -735,8 +752,10 @@ private FileOpenResult performOpenFiles(List fxmlFiles) { hostWindow.loadFromFile(fxmlFile); hostWindow.openWindow(); openedFiles.add(fxmlFile); + LOGGER.log(Level.INFO, "Successfully opened file {0}", fxmlFile); } } catch (Exception xx) { + LOGGER.log(Level.WARNING, "Failed to open file: %s".formatted(fxmlFile), xx); exceptions.put(fxmlFile, xx); } } @@ -746,7 +765,11 @@ private FileOpenResult performOpenFiles(List fxmlFiles) { final PreferencesController pc = PreferencesController.getSingleton(); pc.getRecordGlobal().addRecentItems(openedFiles); } - + if (exceptions.size() > 0) { + LOGGER.log(Level.WARNING, "Failed to open {0} of {1} files!", new Object[] {exceptions.size(), fxmlFiles.size()}); + } else { + LOGGER.log(Level.FINE, "Successfully opened all files."); + } return new FileOpenResult(fxmlFiles, exceptions); } @@ -1124,9 +1147,6 @@ public static void applyToAllDocumentWindows(Consumer * a map per file. */ record FileOpenResult(List filesToOpen, Map errors) { - public boolean isSuccess() { - return errors.isEmpty(); - } public boolean hasErrors() { return !errors.isEmpty(); } 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 5ed1924c7..81a297d08 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 @@ -38,6 +38,8 @@ 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; @@ -67,6 +69,8 @@ public class WelcomeDialogWindowController extends TemplatesBaseWindowController { + private static final Logger LOGGER = Logger.getLogger(WelcomeDialogWindowController.class.getName()); + @FXML private BorderPane contentPane; @@ -207,7 +211,6 @@ private void openDocument() { new FileChooser.ExtensionFilter(I18N.getString("file.filter.label.fxml"), "*.fxml") ); fileChooser.setInitialDirectory(EditorController.getNextInitialDirectory()); - List fxmlFiles = fileChooser.showOpenMultipleDialog(getStage()); // no file was selected, so nothing to do @@ -225,7 +228,6 @@ private void openDocument() { protected static AlertDialog questionMissingFilesCleanup(Stage stage, List missingFiles) { String withPath = missingFiles.stream() .collect(Collectors.joining(System.lineSeparator())); - AlertDialog question = new AlertDialog(stage); StringBuilder shortMessage = new StringBuilder(); if (missingFiles.size() > 1) { @@ -292,8 +294,9 @@ private void openFilesAndHideStage(List files) { * @param fileLoader Determines how files are loaded. */ void handleOpen(List filePaths, - Consumer> missingFilesHandler, - Consumer> fileLoader) { + Consumer> missingFilesHandler, + Consumer> fileLoader) { + if (filePaths.isEmpty()) { return; } @@ -303,7 +306,7 @@ void handleOpen(List filePaths, List missingFiles = candidates.getOrDefault(Boolean.FALSE, new ArrayList<>()); missingFilesHandler.accept(missingFiles); - + List paths = candidates.getOrDefault(Boolean.TRUE, new ArrayList<>()) .stream() .toList(); @@ -315,6 +318,7 @@ void handleOpen(List filePaths, } private void removeMissingFilesFromPrefs(List missingFiles) { + missingFiles.forEach(fxmlFileName->LOGGER.log(Level.INFO, "Removing missing file from recent items: {0}", fxmlFileName)); PreferencesRecordGlobal preferencesRecordGlobal = PreferencesController.getSingleton().getRecordGlobal(); preferencesRecordGlobal.removeRecentItems(missingFiles); } @@ -330,6 +334,7 @@ private void showMasker(Runnable onEndAction) { if (isFinished) { Platform.runLater(() -> { onEndAction.run(); + // restore state in case welcome dialog is opened again contentPane.setDisable(false); masker.setVisible(false); }); diff --git a/app/src/test/java/com/oracle/javafx/scenebuilder/app/JfxInitializer.java b/app/src/test/java/com/oracle/javafx/scenebuilder/app/JfxInitializer.java index 44bd21724..0411ba448 100644 --- a/app/src/test/java/com/oracle/javafx/scenebuilder/app/JfxInitializer.java +++ b/app/src/test/java/com/oracle/javafx/scenebuilder/app/JfxInitializer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Gluon and/or its affiliates. + * Copyright (c) 2021, 2022, Gluon and/or its affiliates. * All rights reserved. Use is subject to license terms. * * This file is available and licensed under the following license: diff --git a/kit/pom.xml b/kit/pom.xml index 754d9ba19..343cf1229 100644 --- a/kit/pom.xml +++ b/kit/pom.xml @@ -91,14 +91,6 @@ 1.0.4 runtime - - - - org.assertj - assertj-core - 3.19.0 - test - diff --git a/pom.xml b/pom.xml index 00bb65a19..87a841c31 100644 --- a/pom.xml +++ b/pom.xml @@ -70,6 +70,12 @@ junit-jupiter-engine test + + org.assertj + assertj-core + 3.19.0 + test + From 9a1a7cea33601df1d825901dec1880c207493daf Mon Sep 17 00:00:00 2001 From: Oliver-Loeffler Date: Mon, 27 Mar 2023 22:13:11 +0200 Subject: [PATCH 09/37] Formatted according to checkstyle rules. --- .../com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java | 6 +++--- .../app/welcomedialog/WelcomeDialogWindowController.java | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java index d9d03bac7..3210ceba6 100644 --- a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java +++ b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2022, Gluon and/or its affiliates. + * Copyright (c) 2016, 2023, Gluon and/or its affiliates. * Copyright (c) 2012, 2014, Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * @@ -583,7 +583,7 @@ private void showFileOpenErrors(FileOpenResult openResult) { errorDialog.setDebugInfoWithThrowable(x); errorDialog.setTitle(I18N.getString("alert.open.failure.title")); errorDialog.showAndWait(); - } else if (openResult.errors.size() > 1){ + } else if (openResult.errors.size() > 1) { final ErrorDialog errorDialog = new ErrorDialog(null); if (openResult.errors().size() == openResult.filesToOpen.size()) { // Open operation has failed for all the files @@ -1146,7 +1146,7 @@ public static void applyToAllDocumentWindows(Consumer * files supposed to be opened. In case of errors, the exceptions are stored in * a map per file. */ - record FileOpenResult(List filesToOpen, Map errors) { + record FileOpenResult(List filesToOpen, Map errors) { public boolean hasErrors() { return !errors.isEmpty(); } 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 81a297d08..346a01f8b 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 @@ -178,7 +178,7 @@ private void loadAndPopulateRecentItemsInBackground() { } public static WelcomeDialogWindowController getInstance() { - if (instance == null){ + if (instance == null) { instance = new WelcomeDialogWindowController(); var stage = instance.getStage(); stage.setMinWidth(800); @@ -282,7 +282,7 @@ private void attemptOpenExistingFiles(List paths) { } private void openFilesAndHideStage(List files) { - sceneBuilderApp.handleOpenFilesAction(files,()->getStage().hide()); + sceneBuilderApp.handleOpenFilesAction(files, () -> getStage().hide()); } /** @@ -318,7 +318,7 @@ void handleOpen(List filePaths, } private void removeMissingFilesFromPrefs(List missingFiles) { - missingFiles.forEach(fxmlFileName->LOGGER.log(Level.INFO, "Removing missing file from recent items: {0}", fxmlFileName)); + missingFiles.forEach(fxmlFileName -> LOGGER.log(Level.INFO, "Removing missing file from recent items: {0}", fxmlFileName)); PreferencesRecordGlobal preferencesRecordGlobal = PreferencesController.getSingleton().getRecordGlobal(); preferencesRecordGlobal.removeRecentItems(missingFiles); } From 8ccdefb45798f2ad90e32497a692bce47da51bb9 Mon Sep 17 00:00:00 2001 From: Oliver-Loeffler Date: Thu, 30 Mar 2023 20:52:27 +0200 Subject: [PATCH 10/37] Reworked message which is shown, when the FXML file to be loaded is missing into a less verbose message. Also applied checkstyle formatting fixes for the corresponding test. --- .../WelcomeDialogWindowController.java | 9 ++++++--- .../app/i18n/SceneBuilderApp.properties | 11 +++++------ .../WelcomeDialogWindowControllerTest.java | 15 +++++++-------- 3 files changed, 18 insertions(+), 17 deletions(-) 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 346a01f8b..ce9a50150 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2022, Gluon and/or its affiliates. + * Copyright (c) 2017, 2023, Gluon and/or its affiliates. * All rights reserved. Use is subject to license terms. * * This file is available and licensed under the following license: @@ -49,6 +49,7 @@ 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.template.Template; import com.oracle.javafx.scenebuilder.kit.template.TemplatesBaseWindowController; @@ -229,19 +230,21 @@ protected static AlertDialog questionMissingFilesCleanup(Stage stage, List 1) { shortMessage.append(I18N.getString("alert.welcome.files.not.found.question")); question.setTitle(I18N.getString("alert.welcome.files.not.found.title")); - question.setOKButtonTitle(I18N.getString("alert.welcome.files.not.found.okay")); } else { shortMessage.append(I18N.getString("alert.welcome.file.not.found.question")); question.setTitle(I18N.getString("alert.welcome.file.not.found.title")); - question.setOKButtonTitle(I18N.getString("alert.welcome.file.not.found.okay")); } question.setCancelButtonTitle(I18N.getString("alert.welcome.file.not.found.no")); question.setMessage(shortMessage.toString()); question.setDetails(withPath); + return question; } 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 baa144388..af7a3f2ba 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 @@ -1,4 +1,4 @@ -# Copyright (c) 2016, 2022, Gluon and/or its affiliates. +# Copyright (c) 2016, 2023, Gluon and/or its affiliates. # Copyright (c) 2012, 2014, Oracle and/or its affiliates. # All rights reserved. Use is subject to license terms. # @@ -437,13 +437,12 @@ alert.save.noextension.savewith = Save with '.fxml' alert.save.noextension.savewithout = Save without '.fxml' alert.open.failure.charset.not.found = The given charset could not be set. alert.open.failure.charset.not.found.details = It may be due to the encoding in your document. -alert.welcome.files.not.found.question = Some files were not found. Do you want Scene Builder to remove these files from list of recent projects?\n +alert.welcome.files.not.found.question = Some project files were not found.\nIf thosa are located on a removable or network drive, please make sure the drive is connected.\n alert.welcome.files.not.found.title = Project files not found -alert.welcome.files.not.found.okay = Yes, remove missing files from recent projects. -alert.welcome.file.not.found.question = The selected project file was not found. Do you want Scene Builder to remove this file from list of recent projects?\n +alert.welcome.file.not.found.question = The selected project file was not found.\nIf it is on a removable or network drive, please make sure the drive is connected.\n alert.welcome.file.not.found.title = Project file not found -alert.welcome.file.not.found.okay = Yes, remove missing file from recent projects. -alert.welcome.file.not.found.no = No, keep file(s) in recent projects. +alert.welcome.file.not.found.okay = Remove From List +alert.welcome.file.not.found.no = OK # ----------------------------------------------------------------------------- # Log Messages diff --git a/app/src/test/java/com/oracle/javafx/scenebuilder/app/welcomedialog/WelcomeDialogWindowControllerTest.java b/app/src/test/java/com/oracle/javafx/scenebuilder/app/welcomedialog/WelcomeDialogWindowControllerTest.java index de70fced8..eae7b6a96 100644 --- a/app/src/test/java/com/oracle/javafx/scenebuilder/app/welcomedialog/WelcomeDialogWindowControllerTest.java +++ b/app/src/test/java/com/oracle/javafx/scenebuilder/app/welcomedialog/WelcomeDialogWindowControllerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Gluon and/or its affiliates. + * Copyright (c) 2023, Gluon and/or its affiliates. * All rights reserved. Use is subject to license terms. * * This file is available and licensed under the following license: @@ -67,10 +67,10 @@ void that_missing_files_are_detected_and_handled_and_existing_files_are_loaded() List filesMissing = new ArrayList<>(); List filesLoaded = new ArrayList<>(); - Consumer> missingFilesHandler = missing->filesMissing.addAll(missing); - Consumer> existingFilesHandler = existing->filesLoaded.addAll(existing); + Consumer> missingFilesHandler = missing -> filesMissing.addAll(missing); + Consumer> existingFilesHandler = existing -> filesLoaded.addAll(existing); - assertDoesNotThrow(()->classUnderTest.handleOpen(filesToLoad, + assertDoesNotThrow(() -> classUnderTest.handleOpen(filesToLoad, missingFilesHandler, existingFilesHandler)); @@ -84,7 +84,7 @@ void that_no_actions_are_performed_on_empty_list() { List filesToLoad = Collections.emptyList(); Set actionsPerformed = new HashSet<>(); - Consumer> filesHandler = listOffiles->actionsPerformed.add("some action performed"); + Consumer> filesHandler = listOffiles -> actionsPerformed.add("some action performed"); classUnderTest.handleOpen(filesToLoad, filesHandler, filesHandler); assertTrue(actionsPerformed.isEmpty()); @@ -99,8 +99,8 @@ void that_file_loader_is_not_called_when_all_files_are_missing() { List filesMissing = new ArrayList<>(); List filesLoaded = new ArrayList<>(); - Consumer> missingFilesHandler = missing->filesMissing.addAll(missing); - Consumer> existingFilesHandler = existing->filesLoaded.addAll(existing); + Consumer> missingFilesHandler = missing -> filesMissing.addAll(missing); + Consumer> existingFilesHandler = existing -> filesLoaded.addAll(existing); classUnderTest.handleOpen(filesToLoad, missingFilesHandler, existingFilesHandler); assertTrue(filesLoaded.isEmpty()); @@ -110,5 +110,4 @@ void that_file_loader_is_not_called_when_all_files_are_missing() { private Path getResource(String resourceName) throws Exception { return Path.of(getClass().getResource(resourceName).toURI()); } - } From 3a777a7fbd9a5bfd6fe1a7edb87fa94ba077093f Mon Sep 17 00:00:00 2001 From: Oliver-Loeffler Date: Mon, 18 Mar 2024 20:40:30 +0100 Subject: [PATCH 11/37] Instead of passing null, an empty lambda is passed as runnable. The required null handling was removed as well. --- .../com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java index b3c01def5..e3a439050 100644 --- a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java +++ b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java @@ -506,7 +506,7 @@ private void createEmptyDocumentWindow() { */ @Override public void handleOpenFilesAction(List files) { - handleOpenFilesAction(files, null); + handleOpenFilesAction(files, () -> { /* no operation in this case */ }); } /** @@ -559,9 +559,7 @@ private void handleFileOpenResult(FileOpenResult result, Runnable onSuccess) { if (result.hasErrors()) { showFileOpenErrors(result); } else { - if (onSuccess != null) { - onSuccess.run(); - } + onSuccess.run(); } } From d26800e5079ab58dfdaa64d3711e5bc486a804ba Mon Sep 17 00:00:00 2001 From: Oliver-Loeffler Date: Mon, 18 Mar 2024 20:43:02 +0100 Subject: [PATCH 12/37] Updated year in license header. --- .../com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java index e3a439050..f46cec39a 100644 --- a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java +++ b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2023, Gluon and/or its affiliates. + * Copyright (c) 2016, 2024, Gluon and/or its affiliates. * Copyright (c) 2012, 2014, Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * From 3eb469f452ae212727d052b7ca92dec21aa1debb Mon Sep 17 00:00:00 2001 From: Oliver-Loeffler Date: Mon, 18 Mar 2024 20:49:46 +0100 Subject: [PATCH 13/37] Reworked code according to review comment to make intent more clear. --- .../WelcomeDialogWindowController.java | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) 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 ce9a50150..288fe7d56 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2023, Gluon and/or its affiliates. + * Copyright (c) 2017, 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: @@ -304,20 +304,23 @@ void handleOpen(List filePaths, return; } - var candidates = filePaths.stream() - .collect(Collectors.groupingBy(this::filePathExists)); + List existingFiles = new ArrayList<>(); + List missingFiles = new ArrayList<>(); + filePaths.forEach(file->{ + if (filePathExists(file)) { + existingFiles.add(file); + } else { + missingFiles.add(file); + } + }); - List missingFiles = candidates.getOrDefault(Boolean.FALSE, new ArrayList<>()); missingFilesHandler.accept(missingFiles); - - List paths = candidates.getOrDefault(Boolean.TRUE, new ArrayList<>()) - .stream() - .toList(); - - if (paths.isEmpty()) { - return; + + if (existingFiles.isEmpty()) { + return; } - fileLoader.accept(paths); + + fileLoader.accept(existingFiles); } private void removeMissingFilesFromPrefs(List missingFiles) { From b08f2621172f4af74eeb4268cc1fdd295a50a2ec Mon Sep 17 00:00:00 2001 From: Oliver-Loeffler Date: Mon, 18 Mar 2024 21:03:56 +0100 Subject: [PATCH 14/37] Simplified dialog where user is asked wether to keep or remove missing files from start screen. --- .../WelcomeDialogWindowController.java | 13 +++---------- .../app/i18n/SceneBuilderApp.properties | 12 +++++------- 2 files changed, 8 insertions(+), 17 deletions(-) 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 288fe7d56..a14c3bc03 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 @@ -229,22 +229,15 @@ private void openDocument() { protected static AlertDialog questionMissingFilesCleanup(Stage stage, List missingFiles) { String withPath = missingFiles.stream() .collect(Collectors.joining(System.lineSeparator())); + AlertDialog question = new AlertDialog(stage); question.setDefaultButtonID(ButtonID.CANCEL); question.setShowDefaultButton(true); question.setOKButtonTitle(I18N.getString("alert.welcome.file.not.found.okay")); - StringBuilder shortMessage = new StringBuilder(); - if (missingFiles.size() > 1) { - shortMessage.append(I18N.getString("alert.welcome.files.not.found.question")); - question.setTitle(I18N.getString("alert.welcome.files.not.found.title")); - } else { - shortMessage.append(I18N.getString("alert.welcome.file.not.found.question")); - question.setTitle(I18N.getString("alert.welcome.file.not.found.title")); - } + question.setTitle(I18N.getString("alert.welcome.files.not.found.title")); + question.setMessage(I18N.getString("alert.welcome.files.not.found.question")); question.setCancelButtonTitle(I18N.getString("alert.welcome.file.not.found.no")); - question.setMessage(shortMessage.toString()); question.setDetails(withPath); - return question; } 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 efb469e7c..ec4fe0473 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 @@ -1,4 +1,4 @@ -# Copyright (c) 2016, 2023, Gluon and/or its affiliates. +# Copyright (c) 2016, 2024, Gluon and/or its affiliates. # Copyright (c) 2012, 2014, Oracle and/or its affiliates. # All rights reserved. Use is subject to license terms. # @@ -437,12 +437,10 @@ alert.save.noextension.savewith = Save with '.fxml' alert.save.noextension.savewithout = Save without '.fxml' alert.open.failure.charset.not.found = The given charset could not be set. alert.open.failure.charset.not.found.details = It may be due to the encoding in your document. -alert.welcome.files.not.found.question = Some project files were not found.\nIf thosa are located on a removable or network drive, please make sure the drive is connected.\n -alert.welcome.files.not.found.title = Project files not found -alert.welcome.file.not.found.question = The selected project file was not found.\nIf it is on a removable or network drive, please make sure the drive is connected.\n -alert.welcome.file.not.found.title = Project file not found -alert.welcome.file.not.found.okay = Remove From List -alert.welcome.file.not.found.no = OK +alert.welcome.file.not.found.question = One or more project files were not found.\nIf those are located on a removable or network drive, please make sure the drive is connected.\n +alert.welcome.file.not.found.title = Project file(s) not found +alert.welcome.file.not.found.okay = Remove missing file(s) +alert.welcome.file.not.found.no = Keep missing file(s) # ----------------------------------------------------------------------------- # Log Messages From 83186957bb8cdf7f9811d8b1f22176294376c500 Mon Sep 17 00:00:00 2001 From: Oliver-Loeffler Date: Mon, 18 Mar 2024 21:37:55 +0100 Subject: [PATCH 15/37] Reworked Missing Project Files dialog as proposed by reviewer. --- .../app/welcomedialog/WelcomeDialogWindowController.java | 8 ++++---- .../scenebuilder/app/i18n/SceneBuilderApp.properties | 7 ++++--- 2 files changed, 8 insertions(+), 7 deletions(-) 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 a14c3bc03..c7f2e3aed 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 @@ -234,10 +234,10 @@ protected static AlertDialog questionMissingFilesCleanup(Stage stage, List filePaths, missingFiles.add(file); } }); - + missingFilesHandler.accept(missingFiles); if (existingFiles.isEmpty()) { 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 ec4fe0473..560d5bd71 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 @@ -437,10 +437,11 @@ alert.save.noextension.savewith = Save with '.fxml' alert.save.noextension.savewithout = Save without '.fxml' alert.open.failure.charset.not.found = The given charset could not be set. alert.open.failure.charset.not.found.details = It may be due to the encoding in your document. -alert.welcome.file.not.found.question = One or more project files were not found.\nIf those are located on a removable or network drive, please make sure the drive is connected.\n +alert.welcome.file.not.found.question = One or more project files were not found. +alert.welcome.file.not.found.message = If those are located on a removable or network drive, please make sure the drive is connected.\n\n alert.welcome.file.not.found.title = Project file(s) not found -alert.welcome.file.not.found.okay = Remove missing file(s) -alert.welcome.file.not.found.no = Keep missing file(s) +alert.welcome.file.not.found.okay = Remove From List +alert.welcome.file.not.found.no = OK # ----------------------------------------------------------------------------- # Log Messages From d5e3bc016653f257ced7f0d33101cfdf4b24cd71 Mon Sep 17 00:00:00 2001 From: Oliver-Loeffler Date: Mon, 18 Mar 2024 22:07:39 +0100 Subject: [PATCH 16/37] Updated error dialog according to code review recommendation. --- .../scenebuilder/app/SceneBuilderApp.java | 56 ++++++++----------- .../app/i18n/SceneBuilderApp.properties | 4 -- 2 files changed, 24 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java index f46cec39a..187e083f7 100644 --- a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java +++ b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java @@ -83,6 +83,7 @@ import java.nio.file.Paths; import java.time.LocalDate; import java.util.*; +import java.util.Map.Entry; import java.util.concurrent.CountDownLatch; import java.util.function.Consumer; import java.util.logging.Level; @@ -139,7 +140,7 @@ public static SceneBuilderApp getSingleton() { public SceneBuilderApp() { assert singleton == null; singleton = this; - + // set design time flag java.beans.Beans.setDesignTime(true); @@ -526,7 +527,7 @@ public void handleOpenFilesAction(List files, Runnable onSuccess) { } EditorController.updateNextInitialDirectory(fileObjs.get(0)); - + // Fix for #45 if (userLibrary.isFirstExplorationCompleted()) { var openResult = performOpenFiles(fileObjs); @@ -564,37 +565,28 @@ private void handleFileOpenResult(FileOpenResult result, Runnable onSuccess) { } /** - * In case of errors (>= 1 exception) a message dialog is shown to the user. - * There are versions for exactly 1 error and for more than 1 errors. - * - * @param openResult {@link FileOpenResult} holding a map of files with - * exceptions. - */ + * For each file open error (when opened through the welcome dialog), the file + * name and the related exception text are presented to the user to confirm. + * + * @param openResult {@link FileOpenResult} holding a map of files with + * exceptions. + */ private void showFileOpenErrors(FileOpenResult openResult) { - if (openResult.errors.size() == 1) { - final File fxmlFile = openResult.errors().keySet().iterator().next(); - final Exception x = openResult.errors().get(fxmlFile); - final ErrorDialog errorDialog = new ErrorDialog(null); - errorDialog.setMessage(I18N.getString("alert.open.failure1.message", displayName(fxmlFile.getPath()))); - errorDialog.setDetails(I18N.getString("alert.open.failure1.details")); - errorDialog.setDebugInfoWithThrowable(x); - errorDialog.setTitle(I18N.getString("alert.open.failure.title")); - errorDialog.showAndWait(); - } else if (openResult.errors.size() > 1) { - final ErrorDialog errorDialog = new ErrorDialog(null); - if (openResult.errors().size() == openResult.filesToOpen.size()) { - // Open operation has failed for all the files - errorDialog.setMessage(I18N.getString("alert.open.failureN.message")); - errorDialog.setDetails(I18N.getString("alert.open.failureN.details")); - } else { - // Open operation has failed for some files - errorDialog.setMessage(I18N.getString("alert.open.failureMofN.message", - openResult.errors().size(), openResult.filesToOpen.size())); - errorDialog.setDetails(I18N.getString("alert.open.failureMofN.details")); - } - errorDialog.setTitle(I18N.getString("alert.open.failure.title")); - errorDialog.showAndWait(); - } + if (openResult.errors().isEmpty()) { + return; + } + + Map errors = openResult.errors(); + for (Entry error : errors.entrySet()) { + final File fxmlFile = error.getKey(); + final Exception x = error.getValue(); + final ErrorDialog errorDialog = new ErrorDialog(WelcomeDialogWindowController.getInstance().getStage()); + errorDialog.setMessage(I18N.getString("alert.open.failure1.message", displayName(fxmlFile.getPath()))); + errorDialog.setDetails(I18N.getString("alert.open.failure1.details")); + errorDialog.setDebugInfoWithThrowable(x); + errorDialog.setTitle(I18N.getString("alert.open.failure.title")); + errorDialog.showAndWait(); + } } @Override 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 560d5bd71..90350b145 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 @@ -400,10 +400,6 @@ alert.title.messagebox = External Open alert.open.failure.title = File Open Error alert.open.failure1.message = Could not open ''{0}'' alert.open.failure1.details = Open operation has failed. Make sure that the chosen file is a valid FXML document. -alert.open.failureN.message = Could not open the specified files -alert.open.failureN.details = Open operation has failed. Make sure that those files are valid FXML documents. -alert.open.failureMofN.message = Could not open {0} files -alert.open.failureMofN.details = Open operation has failed for some files. Make sure that those files are valid FXML documents. alert.review.question.message = {0} of your documents contain unsaved changes. Do you want to review them before exiting ? alert.review.question.details = Your changes will be lost if you don't review them. alert.overwrite.message = The file ''{0}'' was modified externally. Do you want to overwrite it ? From 0dcbf6fc55f4e5edf8826581c8fba3c7cf5feaf9 Mon Sep 17 00:00:00 2001 From: Oliver-Loeffler Date: Mon, 18 Mar 2024 22:08:06 +0100 Subject: [PATCH 17/37] Removed no longer needed I18N keys. --- .../scenebuilder/app/i18n/SceneBuilderApp_ja.properties | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/src/main/resources/com/oracle/javafx/scenebuilder/app/i18n/SceneBuilderApp_ja.properties b/app/src/main/resources/com/oracle/javafx/scenebuilder/app/i18n/SceneBuilderApp_ja.properties index 280754a22..131863bdc 100644 --- a/app/src/main/resources/com/oracle/javafx/scenebuilder/app/i18n/SceneBuilderApp_ja.properties +++ b/app/src/main/resources/com/oracle/javafx/scenebuilder/app/i18n/SceneBuilderApp_ja.properties @@ -394,10 +394,6 @@ alert.title.messagebox = 外部プログラムで開く alert.open.failure1.message = ''{0}''を開けませんでした alert.open.failure1.details = 開くのに失敗しました。選択されたファイルが有効なFXMLドキュメントであることを確認してください。 -alert.open.failureN.message = 指定されたファイルを開けませんでした -alert.open.failureN.details = 開くのに失敗しました。それらのファイルが有効なFXMLドキュメントであることを確認してください。 -alert.open.failureMofN.message = {0}個のファイルを開けませんでした -alert.open.failureMofN.details = いくつかのファイルを開くのに失敗しました。それらのファイルが有効なFXMLドキュメントであることを確認してください。 alert.review.question.message = ドキュメントのうち{0}個に未保存の変更があります。終了する前にそれらをレビューしますか。 alert.review.question.details = レビューしないと、変更は失われます。 alert.overwrite.message = ファイル''{0}''は外部で変更されました。上書きしますか。 From 10550c32004c0f6e6012e4fba03e1c987561f2e1 Mon Sep 17 00:00:00 2001 From: Oliver-Loeffler Date: Mon, 18 Mar 2024 22:11:44 +0100 Subject: [PATCH 18/37] Updated license year in test. --- .../app/welcomedialog/WelcomeDialogWindowControllerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/test/java/com/oracle/javafx/scenebuilder/app/welcomedialog/WelcomeDialogWindowControllerTest.java b/app/src/test/java/com/oracle/javafx/scenebuilder/app/welcomedialog/WelcomeDialogWindowControllerTest.java index eae7b6a96..81666f9e7 100644 --- a/app/src/test/java/com/oracle/javafx/scenebuilder/app/welcomedialog/WelcomeDialogWindowControllerTest.java +++ b/app/src/test/java/com/oracle/javafx/scenebuilder/app/welcomedialog/WelcomeDialogWindowControllerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Gluon and/or its affiliates. + * 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: From 77f2067f3a17d59554c2f39d354b462daafaf489 Mon Sep 17 00:00:00 2001 From: Oliver-Loeffler Date: Mon, 18 Mar 2024 22:42:32 +0100 Subject: [PATCH 19/37] Error messages are now shown also when opened from file menu. --- .../com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java index 187e083f7..915b710c4 100644 --- a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java +++ b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java @@ -672,7 +672,8 @@ private void performOpenFile() { if (fxmlFiles != null) { assert fxmlFiles.isEmpty() == false; EditorController.updateNextInitialDirectory(fxmlFiles.get(0)); - performOpenFiles(fxmlFiles); + FileOpenResult openResult = performOpenFiles(fxmlFiles); + showFileOpenErrors(openResult); } } From 1b679d4a83c5feb0ad22e4f244053e9c4b523934 Mon Sep 17 00:00:00 2001 From: Oliver-Loeffler Date: Tue, 19 Mar 2024 17:30:23 +0100 Subject: [PATCH 20/37] Dialog has now right aligned buttons on Windows. --- .../kit/editor/panel/util/dialog/AbstractModalDialogW.fxml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/kit/src/main/resources/com/oracle/javafx/scenebuilder/kit/editor/panel/util/dialog/AbstractModalDialogW.fxml b/kit/src/main/resources/com/oracle/javafx/scenebuilder/kit/editor/panel/util/dialog/AbstractModalDialogW.fxml index c847ccc9f..84284225a 100644 --- a/kit/src/main/resources/com/oracle/javafx/scenebuilder/kit/editor/panel/util/dialog/AbstractModalDialogW.fxml +++ b/kit/src/main/resources/com/oracle/javafx/scenebuilder/kit/editor/panel/util/dialog/AbstractModalDialogW.fxml @@ -1,4 +1,5 @@ + - + + @@ -55,7 +57,7 @@ - + From 2a99f7d4ee3577579f45cf791df4c4b8be993db2 Mon Sep 17 00:00:00 2001 From: Oliver-Loeffler Date: Tue, 19 Mar 2024 18:48:37 +0100 Subject: [PATCH 21/37] Reformatted according to checkstyle. --- .../WelcomeDialogWindowController.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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 c7f2e3aed..deee8cdb7 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 @@ -299,18 +299,18 @@ void handleOpen(List filePaths, List existingFiles = new ArrayList<>(); List missingFiles = new ArrayList<>(); - filePaths.forEach(file->{ - if (filePathExists(file)) { - existingFiles.add(file); - } else { - missingFiles.add(file); - } + filePaths.forEach(file -> { + if (filePathExists(file)) { + existingFiles.add(file); + } else { + missingFiles.add(file); + } }); missingFilesHandler.accept(missingFiles); - + if (existingFiles.isEmpty()) { - return; + return; } fileLoader.accept(existingFiles); From bfb12e970ed7514dd9e5a844427586ebb8f3a34d Mon Sep 17 00:00:00 2001 From: Oliver-Loeffler Date: Tue, 19 Mar 2024 18:50:23 +0100 Subject: [PATCH 22/37] Reformatted according to checkstyle. --- .../scenebuilder/app/SceneBuilderApp.java | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java index 915b710c4..b24a78b3a 100644 --- a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java +++ b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java @@ -565,28 +565,28 @@ private void handleFileOpenResult(FileOpenResult result, Runnable onSuccess) { } /** - * For each file open error (when opened through the welcome dialog), the file - * name and the related exception text are presented to the user to confirm. - * - * @param openResult {@link FileOpenResult} holding a map of files with - * exceptions. - */ + * For each file open error (when opened through the welcome dialog), the file + * name and the related exception text are presented to the user to confirm. + * + * @param openResult {@link FileOpenResult} holding a map of files with + * exceptions. + */ private void showFileOpenErrors(FileOpenResult openResult) { - if (openResult.errors().isEmpty()) { - return; - } - - Map errors = openResult.errors(); - for (Entry error : errors.entrySet()) { - final File fxmlFile = error.getKey(); - final Exception x = error.getValue(); - final ErrorDialog errorDialog = new ErrorDialog(WelcomeDialogWindowController.getInstance().getStage()); - errorDialog.setMessage(I18N.getString("alert.open.failure1.message", displayName(fxmlFile.getPath()))); - errorDialog.setDetails(I18N.getString("alert.open.failure1.details")); - errorDialog.setDebugInfoWithThrowable(x); - errorDialog.setTitle(I18N.getString("alert.open.failure.title")); - errorDialog.showAndWait(); - } + if (openResult.errors().isEmpty()) { + return; + } + + Map errors = openResult.errors(); + for (Entry error : errors.entrySet()) { + final File fxmlFile = error.getKey(); + final Exception x = error.getValue(); + final ErrorDialog errorDialog = new ErrorDialog(WelcomeDialogWindowController.getInstance().getStage()); + errorDialog.setMessage(I18N.getString("alert.open.failure1.message", displayName(fxmlFile.getPath()))); + errorDialog.setDetails(I18N.getString("alert.open.failure1.details")); + errorDialog.setDebugInfoWithThrowable(x); + errorDialog.setTitle(I18N.getString("alert.open.failure.title")); + errorDialog.showAndWait(); + } } @Override From 0089e1fbbe542a9b3dba7bb4b243d6ef052762d7 Mon Sep 17 00:00:00 2001 From: Oliver-Loeffler Date: Tue, 19 Mar 2024 19:12:47 +0100 Subject: [PATCH 23/37] Reworked ErrorDialog to have proper modality and icon. --- .../javafx/scenebuilder/app/SceneBuilderApp.java | 13 ++++++++++++- .../kit/editor/panel/util/dialog/ErrorDialog.java | 13 ++++++++++--- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java index b24a78b3a..8c978ee9c 100644 --- a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java +++ b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java @@ -580,15 +580,26 @@ private void showFileOpenErrors(FileOpenResult openResult) { for (Entry error : errors.entrySet()) { final File fxmlFile = error.getKey(); final Exception x = error.getValue(); - final ErrorDialog errorDialog = new ErrorDialog(WelcomeDialogWindowController.getInstance().getStage()); + final ErrorDialog errorDialog = new ErrorDialog(getOwnerWindow()); errorDialog.setMessage(I18N.getString("alert.open.failure1.message", displayName(fxmlFile.getPath()))); errorDialog.setDetails(I18N.getString("alert.open.failure1.details")); errorDialog.setDebugInfoWithThrowable(x); errorDialog.setTitle(I18N.getString("alert.open.failure.title")); + errorDialog.setDetailsTitle(I18N.getString("alert.open.failure.title")+": "+fxmlFile.getName()); errorDialog.showAndWait(); } } + private Stage getOwnerWindow() { + Stage owner = null; + if (windowList.isEmpty()) { + owner = WelcomeDialogWindowController.getInstance().getStage(); + } else { + owner = windowList.get(0).getStage(); + } + return owner; + } + @Override public void handleMessageBoxFailure(Exception x) { final ErrorDialog errorDialog = new ErrorDialog(null); diff --git a/kit/src/main/java/com/oracle/javafx/scenebuilder/kit/editor/panel/util/dialog/ErrorDialog.java b/kit/src/main/java/com/oracle/javafx/scenebuilder/kit/editor/panel/util/dialog/ErrorDialog.java index 2696e8518..8ee7f5080 100644 --- a/kit/src/main/java/com/oracle/javafx/scenebuilder/kit/editor/panel/util/dialog/ErrorDialog.java +++ b/kit/src/main/java/com/oracle/javafx/scenebuilder/kit/editor/panel/util/dialog/ErrorDialog.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2014, Oracle and/or its affiliates. + * Copyright (c) 2012, 2024, Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * * This file is available and licensed under the following license: @@ -43,6 +43,7 @@ public class ErrorDialog extends AlertDialog { private String debugInfo; + private String detailsTitle; public ErrorDialog(Window owner) { super(owner); @@ -80,7 +81,10 @@ public void setDebugInfoWithThrowable(Throwable t) { setDebugInfo(info); } - + public void setDetailsTitle(String detailsTitle) { + this.detailsTitle = detailsTitle; + } + /* * Private */ @@ -90,8 +94,11 @@ private void updateActionButtonVisibility() { } private void showDetailsDialog() { - final TextViewDialog detailDialog = new TextViewDialog(null); + final TextViewDialog detailDialog = new TextViewDialog(this.getStage()); detailDialog.setText(debugInfo); + if (detailsTitle != null) { + detailDialog.setTitle(detailsTitle); + } detailDialog.showAndWait(); } } From d14bcebb5bf934e4890b6aefe8f8c19e47087ac6 Mon Sep 17 00:00:00 2001 From: Oliver-Loeffler Date: Tue, 19 Mar 2024 19:18:49 +0100 Subject: [PATCH 24/37] Again reworked how ownership is detected to preserve modality. --- .../javafx/scenebuilder/app/SceneBuilderApp.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java index 8c978ee9c..9008c8811 100644 --- a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java +++ b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java @@ -531,7 +531,7 @@ public void handleOpenFilesAction(List files, Runnable onSuccess) { // Fix for #45 if (userLibrary.isFirstExplorationCompleted()) { var openResult = performOpenFiles(fileObjs); - handleFileOpenResult(openResult, onSuccess); + handleFileOpenResult(openResult, onSuccess, WelcomeDialogWindowController.getInstance().getStage()); } else { // open files only after the first exploration has finished userLibrary.firstExplorationCompletedProperty().addListener(new InvalidationListener() { @@ -540,7 +540,7 @@ public void invalidated(Observable observable) { if (userLibrary.isFirstExplorationCompleted()) { var openResult = performOpenFiles(fileObjs); userLibrary.firstExplorationCompletedProperty().removeListener(this); - handleFileOpenResult(openResult, onSuccess); + handleFileOpenResult(openResult, onSuccess, WelcomeDialogWindowController.getInstance().getStage()); } } }); @@ -555,10 +555,11 @@ public void invalidated(Observable observable) { * opened and a map of files with exceptions in case of an * error. * @param onSuccess A runnable holding the action to be performed on success. + * @param owner Owner window ({@link Stage}) */ - private void handleFileOpenResult(FileOpenResult result, Runnable onSuccess) { + private void handleFileOpenResult(FileOpenResult result, Runnable onSuccess, Stage owner) { if (result.hasErrors()) { - showFileOpenErrors(result); + showFileOpenErrors(result, owner); } else { onSuccess.run(); } @@ -570,8 +571,9 @@ private void handleFileOpenResult(FileOpenResult result, Runnable onSuccess) { * * @param openResult {@link FileOpenResult} holding a map of files with * exceptions. + * @param owner Owner {@link Stage} */ - private void showFileOpenErrors(FileOpenResult openResult) { + private void showFileOpenErrors(FileOpenResult openResult, Stage owner) { if (openResult.errors().isEmpty()) { return; } @@ -580,7 +582,7 @@ private void showFileOpenErrors(FileOpenResult openResult) { for (Entry error : errors.entrySet()) { final File fxmlFile = error.getKey(); final Exception x = error.getValue(); - final ErrorDialog errorDialog = new ErrorDialog(getOwnerWindow()); + final ErrorDialog errorDialog = new ErrorDialog(owner); errorDialog.setMessage(I18N.getString("alert.open.failure1.message", displayName(fxmlFile.getPath()))); errorDialog.setDetails(I18N.getString("alert.open.failure1.details")); errorDialog.setDebugInfoWithThrowable(x); @@ -684,7 +686,7 @@ private void performOpenFile() { assert fxmlFiles.isEmpty() == false; EditorController.updateNextInitialDirectory(fxmlFiles.get(0)); FileOpenResult openResult = performOpenFiles(fxmlFiles); - showFileOpenErrors(openResult); + showFileOpenErrors(openResult, getOwnerWindow()); } } From c2f76a2960c0e1d75b54f21c116966ec3fd17715 Mon Sep 17 00:00:00 2001 From: Oliver-Loeffler Date: Thu, 21 Mar 2024 22:49:32 +0100 Subject: [PATCH 25/37] Consolidated calls of performOpenFiles() and handleFileOpenResul() with WelcomeDialogWindow as owner as a new method. --- .../scenebuilder/app/SceneBuilderApp.java | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java index 9008c8811..98d46818c 100644 --- a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java +++ b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java @@ -140,7 +140,7 @@ public static SceneBuilderApp getSingleton() { public SceneBuilderApp() { assert singleton == null; singleton = this; - + // set design time flag java.beans.Beans.setDesignTime(true); @@ -527,26 +527,38 @@ public void handleOpenFilesAction(List files, Runnable onSuccess) { } EditorController.updateNextInitialDirectory(fileObjs.get(0)); - + // Fix for #45 if (userLibrary.isFirstExplorationCompleted()) { - var openResult = performOpenFiles(fileObjs); - handleFileOpenResult(openResult, onSuccess, WelcomeDialogWindowController.getInstance().getStage()); + performOpenFiles(fileObjs, onSuccess); } else { // open files only after the first exploration has finished userLibrary.firstExplorationCompletedProperty().addListener(new InvalidationListener() { @Override public void invalidated(Observable observable) { if (userLibrary.isFirstExplorationCompleted()) { - var openResult = performOpenFiles(fileObjs); userLibrary.firstExplorationCompletedProperty().removeListener(this); - handleFileOpenResult(openResult, onSuccess, WelcomeDialogWindowController.getInstance().getStage()); + performOpenFiles(fileObjs, onSuccess); } } }); } } + /** + * Opens the given list of files. For all resulting dialogs (success, error), + * the welcome dialog window is the configured owner. + * + * @param files List of files which SceneBuilder will attempt to open. + * @param onSuccess A runnable holding the action to be performed after + * successfully loading the files. This is only executed when + * all files have been opened without errors. + */ + private void performOpenFiles(final List files, Runnable onSuccess) { + var openResult = performOpenFiles(files); + handleFileOpenResult(openResult, onSuccess, WelcomeDialogWindowController.getInstance().getStage()); + } + /** * The onSuccess Runnable is only executed if the {@link FileOpenResult} has no * errors at all. From 19cabebd0475a8b2854327320735203cde1846c9 Mon Sep 17 00:00:00 2001 From: Oliver-Loeffler Date: Thu, 21 Mar 2024 22:56:23 +0100 Subject: [PATCH 26/37] Licens header updated. --- .../java/com/oracle/javafx/scenebuilder/app/JfxInitializer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/test/java/com/oracle/javafx/scenebuilder/app/JfxInitializer.java b/app/src/test/java/com/oracle/javafx/scenebuilder/app/JfxInitializer.java index 0411ba448..5a29e1636 100644 --- a/app/src/test/java/com/oracle/javafx/scenebuilder/app/JfxInitializer.java +++ b/app/src/test/java/com/oracle/javafx/scenebuilder/app/JfxInitializer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2022, Gluon and/or its affiliates. + * Copyright (c) 2021, 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: From 45f2ce848a9e85f41f5fbb0184f60ac124d8e6d8 Mon Sep 17 00:00:00 2001 From: Oliver-Loeffler Date: Mon, 25 Mar 2024 22:19:44 +0100 Subject: [PATCH 27/37] Reworked consolidation so, that the FileOpenResult record is no longer used. --- .../scenebuilder/app/SceneBuilderApp.java | 129 ++++++++---------- 1 file changed, 54 insertions(+), 75 deletions(-) diff --git a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java index 98d46818c..2e041db8e 100644 --- a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java +++ b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java @@ -32,6 +32,28 @@ */ package com.oracle.javafx.scenebuilder.app; +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.concurrent.CountDownLatch; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.Logger; + import com.oracle.javafx.scenebuilder.app.DocumentWindowController.ActionStatus; import com.oracle.javafx.scenebuilder.app.about.AboutWindowController; import com.oracle.javafx.scenebuilder.app.i18n.I18N; @@ -60,6 +82,7 @@ import com.oracle.javafx.scenebuilder.kit.template.TemplatesWindowController; import com.oracle.javafx.scenebuilder.kit.template.Type; import com.oracle.javafx.scenebuilder.kit.util.control.effectpicker.EffectPicker; + import javafx.application.Application; import javafx.application.Platform; import javafx.beans.InvalidationListener; @@ -74,22 +97,6 @@ import javafx.stage.FileChooser; import javafx.stage.Stage; -import java.io.File; -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.time.LocalDate; -import java.util.*; -import java.util.Map.Entry; -import java.util.concurrent.CountDownLatch; -import java.util.function.Consumer; -import java.util.logging.Level; -import java.util.logging.LogManager; -import java.util.logging.Logger; - /** * This is the SB main entry point. */ @@ -266,7 +273,7 @@ public void performOpenRecent(DocumentWindowController source, final File fxmlFi final List fxmlFiles = new ArrayList<>(); fxmlFiles.add(fxmlFile); - performOpenFiles(fxmlFiles); + performOpenFiles(fxmlFiles,r->showFileOpenErrors(r, getOwnerWindow()), ()->{}); } public void documentWindowRequestClose(DocumentWindowController fromWindow) { @@ -527,10 +534,10 @@ public void handleOpenFilesAction(List files, Runnable onSuccess) { } EditorController.updateNextInitialDirectory(fileObjs.get(0)); - + Supplier ownerWindow = ()->WelcomeDialogWindowController.getInstance().getStage(); // Fix for #45 if (userLibrary.isFirstExplorationCompleted()) { - performOpenFiles(fileObjs, onSuccess); + performOpenFiles(fileObjs, errors->showFileOpenErrors(errors, ownerWindow), onSuccess); } else { // open files only after the first exploration has finished userLibrary.firstExplorationCompletedProperty().addListener(new InvalidationListener() { @@ -538,63 +545,31 @@ public void handleOpenFilesAction(List files, Runnable onSuccess) { public void invalidated(Observable observable) { if (userLibrary.isFirstExplorationCompleted()) { userLibrary.firstExplorationCompletedProperty().removeListener(this); - performOpenFiles(fileObjs, onSuccess); + performOpenFiles(fileObjs, errors->showFileOpenErrors(errors, ownerWindow), onSuccess); } } }); } } - /** - * Opens the given list of files. For all resulting dialogs (success, error), - * the welcome dialog window is the configured owner. - * - * @param files List of files which SceneBuilder will attempt to open. - * @param onSuccess A runnable holding the action to be performed after - * successfully loading the files. This is only executed when - * all files have been opened without errors. - */ - private void performOpenFiles(final List files, Runnable onSuccess) { - var openResult = performOpenFiles(files); - handleFileOpenResult(openResult, onSuccess, WelcomeDialogWindowController.getInstance().getStage()); - } - - /** - * The onSuccess Runnable is only executed if the {@link FileOpenResult} has no - * errors at all. - * - * @param result {@link FileOpenResult} holding the list of files to be - * opened and a map of files with exceptions in case of an - * error. - * @param onSuccess A runnable holding the action to be performed on success. - * @param owner Owner window ({@link Stage}) - */ - private void handleFileOpenResult(FileOpenResult result, Runnable onSuccess, Stage owner) { - if (result.hasErrors()) { - showFileOpenErrors(result, owner); - } else { - onSuccess.run(); - } - } - /** * For each file open error (when opened through the welcome dialog), the file * name and the related exception text are presented to the user to confirm. + * For an empty collection, no dialog is displayed. * - * @param openResult {@link FileOpenResult} holding a map of files with - * exceptions. - * @param owner Owner {@link Stage} + * @param errors A {@link Map} with having the file to be opened as the key and + * the occurred {@link Exception} as value. exceptions. + * @param owner Owner Supplier function to obtain the owners {@link Stage} */ - private void showFileOpenErrors(FileOpenResult openResult, Stage owner) { - if (openResult.errors().isEmpty()) { + private void showFileOpenErrors(Map errors, Supplier owner) { + if (errors.isEmpty()) { return; } - Map errors = openResult.errors(); for (Entry error : errors.entrySet()) { final File fxmlFile = error.getKey(); final Exception x = error.getValue(); - final ErrorDialog errorDialog = new ErrorDialog(owner); + final ErrorDialog errorDialog = new ErrorDialog(owner.get()); errorDialog.setMessage(I18N.getString("alert.open.failure1.message", displayName(fxmlFile.getPath()))); errorDialog.setDetails(I18N.getString("alert.open.failure1.details")); errorDialog.setDebugInfoWithThrowable(x); @@ -604,14 +579,14 @@ private void showFileOpenErrors(FileOpenResult openResult, Stage owner) { } } - private Stage getOwnerWindow() { - Stage owner = null; - if (windowList.isEmpty()) { - owner = WelcomeDialogWindowController.getInstance().getStage(); - } else { - owner = windowList.get(0).getStage(); - } - return owner; + private Supplier getOwnerWindow() { + return ()->{ + if (windowList.isEmpty()) { + return WelcomeDialogWindowController.getInstance().getStage(); + } else { + return windowList.get(0).getStage(); + } + }; } @Override @@ -697,8 +672,7 @@ private void performOpenFile() { if (fxmlFiles != null) { assert fxmlFiles.isEmpty() == false; EditorController.updateNextInitialDirectory(fxmlFiles.get(0)); - FileOpenResult openResult = performOpenFiles(fxmlFiles); - showFileOpenErrors(openResult, getOwnerWindow()); + performOpenFiles(fxmlFiles, (errors)->showFileOpenErrors(errors, getOwnerWindow()), ()->{}); } } @@ -746,12 +720,12 @@ public DocumentWindowController getFrontDocumentWindow() { return null; } - private FileOpenResult performOpenFiles(List fxmlFiles) { + private void performOpenFiles(List fxmlFiles, Consumer> onError, Runnable onSuccess) { assert fxmlFiles != null; assert fxmlFiles.isEmpty() == false; LOGGER.log(Level.FINE, "Opening {0} files...", fxmlFiles.size()); - final Map exceptions = new HashMap<>(); + final Map exceptionsPerFile = new HashMap<>(); final List openedFiles = new ArrayList<>(); for (File fxmlFile : fxmlFiles) { LOGGER.log(Level.FINE, "Attempting to open file {0}", fxmlFile); @@ -771,7 +745,7 @@ private FileOpenResult performOpenFiles(List fxmlFiles) { } } catch (Exception xx) { LOGGER.log(Level.WARNING, "Failed to open file: %s".formatted(fxmlFile), xx); - exceptions.put(fxmlFile, xx); + exceptionsPerFile.put(fxmlFile, xx); } } @@ -780,12 +754,17 @@ private FileOpenResult performOpenFiles(List fxmlFiles) { final PreferencesController pc = PreferencesController.getSingleton(); pc.getRecordGlobal().addRecentItems(openedFiles); } - if (exceptions.size() > 0) { - LOGGER.log(Level.WARNING, "Failed to open {0} of {1} files!", new Object[] {exceptions.size(), fxmlFiles.size()}); + if (exceptionsPerFile.size() > 0) { + LOGGER.log(Level.WARNING, "Failed to open {0} of {1} files!", new Object[] {exceptionsPerFile.size(), fxmlFiles.size()}); } else { LOGGER.log(Level.FINE, "Successfully opened all files."); } - return new FileOpenResult(fxmlFiles, exceptions); + + if (exceptionsPerFile.isEmpty()) { + onSuccess.run(); + } else { + onError.accept(exceptionsPerFile); + } } private void performExit() { From eed0bf9d452ca9697d539eee1acec98a18ef7882 Mon Sep 17 00:00:00 2001 From: Oliver-Loeffler Date: Mon, 25 Mar 2024 22:20:06 +0100 Subject: [PATCH 28/37] Removed FileOpenRecord. --- .../javafx/scenebuilder/app/SceneBuilderApp.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java index 2e041db8e..760ca2db8 100644 --- a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java +++ b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java @@ -1112,16 +1112,4 @@ public static void applyToAllDocumentWindows(Consumer consumer.accept(dwc); } } - - /** - * Describes the result of FXML file loading process. As in general SceneBuilder - * can open multiple files at the same time. Hence this record can hold of the - * files supposed to be opened. In case of errors, the exceptions are stored in - * a map per file. - */ - record FileOpenResult(List filesToOpen, Map errors) { - public boolean hasErrors() { - return !errors.isEmpty(); - } - } } From 4bb357a4017286db3dbf44565f104a8e9fc038ba Mon Sep 17 00:00:00 2001 From: Oliver-Loeffler Date: Mon, 25 Mar 2024 22:23:45 +0100 Subject: [PATCH 29/37] Reorganized imports. --- .../scenebuilder/app/SceneBuilderApp.java | 40 ++++++++----------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java index 760ca2db8..a1b5b2079 100644 --- a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java +++ b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java @@ -32,28 +32,6 @@ */ package com.oracle.javafx.scenebuilder.app; -import java.io.File; -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Optional; -import java.util.concurrent.CountDownLatch; -import java.util.function.Consumer; -import java.util.function.Supplier; -import java.util.logging.Level; -import java.util.logging.LogManager; -import java.util.logging.Logger; - import com.oracle.javafx.scenebuilder.app.DocumentWindowController.ActionStatus; import com.oracle.javafx.scenebuilder.app.about.AboutWindowController; import com.oracle.javafx.scenebuilder.app.i18n.I18N; @@ -82,7 +60,6 @@ import com.oracle.javafx.scenebuilder.kit.template.TemplatesWindowController; import com.oracle.javafx.scenebuilder.kit.template.Type; import com.oracle.javafx.scenebuilder.kit.util.control.effectpicker.EffectPicker; - import javafx.application.Application; import javafx.application.Platform; import javafx.beans.InvalidationListener; @@ -97,6 +74,23 @@ import javafx.stage.FileChooser; import javafx.stage.Stage; +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDate; +import java.util.*; +import java.util.function.*; +import java.util.Map.Entry; +import java.util.concurrent.CountDownLatch; +import java.util.function.Consumer; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.Logger; + /** * This is the SB main entry point. */ From a3e68ec63171972f8f0f15441b9746d143cb79e8 Mon Sep 17 00:00:00 2001 From: Oliver-Loeffler Date: Mon, 25 Mar 2024 23:10:37 +0100 Subject: [PATCH 30/37] Removed conditional for logging. --- .../oracle/javafx/scenebuilder/app/SceneBuilderApp.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java index a1b5b2079..b75eb734d 100644 --- a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java +++ b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java @@ -748,15 +748,12 @@ private void performOpenFiles(List fxmlFiles, Consumer 0) { - LOGGER.log(Level.WARNING, "Failed to open {0} of {1} files!", new Object[] {exceptionsPerFile.size(), fxmlFiles.size()}); - } else { - LOGGER.log(Level.FINE, "Successfully opened all files."); - } if (exceptionsPerFile.isEmpty()) { + LOGGER.log(Level.FINE, "Successfully opened all files."); onSuccess.run(); } else { + LOGGER.log(Level.WARNING, "Failed to open {0} of {1} files!", new Object[] {exceptionsPerFile.size(), fxmlFiles.size()}); onError.accept(exceptionsPerFile); } } From 3f4cdff43a86a6d65c48029bfc5d9479392dd7ef Mon Sep 17 00:00:00 2001 From: Oliver-Loeffler Date: Mon, 25 Mar 2024 23:20:57 +0100 Subject: [PATCH 31/37] Removed the supplier. --- .../scenebuilder/app/SceneBuilderApp.java | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java index b75eb734d..be92fd990 100644 --- a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java +++ b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java @@ -528,10 +528,13 @@ public void handleOpenFilesAction(List files, Runnable onSuccess) { } EditorController.updateNextInitialDirectory(fileObjs.get(0)); - Supplier ownerWindow = ()->WelcomeDialogWindowController.getInstance().getStage(); + + Consumer> onError = errors->showFileOpenErrors(errors, + ()->WelcomeDialogWindowController.getInstance().getStage()); + // Fix for #45 if (userLibrary.isFirstExplorationCompleted()) { - performOpenFiles(fileObjs, errors->showFileOpenErrors(errors, ownerWindow), onSuccess); + performOpenFiles(fileObjs, onError, onSuccess); } else { // open files only after the first exploration has finished userLibrary.firstExplorationCompletedProperty().addListener(new InvalidationListener() { @@ -539,7 +542,7 @@ public void handleOpenFilesAction(List files, Runnable onSuccess) { public void invalidated(Observable observable) { if (userLibrary.isFirstExplorationCompleted()) { userLibrary.firstExplorationCompletedProperty().removeListener(this); - performOpenFiles(fileObjs, errors->showFileOpenErrors(errors, ownerWindow), onSuccess); + performOpenFiles(fileObjs, onError, onSuccess); } } }); @@ -574,13 +577,9 @@ private void showFileOpenErrors(Map errors, Supplier owne } private Supplier getOwnerWindow() { - return ()->{ - if (windowList.isEmpty()) { - return WelcomeDialogWindowController.getInstance().getStage(); - } else { - return windowList.get(0).getStage(); - } - }; + return ()->findFirstUnusedDocumentWindowController() + .map(DocumentWindowController::getStage) + .orElse(WelcomeDialogWindowController.getInstance().getStage()); } @Override From bc791e226ce79ad8d4b6cfa69461392f5062254b Mon Sep 17 00:00:00 2001 From: Oliver-Loeffler Date: Mon, 25 Mar 2024 23:22:11 +0100 Subject: [PATCH 32/37] Formatting. --- .../com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java index be92fd990..777719888 100644 --- a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java +++ b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java @@ -665,7 +665,7 @@ private void performOpenFile() { if (fxmlFiles != null) { assert fxmlFiles.isEmpty() == false; EditorController.updateNextInitialDirectory(fxmlFiles.get(0)); - performOpenFiles(fxmlFiles, (errors)->showFileOpenErrors(errors, getOwnerWindow()), ()->{}); + performOpenFiles(fxmlFiles, errors -> showFileOpenErrors(errors, getOwnerWindow()), ()->{}); } } From 38f3729d5ec22da1128882ebfc99575ed3796b03 Mon Sep 17 00:00:00 2001 From: Oliver-Loeffler Date: Mon, 25 Mar 2024 23:28:01 +0100 Subject: [PATCH 33/37] Added an override for performOpenFiles and applied formatting corrections. --- .../scenebuilder/app/SceneBuilderApp.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java index 777719888..e1e4cc5c4 100644 --- a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java +++ b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java @@ -267,7 +267,7 @@ public void performOpenRecent(DocumentWindowController source, final File fxmlFi final List fxmlFiles = new ArrayList<>(); fxmlFiles.add(fxmlFile); - performOpenFiles(fxmlFiles,r->showFileOpenErrors(r, getOwnerWindow()), ()->{}); + performOpenFiles(fxmlFiles); } public void documentWindowRequestClose(DocumentWindowController fromWindow) { @@ -529,8 +529,8 @@ public void handleOpenFilesAction(List files, Runnable onSuccess) { EditorController.updateNextInitialDirectory(fileObjs.get(0)); - Consumer> onError = errors->showFileOpenErrors(errors, - ()->WelcomeDialogWindowController.getInstance().getStage()); + Consumer> onError = errors -> showFileOpenErrors(errors, + () -> WelcomeDialogWindowController.getInstance().getStage()); // Fix for #45 if (userLibrary.isFirstExplorationCompleted()) { @@ -558,7 +558,7 @@ public void invalidated(Observable observable) { * the occurred {@link Exception} as value. exceptions. * @param owner Owner Supplier function to obtain the owners {@link Stage} */ - private void showFileOpenErrors(Map errors, Supplier owner) { + private void showFileOpenErrors(Map errors, Supplier owner) { if (errors.isEmpty()) { return; } @@ -571,13 +571,13 @@ private void showFileOpenErrors(Map errors, Supplier owne errorDialog.setDetails(I18N.getString("alert.open.failure1.details")); errorDialog.setDebugInfoWithThrowable(x); errorDialog.setTitle(I18N.getString("alert.open.failure.title")); - errorDialog.setDetailsTitle(I18N.getString("alert.open.failure.title")+": "+fxmlFile.getName()); + errorDialog.setDetailsTitle(I18N.getString("alert.open.failure.title") + ": " + fxmlFile.getName()); errorDialog.showAndWait(); } } private Supplier getOwnerWindow() { - return ()->findFirstUnusedDocumentWindowController() + return () -> findFirstUnusedDocumentWindowController() .map(DocumentWindowController::getStage) .orElse(WelcomeDialogWindowController.getInstance().getStage()); } @@ -665,7 +665,7 @@ private void performOpenFile() { if (fxmlFiles != null) { assert fxmlFiles.isEmpty() == false; EditorController.updateNextInitialDirectory(fxmlFiles.get(0)); - performOpenFiles(fxmlFiles, errors -> showFileOpenErrors(errors, getOwnerWindow()), ()->{}); + performOpenFiles(fxmlFiles); } } @@ -713,6 +713,10 @@ public DocumentWindowController getFrontDocumentWindow() { return null; } + private void performOpenFiles(List fxmlFiles) { + performOpenFiles(fxmlFiles, r -> showFileOpenErrors(r, getOwnerWindow()), () -> { /* no action here */ } ); + } + private void performOpenFiles(List fxmlFiles, Consumer> onError, Runnable onSuccess) { assert fxmlFiles != null; assert fxmlFiles.isEmpty() == false; From 323e4945e81dd1e8cc86cd7c04a44179ff099307 Mon Sep 17 00:00:00 2001 From: Oliver-Loeffler Date: Wed, 27 Mar 2024 18:00:53 +0100 Subject: [PATCH 34/37] Replaced wildcards with fully qualified names. --- .../oracle/javafx/scenebuilder/app/SceneBuilderApp.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java index e1e4cc5c4..351a99732 100644 --- a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java +++ b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java @@ -82,11 +82,16 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.time.LocalDate; -import java.util.*; -import java.util.function.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Map.Entry; +import java.util.Optional; import java.util.concurrent.CountDownLatch; import java.util.function.Consumer; +import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.LogManager; import java.util.logging.Logger; From 1c945e4a6df883cb0182f787702fb962ac674164 Mon Sep 17 00:00:00 2001 From: Oliver-Loeffler Date: Wed, 27 Mar 2024 18:09:30 +0100 Subject: [PATCH 35/37] Removed unused keys from Chinese translation. --- .../scenebuilder/app/i18n/SceneBuilderApp_zh_CN.properties | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/src/main/resources/com/oracle/javafx/scenebuilder/app/i18n/SceneBuilderApp_zh_CN.properties b/app/src/main/resources/com/oracle/javafx/scenebuilder/app/i18n/SceneBuilderApp_zh_CN.properties index 39a31e900..5a5b839c9 100644 --- a/app/src/main/resources/com/oracle/javafx/scenebuilder/app/i18n/SceneBuilderApp_zh_CN.properties +++ b/app/src/main/resources/com/oracle/javafx/scenebuilder/app/i18n/SceneBuilderApp_zh_CN.properties @@ -397,12 +397,9 @@ alert.title.copy = 复制 alert.title.start = 启动 alert.title.messagebox = 外部打开 +alert.open.failure.title = File Open Error alert.open.failure1.message = 无法打开 ''{0}'' alert.open.failure1.details = 打开操作失败。确保所选文件是有效的 FXML 文档。 -alert.open.failureN.message = 无法打开指定的文件 -alert.open.failureN.details = 打开操作失败。请确保这些文件是有效的 FXML 文档。 -alert.open.failureMofN.message = 无法打开 {0} 文件 -alert.open.failureMofN.details = 某些文件的打开操作失败。请确保这些文件是有效的 FXML 文档。 alert.review.question.message = {0} 文档包含未保存的更改。您想在退出之前查看它们吗? alert.review.question.details = 如果您不查看更改,则更改将丢失。 alert.overwrite.message = 文件 ''{0}'' 已在外部修改。你想覆盖它吗? From 0df317d7bcdcee3c9f3b41fdd56509d3e20e4d08 Mon Sep 17 00:00:00 2001 From: Oliver-Loeffler Date: Wed, 27 Mar 2024 18:10:22 +0100 Subject: [PATCH 36/37] Corrected license header for this file to state only 2024 as inception year. --- .../java/com/oracle/javafx/scenebuilder/app/JfxInitializer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/test/java/com/oracle/javafx/scenebuilder/app/JfxInitializer.java b/app/src/test/java/com/oracle/javafx/scenebuilder/app/JfxInitializer.java index 5a29e1636..78a9cb1ca 100644 --- a/app/src/test/java/com/oracle/javafx/scenebuilder/app/JfxInitializer.java +++ b/app/src/test/java/com/oracle/javafx/scenebuilder/app/JfxInitializer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2024, Gluon and/or its affiliates. + * 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: From ff9f3caf6ae7e49388c2388d54147361e42c042e Mon Sep 17 00:00:00 2001 From: Oliver-Loeffler Date: Wed, 27 Mar 2024 18:11:58 +0100 Subject: [PATCH 37/37] Removed english text from zh_CN translation. --- .../scenebuilder/app/i18n/SceneBuilderApp_zh_CN.properties | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/resources/com/oracle/javafx/scenebuilder/app/i18n/SceneBuilderApp_zh_CN.properties b/app/src/main/resources/com/oracle/javafx/scenebuilder/app/i18n/SceneBuilderApp_zh_CN.properties index 5a5b839c9..0764eba7b 100644 --- a/app/src/main/resources/com/oracle/javafx/scenebuilder/app/i18n/SceneBuilderApp_zh_CN.properties +++ b/app/src/main/resources/com/oracle/javafx/scenebuilder/app/i18n/SceneBuilderApp_zh_CN.properties @@ -397,7 +397,6 @@ alert.title.copy = 复制 alert.title.start = 启动 alert.title.messagebox = 外部打开 -alert.open.failure.title = File Open Error alert.open.failure1.message = 无法打开 ''{0}'' alert.open.failure1.details = 打开操作失败。确保所选文件是有效的 FXML 文档。 alert.review.question.message = {0} 文档包含未保存的更改。您想在退出之前查看它们吗?