Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: SceneBuilder will not load missing files from recent projects. #585

Merged
merged 44 commits into from
Mar 29, 2024
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
1641ee4
fix: Scene Builder will ask to remove missing files from list of rece…
Oliver-Loeffler Oct 10, 2022
b303f9d
fix: incremented version to 20.0.0-SNAPSHOT. (#598)
Oliver-Loeffler Oct 31, 2022
484c4dd
build: Moved maven surefire plugin from kit-pom to parent-pom so that…
Oliver-Loeffler Nov 8, 2022
4cd7775
build: Add Maven Wrapper to the project (#588)
Oliver-Loeffler Nov 2, 2022
484a1fe
fix: Removed FX8 qualifier in BuiltInLibrary (#592)
Oliver-Loeffler Nov 2, 2022
437dadc
fix: Disabled mnemonic parsing on RecentDocumentsButton instances on …
Oliver-Loeffler Nov 2, 2022
e676e1a
Modified error handling so that WelcomePage is no longer closed after…
Oliver-Loeffler Nov 7, 2022
8d14862
Merge branch 'gluonhq:master' into issue-582
Oliver-Loeffler Nov 8, 2022
f11a8a1
Added warning and informative logging for cases of removing files fro…
Oliver-Loeffler Nov 8, 2022
8ef7724
Merge branch 'gluonhq:master' into issue-582
Oliver-Loeffler Mar 27, 2023
9a1a7ce
Formatted according to checkstyle rules.
Oliver-Loeffler Mar 27, 2023
3e4ed5e
Merge branch 'issue-582' of [email protected]:Oliver-Loeffler/scenebuild…
Oliver-Loeffler Mar 27, 2023
8d9a36f
Merge branch 'gluonhq:master' into issue-582
Oliver-Loeffler Mar 30, 2023
8ccdefb
Reworked message which is shown, when the FXML file to be loaded is m…
Oliver-Loeffler Mar 30, 2023
f0aef40
Merge branch 'gluonhq:master' into issue-582
Oliver-Loeffler Mar 18, 2024
3a777a7
Instead of passing null, an empty lambda is passed as runnable.
Oliver-Loeffler Mar 18, 2024
d26800e
Updated year in license header.
Oliver-Loeffler Mar 18, 2024
3eb469f
Reworked code according to review comment to make intent more clear.
Oliver-Loeffler Mar 18, 2024
b08f262
Simplified dialog where user is asked wether to keep or remove missin…
Oliver-Loeffler Mar 18, 2024
8318695
Reworked Missing Project Files dialog as proposed by reviewer.
Oliver-Loeffler Mar 18, 2024
d5e3bc0
Updated error dialog according to code review recommendation.
Oliver-Loeffler Mar 18, 2024
0dcbf6f
Removed no longer needed I18N keys.
Oliver-Loeffler Mar 18, 2024
10550c3
Updated license year in test.
Oliver-Loeffler Mar 18, 2024
77f2067
Error messages are now shown also when opened from file menu.
Oliver-Loeffler Mar 18, 2024
1b679d4
Dialog has now right aligned buttons on Windows.
Oliver-Loeffler Mar 19, 2024
2a99f7d
Reformatted according to checkstyle.
Oliver-Loeffler Mar 19, 2024
bfb12e9
Reformatted according to checkstyle.
Oliver-Loeffler Mar 19, 2024
0089e1f
Reworked ErrorDialog to have proper modality and icon.
Oliver-Loeffler Mar 19, 2024
d14bceb
Again reworked how ownership is detected to preserve modality.
Oliver-Loeffler Mar 19, 2024
c2f76a2
Consolidated calls of performOpenFiles() and handleFileOpenResul() wi…
Oliver-Loeffler Mar 21, 2024
19cabeb
Licens header updated.
Oliver-Loeffler Mar 21, 2024
45f2ce8
Reworked consolidation so, that the FileOpenResult record is no longe…
Oliver-Loeffler Mar 25, 2024
eed0bf9
Removed FileOpenRecord.
Oliver-Loeffler Mar 25, 2024
ccff81c
Merge branch 'gluonhq:master' into issue-582
Oliver-Loeffler Mar 25, 2024
4bb357a
Reorganized imports.
Oliver-Loeffler Mar 25, 2024
a3e68ec
Removed conditional for logging.
Oliver-Loeffler Mar 25, 2024
3f4cdff
Removed the supplier.
Oliver-Loeffler Mar 25, 2024
bc791e2
Formatting.
Oliver-Loeffler Mar 25, 2024
38f3729
Added an override for performOpenFiles and applied formatting correct…
Oliver-Loeffler Mar 25, 2024
323e494
Replaced wildcards with fully qualified names.
Oliver-Loeffler Mar 27, 2024
1aea4be
Merge branch 'gluonhq:master' into issue-582
Oliver-Loeffler Mar 27, 2024
1c945e4
Removed unused keys from Chinese translation.
Oliver-Loeffler Mar 27, 2024
0df317d
Corrected license header for this file to state only 2024 as inceptio…
Oliver-Loeffler Mar 27, 2024
ff9f3ca
Removed english text from zh_CN translation.
Oliver-Loeffler Mar 27, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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.
*
Expand Down Expand Up @@ -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;
Expand All @@ -94,6 +95,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,
Expand Down Expand Up @@ -496,8 +499,25 @@ 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<String> 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<String> files) {
handleOpenFilesAction(files, () -> { /* no operation in this case */ });
}

/**
* 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<String> files, Runnable onSuccess) {
assert files != null;
assert files.isEmpty() == false;

Expand All @@ -510,21 +530,90 @@ public void handleOpenFilesAction(List<String> files) {

// Fix for #45
if (userLibrary.isFirstExplorationCompleted()) {
performOpenFiles(fileObjs);
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()) {
performOpenFiles(fileObjs);
Oliver-Loeffler marked this conversation as resolved.
Show resolved Hide resolved
userLibrary.firstExplorationCompletedProperty().removeListener(this);
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<File> 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.
*
* @param openResult {@link FileOpenResult} holding a map of files with
* exceptions.
* @param owner Owner {@link Stage}
*/
private void showFileOpenErrors(FileOpenResult openResult, Stage owner) {
if (openResult.errors().isEmpty()) {
return;
}

Map<File, Exception> errors = openResult.errors();
for (Entry<File, Exception> error : errors.entrySet()) {
final File fxmlFile = error.getKey();
final Exception x = error.getValue();
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);
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);
Expand Down Expand Up @@ -608,7 +697,8 @@ private void performOpenFile() {
if (fxmlFiles != null) {
assert fxmlFiles.isEmpty() == false;
EditorController.updateNextInitialDirectory(fxmlFiles.get(0));
performOpenFiles(fxmlFiles);
FileOpenResult openResult = performOpenFiles(fxmlFiles);
showFileOpenErrors(openResult, getOwnerWindow());
}
}

Expand Down Expand Up @@ -656,12 +746,15 @@ public DocumentWindowController getFrontDocumentWindow() {
return null;
}

private void performOpenFiles(List<File> fxmlFiles) {
private FileOpenResult performOpenFiles(List<File> fxmlFiles) {
Oliver-Loeffler marked this conversation as resolved.
Show resolved Hide resolved
assert fxmlFiles != null;
assert fxmlFiles.isEmpty() == false;

LOGGER.log(Level.FINE, "Opening {0} files...", fxmlFiles.size());
final Map<File, Exception> exceptions = new HashMap<>();
final List<File> 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());
Expand All @@ -673,47 +766,26 @@ private void performOpenFiles(List<File> fxmlFiles) {
var hostWindow = findFirstUnusedDocumentWindowController().orElse(makeNewWindow());
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);
}
}

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);
}
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);
}

private void performExit() {
Expand Down Expand Up @@ -1061,4 +1133,16 @@ public static void applyToAllDocumentWindows(Consumer<DocumentWindowController>
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<File> filesToOpen, Map<File, Exception> errors) {
public boolean hasErrors() {
return !errors.isEmpty();
}
}
}
Loading