All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.felixroske.jfxsupport.AbstractJavaFxApplicationSupport Maven / Gradle / Ivy

package de.felixroske.jfxsupport;

import javafx.application.Application;
import javafx.application.HostServices;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.image.Image;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;

import java.awt.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;

/**
 * The Class AbstractJavaFxApplicationSupport.
 *
 * @author Felix Roske
 */
@SuppressWarnings("WeakerAccess")
public abstract class AbstractJavaFxApplicationSupport extends Application {
    private static Logger LOGGER = LoggerFactory.getLogger(AbstractJavaFxApplicationSupport.class);

    private static String[] savedArgs = new String[0];

    private static Class savedInitialView;

    private static ConfigurableApplicationContext applicationContext;

    private static SplashScreen splashScreen;

    private static List icons = new ArrayList<>();

    private final BooleanProperty appCtxLoaded = new SimpleBooleanProperty(false);

    public static Stage getStage() {
        return GUIState.getStage();
    }

    public static Scene getScene() {
        return GUIState.getScene();
    }

    public static HostServices getAppHostServices() {
        return GUIState.getHostServices();
    }

    public static SystemTray getSystemTray() {
        return GUIState.getSystemTray();
    }

    /**
     * @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) {
            // This view was already shown so
            // we have a scene for it and use this one.
            newScene = view.getView().getScene();
        }
        else {
            newScene = new Scene(view.getView());
        }

        newStage.setScene(newScene);
        newStage.initModality(mode);
        newStage.initOwner(getStage());
        newStage.setTitle(view.getDefaultTitle());
        newStage.initStyle(view.getDefaultStyle());

        newStage.showAndWait();
    }

  private void loadIcons(ConfigurableApplicationContext ctx)
  {
    try {
      final List fsImages = PropertyReaderHelper.get(ctx.getEnvironment(), "javafx.appicons");

      if (!fsImages.isEmpty()) {
        fsImages.forEach((s) ->
            {
              Image img =  new Image(getClass().getResource(s).toExternalForm());
              icons.add(img);
            }
        );
      } 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("javafx.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(), "javafx.title", String.class,
				GUIState.getStage()::setTitle);

		PropertyReaderHelper.setIfPresent(applicationContext.getEnvironment(), "javafx.stage.width", Double.class,
				GUIState.getStage()::setWidth);

		PropertyReaderHelper.setIfPresent(applicationContext.getEnvironment(), "javafx.stage.height", Double.class,
				GUIState.getStage()::setHeight);

		PropertyReaderHelper.setIfPresent(applicationContext.getEnvironment(), "javafx.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 launchApp(final Class appClass,
			final Class view, final String[] args) {

		launchApp(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 launchApp(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) {
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy