diff --git a/README.md b/README.md index 34165b1..241e6f7 100644 --- a/README.md +++ b/README.md @@ -36,3 +36,11 @@ The updated version is contained in this repository in [Topping Recipe](/tutoria This is the _Decoration Recipe_, which means that we add the functionality that a user can change the behavior or the look&feel of an application by changing preferences. This is done by using Eclipse Preferences with a custom plugin that adapts a plain E4 preferences mechanism. The tutorial is contained in this repository in [Decoration Recipe](/tutorials/Eclipse_RCP_Cookbook_Preferences.md) + +## Recipe #6 - The Veggie Recipe (Add JavaFX controls to an SWT Eclipse 4 application) + +This is the _Veggie Recipe_, which means that we use an *alternative* UI technology with our Eclipse application. So it is like an alternative to meat (SWT). But instead of doing a full SWT to JavaFX migration, we integrate JavaFX controls to the SWT based Eclipse application. + +The original blog post can be found here: [Add JavaFX controls to a SWT Eclipse 4 application – Eclipse RCP Cookbook](https://www.codecentric.de/wissens-hub/blog/add-javafx-controls-swt-eclipse-4-application-eclipse-rcp-cookbook) +An updated was already published here: [Add JavaFX controls to a SWT Eclipse 4 application – Eclipse RCP Cookbook UPDATE](https://vogella.com/blog/add-javafx-controls-to-a-swt-eclipse-4-application-eclipse-rcp-cookbook-update/) +The most current updated version is contained in this repository in [Veggie Recipe](/tutorials/Eclipse_RCP_Cookbook_JavaFX.md) diff --git a/org.fipro.eclipse.tutorial.app/Application.e4xmi b/org.fipro.eclipse.tutorial.app/Application.e4xmi index 4c15459..f174f23 100644 --- a/org.fipro.eclipse.tutorial.app/Application.e4xmi +++ b/org.fipro.eclipse.tutorial.app/Application.e4xmi @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <application:Application xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:application="http://www.eclipse.org/ui/2010/UIModel/application" xmlns:basic="http://www.eclipse.org/ui/2010/UIModel/application/ui/basic" xmlns:menu="http://www.eclipse.org/ui/2010/UIModel/application/ui/menu" xmi:id="_tv09MKYzEeSD7pZHnwEr_g" elementId="org.eclipse.e4.ide.application" bindingContexts="_tv09OaYzEeSD7pZHnwEr_g"> - <children xsi:type="basic:TrimmedWindow" xmi:id="_tv09MaYzEeSD7pZHnwEr_g" elementId="org.eclipse.e4.window.main" label="Eclipse Cookbook Application" width="500" height="400"> + <children xsi:type="basic:TrimmedWindow" xmi:id="_tv09MaYzEeSD7pZHnwEr_g" elementId="org.eclipse.e4.window.main" label="Eclipse Cookbook Application" width="800" height="600"> <children xsi:type="basic:PartSashContainer" xmi:id="_eJLHQKbJEeSD7pZHnwEr_g" elementId="org.fipro.eclipse.tutorial.app.main"/> <mainMenu xmi:id="_TjcysAYtEe-e_PdO_TMKWw" elementId="org.eclipse.ui.main.menu"> <children xsi:type="menu:Menu" xmi:id="_fvhG8AYtEe-e_PdO_TMKWw" elementId="org.eclipse.ui.file.menu" label="File"> diff --git a/org.fipro.eclipse.tutorial.inverter/META-INF/MANIFEST.MF b/org.fipro.eclipse.tutorial.inverter/META-INF/MANIFEST.MF index e848c51..ab4e9b6 100644 --- a/org.fipro.eclipse.tutorial.inverter/META-INF/MANIFEST.MF +++ b/org.fipro.eclipse.tutorial.inverter/META-INF/MANIFEST.MF @@ -7,14 +7,14 @@ Require-Bundle: org.eclipse.swt;bundle-version="3.125.0", org.eclipse.jface;bundle-version="3.33.0", org.eclipse.e4.ui.model.workbench;bundle-version="2.4.200.v20240109-1025" Bundle-RequiredExecutionEnvironment: JavaSE-17 -Import-Package: jakarta.annotation;version="[2.1.0,3.0.0]", - jakarta.inject;version="[2.0.0,3.0.0]", +Automatic-Module-Name: org.fipro.eclipse.tutorial.inverter +Model-Fragment: fragment.e4xmi +Import-Package: jakarta.annotation;version="[2.1.0,3.0.0)";resolution:=optional, + jakarta.inject;version="[2.0.0,3.0.0)", org.eclipse.e4.core.di.annotations;version="[1.6.0,2.0.0]", org.eclipse.e4.core.di.extensions;version="[0.16.0,1.0.0]", org.eclipse.e4.core.services.events, org.fipro.e4.service.preferences;version="[0.4.0,1.0.0]", org.fipro.eclipse.tutorial.service.inverter;version="[1.0.0,2.0.0]" -Model-Fragment: fragment.e4xmi -Automatic-Module-Name: org.fipro.eclipse.tutorial.inverter Bundle-ActivationPolicy: lazy Service-Component: OSGI-INF/org.fipro.eclipse.tutorial.inverter.preferences.InverterPreferencesContribution.xml diff --git a/org.fipro.eclipse.tutorial.inverter/fragment.e4xmi b/org.fipro.eclipse.tutorial.inverter/fragment.e4xmi index 2f08efa..910b0f1 100644 --- a/org.fipro.eclipse.tutorial.inverter/fragment.e4xmi +++ b/org.fipro.eclipse.tutorial.inverter/fragment.e4xmi @@ -2,6 +2,9 @@ <fragment:ModelFragments xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:basic="http://www.eclipse.org/ui/2010/UIModel/application/ui/basic" xmlns:fragment="http://www.eclipse.org/ui/2010/UIModel/fragment" xmi:id="_GBlIcP2CEe6ghYpA0qsMeA"> <imports xsi:type="basic:PartSashContainer" xmi:id="_Ywx9EP2DEe6ghYpA0qsMeA" elementId="org.fipro.eclipse.tutorial.app.main"/> <fragments xsi:type="fragment:StringModelFragment" xmi:id="_HZoggP2CEe6ghYpA0qsMeA" featurename="children" parentElementId="org.fipro.eclipse.tutorial.app.main"> - <elements xsi:type="basic:Part" xmi:id="_zS1fcP2DEe6ghYpA0qsMeA" elementId="org.fipro.eclipse.tutorial.inverter.part.0" contributionURI="bundleclass://org.fipro.eclipse.tutorial.inverter/org.fipro.eclipse.tutorial.inverter.part.InverterPart"/> + <elements xsi:type="basic:PartStack" xmi:id="_JxRIMBQpEe-ro58EMQ7HTQ" elementId="org.fipro.eclipse.tutorial.inverter.partstack.0"> + <children xsi:type="basic:Part" xmi:id="_zS1fcP2DEe6ghYpA0qsMeA" elementId="org.fipro.eclipse.tutorial.inverter.part.inverterswt" contributionURI="bundleclass://org.fipro.eclipse.tutorial.inverter/org.fipro.eclipse.tutorial.inverter.part.InverterPart" label="Inverter (SWT)"/> + <children xsi:type="basic:Part" xmi:id="_RtVm4BQpEe-ro58EMQ7HTQ" elementId="org.fipro.eclipse.tutorial.inverter.part.inverterjavafx" contributionURI="bundleclass://org.fipro.eclipse.tutorial.inverter/org.fipro.eclipse.tutorial.inverter.part.InverterFXPart" label="Inverter (JavaFX)"/> + </elements> </fragments> </fragment:ModelFragments> diff --git a/org.fipro.eclipse.tutorial.inverter/src/org/fipro/eclipse/tutorial/inverter/part/InverterFXPart.java b/org.fipro.eclipse.tutorial.inverter/src/org/fipro/eclipse/tutorial/inverter/part/InverterFXPart.java new file mode 100644 index 0000000..ac3dc87 --- /dev/null +++ b/org.fipro.eclipse.tutorial.inverter/src/org/fipro/eclipse/tutorial/inverter/part/InverterFXPart.java @@ -0,0 +1,151 @@ +package org.fipro.eclipse.tutorial.inverter.part; + +import org.eclipse.e4.core.di.annotations.Optional; +import org.eclipse.e4.core.di.extensions.Preference; +import org.eclipse.e4.core.di.extensions.Service; +import org.eclipse.e4.core.services.events.IEventBroker; +import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.layout.GridLayoutFactory; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.fipro.eclipse.tutorial.service.inverter.InverterService; + +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; +import javafx.animation.ParallelTransition; +import javafx.animation.RotateTransition; +import javafx.animation.ScaleTransition; +import javafx.embed.swt.FXCanvas; +import javafx.geometry.HPos; +import javafx.geometry.Insets; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Priority; +import javafx.scene.paint.Color; +import javafx.util.Duration; + +public class InverterFXPart { + + @Inject + @Service + private InverterService inverter; + + @Inject + IEventBroker broker; + + TextField input; + Label output; + + Color textColor = Color.BLACK; + + @PostConstruct + public void postConstruct(Composite parent) { + GridLayoutFactory.fillDefaults().applyTo(parent); + + // add FXCanvas for adding JavaFX controls to the UI + FXCanvas canvas = new FXCanvas(parent, SWT.NONE); + GridDataFactory + .fillDefaults() + .grab(true, true) + .span(3, 1) + .applyTo(canvas); + + // create the root layout pane + GridPane layout = new GridPane(); + + // create a Scene instance + // set the layout container as root + // set the background fill to the background color of the shell + Scene scene = new Scene(layout, Color.rgb( + parent.getShell().getBackground().getRed(), + parent.getShell().getBackground().getGreen(), + parent.getShell().getBackground().getBlue())); + + // set the Scene to the FXCanvas + canvas.setScene(scene); + + // create the controls + Label inputLabel = new Label(); + inputLabel.setText("String to revert:"); + GridPane.setConstraints(inputLabel, 0, 0); + GridPane.setMargin(inputLabel, new Insets(5.0)); + + input = new TextField(); + input.setStyle("-fx-text-fill: " + (textColor == Color.BLUE ? "blue" : "black") + ";"); + GridPane.setConstraints(input, 1, 0); + GridPane.setHgrow(input, Priority.ALWAYS); + GridPane.setMargin(input, new Insets(5.0)); + + Button button = new Button(); + button.setText("Revert"); + GridPane.setConstraints(button, 2, 0); + GridPane.setMargin(button, new Insets(5.0)); + + Label outputLabel = new Label(); + outputLabel.setText("Inverted String:"); + GridPane.setConstraints(outputLabel, 0, 1); + GridPane.setMargin(outputLabel, new Insets(5.0)); + + output = new Label(); + output.setTextFill(textColor); + GridPane.setConstraints(output, 0, 2); + GridPane.setColumnSpan(output, 3); + GridPane.setHgrow(output, Priority.ALWAYS); + GridPane.setHalignment(output, HPos.CENTER); + + // don't forget to add children to gridpane + layout.getChildren().addAll( + inputLabel, input, button, outputLabel, output); + + // add an animation for the output + RotateTransition rotateTransition = + new RotateTransition(Duration.seconds(1), output); + rotateTransition.setByAngle(360); + + ScaleTransition scaleTransition = + new ScaleTransition(Duration.seconds(1), output); + scaleTransition.setFromX(1.0); + scaleTransition.setFromY(1.0); + scaleTransition.setToX(4.0); + scaleTransition.setToY(4.0); + + ParallelTransition parallelTransition = + new ParallelTransition(rotateTransition, scaleTransition); + + // add the action listener + button.setOnAction(event -> { + output.setText(inverter.invert(input.getText())); + broker.post("TOPIC_LOGGING", "triggered via button (FX)"); + parallelTransition.play(); + }); + + input.setOnAction(event -> { + output.setText(inverter.invert(input.getText())); + broker.post("TOPIC_LOGGING", "triggered via field (FX)"); + parallelTransition.play(); + }); + + } + + @Inject + @Optional + public void setTextColor( + @Preference(nodePath = "org.fipro.eclipse.tutorial.inverter", value = "inverter_color") String color) { + + textColor = "blue".equals(color) + ? Color.BLUE + : Color.BLACK; + + if (input != null) { + input.setStyle("-fx-text-fill: " + color + ";"); + } + + if (output != null) { + output.setTextFill(textColor); + } + } + +} \ No newline at end of file diff --git a/org.fipro.eclipse.tutorial.logview/fragment.e4xmi b/org.fipro.eclipse.tutorial.logview/fragment.e4xmi index 33af540..4b4dfc2 100644 --- a/org.fipro.eclipse.tutorial.logview/fragment.e4xmi +++ b/org.fipro.eclipse.tutorial.logview/fragment.e4xmi @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="ASCII"?> <fragment:ModelFragments xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:basic="http://www.eclipse.org/ui/2010/UIModel/application/ui/basic" xmlns:fragment="http://www.eclipse.org/ui/2010/UIModel/fragment" xmi:id="_KxgI4ABvEe-Sga9XhUrP7g"> <imports xsi:type="basic:PartSashContainer" xmi:id="_Ma03IABvEe-Sga9XhUrP7g" elementId="org.fipro.eclipse.tutorial.app.main"/> - <fragments xsi:type="fragment:StringModelFragment" xmi:id="_PGfzMABvEe-Sga9XhUrP7g" featurename="children" parentElementId="org.fipro.eclipse.tutorial.app.main" positionInList="after:org.fipro.eclipse.tutorial.inverter.part.0"> + <fragments xsi:type="fragment:StringModelFragment" xmi:id="_PGfzMABvEe-Sga9XhUrP7g" featurename="children" parentElementId="org.fipro.eclipse.tutorial.app.main" positionInList="after:org.fipro.eclipse.tutorial.inverter.partstack.0"> <elements xsi:type="basic:PartStack" xmi:id="_ml410ABvEe-Sga9XhUrP7g" elementId="org.fipro.eclipse.tutorial.logview.partstack.0"> <children xsi:type="basic:Part" xmi:id="_t7d8QABvEe-Sga9XhUrP7g" elementId="org.fipro.eclipse.tutorial.logview.part.logview" contributionURI="bundleclass://org.fipro.eclipse.tutorial.logview/org.fipro.eclipse.tutorial.logview.part.LogViewPart" label="Log View"/> </elements> diff --git a/org.fipro.eclipse.tutorial.product/org.fipro.eclipse.tutorial.app.product b/org.fipro.eclipse.tutorial.product/org.fipro.eclipse.tutorial.app.product index 66fe4ee..66c1974 100644 --- a/org.fipro.eclipse.tutorial.product/org.fipro.eclipse.tutorial.app.product +++ b/org.fipro.eclipse.tutorial.product/org.fipro.eclipse.tutorial.app.product @@ -9,6 +9,8 @@ <launcherArgs> <programArgs>-clearPersistedState </programArgs> + <vmArgs>-Dosgi.framework.extensions=org.eclipse.fx.osgi + </vmArgs> <vmArgsMac>-XstartOnFirstThread -Dorg.eclipse.swt.internal.carbon.smallFonts </vmArgsMac> </launcherArgs> @@ -38,6 +40,11 @@ <feature id="org.eclipse.ecf.filetransfer.feature" installMode="root"/> <feature id="org.eclipse.ecf.filetransfer.httpclientjava.feature" installMode="root"/> <feature id="org.fipro.e4.service.preferences.feature" installMode="root"/> + <feature id="org.eclipse.fx.runtime.min.feature" installMode="root"/> + <feature id="openjfx.media.feature" installMode="root"/> + <feature id="openjfx.standard.feature" installMode="root"/> + <feature id="openjfx.swt.feature" installMode="root"/> + <feature id="openjfx.web.feature" installMode="root"/> </features> <configurations> diff --git a/org.fipro.eclipse.tutorial.product/org.fipro.eclipse.tutorial.app.product.fxrcp b/org.fipro.eclipse.tutorial.product/org.fipro.eclipse.tutorial.app.product.fxrcp new file mode 100644 index 0000000..4f8e9dc --- /dev/null +++ b/org.fipro.eclipse.tutorial.product/org.fipro.eclipse.tutorial.app.product.fxrcp @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="UTF-8"?> +<?pde version="3.5"?> + +<product name="Eclipse Cookbook Application" uid="org.fipro.eclipse.tutorial" id="org.fipro.eclipse.tutorial.app.product" application="org.eclipse.e4.ui.workbench.swt.E4Application" version="1.1.0.qualifier" type="features" includeLaunchers="true" autoIncludeRequirements="true"> + + <configIni use="default"> + </configIni> + + <launcherArgs> + <programArgs>-clearPersistedState + </programArgs> + <vmArgs>-Dosgi.framework.extensions=org.eclipse.fx.osgi + </vmArgs> + <vmArgsMac>-XstartOnFirstThread -Dorg.eclipse.swt.internal.carbon.smallFonts + </vmArgsMac> + </launcherArgs> + + <launcher> + <win useIco="false"> + <bmp/> + </win> + </launcher> + + <vm> + </vm> + + <plugins> + </plugins> + + <features> + <feature id="org.eclipse.e4.rcp"/> + <feature id="org.eclipse.emf.ecore"/> + <feature id="org.eclipse.emf.common"/> + <feature id="org.fipro.eclipse.tutorial.feature" installMode="root"/> + <feature id="org.eclipse.equinox.p2.core.feature" installMode="root"/> + <feature id="org.eclipse.ecf.core.ssl.feature" installMode="root"/> + <feature id="org.eclipse.ecf.filetransfer.ssl.feature" installMode="root"/> + <feature id="org.eclipse.ecf.filetransfer.httpclient5.feature" installMode="root"/> + <feature id="org.eclipse.ecf.core.feature" installMode="root"/> + <feature id="org.eclipse.ecf.filetransfer.feature" installMode="root"/> + <feature id="org.eclipse.ecf.filetransfer.httpclientjava.feature" installMode="root"/> + <feature id="org.fipro.e4.service.preferences.feature" installMode="root"/> + <feature id="openjfx.media.feature" installMode="root"/> + <feature id="openjfx.standard.feature" installMode="root"/> + <feature id="openjfx.swt.feature" installMode="root"/> + <feature id="openjfx.web.feature" installMode="root"/> + <feature id="org.eclipse.fx.target.rcp4.feature" installMode="root"/> + </features> + + <configurations> + <plugin id="org.apache.felix.scr" autoStart="true" startLevel="2" /> + <plugin id="org.eclipse.core.runtime" autoStart="true" startLevel="0" /> + <plugin id="org.eclipse.equinox.common" autoStart="true" startLevel="2" /> + <plugin id="org.eclipse.equinox.event" autoStart="true" startLevel="2" /> + <plugin id="org.eclipse.equinox.simpleconfigurator" autoStart="true" startLevel="1" /> + </configurations> + +</product> diff --git a/org.fipro.eclipse.tutorial.target/org.fipro.eclipse.tutorial.target.target b/org.fipro.eclipse.tutorial.target/org.fipro.eclipse.tutorial.target.target index 47714b9..ebfed15 100644 --- a/org.fipro.eclipse.tutorial.target/org.fipro.eclipse.tutorial.target.target +++ b/org.fipro.eclipse.tutorial.target/org.fipro.eclipse.tutorial.target.target @@ -40,5 +40,45 @@ <repository location="https://github.com/fipro78/e4-preferences/raw/master/releases/0.5.0"/> </location> + <location + includeAllPlatforms="false" + includeConfigurePhase="false" + includeMode="planner" + includeSource="true" + type="InstallableUnit"> + + <unit + id="org.eclipse.fx.runtime.min.feature.feature.group" + version="3.9.0.202210162353"/> + <unit + id="org.eclipse.fx.target.rcp4.feature.feature.group" + version="3.9.0.202210170008"/> + + <repository + location="http://download.eclipse.org/efxclipse/runtime-released/3.9.0/site"/> + </location> + <location + includeAllPlatforms="false" + includeConfigurePhase="false" + includeMode="planner" + includeSource="true" + type="InstallableUnit"> + + <unit + id="openjfx.media.feature.feature.group" + version="17.0.2.202204012121"/> + <unit + id="openjfx.standard.feature.feature.group" + version="17.0.2.202204012121"/> + <unit + id="openjfx.swt.feature.feature.group" + version="17.0.2.202204012121"/> + <unit + id="openjfx.web.feature.feature.group" + version="17.0.2.202204012121"/> + + <repository + location="https://downloads.efxclipse.bestsolution.at/p2-repos/openjfx-17.0.2/"/> + </location> </locations> </target> \ No newline at end of file diff --git a/pom.xml b/pom.xml index 6a42089..0adfd4a 100644 --- a/pom.xml +++ b/pom.xml @@ -82,17 +82,39 @@ </plugin> </plugins> - <!-- - Add this to avoid the warning: - 'build.plugins.plugin.version' for org.eclipse.tycho:tycho-p2-director-plugin is missing. - --> <pluginManagement> <plugins> + <!-- + Add this to avoid the warning: + 'build.plugins.plugin.version' for org.eclipse.tycho:tycho-p2-director-plugin is missing. + --> <plugin> <groupId>org.eclipse.tycho</groupId> <artifactId>tycho-p2-director-plugin</artifactId> <version>${tycho.version}</version> </plugin> + <plugin> + <groupId>org.eclipse.tycho</groupId> + <artifactId>tycho-compiler-plugin</artifactId> + <version>${tycho.version}</version> + <configuration> + <encoding>UTF-8</encoding> + <extraClasspathElements> + <extraClasspathElement> + <groupId>org.openjfx</groupId> + <artifactId>javafx-controls</artifactId> + <version>17.0.11</version> + </extraClasspathElement> + <extraClasspathElement> + <groupId>org.openjfx</groupId> + <artifactId>javafx-swt</artifactId> + <version>17.0.11</version> + <systemPath>${JAVAFX_HOME}/lib/javafx-swt.jar</systemPath> + <scope>system</scope> + </extraClasspathElement> + </extraClasspathElements> + </configuration> + </plugin> </plugins> </pluginManagement> </build> diff --git a/tutorials/Eclipse_RCP_Cookbook_JavaFX.md b/tutorials/Eclipse_RCP_Cookbook_JavaFX.md new file mode 100644 index 0000000..757c658 --- /dev/null +++ b/tutorials/Eclipse_RCP_Cookbook_JavaFX.md @@ -0,0 +1,447 @@ +# Eclipse RCP Cookbook – The Veggie Recipe (Add JavaFX controls to an SWT Eclipse 4 application) + +As explained in [JavaFX Interoperability with SWT](https://docs.oracle.com/javafx/2/swt_interoperability/jfxpub-swt_interoperability.htm "JavaFX Interoperability with SWT") it is possible to embed JavaFX controls in a SWT UI. This is useful for example if you want to softly migrate big applications from SWT to JavaFX or if you need to add animations or special JavaFX controls without completely migrating your application. + +The following recipe will show how to integrate JavaFX with an Eclipse 4 application. It will cover the usage of Java 17 and JavaFX 17. + +**_Note:_** +If you are interested in the usage of Java 8 with integrated JavaFX, have a look at the older versions of this tutorial, e.g. the one published at [vogella Blog](https://vogella.com/blog/add-javafx-controls-to-a-swt-eclipse-4-application-eclipse-rcp-cookbook-update/). + +## Cookware +- JDK >= 17 + - e.g. [Eclipse Temurin](https://adoptium.net/temurin/releases/) + - Simply run the executable and follow the installation instructions +- Eclipse IDE >= 2024-03 + - [Eclipse IDE Downloads](https://www.eclipse.org/downloads/) + - Choose the package that fits your needs the best, e.g. _Eclipse for RCP and RAP Developers_ + - After starting the IDE and choosing a workspace, update the IDE to ensure the latest service release is installed. This is necessary to get the latest bugfixes and security patches. + - Main Menu → Help → Check for Updates +- e(fx)clipse 3.9.0 + - Update your Eclipse installation + - _Main Menu → Help → Install New Software..._ + - Use the e(fx)clipse 3.9.0 Update Site _https://download.eclipse.org/efxclipse/updates-released/3.9.0/site_ + - Select _**e(fx)clipse - install / e(fx)clipse - IDE**_ to get the all IDE features installed needed to develop JavaFX within Eclipse ([https://www.eclipse.org/efxclipse/index.html](https://www.eclipse.org/efxclipse/index.html)) + - Restart the IDE and choose a workspace +- JavaFX >= 17 + - [https://openjfx.io/](https://openjfx.io/) + - Download the JavaFX archive for your operating system + - Extract it +- e(fx)clipse + - Configure the JavaFX SDK **_lib_** folder + - _Main Menu → Window → Preferences → JavaFX_ + + <img src="efxclipse_preferences.png" width="75%"/> + +## Ingredients + +This recipe is based on the [Eclipse RCP Cookbook – The Decoration Recipe](Eclipse_RCP_Cookbook_Preferences.md). To get started fast with this recipe, the recipe is prepared for you on GitHub. + +To use the prepared recipe, import the project by cloning the Git repository: + +- _File → Import → Git → Projects from Git_ +- Click _Next_ +- Select _Clone URI_ +- Enter URI _https://github.com/fipro78/e4-cookbook-basic-recipe.git_ +- Click _Next_ +- Select the **preferences** branch +- Click _Next_ +- Choose a directory where you want to store the checked out sources +- Select _Import existing Eclipse projects_ +- Click _Finish_ + +## Preparation + +### Step 1: Update the Target Platform + +- Open the target definition _org.fipro.eclipse.tutorial.target.target_ in the project _org.fipro.eclipse.tutorial.target_ +- Update the Software Sites in the opened _Target Definition Editor_ + - Alternative A + - Switch to the _Source_ tab and add the following snippet to the editor + ```xml + <?xml version="1.0" encoding="UTF-8" standalone="no"?> + <?pde?> + <target name="E4 Cookbook Target Platform" sequenceNumber="1568034040"> + <locations> + <location + includeAllPlatforms="false" + includeConfigurePhase="false" + includeMode="planner" + includeSource="true" + type="InstallableUnit"> + + <unit + id="org.eclipse.equinox.executable.feature.group" + version="3.8.2400.v20240213-1244"/> + <unit + id="org.eclipse.sdk.feature.group" + version="4.31.0.v20240229-1022"/> + + <unit + id="org.eclipse.equinox.core.feature.feature.group" + version="1.15.0.v20240214-0846"/> + <unit + id="org.eclipse.equinox.p2.core.feature.feature.group" + version="1.7.100.v20240220-1431"/> + + <repository + location="https://download.eclipse.org/releases/2024-03"/> + </location> + <location + includeAllPlatforms="false" + includeConfigurePhase="false" + includeMode="planner" + includeSource="true" + type="InstallableUnit"> + + <unit + id="org.fipro.e4.service.preferences.feature.feature.group" + version="0.4.0.202405151311"/> + + <repository + location="https://github.com/fipro78/e4-preferences/raw/master/releases/0.4.0"/> + </location> + <location + includeAllPlatforms="false" + includeConfigurePhase="false" + includeMode="planner" + includeSource="true" + type="InstallableUnit"> + + <repository + location="http://download.eclipse.org/efxclipse/runtime-released/3.9.0/site"/> + <unit + id="org.eclipse.fx.runtime.min.feature.feature.group" + version="3.9.0.202210162353"/> + <unit + id="org.eclipse.fx.target.rcp4.feature.feature.group" + version="3.9.0.202210170008"/> + </location> + <location + includeAllPlatforms="false" + includeConfigurePhase="false" + includeMode="planner" + includeSource="true" + type="InstallableUnit"> + + <repository + location="https://downloads.efxclipse.bestsolution.at/p2-repos/openjfx-17.0.2/"/> + <unit + id="openjfx.media.feature.feature.group" + version="17.0.2.202204012121"/> + <unit + id="openjfx.standard.feature.feature.group" + version="17.0.2.202204012121"/> + <unit + id="openjfx.swt.feature.feature.group" + version="17.0.2.202204012121"/> + <unit + id="openjfx.web.feature.feature.group" + version="17.0.2.202204012121"/> + </location> + </locations> + </target> + ``` + + - Alternative B + - Add the e(fx)clipse Runtime extension to add JavaFX support + - Select _Add..._ + - Select _Software Site_ + - Click _Next_ + - Enter _http://download.eclipse.org/efxclipse/runtime-released/3.9.0/site_ in _Work with:_ for the e(fx)clipse 3.9.0 release build + - Expand **_FX Target_** + - Check **_Minimal JavaFX OSGi integration bundles_** + - Check **_RCP e4 Target Platform Feature_** + _(in case additional features provided by e(fx)clipse should be used)_ + - Click _Finish_ + - Add the JavaFX libraries as OSGi bundles + _Further information [here](https://github.com/eclipse-efx/efxclipse-eclipse/wiki/Eclipse4-application-on-OpenJFX-11)_ + - Select _Add..._ + - Select _Software Site_ + - Click _Next_ + - Enter _https://downloads.efxclipse.bestsolution.at/p2-repos/openjfx-17.0.2/_ in _Work with:_ + - Disable _Group by Category_ as the items are not categorized and check all available items + - _**openjfx.media.feature**_ + - _**openjfx.standard.feature**_ + - _**openjfx.swt.feature**_ + - _**openjfx.web.feature**_ + - Click _Finish_ +- Switch to the _Definition_ tab + - Wait until the Target Definition is completely resolved (check the progress at the bottom right) + - Reload and activate the target platform by clicking _Reload Target Platform_ in the upper right corner of the Target Definition Editor + +### Step 2: Update the Plug-in project + +Instead of changing the `InverterPart` to use a single JavaFX control instead of a SWT control for the output, we add a new `InverterFXPart` that completely uses JavaFX controls. This is a difference to the original blog post. + +- Update the application model fragments + - Open the file _fragment.e4xmi_ in the project _org.fipro.eclipse.tutorial.inverter_ + - Add a _PartStack_ + - Select _Model Fragment Definition → Model Fragments → Model Fragment for PartSashContainer_ + - On the details side select _PartStack_ in the dropdown and click _Add_ + - In the tree view drag the existing _Part_ to the created _PartStack_ + - Select the _Part_ and set _Label_ to _Inverter (SWT)_ + - Select the _PartStack_ in the tree view + - Select _Part_ in the combo + - Click on the _Add_ button + - Set _Label_ to _Inverter (JavaFX)_ + + - Create the part implementation + - Click the _Class URI_ link in the part detail view + - Set the values in the opened dialog + + | Property | Value | + | --- | --- | + | Package | org.fipro.eclipse.tutorial.inverter.part | + | Name | InverterFXPart | + | PostConstruct Method | check | + + - Click _Finish_ + - Save the _fragment.e4xmi_ file + - Open the file _fragment.e4xmi_ in the project _org.fipro.eclipse.tutorial.logview_ + - Select _Model Fragment Definition → Model Fragments → Model Fragment for PartSashContainer_ + - Change the value in _Position in list:_ to `after:org.fipro.eclipse.tutorial.inverter.partstack.0` + + - Implement the `InverterFXPart` + - Create the content in the method annotated with `@PostConstruct` + - Ensure that a `org.eclipse.swt.layout.GridLayout` is set on the parent `Composite` + - Add a `javafx.embed.swt.FXCanvas` to the parent `Composite` in `InverterFXPart#postConstruct(Composite)` + - Create an instance of `javafx.scene.layout.GridPane` + - Create a `javafx.scene.Scene` instance that takes the created `GridPane` as root node and sets the background color to be the same as the background color of the parent `Shell` + - Set the created `javafx.scene.Scene` to the `FXCanvas` + +``` java +GridLayoutFactory.fillDefaults().applyTo(parent); + +// add FXCanvas for adding JavaFX controls to the UI +FXCanvas canvas = new FXCanvas(parent, SWT.NONE); +GridDataFactory + .fillDefaults() + .grab(true, true) + .span(3, 1) + .applyTo(canvas); + +// create the root layout pane +GridPane layout = new GridPane(); + +// create a Scene instance +// set the layout container as root +// set the background fill to the background color of the shell +Scene scene = new Scene(layout, Color.rgb( + parent.getShell().getBackground().getRed(), + parent.getShell().getBackground().getGreen(), + parent.getShell().getBackground().getBlue())); + +// set the Scene to the FXCanvas +canvas.setScene(scene); +``` + +Now JavaFX controls can be added to the scene graph via the `GridPane` instance. + +- Create an instance field for the `input` control of type `javafx.scene.control.TextField` +- Create an instance field for the `output` control of type `javafx.scene.control.Label` +- Create an instance field for the `Color` to be used with the controls (needed for the preference support on startup) + +```java +TextField input; +Label output; +Color textColor; +``` + +- Create the user interface with labels and buttons and configure the layout via `javafx.scene.layout.GridPane` +- Add the created controls to the `GridPane` + +``` java +// create the controls +Label inputLabel = new Label(); +inputLabel.setText("String to revert:"); +GridPane.setConstraints(inputLabel, 0, 0); +GridPane.setMargin(inputLabel, new Insets(5.0)); + +input = new TextField(); +input.setStyle("-fx-text-fill: " + (textColor == Color.BLUE ? "blue" : "black") + ";"); +GridPane.setConstraints(input, 1, 0); +GridPane.setHgrow(input, Priority.ALWAYS); +GridPane.setMargin(input, new Insets(5.0)); + +Button button = new Button(); +button.setText("Revert"); +GridPane.setConstraints(button, 2, 0); +GridPane.setMargin(button, new Insets(5.0)); + +Label outputLabel = new Label(); +outputLabel.setText("Inverted String:"); +GridPane.setConstraints(outputLabel, 0, 1); +GridPane.setMargin(outputLabel, new Insets(5.0)); + +output = new Label(); +output.setTextFill(textColor); +GridPane.setConstraints(output, 0, 2); +GridPane.setColumnSpan(output, 3); +GridPane.setHgrow(output, Priority.ALWAYS); +GridPane.setHalignment(output, HPos.CENTER); + +// don't forget to add children to gridpane +layout.getChildren().addAll( + inputLabel, input, button, outputLabel, output); +``` + +Add some animations to see some more JavaFX features: + +- Create a `javafx.animation.RotateTransition` that rotates the output label. +- Create a `javafx.animation.ScaleTransition` that scales the output label. +- Create a `javafx.animation.ParallelTransition` that combines the `RotateTransition` and the `ScaleTransition`. This way both transitions are executed in parallel. +- Add starting the animation in the `SelectionAdapter` and the `KeyAdapter` that are executed for reverting a String. + + ``` java + // add an animation for the output + RotateTransition rotateTransition = + new RotateTransition(Duration.seconds(1), output); + rotateTransition.setByAngle(360); + + ScaleTransition scaleTransition = + new ScaleTransition(Duration.seconds(1), output); + scaleTransition.setFromX(1.0); + scaleTransition.setFromY(1.0); + scaleTransition.setToX(4.0); + scaleTransition.setToY(4.0); + + ParallelTransition parallelTransition = + new ParallelTransition(rotateTransition, scaleTransition); + ``` + +Align the functionality with the `InverterPart`: +- Get the `InverterService` and the `IEventBroker` injected + + ```java + @Inject + @Service + private InverterService inverter; + + @Inject + IEventBroker broker; + ``` + +- Add the action listener for the button and the input field + ```java + // add the action listener + button.setOnAction(event -> { + output.setText(inverter.invert(input.getText())); + broker.post("TOPIC_LOGGING", "triggered via button (FX)"); + parallelTransition.play(); + }); + + input.setOnAction(event -> { + output.setText(inverter.invert(input.getText())); + broker.post("TOPIC_LOGGING", "triggered via field (FX)"); + parallelTransition.play(); + }); + ``` + +- Add the following method to add the preference support like in the SWT `InverterPart`: + ```java + @Inject + @Optional + public void setTextColor( + @Preference(nodePath = "org.fipro.eclipse.tutorial.inverter", value = "inverter_color") String color) { + + textColor = "blue".equals(color) + ? Color.BLUE + : Color.BLACK; + + if (input != null) { + input.setStyle("-fx-text-fill: " + color + ";"); + } + + if (output != null) { + output.setTextFill(textColor); + } + } + ``` + +### Step 3: Update the Product Configuration + +- Open the file _org.fipro.eclipse.tutorial.app.product_ in the project _org.fipro.eclipse.tutorial.product_ +- Switch to the _Contents_ tab and add additional features + - **Option A:** Use the **_Minimal JavaFX OSGi integration bundles_** + - _org.eclipse.fx.runtime.min.feature_ + - **Option B:** Use the **_RCP e4 Target Platform Feature_** + __*Note:*__ As _Include required Features and Plug-ins automatically_ is active, the additional required features and plug-ins will be added automatically. To have more control over the included Features and Plug-ins, disable this option and ensure manually that everything that is required is included. + - _org.eclipse.fx.target.rcp4.feature_ + - JavaFX OSGi bundles + - _openjfx.media.feature_ + - _openjfx.standard.feature_ + - _openjfx.swt.feature_ + - _openjfx.web.feature_ +- Switch to the _Launching_ tab + - Add _\-Dosgi.framework.extensions=org.eclipse.fx.osgi_ to the _VM Arguments_ (adapter hook to get JavaFX-SWT integration on the classpath) + +## Taste + +- Start the application from within the IDE + - Open the Product Configuration in the _org.fipro.eclipse.tutorial.product_ project + - Select the _Overview_ tab + - Click _Launch an Eclipse Application_ in the _Testing_ section + +The started application should look similar to the following screenshot. + +<img src="application_fx.png" + style="width:600px" /> + +## Thermomix Update - Maven Tycho build + +To build a deliverable product it is recommended to use Maven Tycho. The project was already prepared to build via pomless Tycho in the [Eclipse RCP Cookbook – The Thermomix Recipe](Eclipse_RCP_Cookbook_Tycho.md). + +JavaFX is not on the default classpath, therefore the location of the JavaFX libraries need to be configured in the Tycho build for compile time resolution. The OpenJFX libraries are available via Maven Central and can be added as extra classpath elements via Maven. But the `javafx-swt` module is not available via Maven Central as reported [here](https://github.com/javafxports/openjdk-jfx/issues/268). + + +That means for OpenJFX 17 following section needs to be added in the `pluginManagement` section, where the **JAVAFX\_HOME** environment variable points to your OpenJFX installation: +```xml +<plugin> + <groupId>org.eclipse.tycho</groupId> + <artifactId>tycho-compiler-plugin</artifactId> + <version>${tycho.version}</version> + <configuration> + <encoding>UTF-8</encoding> + <extraClasspathElements> + <extraClasspathElement> + <groupId>org.openjfx</groupId> + <artifactId>javafx-controls</artifactId> + <version>17.0.11</version> + </extraClasspathElement> + <extraClasspathElement> + <groupId>org.openjfx</groupId> + <artifactId>javafx-swt</artifactId> + <version>17.0.11</version> + <systemPath>${JAVAFX_HOME}/lib/javafx-swt.jar</systemPath> + <scope>system</scope> + </extraClasspathElement> + </extraClasspathElements> + </configuration> +</plugin> +``` +Start the build + +``` console +mvn clean verify +``` + +The resulting product variants for each platform is located under _e4-cookbook-basic-recipe/org.fipro.eclipse.tutorial.product/target/products_ + +The currently available re-bundled OpenJFX versions can be found in [this download area](https://downloads.efxclipse.bestsolution.at/p2-repos/). If you are interested about newer OpenJFX versions you can have a look at the [openjfx-osgi](https://github.com/BestSolution-at/openjfx-osgi) repository on GitHub or get in contact with [BestSolution.at](https://www.bestsolution.at/) who created and provide the bundles. + +**Note:** +As an alternative to bundling JavaFX with your application, you can also configure to use externally located JavaFX libraries. For this add **_\-Defxclipse.java-modules.dir=<PATH\_TO\_YOUR\_JAVAFX\_LIBS>_** to the VM Arguments of the Product Configuration. As this approach makes the installation dependent on external states, I did not cover it here. But it is worth to mention as it might be interesting in some cases. There is also a [blog post by Tom Schindl](https://tomsondev.bestsolution.at/2020/01/28/setting-up-efxclipse-rcp-development-for-java11-and-pde/) about JavaFX, Java 11, RCP and PDE. + +## Interlude: UI Freeze issue + +There is an UI freeze issue related to JavaFX in Eclipse. It is an incompatibility of JavaFX and the _glass.dll_ it tries to load when there are multiple Java versions available, e.g. if you start your application with a Java version that is different to the Java version configured in your PATH system environment variable. + +When Eclipse launches it automatically generates an entry for `java.library.path` that contains the path to the Java installation that is used to start Eclipse, and the PATH system environment variable. If another or additional Java installations are on the PATH, the `java.library.path` environment variable contains paths to multiple Java versions. It can then happen that JavaFX loads the _glass.dll_ from the not matching Java installation, which then leads to a `NoSuchMethodError` or similar that cause a crash. + +This issue is already reported [here](https://bugs.eclipse.org/bugs/show_bug.cgi?id=540247). + +`java.library.path` is a Java environment variable that is used to add additional native libraries to the runtime. It is only used in case native libraries should be added to an application that are loaded inside Java via `System.loadLibrary()`. In Eclipse/OSGi development, native libraries are typically included inside a plug-in project and do not reside external, as this would make the installation dependent on locally installed native libraries on the consumer side. Therefore setting `java.library.path` to an empty value (or an appropriate value in case it is needed) should not have any effect on other functionalities. + +- Open the file _org.fipro.eclipse.tutorial.app.product_ in the project _org.fipro.eclipse.tutorial.product_ +- Switch to the _Launching_ tab + - Add _\-Djava.library.path=_ to the _VM Arguments_ (java.library.path will be empty and JavaFX will load the _glass.dll_ from the Java installation that was used to start the application) diff --git a/tutorials/Eclipse_RCP_Cookbook_Preferences.md b/tutorials/Eclipse_RCP_Cookbook_Preferences.md index af41df2..8746bb6 100644 --- a/tutorials/Eclipse_RCP_Cookbook_Preferences.md +++ b/tutorials/Eclipse_RCP_Cookbook_Preferences.md @@ -8,7 +8,7 @@ This recipe will explain and show how to add a preference page to an Eclipse 4 a ## Ingredients -This recipe is based on the [Eclipse RCP Cookbook – The Topping Recipe](Eclipse_RCP_Cookbook_p2.md). To get started fast with this recipe, the recipe is prepared for you on GitHub . +This recipe is based on the [Eclipse RCP Cookbook – The Topping Recipe](Eclipse_RCP_Cookbook_p2.md). To get started fast with this recipe, the recipe is prepared for you on GitHub. To use the prepared recipe, import the project by cloning the Git repository: @@ -31,7 +31,7 @@ In a plain Eclipse 4 application, you don't want to use the _Compatibility Layer - Open the target definition _org.fipro.eclipse.tutorial.target.target_ in the project _org.fipro.eclipse.tutorial.target_ -- Update the Software Site in the opened _Target Definition Editor_ +- Update the Software Sites in the opened _Target Definition Editor_ - Alternative A - Switch to the _Source_ tab and add the following snippet to the editor ```xml diff --git a/tutorials/application_fx.png b/tutorials/application_fx.png new file mode 100644 index 0000000..38f9792 Binary files /dev/null and b/tutorials/application_fx.png differ diff --git a/tutorials/efxclipse_preferences.png b/tutorials/efxclipse_preferences.png new file mode 100644 index 0000000..6bd91ad Binary files /dev/null and b/tutorials/efxclipse_preferences.png differ