package eu.ggnet.saft.core; import java.awt.*; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.lang.ref.WeakReference; import java.lang.reflect.InvocationTargetException; import java.util.*; import java.util.concurrent.*; import java.util.function.Consumer; import javax.swing.JFrame; import javafx.application.Platform; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.scene.*; import javafx.stage.Stage; import eu.ggnet.saft.core.all.UiUtil; import eu.ggnet.saft.core.exception.ExceptionUtil; import eu.ggnet.saft.core.exception.SwingExceptionDialog; import eu.ggnet.saft.core.fx.FxSaft; import eu.ggnet.saft.core.swing.SwingSaft; /** * * @author oliver.guenther */ public class UiCore { private final static BooleanProperty backgroundActivity = new SimpleBooleanProperty(); // We need the raw type here. Otherwise we cannot get different typs of cosumers in and out. @SuppressWarnings("unchecked") private static final Map<Class, Consumer> exceptionConsumer = new HashMap<>(); private static Consumer<Throwable> finalConsumer = (b) -> { Runnable r = () -> { SwingExceptionDialog.show(SwingCore.mainFrame(), "Systemfehler", ExceptionUtil.extractDeepestMessage(b), ExceptionUtil.toMultilineStacktraceMessages(b), ExceptionUtil.toStackStrace(b)); }; if ( EventQueue.isDispatchThread() ) r.run(); else { try { EventQueue.invokeAndWait(r); } catch (InterruptedException | InvocationTargetException e) { // This will never happen. } } }; public static BooleanProperty backgroundActivityProperty() { return backgroundActivity; } /** * interim Mode, Saft connects to a running environment. * * @param mainView */ public static void continueSwing(JFrame mainView) { if ( isRunning() ) throw new IllegalStateException("UiCore is already initialised and running"); Platform.setImplicitExit(false); // Need this, as we asume many javafx elements opening and closing. SwingCore.mainFrame = mainView; mainView.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { for (WeakReference<Window> windowRef : SwingCore.ACTIVE_WINDOWS.values()) { if ( windowRef.get() == null ) continue; windowRef.get().setVisible(false); // Close all windows. windowRef.get().dispose(); } } @Override public void windowClosed(WindowEvent e) { Platform.exit(); } }); } /** * Starts the Core in Swing mode, may only be called once. * * @param <T> * @param builder */ public static <T extends Component> void startSwing(final Callable<T> builder) { if ( isRunning() ) throw new IllegalStateException("UiCore is already initialised and running"); try { JFrame panel = SwingSaft.dispatch(() -> { JFrame p = new JFrame(); p.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); p.getContentPane().add(builder.call()); p.pack(); p.setLocationByPlatform(true); p.setVisible(true); return p; }); continueSwing(panel); } catch (InterruptedException | InvocationTargetException | ExecutionException ex) { handle(ex); } } /** * Starts the Ui in JavaFx variant. * * This also assumes two things: * <ul> * <li>The JavaFX Platfrom is already running (as a Stage already exists), most likely created through default lifecycle of javaFx</li> * <li>This Stage will always be open or the final to be closed, so implicitExit is ok</li> * </ul> * * @param <T> type restriction. * @param primaryStage the primaryStage for the application, not yet visible. * @param builder the build for the main ui. */ public static <T extends Parent> void startJavaFx(final Stage primaryStage, final Callable<T> builder) { if ( isRunning() ) throw new IllegalStateException("UiCore is already initialised and running"); FxCore.mainStage = primaryStage; FxSaft.dispatch(() -> { T node = builder.call(); primaryStage.setTitle(UiUtil.title(node.getClass())); primaryStage.setScene(new Scene(node)); primaryStage.centerOnScreen(); primaryStage.sizeToScene(); primaryStage.show(); return null; }); } /** * Registers an extra renderer for an Exception in any stacktrace. HINT: There is no order or hierachy in the engine. So if you register duplicates or have * more than one match in a StackTrace, no one knows what might happen. * * @param <T> type of the Exception * @param clazz the class of the Exception * @param consumer the consumer to handle it. */ public static <T> void registerExceptionConsumer(Class<T> clazz, Consumer<T> consumer) { exceptionConsumer.put(clazz, consumer); } /** * Allows to overwrite the default final consumer of all exceptions. * * @param <T> type of consumer * @param consumer the consumer */ public static <T> void overwriteFinalExceptionConsumer(Consumer<Throwable> consumer) { if ( consumer != null ) finalConsumer = consumer; } /** * Passes the Exception to * <p> * @param b */ public static void handle(Throwable b) { backgroundActivity.set(false); // Cleanup for (Class<?> clazz : exceptionConsumer.keySet()) { if ( ExceptionUtil.containsInStacktrace(clazz, b) ) { exceptionConsumer.get(clazz).accept(ExceptionUtil.extractFromStraktrace(clazz, b)); return; } } finalConsumer.accept(b); } public static boolean isRunning() { return SwingCore.mainFrame() != null || FxCore.mainStage() != null; } public static boolean isFx() { return (FxCore.mainStage() != null); } public static boolean isSwing() { return (SwingCore.mainFrame() != null); } }