From 914681bb392d8b05ffbb60756cd0d38950234f80 Mon Sep 17 00:00:00 2001 From: clalleme Date: Fri, 3 Nov 2017 12:34:25 +0100 Subject: [PATCH 1/3] load icons for splash screen and reuse into main stage if not found from properties. unfortunately we can not reuse from properties because at splast time, spring ctx is not yet available. --- .../AbstractJavaFxApplicationSupport.java | 505 +++++++++--------- 1 file changed, 254 insertions(+), 251 deletions(-) diff --git a/src/main/java/de/felixroske/jfxsupport/AbstractJavaFxApplicationSupport.java b/src/main/java/de/felixroske/jfxsupport/AbstractJavaFxApplicationSupport.java index 51cac29..5860041 100644 --- a/src/main/java/de/felixroske/jfxsupport/AbstractJavaFxApplicationSupport.java +++ b/src/main/java/de/felixroske/jfxsupport/AbstractJavaFxApplicationSupport.java @@ -19,6 +19,8 @@ import java.awt.*; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -40,6 +42,7 @@ public abstract class AbstractJavaFxApplicationSupport extends Application { private static SplashScreen splashScreen; private static List icons = new ArrayList<>(); + private final List defaultIcons = new ArrayList<>(); private final BooleanProperty appCtxLoaded = new SimpleBooleanProperty(false); @@ -60,22 +63,19 @@ public static SystemTray getSystemTray() { } /** - * @param window - * The FxmlView derived class that should be shown. - * @param mode - * See {@code javafx.stage.Modality}. + * @param window The FxmlView derived class that should be shown. + * @param mode See {@code javafx.stage.Modality}. */ public static void showView(final Class window, final Modality mode) { final AbstractFxmlView view = applicationContext.getBean(window); Stage newStage = new Stage(); Scene newScene; - if(view.getView().getScene() != null) { + if (view.getView().getScene() != null) { // This view was already shown so // we have a scene for it and use this one. newScene = view.getView().getScene(); - } - else { + } else { newScene = new Scene(view.getView()); } @@ -88,258 +88,261 @@ public static void showView(final Class window, fina newStage.showAndWait(); } - private void loadIcons(ConfigurableApplicationContext ctx) - { - try { - final List fsImages = PropertyReaderHelper.get(ctx.getEnvironment(), Constant.KEY_APPICONS); + private void loadIcons(ConfigurableApplicationContext ctx) { + try { + final List fsImages = PropertyReaderHelper.get(ctx.getEnvironment(), Constant.KEY_APPICONS); + + if (!fsImages.isEmpty()) { + fsImages.forEach((s) -> + { + Image img = new Image(getClass().getResource(s).toExternalForm()); + icons.add(img); + } + ); + } else { // add factory images + icons.addAll(defaultIcons); + } + } catch (Exception e) { + LOGGER.error("Failed to load icons: ", e); + } + + + } - if (!fsImages.isEmpty()) { - fsImages.forEach((s) -> - { - Image img = new Image(getClass().getResource(s).toExternalForm()); - icons.add(img); + /* + * (non-Javadoc) + * + * @see javafx.application.Application#init() + */ + @Override + public void init() throws Exception { + // Load in JavaFx Thread and reused by Completable Future, but should no be a big deal. + defaultIcons.addAll(loadDefaultIcons()); + CompletableFuture.supplyAsync(() -> { + return SpringApplication.run(this.getClass(), savedArgs); + }).whenComplete((ctx, throwable) -> { + if (throwable != null) { + LOGGER.error("Failed to load spring application context: ", throwable); + Platform.runLater(() -> showErrorAlert(throwable)); + } else { + Platform.runLater(() -> { + loadIcons(ctx); + launchApplicationView(ctx); + }); } - ); - } else { // add factory images - icons.add(new Image(getClass().getResource("/icons/gear_16x16.png").toExternalForm())); - icons.add(new Image(getClass().getResource("/icons/gear_24x24.png").toExternalForm())); - icons.add(new Image(getClass().getResource("/icons/gear_36x36.png").toExternalForm())); - icons.add(new Image(getClass().getResource("/icons/gear_42x42.png").toExternalForm())); - icons.add(new Image(getClass().getResource("/icons/gear_64x64.png").toExternalForm())); - } - } catch (Exception e) { - LOGGER.error("Failed to load icons: ", e); + }); } - } - /* - * (non-Javadoc) - * - * @see javafx.application.Application#init() - */ - @Override - public void init() throws Exception - { - CompletableFuture.supplyAsync(() -> { - return SpringApplication.run(this.getClass(), savedArgs); - }).whenComplete((ctx, throwable) -> { - if (throwable != null) { - LOGGER.error("Failed to load spring application context: ", throwable); - Platform.runLater(() -> showErrorAlert(throwable)); - } else { - Platform.runLater(() -> { - loadIcons(ctx); - launchApplicationView(ctx);}); - } - }); - } - - /* - * (non-Javadoc) - * - * @see javafx.application.Application#start(javafx.stage.Stage) - */ - @Override - public void start(final Stage stage) throws Exception { - - GUIState.setStage(stage); - GUIState.setHostServices(this.getHostServices()); - final Stage splashStage = new Stage(StageStyle.UNDECORATED); - - if (AbstractJavaFxApplicationSupport.splashScreen.visible()) { - final Scene splashScene = new Scene(splashScreen.getParent()); - splashStage.setScene(splashScene); - splashStage.show(); - } - - final Runnable showMainAndCloseSplash = () -> { - showInitialView(); - if (AbstractJavaFxApplicationSupport.splashScreen.visible()) { - splashStage.hide(); - splashStage.setScene(null); - } - }; - - synchronized (this) { - if (appCtxLoaded.get()) { - // Spring ContextLoader was faster - Platform.runLater(showMainAndCloseSplash); - } else { - appCtxLoaded.addListener((ov, oVal, nVal) -> { - Platform.runLater(showMainAndCloseSplash); - }); - } - } - - } - - /** - * Show initial view. - */ - private void showInitialView() { - final String stageStyle = applicationContext.getEnvironment().getProperty(Constant.KEY_STAGE_STYLE); - if (stageStyle != null) { - GUIState.getStage().initStyle(StageStyle.valueOf(stageStyle.toUpperCase())); - } else { - GUIState.getStage().initStyle(StageStyle.DECORATED); - } - - beforeInitialView(GUIState.getStage(), applicationContext); - - showView(savedInitialView); - } + /* + * (non-Javadoc) + * + * @see javafx.application.Application#start(javafx.stage.Stage) + */ + @Override + public void start(final Stage stage) throws Exception { + + GUIState.setStage(stage); + GUIState.setHostServices(this.getHostServices()); + final Stage splashStage = new Stage(StageStyle.UNDECORATED); + + if (AbstractJavaFxApplicationSupport.splashScreen.visible()) { + final Scene splashScene = new Scene(splashScreen.getParent()); + splashStage.setScene(splashScene); + splashStage.getIcons().addAll(defaultIcons); + beforeShowingSplash(splashStage); + splashStage.show(); + } + final Runnable showMainAndCloseSplash = () -> { + showInitialView(); + if (AbstractJavaFxApplicationSupport.splashScreen.visible()) { + splashStage.hide(); + splashStage.setScene(null); + } + }; + + synchronized (this) { + if (appCtxLoaded.get()) { + // Spring ContextLoader was faster + Platform.runLater(showMainAndCloseSplash); + } else { + appCtxLoaded.addListener((ov, oVal, nVal) -> { + Platform.runLater(showMainAndCloseSplash); + }); + } + } + + } /** - * Launch application view. - * - */ - private void launchApplicationView(final ConfigurableApplicationContext ctx) { - AbstractJavaFxApplicationSupport.applicationContext = ctx; - appCtxLoaded.set(true); - } - - /** - * Show view. - * - * @param newView - * the new view - */ - public static void showView(final Class newView) { - try { - final AbstractFxmlView view = applicationContext.getBean(newView); - - if(GUIState.getScene() == null) { - GUIState.setScene(new Scene(view.getView())); - } - else { - GUIState.getScene().setRoot(view.getView()); - } - GUIState.getStage().setScene(GUIState.getScene()); - - applyEnvPropsToView(); - - GUIState.getStage().getIcons().addAll(icons); - GUIState.getStage().show(); - - } catch(Throwable t) { - LOGGER.error("Failed to load application: ", t); - showErrorAlert(t); - } - } - - /** - * Show error alert that close app. - * - * @param throwable - * cause of error - */ - private static void showErrorAlert(Throwable throwable) { - Alert alert = new Alert(AlertType.ERROR, "Oops! An unrecoverable error occurred.\n" + - "Please contact your software vendor.\n\n" + - "The application will stop now."); - alert.showAndWait().ifPresent(response -> Platform.exit()); - } - /** - * Apply env props to view. - */ - private static void applyEnvPropsToView() { - PropertyReaderHelper.setIfPresent(applicationContext.getEnvironment(), Constant.KEY_TITLE, String.class, - GUIState.getStage()::setTitle); - - PropertyReaderHelper.setIfPresent(applicationContext.getEnvironment(), Constant.KEY_STAGE_WIDTH, Double.class, - GUIState.getStage()::setWidth); - - PropertyReaderHelper.setIfPresent(applicationContext.getEnvironment(), Constant.KEY_STAGE_HEIGHT, Double.class, - GUIState.getStage()::setHeight); - - PropertyReaderHelper.setIfPresent(applicationContext.getEnvironment(), Constant.KEY_STAGE_RESIZABLE, Boolean.class, - GUIState.getStage()::setResizable); - } - - /* - * (non-Javadoc) - * - * @see javafx.application.Application#stop() - */ - @Override - public void stop() throws Exception { - super.stop(); - if (applicationContext != null) { - applicationContext.close(); - } // else: someone did it already - } - - /** - * Sets the title. Allows to overwrite values applied during construction at - * a later time. - * - * @param title - * the new title - */ - protected static void setTitle(final String title) { - GUIState.getStage().setTitle(title); - } - - /** - * Launch app. - * - * @param appClass - * the app class - * @param view - * the view - * @param args - * the args - */ - public static void launch(final Class appClass, - final Class view, final String[] args) { - - launch(appClass, view, new SplashScreen(), args); - } - - /** - * Launch app. - * - * @param appClass - * the app class - * @param view - * the view - * @param splashScreen - * the splash screen - * @param args - * the args - */ - public static void launch(final Class appClass, - final Class view, final SplashScreen splashScreen, final String[] args) { - savedInitialView = view; - savedArgs = args; - - if (splashScreen != null) { - AbstractJavaFxApplicationSupport.splashScreen = splashScreen; - } else { - AbstractJavaFxApplicationSupport.splashScreen = new SplashScreen(); - } - - if(SystemTray.isSupported()) { + * Show initial view. + */ + private void showInitialView() { + final String stageStyle = applicationContext.getEnvironment().getProperty(Constant.KEY_STAGE_STYLE); + if (stageStyle != null) { + GUIState.getStage().initStyle(StageStyle.valueOf(stageStyle.toUpperCase())); + } else { + GUIState.getStage().initStyle(StageStyle.DECORATED); + } + + beforeInitialView(GUIState.getStage(), applicationContext); + + showView(savedInitialView); + } + + + /** + * Launch application view. + */ + private void launchApplicationView(final ConfigurableApplicationContext ctx) { + AbstractJavaFxApplicationSupport.applicationContext = ctx; + appCtxLoaded.set(true); + } + + /** + * Show view. + * + * @param newView the new view + */ + public static void showView(final Class newView) { + try { + final AbstractFxmlView view = applicationContext.getBean(newView); + + if (GUIState.getScene() == null) { + GUIState.setScene(new Scene(view.getView())); + } else { + GUIState.getScene().setRoot(view.getView()); + } + GUIState.getStage().setScene(GUIState.getScene()); + + applyEnvPropsToView(); + + GUIState.getStage().getIcons().addAll(icons); + GUIState.getStage().show(); + + } catch (Throwable t) { + LOGGER.error("Failed to load application: ", t); + showErrorAlert(t); + } + } + + /** + * Show error alert that close app. + * + * @param throwable cause of error + */ + private static void showErrorAlert(Throwable throwable) { + Alert alert = new Alert(AlertType.ERROR, "Oops! An unrecoverable error occurred.\n" + + "Please contact your software vendor.\n\n" + + "The application will stop now."); + alert.showAndWait().ifPresent(response -> Platform.exit()); + } + + /** + * Apply env props to view. + */ + private static void applyEnvPropsToView() { + PropertyReaderHelper.setIfPresent(applicationContext.getEnvironment(), Constant.KEY_TITLE, String.class, + GUIState.getStage()::setTitle); + + PropertyReaderHelper.setIfPresent(applicationContext.getEnvironment(), Constant.KEY_STAGE_WIDTH, Double.class, + GUIState.getStage()::setWidth); + + PropertyReaderHelper.setIfPresent(applicationContext.getEnvironment(), Constant.KEY_STAGE_HEIGHT, Double.class, + GUIState.getStage()::setHeight); + + PropertyReaderHelper.setIfPresent(applicationContext.getEnvironment(), Constant.KEY_STAGE_RESIZABLE, Boolean.class, + GUIState.getStage()::setResizable); + } + + /* + * (non-Javadoc) + * + * @see javafx.application.Application#stop() + */ + @Override + public void stop() throws Exception { + super.stop(); + if (applicationContext != null) { + applicationContext.close(); + } // else: someone did it already + } + + /** + * Sets the title. Allows to overwrite values applied during construction at + * a later time. + * + * @param title the new title + */ + protected static void setTitle(final String title) { + GUIState.getStage().setTitle(title); + } + + /** + * Launch app. + * + * @param appClass the app class + * @param view the view + * @param args the args + */ + public static void launch(final Class appClass, + final Class view, final String[] args) { + + launch(appClass, view, new SplashScreen(), args); + } + + /** + * Launch app. + * + * @param appClass the app class + * @param view the view + * @param splashScreen the splash screen + * @param args the args + */ + public static void launch(final Class appClass, + final Class view, final SplashScreen splashScreen, final String[] args) { + savedInitialView = view; + savedArgs = args; + + if (splashScreen != null) { + AbstractJavaFxApplicationSupport.splashScreen = splashScreen; + } else { + AbstractJavaFxApplicationSupport.splashScreen = new SplashScreen(); + } + + if (SystemTray.isSupported()) { GUIState.setSystemTray(SystemTray.getSystemTray()); - } - - Application.launch(appClass, args); - } - - /** - * Gets called after full initialization of Spring application context - * and JavaFX platform right before the initial view is shown. - * Override this method as a hook to add special code for your app. Especially meant to - * add AWT code to add a system tray icon and behavior by calling - * GUIState.getSystemTray() and modifying it accordingly. - * - * By default noop. - * @param stage can be used to customize the stage before being displayed - * @param ctx represents spring ctx where you can loog for beans. - */ - public void beforeInitialView(final Stage stage, final ConfigurableApplicationContext ctx) { - } + } + + Application.launch(appClass, args); + } + + /** + * Gets called after full initialization of Spring application context + * and JavaFX platform right before the initial view is shown. + * Override this method as a hook to add special code for your app. Especially meant to + * add AWT code to add a system tray icon and behavior by calling + * GUIState.getSystemTray() and modifying it accordingly. + *

+ * By default noop. + * + * @param stage can be used to customize the stage before being displayed + * @param ctx represents spring ctx where you can loog for beans. + */ + public void beforeInitialView(final Stage stage, final ConfigurableApplicationContext ctx) { + } + + public void beforeShowingSplash(Stage splashStage) { + + } + + public Collection loadDefaultIcons() { + return Arrays.asList(new Image(getClass().getResource("/icons/gear_16x16.png").toExternalForm()), + new Image(getClass().getResource("/icons/gear_24x24.png").toExternalForm()), + new Image(getClass().getResource("/icons/gear_36x36.png").toExternalForm()), + new Image(getClass().getResource("/icons/gear_42x42.png").toExternalForm()), + new Image(getClass().getResource("/icons/gear_64x64.png").toExternalForm())); + } } From 52edf6d446547c24f5621017c13a5adf82b9bc1e Mon Sep 17 00:00:00 2001 From: clalleme Date: Fri, 3 Nov 2017 12:35:49 +0100 Subject: [PATCH 2/3] Use transparent style for splash. Looks more appropriate and make sure image with transparent background are well managed. --- .../jfxsupport/AbstractJavaFxApplicationSupport.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/de/felixroske/jfxsupport/AbstractJavaFxApplicationSupport.java b/src/main/java/de/felixroske/jfxsupport/AbstractJavaFxApplicationSupport.java index 5860041..9e59f80 100644 --- a/src/main/java/de/felixroske/jfxsupport/AbstractJavaFxApplicationSupport.java +++ b/src/main/java/de/felixroske/jfxsupport/AbstractJavaFxApplicationSupport.java @@ -9,6 +9,7 @@ import javafx.scene.control.Alert; import javafx.scene.control.Alert.AlertType; import javafx.scene.image.Image; +import javafx.scene.paint.Color; import javafx.stage.Modality; import javafx.stage.Stage; import javafx.stage.StageStyle; @@ -144,12 +145,13 @@ public void start(final Stage stage) throws Exception { GUIState.setStage(stage); GUIState.setHostServices(this.getHostServices()); - final Stage splashStage = new Stage(StageStyle.UNDECORATED); + final Stage splashStage = new Stage(StageStyle.TRANSPARENT); if (AbstractJavaFxApplicationSupport.splashScreen.visible()) { - final Scene splashScene = new Scene(splashScreen.getParent()); + final Scene splashScene = new Scene(splashScreen.getParent(), Color.TRANSPARENT); splashStage.setScene(splashScene); splashStage.getIcons().addAll(defaultIcons); + splashStage.initStyle(StageStyle.TRANSPARENT); beforeShowingSplash(splashStage); splashStage.show(); } From 472834e212793dc1af42168240461713fb2ae12d Mon Sep 17 00:00:00 2001 From: clalleme Date: Mon, 6 Nov 2017 10:50:14 +0100 Subject: [PATCH 3/3] Improve Coverage --- pom.xml | 12 ++++ .../AbstractJavaFxApplicationSupport.java | 5 +- .../AbstractJavaFxApplicationSupportTest.java | 56 +++++++++++++++++++ 3 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 src/test/java/de/felixroske/jfxsupport/AbstractJavaFxApplicationSupportTest.java diff --git a/pom.xml b/pom.xml index d2ec44f..380c037 100644 --- a/pom.xml +++ b/pom.xml @@ -80,6 +80,18 @@ 4.12 test + + org.testfx + testfx-core + 4.0.8-alpha + test + + + org.testfx + testfx-junit + 4.0.8-alpha + test + diff --git a/src/main/java/de/felixroske/jfxsupport/AbstractJavaFxApplicationSupport.java b/src/main/java/de/felixroske/jfxsupport/AbstractJavaFxApplicationSupport.java index 9e59f80..21851ad 100644 --- a/src/main/java/de/felixroske/jfxsupport/AbstractJavaFxApplicationSupport.java +++ b/src/main/java/de/felixroske/jfxsupport/AbstractJavaFxApplicationSupport.java @@ -36,11 +36,10 @@ public abstract class AbstractJavaFxApplicationSupport extends Application { private static String[] savedArgs = new String[0]; - private static Class savedInitialView; - + static Class savedInitialView; + static SplashScreen splashScreen; private static ConfigurableApplicationContext applicationContext; - private static SplashScreen splashScreen; private static List icons = new ArrayList<>(); private final List defaultIcons = new ArrayList<>(); diff --git a/src/test/java/de/felixroske/jfxsupport/AbstractJavaFxApplicationSupportTest.java b/src/test/java/de/felixroske/jfxsupport/AbstractJavaFxApplicationSupportTest.java new file mode 100644 index 0000000..8ec2502 --- /dev/null +++ b/src/test/java/de/felixroske/jfxsupport/AbstractJavaFxApplicationSupportTest.java @@ -0,0 +1,56 @@ +package de.felixroske.jfxsupport; + +import javafx.scene.image.Image; +import jfxtest.annotated.AnnotatedView; +import org.hamcrest.CoreMatchers; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.testfx.api.FxToolkit; + +import java.util.ArrayList; +import java.util.Collection; + +import static org.junit.Assert.assertThat; + +/** + * Created on 11/3/2017 for Onyx. + */ +//@RunWith(SpringRunner.class) +//@SpringBootTest +public class AbstractJavaFxApplicationSupportTest { //extends GuiTest { + + private AbstractJavaFxApplicationSupport app; + + public class TestApp extends AbstractJavaFxApplicationSupport { + + } + + @BeforeClass + public static void beforeClass() { + System.setProperty("testfx.headless", "true"); + } + + @AfterClass + public static void afterClass() { + System.setProperty("testfx.headless", "false"); + } + + @Before + public void setup() throws Exception { + FxToolkit.registerPrimaryStage(); + app = new TestApp(); + app.savedInitialView = AnnotatedView.class; + app.splashScreen = new SplashScreen(); + FxToolkit.setupApplication(() -> app); + } + + @Test + public void loadDefaultIcons() throws Exception { + final Collection images = new ArrayList<>(); + images.addAll(app.loadDefaultIcons()); + assertThat(images.size(), CoreMatchers.is(5)); + } + +} \ No newline at end of file