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 51cac29..21851ad 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; @@ -19,6 +20,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; @@ -33,13 +36,13 @@ 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<>(); 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,262 @@ 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#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.TRANSPARENT); + + if (AbstractJavaFxApplicationSupport.splashScreen.visible()) { + final Scene splashScene = new Scene(splashScreen.getParent(), Color.TRANSPARENT); + splashStage.setScene(splashScene); + splashStage.getIcons().addAll(defaultIcons); + splashStage.initStyle(StageStyle.TRANSPARENT); + 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); + }); + } + } + + } + + + /** + * 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); - } - /* - * (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); - } + 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 + } /** - * 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()) { + * 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())); + } } 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