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: Scene Builder now shows a message which FXML imports were not resolvable #577

Closed
wants to merge 11 commits into from
Closed
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,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;
Expand Down Expand Up @@ -396,7 +397,13 @@ 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 = PreferencesController.getSingleton().getRecordGlobal()
.isPreserveUnresolvedImports()
? new FXOMDocumentSwitch[] {FXOMDocumentSwitch.PRESERVE_UNRESOLVED_IMPORTS}
: new FXOMDocumentSwitch[0];

editorController.setFxmlTextAndLocation(fxmlText, fxmlURL, false, options);
updateLoadFileTime();
updateStageTitle(); // No-op if fxml has not been loaded yet
updateFromDocumentPreferences(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,10 @@ public String toString() {
static final int DEFAULT_RECENT_ITEMS_SIZE = 15;
static final boolean DEFAULT_ACCORDION_ANIMATION = true;
static final boolean DEFAULT_WILDCARD_IMPORTS = false;
static final boolean DEFAULT_PRESERVE_UNRESOLVED_IMPORTS = true;

static final boolean DEFAULT_ALTERNATE_TEXT_INPUT_PASTE = EditorPlatform.IS_MAC;

/***************************************************************************
* *
* Instance fields *
Expand All @@ -115,6 +117,7 @@ public String toString() {
private int recentItemsSize = DEFAULT_RECENT_ITEMS_SIZE;
private boolean accordionAnimation = DEFAULT_ACCORDION_ANIMATION;
private boolean wildcardImports = DEFAULT_WILDCARD_IMPORTS;
private boolean preserveUnresolvedImports = DEFAULT_PRESERVE_UNRESOLVED_IMPORTS;
private final List<String> recentItems = new ArrayList<>();

private LocalDate showUpdateDialogDate = null;
Expand Down Expand Up @@ -386,10 +389,18 @@ public void setWildcardImports(boolean wildcardImports) {
this.wildcardImports = wildcardImports;
}

public boolean isPreserveUnresolvedImports() {
return this.preserveUnresolvedImports;
}

public void setPreserveUnresolvedImports(boolean preserveUnresolvedImports) {
this.preserveUnresolvedImports = preserveUnresolvedImports;
}

public boolean isAlternateTextInputControlPaste() {
return alternatePasteBehavior;
}

public void setAlternateTextInputControlPaste(boolean alternatePasteBehavior) {
this.alternatePasteBehavior = alternatePasteBehavior;
}
Expand Down Expand Up @@ -480,6 +491,7 @@ public void readFromJavaPreferences() {

// Wildcard imports
setWildcardImports(applicationRootPreferences.getBoolean(WILDCARD_IMPORT, DEFAULT_WILDCARD_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));
Expand Down Expand Up @@ -549,6 +561,8 @@ public void writeToJavaPreferences(String key) {
case WILDCARD_IMPORT:
applicationRootPreferences.putBoolean(WILDCARD_IMPORT, isWildcardImports());
break;
case PRESERVE_UNRESOLVED_IMPORTS:
applicationRootPreferences.putBoolean(PRESERVE_UNRESOLVED_IMPORTS, isPreserveUnresolvedImports());
case ALTERNATE_TEXT_INPUT_PASTE:
applicationRootPreferences.putBoolean(ALTERNATE_TEXT_INPUT_PASTE,isAlternateTextInputControlPaste());
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import static com.oracle.javafx.scenebuilder.app.preferences.PreferencesController.RECENT_ITEMS_SIZE;
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.PRESERVE_UNRESOLVED_IMPORTS;
import static com.oracle.javafx.scenebuilder.app.preferences.PreferencesController.ALTERNATE_TEXT_INPUT_PASTE;

import static com.oracle.javafx.scenebuilder.kit.preferences.PreferencesRecordGlobalBase.DEFAULT_ALIGNMENT_GUIDES_COLOR;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -268,6 +271,8 @@ protected void controllerDidLoadFxml() {
// Wildcard Imports
wildcardImports.setSelected(recordGlobal.isWildcardImports());
wildcardImports.selectedProperty().addListener(new WildcardImportListener());
preserveUnresolvedImports.setSelected(recordGlobal.isPreserveUnresolvedImports());
preserveUnresolvedImports.selectedProperty().addListener(new PreserveUnresolvedImportListener());

if (EditorPlatform.IS_MAC) {
// Alternate paste behavior for Text Input Controls
Expand Down Expand Up @@ -597,6 +602,17 @@ public void changed(ObservableValue<? extends Boolean> observable, Boolean oldVa
}
}

private static class PreserveUnresolvedImportListener implements ChangeListener<Boolean> {

@Override
public void changed(ObservableValue<? extends Boolean> 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<Boolean> {

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>

<!--
Copyright (c) 2016, 2019, Gluon and/or its affiliates.
Copyright (c) 2016, 2022, 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 @@ -50,7 +50,7 @@
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.shape.Rectangle?>

<GridPane hgap="6.0" maxWidth="1.7976931348623157E308" styleClass="theme-presets" vgap="10.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1">
<GridPane hgap="6.0" maxWidth="1.7976931348623157E308" prefHeight="-Infinity" prefWidth="550.0" styleClass="theme-presets" vgap="10.0" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Label text="%prefs.doc.width" GridPane.columnIndex="0" GridPane.rowIndex="0" />
<DoubleField fx:id="rootContainerWidth" prefWidth="200.0" text="" GridPane.columnIndex="1" GridPane.rowIndex="0" />
Expand Down Expand Up @@ -127,11 +127,11 @@
<Label text="%prefs.wildcard.import" GridPane.columnIndex="0" GridPane.rowIndex="18" />
<CheckBox fx:id="wildcardImports" selected="true" GridPane.columnIndex="1" GridPane.rowIndex="18" />

<Button onAction="#resetToDefaultAction" prefWidth="-1.0" text="%prefs.reset.default" GridPane.columnSpan="2" GridPane.halignment="CENTER" GridPane.rowIndex="21">
<Button onAction="#resetToDefaultAction" prefWidth="-1.0" text="%prefs.reset.default" GridPane.columnSpan="2" GridPane.halignment="CENTER" GridPane.rowIndex="22">
<GridPane.margin>
<Insets top="-10.0" />
</GridPane.margin></Button>
<GridPane fx:id="alternatePasteBehaviorPane" hgap="5.0" vgap="10.0" GridPane.columnSpan="2" GridPane.rowIndex="20">
<GridPane fx:id="alternatePasteBehaviorPane" hgap="5.0" vgap="10.0" GridPane.columnSpan="2" GridPane.rowIndex="21">
<columnConstraints>
<ColumnConstraints halignment="RIGHT" hgrow="ALWAYS" percentWidth="50.0" />
<ColumnConstraints halignment="LEFT" hgrow="ALWAYS" percentWidth="50.0" />
Expand All @@ -157,7 +157,9 @@
</GridPane.margin></Separator>
</children>
</GridPane>
<Separator layoutX="30.0" layoutY="500.0" prefWidth="-1.0" GridPane.columnSpan="2" GridPane.rowIndex="19" />
<Separator layoutX="30.0" layoutY="500.0" prefWidth="-1.0" GridPane.columnSpan="2" GridPane.rowIndex="20" />
<Label text="%prefs.preserve.unresolved.import" GridPane.rowIndex="19" />
<CheckBox fx:id="preserveUnresolvedImports" mnemonicParsing="false" GridPane.columnIndex="1" GridPane.rowIndex="19" />
</children>
<columnConstraints>
<ColumnConstraints halignment="RIGHT" hgrow="SOMETIMES" maxWidth="-Infinity" minWidth="-Infinity" percentWidth="50.0" />
Expand Down Expand Up @@ -189,5 +191,6 @@
<RowConstraints maxHeight="-Infinity" minHeight="-Infinity" vgrow="NEVER" />
<RowConstraints maxHeight="-Infinity" minHeight="-Infinity" vgrow="NEVER" />
<RowConstraints maxHeight="-Infinity" minHeight="-Infinity" vgrow="NEVER" />
<RowConstraints maxHeight="-Infinity" minHeight="-Infinity" vgrow="NEVER" />
</rowConstraints>
</GridPane>
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,13 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.stream.Collectors;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ListProperty;
Expand Down Expand Up @@ -759,10 +761,11 @@ public void setFxmlTextAndLocation(String fxmlText, URL fxmlLocation) throws IOE
* @param fxmlLocation null or the location of the fxml text being edited
* @param checkTheme if set to true a check will be made if the fxml contains
* Gluon controls and if so, the correct theme is set
* @param switches Optional switches to configure FXOM and FXML processes
* @throws IOException if fxml text cannot be parsed and loaded correctly.
*/
public void setFxmlTextAndLocation(String fxmlText, URL fxmlLocation, boolean checkTheme) throws IOException {
updateFxomDocument(fxmlText, fxmlLocation, getResources(), checkTheme);
public void setFxmlTextAndLocation(String fxmlText, URL fxmlLocation, boolean checkTheme, FXOMDocumentSwitch ... switches) throws IOException {
updateFxomDocument(fxmlText, fxmlLocation, getResources(), checkTheme, switches);
this.fxmlLocationProperty.setValue(fxmlLocation);
}

Expand Down Expand Up @@ -2562,11 +2565,13 @@ private boolean isSelectionControl() {
return true;
}

private void updateFxomDocument(String fxmlText, URL fxmlLocation, ResourceBundle resources, boolean checkTheme) throws IOException {
private void updateFxomDocument(String fxmlText, URL fxmlLocation, ResourceBundle resources, boolean checkTheme, FXOMDocumentSwitch ... switches) throws IOException {
final FXOMDocument newFxomDocument;

if (fxmlText != null) {
newFxomDocument = new FXOMDocument(fxmlText, fxmlLocation, getLibrary().getClassLoader(), resources, FXOMDocumentSwitch.NORMALIZED);
Set<FXOMDocumentSwitch> options = EnumSet.of(FXOMDocumentSwitch.NORMALIZED);
options.addAll(Set.of(switches));
newFxomDocument = new FXOMDocument(fxmlText, fxmlLocation, getLibrary().getClassLoader(), resources, options.toArray(new FXOMDocumentSwitch[0]));
} else {
newFxomDocument = null;
}
Expand All @@ -2581,8 +2586,35 @@ private void updateFxomDocument(String fxmlText, URL fxmlLocation, ResourceBundl
if (checkTheme) {
WarnThemeAlert.showAlertIfRequired(this, newFxomDocument, ownerWindow);
}

if (unresolvedImportsFound(newFxomDocument)) {
showUnresolvedImportsDialog(newFxomDocument);
}
}


private boolean unresolvedImportsFound(final FXOMDocument newFxomDocument) {
return newFxomDocument != null && newFxomDocument.isClassesMissing();
}

private void showUnresolvedImportsDialog(FXOMDocument document) {
final ErrorDialog errorDialog = new ErrorDialog(ownerWindow);
List<String> missingClasses = document.getMissingClasses();
String first10 = missingClasses.stream()
.limit(10)
.collect(Collectors.joining(";"+System.lineSeparator()));

errorDialog.setMessage(I18N.getString("alert.open.failure.unresolved.imports", Integer.toString(missingClasses.size())));
errorDialog.setDetails(I18N.getString("alert.open.failure.unresolved.imports.details", first10));

String allMissing = document.getMissingClasses()
.stream()
.collect(Collectors.joining(";"+System.lineSeparator()));

errorDialog.setDebugInfo(I18N.getString("alert.open.failure.unresolved.imports.advice", allMissing, missingClasses.size()));
errorDialog.setTitle(I18N.getString("alert.title.open"));
errorDialog.showAndWait();
}

private final ChangeListener<ClassLoader> libraryClassLoaderListener
= (ov, t, t1) -> libraryClassLoaderDidChange();

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
*
Expand Down Expand Up @@ -88,9 +89,10 @@ public void setDebugInfoWithThrowable(Throwable t) {
private void updateActionButtonVisibility() {
setActionButtonVisible(debugInfo != null);
}

private void showDetailsDialog() {
final TextViewDialog detailDialog = new TextViewDialog(null);
final TextViewDialog detailDialog = new TextViewDialog(super.getStage());
detailDialog.setTitle(I18N.getString("alert.title.details"));
detailDialog.setText(debugInfo);
detailDialog.showAndWait();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@

import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;

import com.oracle.javafx.scenebuilder.kit.fxom.glue.GlueDocument;
Expand Down Expand Up @@ -81,6 +82,7 @@ public class FXOMDocument {
private final SimpleIntegerProperty cssRevision = new SimpleIntegerProperty();
private SceneGraphHolder sceneGraphHolder;
private int updateDepth;
private List<String> missingClasses = new ArrayList<>();

private boolean hasGluonControls;

Expand Down Expand Up @@ -116,7 +118,7 @@ public FXOMDocument(String fxmlText, URL location, ClassLoader classLoader, Reso
fxmlTextToLoad = fxmlPropertiesDisabler.disableProperties(fxmlText);
}
final FXOMLoader loader = new FXOMLoader(this);
loader.load(fxmlTextToLoad);
loader.load(fxmlTextToLoad, switches);
if (availableSwitches.contains(FXOMDocumentSwitch.NORMALIZED)) {
final FXOMNormalizer normalizer = new FXOMNormalizer(this);
normalizer.normalize();
Expand All @@ -130,7 +132,29 @@ public FXOMDocument(String fxmlText, URL location, ClassLoader classLoader, Reso

hasGluonControls = fxmlText.contains(EditorPlatform.GLUON_PACKAGE);
}


/**
* Adds the name of a class or a name space which could not be resolved by FXMLLoader.
*
* @param missingClassName Either a class namr from an import or a name space from a wild card import.
*/
public void addMissingClass(String missingClassName) {
this.missingClasses.add(missingClassName);
}

/**
* If there were unresolved FXML import statements, affected class names and package name spaces are provided here.
*
* @return list of unresolved FXML import class names or name spaces.
*/
public List<String> getMissingClasses() {
return this.missingClasses;
}

public boolean isClassesMissing() {
return !this.missingClasses.isEmpty();
}

public FXOMDocument() {
this.glue = new GlueDocument();
}
Expand Down Expand Up @@ -185,7 +209,7 @@ public void setClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
endUpdate();
}

public List<Class<?>> getInitialDeclaredClasses() {
return initialDeclaredClasses;
}
Expand Down Expand Up @@ -227,8 +251,8 @@ public void setSampleDataEnabled(boolean sampleDataEnabled) {
sampleDataGenerator.assignSampleData(fxomRoot);
}
}
}
}

public FXOMObject getFxomRoot() {
return fxomRoot;
}
Expand Down Expand Up @@ -506,6 +530,12 @@ public enum FXOMDocumentSwitch {
* configuration. One possible example here is the option to use the MacOS
* system menu bar.
*/
FOR_PREVIEW;
FOR_PREVIEW,

/**
* This flag ensures that the {@link FXOMLoader} preserves imports which were not resolvable by {@link FXMLLoader}.
* This behavior is controlled using Preferences-API.
*/
PRESERVE_UNRESOLVED_IMPORTS;
}
}
Loading
Loading