diff --git a/app/src/main/java/com/oracle/javafx/scenebuilder/app/DocumentWindowController.java b/app/src/main/java/com/oracle/javafx/scenebuilder/app/DocumentWindowController.java index 696dfbbc3e..8820b83db7 100644 --- a/app/src/main/java/com/oracle/javafx/scenebuilder/app/DocumentWindowController.java +++ b/app/src/main/java/com/oracle/javafx/scenebuilder/app/DocumentWindowController.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2024, Gluon and/or its affiliates. + * Copyright (c) 2016, 2025, Gluon and/or its affiliates. * Copyright (c) 2012, 2014, Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * @@ -69,6 +69,7 @@ import com.oracle.javafx.scenebuilder.kit.editor.selection.AbstractSelectionGroup; import com.oracle.javafx.scenebuilder.kit.editor.selection.ObjectSelectionGroup; import com.oracle.javafx.scenebuilder.kit.fxom.FXOMDocument; +import com.oracle.javafx.scenebuilder.kit.fxom.FXOMDocument.FXOMDocumentSwitch; import com.oracle.javafx.scenebuilder.kit.fxom.FXOMNodes; import com.oracle.javafx.scenebuilder.kit.fxom.FXOMObject; import com.oracle.javafx.scenebuilder.kit.library.Library; @@ -98,6 +99,7 @@ import java.util.Optional; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.stream.Collectors; import javafx.beans.InvalidationListener; import javafx.beans.binding.Bindings; @@ -125,6 +127,7 @@ import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.stage.FileChooser; +import javafx.stage.Stage; import javafx.stage.FileChooser.ExtensionFilter; import javafx.stage.Window; import javafx.stage.WindowEvent; @@ -409,7 +412,11 @@ public SplitController getDocumentSplitController() { public void loadFromFile(File fxmlFile) throws IOException { final URL fxmlURL = fxmlFile.toURI().toURL(); final String fxmlText = FXOMDocument.readContentFromURL(fxmlURL); - editorController.setFxmlTextAndLocation(fxmlText, fxmlURL, false); + + FXOMDocumentSwitch[] options = FXOMDocumentSwitch.PRESERVE_UNRESOLVED_IMPORTS + .fromToggle(getPreferencesRecordGlobal().isPreserveUnresolvedImports()); + + editorController.setFxmlTextAndLocation(fxmlText, fxmlURL, false, options); updateLoadFileTime(); updateStageTitle(); // No-op if fxml has not been loaded yet updateFromDocumentPreferences(true); @@ -2362,6 +2369,37 @@ private PreferencesRecordGlobal getPreferencesRecordGlobal() { } return recordGlobal; } + + public void showMissingTypesNotificationIfNeeded(File fxmlFile) { + FXOMDocument doc = getEditorController().getFxomDocument(); + if (doc.hasUnresolvableTypes()) { + notifyUserAboutUnresolvableImports(fxmlFile, doc.getUnresolvableTypes(), getStage()); + } + } + + private void notifyUserAboutUnresolvableImports(File fxmlFile, List missingTypes, Stage owner) { + LOGGER.log(Level.WARNING, "Detected unresolved types in FXML: {0}: {1}", + new Object[] {fxmlFile, String.join(",", missingTypes) }); + + final ErrorDialog errorDialog = new ErrorDialog(owner); + String first10 = missingTypes.stream() + .limit(10) + .collect(Collectors.joining(";" + System.lineSeparator())); + + errorDialog.setMessage(I18N.getString("alert.open.failure.unresolved.imports", + Integer.toString(missingTypes.size()))); + errorDialog.setDetails(I18N.getString("alert.open.failure.unresolved.imports.details", first10)); + errorDialog.setDetailsTitle(I18N.getString("alert.open.failure.unresolved.imports", + Integer.toString(missingTypes.size()))); + + String allMissing = missingTypes.stream() + .collect(Collectors.joining(";" + System.lineSeparator())); + + errorDialog.setDebugInfo(I18N.getString("alert.open.failure.unresolved.imports.advice", + allMissing, missingTypes.size())); + errorDialog.setTitle(I18N.getString("alert.title.open") + ": " + fxmlFile.getName()); + errorDialog.showAndWait(); + } } ///** 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 99033c5f7f..befd604bd6 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, 2024, Gluon and/or its affiliates. + * Copyright (c) 2016, 2025, Gluon and/or its affiliates. * Copyright (c) 2012, 2014, Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * @@ -740,6 +740,7 @@ private void performOpenFiles(List fxmlFiles, Consumer recentItems = new ArrayList<>(); private LocalDate showUpdateDialogDate = null; @@ -400,6 +402,14 @@ public void setWildcardImports(boolean wildcardImports) { this.wildcardImports = wildcardImports; } + public boolean isPreserveUnresolvedImports() { + return preserveUnresolvedImports; + } + + public void setPreserveUnresolvedImports(boolean preserveUnresolvedImports) { + this.preserveUnresolvedImports = preserveUnresolvedImports; + } + public boolean isAlternateTextInputControlPaste() { return alternatePasteBehavior; } @@ -501,6 +511,9 @@ public void readFromJavaPreferences() { // Wildcard imports setWildcardImports(applicationRootPreferences.getBoolean(WILDCARD_IMPORT, DEFAULT_WILDCARD_IMPORTS)); + // Unresolvable Imports + setPreserveUnresolvedImports(applicationRootPreferences.getBoolean(PRESERVE_UNRESOLVED_IMPORTS, DEFAULT_PRESERVE_UNRESOLVED_IMPORTS)); + // Alternate paste behavior for Text Input Controls setAlternateTextInputControlPaste(applicationRootPreferences.getBoolean(ALTERNATE_TEXT_INPUT_PASTE, DEFAULT_ALTERNATE_TEXT_INPUT_PASTE)); } @@ -578,6 +591,9 @@ public void writeToJavaPreferences(String key) { case ALTERNATE_TEXT_INPUT_PASTE: applicationRootPreferences.putBoolean(ALTERNATE_TEXT_INPUT_PASTE,isAlternateTextInputControlPaste()); break; + case PRESERVE_UNRESOLVED_IMPORTS: + applicationRootPreferences.putBoolean(PRESERVE_UNRESOLVED_IMPORTS, isPreserveUnresolvedImports()); + break; default: super.writeToJavaPreferences(key); break; diff --git a/app/src/main/java/com/oracle/javafx/scenebuilder/app/preferences/PreferencesWindowController.java b/app/src/main/java/com/oracle/javafx/scenebuilder/app/preferences/PreferencesWindowController.java index 055f17f7c6..56530d9faa 100644 --- a/app/src/main/java/com/oracle/javafx/scenebuilder/app/preferences/PreferencesWindowController.java +++ b/app/src/main/java/com/oracle/javafx/scenebuilder/app/preferences/PreferencesWindowController.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2024, Gluon and/or its affiliates. + * Copyright (c) 2016, 2025, Gluon and/or its affiliates. * Copyright (c) 2012, 2014, Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * @@ -55,6 +55,7 @@ import static com.oracle.javafx.scenebuilder.app.preferences.PreferencesController.TOOL_THEME; import static com.oracle.javafx.scenebuilder.app.preferences.PreferencesController.WILDCARD_IMPORT; import static com.oracle.javafx.scenebuilder.app.preferences.PreferencesController.ALTERNATE_TEXT_INPUT_PASTE; +import static com.oracle.javafx.scenebuilder.app.preferences.PreferencesController.PRESERVE_UNRESOLVED_IMPORTS; import static com.oracle.javafx.scenebuilder.kit.preferences.PreferencesRecordGlobalBase.DEFAULT_ALIGNMENT_GUIDES_COLOR; import static com.oracle.javafx.scenebuilder.kit.preferences.PreferencesRecordGlobalBase.DEFAULT_BACKGROUND_IMAGE; @@ -143,6 +144,8 @@ public class PreferencesWindowController extends AbstractFxmlWindowController { @FXML private CheckBox wildcardImports; @FXML + private CheckBox preserveUnresolvedImports; + @FXML private CheckBox alternatePasteBehavior; @FXML private Label alternatePasteBehaviorLabel; @@ -265,6 +268,10 @@ protected void controllerDidLoadFxml() { // Wildcard Imports wildcardImports.setSelected(recordGlobal.isWildcardImports()); wildcardImports.selectedProperty().addListener(new WildcardImportListener()); + + // Unresolvable Imports + preserveUnresolvedImports.setSelected(recordGlobal.isPreserveUnresolvedImports()); + preserveUnresolvedImports.selectedProperty().addListener(new PreserveUnresolvedImportListener()); if (EditorPlatform.IS_MAC) { // Alternate paste behavior for Text Input Controls @@ -594,6 +601,17 @@ public void changed(ObservableValue observable, Boolean oldVa } } + private static class PreserveUnresolvedImportListener implements ChangeListener { + + @Override + public void changed(ObservableValue observable, Boolean oldValue, Boolean newValue) { + final PreferencesController preferencesController = PreferencesController.getSingleton(); + final PreferencesRecordGlobal recordGlobal = preferencesController.getRecordGlobal(); + recordGlobal.setPreserveUnresolvedImports(newValue); + recordGlobal.writeToJavaPreferences(PRESERVE_UNRESOLVED_IMPORTS); + } + } + private static class AlternatePasteListener implements ChangeListener { @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 b3f4b97ace..e9ed19ebe2 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 @@ -250,6 +250,7 @@ prefs.cssanalyzer.columns.defaults.last = "Defaults" Column Last prefs.recent.items = Recent items : prefs.animate.accordion = Animate Accordion : prefs.wildcard.import = Use Wildcard Imports : +prefs.preserve.unresolved.import = Load FXML with unresolved imports: prefs.tic.paste.alternate.behavior = Alternative paste behavior for text input : prefs.tic.paste.alternate.behavior.tooltip = MacOS only: Enables workaround for pasting into text input controls.\nDisable when pasting text is not working as expected. prefs.reset.default = Reset to Builtin Default Values @@ -431,12 +432,15 @@ 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.open.failure.unresolved.imports = Failed to resolve {0} FXML import(s): +alert.open.failure.unresolved.imports.details = Use Show Details to obtain a full list of unresolveable imports:\n\n{0}\n\nScene Builder will open the FXML for editing but the custom content will not be rendered correctly.\n\nUnresolved UI elements will be removed from FXML when the file is saved.\n\nPlease add the missing components to your user library within the JAR/FXML Manager. If individual class files are used, make sure those are organized in a directory structure matching the package structure.\n\n +alert.open.failure.unresolved.imports.advice = Please add the missing components to your user library within the JAR/FXML Manager.\nIf individual class files are used, make sure those are organized in a directory structure matching the package structure.\n\n{0}\n\nA total of {1} unresolvable import(s) was found.\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 From List alert.welcome.file.not.found.no = OK - + # ----------------------------------------------------------------------------- # Log Messages # ----------------------------------------------------------------------------- diff --git a/app/src/main/resources/com/oracle/javafx/scenebuilder/app/preferences/Preferences.fxml b/app/src/main/resources/com/oracle/javafx/scenebuilder/app/preferences/Preferences.fxml index 23af4ca44e..a4508d4ad6 100644 --- a/app/src/main/resources/com/oracle/javafx/scenebuilder/app/preferences/Preferences.fxml +++ b/app/src/main/resources/com/oracle/javafx/scenebuilder/app/preferences/Preferences.fxml @@ -1,7 +1,7 @@