Content of file JavaFxFlowController.java.html
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../../jacoco-resources/report.gif" type="image/gif"/><title>JavaFxFlowController.java</title><link rel="stylesheet" href="../../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../../index.html" class="el_report">blueMarine II :: Application :: JavaFX</a> > <a href="../index.html" class="el_bundle">it-tidalwave-bluemarine2-ui-javafx</a> > <a href="index.source.html" class="el_package">it.tidalwave.bluemarine2.ui.commons.flowcontroller.impl.javafx</a> > <span class="el_source">JavaFxFlowController.java</span></div><h1>JavaFxFlowController.java</h1><pre class="source lang-java linenums">/*
* *********************************************************************************************************************
*
* blueMarine II: Semantic Media Centre
* http://tidalwave.it/projects/bluemarine2
*
* Copyright (C) 2015 - 2021 by Tidalwave s.a.s. (http://tidalwave.it)
*
* *********************************************************************************************************************
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* *********************************************************************************************************************
*
* git clone https://bitbucket.org/tidalwave/bluemarine2-src
* git clone https://github.com/tidalwave-it/bluemarine2-src
*
* *********************************************************************************************************************
*/
package it.tidalwave.bluemarine2.ui.commons.flowcontroller.impl.javafx;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Stack;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.scene.Node;
import javafx.scene.layout.StackPane;
import javafx.util.Duration;
import javafx.application.Platform;
import it.tidalwave.bluemarine2.ui.commons.OnActivate;
import it.tidalwave.bluemarine2.ui.commons.OnDeactivate;
import it.tidalwave.bluemarine2.ui.commons.flowcontroller.FlowController;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
/***********************************************************************************************************************
*
* A JavaFX implementation of {@link FlowController}.
*
* This implementation is very basic and suitable to simple applications without memory criticalities. It keeps a
stack of previous Presentations, without disposing their resources. A more efficient implementation should be similar
to the Android Activity life-cycle, which can jettison resources of past activities and recreate them on demand. But
it would require life-cycle methods on the node interfaces.
*
* @stereotype Controller
*
* @author Fabrizio Giudici
*
**********************************************************************************************************************/
<span class="nc" id="L65">@Slf4j</span>
<span class="nc" id="L66">public class JavaFxFlowController implements FlowController</span>
{
<span class="nc" id="L68"> @RequiredArgsConstructor @Getter @ToString</span>
static class NodeAndControl
{
@Nonnull
<span class="nc" id="L72"> private final Node node;</span>
@Nullable
<span class="nc" id="L75"> private final Object control;</span>
}
<span class="nc" id="L78"> @Getter @Setter</span>
private StackPane contentPane;
// TODO: this implementation keeps all the history in the stack, thus wasting some memory.
<span class="nc" id="L82"> private final Stack<NodeAndControl> presentationStack = new Stack<>();</span>
/*******************************************************************************************************************
*
* {@inheritDoc}
*
******************************************************************************************************************/
@Override
public void showPresentation (@Nonnull final Object presentation)
{
<span class="nc" id="L92"> showPresentationImpl(presentation, null);</span>
<span class="nc" id="L93"> }</span>
/*******************************************************************************************************************
*
* {@inheritDoc}
*
******************************************************************************************************************/
@Override
public void showPresentation (@Nonnull final Object presentation, @Nonnull final Object control)
{
<span class="nc" id="L103"> showPresentationImpl(presentation, control);</span>
<span class="nc" id="L104"> }</span>
/*******************************************************************************************************************
*
* {@inheritDoc}
*
******************************************************************************************************************/
private void showPresentationImpl (@Nonnull final Object presentation, @Nullable final Object control)
{
<span class="nc" id="L113"> log.info("showPresentationImpl({}, {})", presentation, control);</span>
// TODO: use an aspect - should be already done in some other project
// FIXME: should not be needed, presentations should already run in JavaFX thread
<span class="nc" id="L117"> Platform.runLater(() -></span>
{
<span class="nc" id="L119"> final Node newNode = (Node)presentation;</span>
<span class="nc bnc" id="L121" title="All 2 branches missed."> if (presentationStack.isEmpty())</span>
{
<span class="nc" id="L123"> contentPane.getChildren().add(newNode);</span>
}
else
{
<span class="nc" id="L127"> final Node oldNode = presentationStack.peek().getNode();</span>
<span class="nc" id="L128"> slide(newNode, oldNode, +1); </span>
}
<span class="nc" id="L131"> presentationStack.push(new NodeAndControl(newNode, control));</span>
<span class="nc" id="L132"> notifyActivated(control);</span>
<span class="nc" id="L133"> });</span>
<span class="nc" id="L134"> }</span>
/*******************************************************************************************************************
*
* {@inheritDoc}
*
******************************************************************************************************************/
@Override
public void dismissCurrentPresentation()
{
<span class="nc" id="L144"> log.info("dismissCurrentPresentation()");</span>
<span class="nc bnc" id="L146" title="All 2 branches missed."> if (presentationStack.size() < 2)</span>
{
// TODO: should ask for user confirmation
<span class="nc" id="L149"> powerOff();</span>
}
else
{
<span class="nc" id="L153"> Platform.runLater(() -></span>
{
<span class="nc" id="L155"> log.debug(">>>> presentationStack: {}", presentationStack);</span>
<span class="nc" id="L156"> final Node oldNode = presentationStack.pop().getNode();</span>
<span class="nc" id="L157"> final Node newNode = presentationStack.peek().getNode();</span>
<span class="nc" id="L158"> slide(newNode, oldNode, -1); </span>
<span class="nc" id="L159"> notifyActivated(presentationStack.peek().getControl());</span>
<span class="nc" id="L160"> });</span>
}
<span class="nc" id="L162"> }</span>
/*******************************************************************************************************************
*
* {@inheritDoc}
*
******************************************************************************************************************/
public void tryToDismissCurrentPresentation()
{
<span class="nc" id="L171"> log.info("tryToDismissCurrentPresentation()");</span>
try
{
<span class="nc" id="L175"> canDeactivate(presentationStack.peek().getControl(), this::dismissCurrentPresentation);</span>
}
<span class="nc" id="L177"> catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)</span>
{
<span class="nc" id="L179"> log.error("", e);</span>
<span class="nc" id="L180"> }</span>
<span class="nc" id="L181"> }</span>
/*******************************************************************************************************************
*
* {@inheritDoc}
*
******************************************************************************************************************/
@Override
public void powerOff()
{
<span class="nc" id="L191"> log.info("Shutting down...");</span>
// TODO: fire a PowerOff event and wait for collaboration completion
| fire a PowerOff event and wait for collaboration completion | |
// TODO: in this case, the responsibility to fire PowerOn should be moved here
// Platform.exit();
<span class="nc" id="L195"> System.exit(0); // needed, otherwise Spring won't necessarily shut down </span>
<span class="nc" id="L196"> }</span>
/*******************************************************************************************************************
*
*
*
******************************************************************************************************************/
private void notifyActivated (@Nullable final Object control)
{
try
{
<span class="nc" id="L207"> log.debug("notifyActivated({})", control);</span>
<span class="nc" id="L208"> final Method method = findAnnotatedMethod(control, OnActivate.class);</span>
<span class="nc bnc" id="L210" title="All 2 branches missed."> if (method != null)</span>
{
// FIXME: should run in a background process
<span class="nc" id="L213"> method.invoke(control);</span>
}
}
<span class="nc" id="L216"> catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)</span>
{
<span class="nc" id="L218"> log.error("", e);</span>
<span class="nc" id="L219"> throw new RuntimeException(e);</span>
<span class="nc" id="L220"> }</span>
<span class="nc" id="L221"> }</span>
/*******************************************************************************************************************
*
* If a Presentation Control is passed, it is inspected for a method annotated with {@link OnDeactivate}. If found,
* it is called. If it returns {@code false}, this method returns {@code false}.
*
* @param control the Presentation Control
*
******************************************************************************************************************/
private void canDeactivate (@Nullable final Object control, @Nonnull final Runnable runnable)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
{
<span class="nc" id="L234"> log.debug("canDeactivate({})", control);</span>
<span class="nc" id="L235"> final Method method = findAnnotatedMethod(control, OnDeactivate.class);</span>
<span class="nc bnc" id="L237" title="All 2 branches missed."> if (method == null)</span>
{
<span class="nc" id="L239"> runnable.run();</span>
}
else
{
// FIXME: should run in a background process
<span class="nc bnc" id="L244" title="All 2 branches missed."> if (method.invoke(control).equals(OnDeactivate.Result.PROCEED))</span>
{
<span class="nc" id="L246"> runnable.run();</span>
}
}
<span class="nc" id="L249"> }</span>
/*******************************************************************************************************************
*
*
*
******************************************************************************************************************/
@Nullable
private static Method findAnnotatedMethod (@Nullable final Object object,
@Nonnull final Class<? extends Annotation> annotationClass)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
{
<span class="nc" id="L261"> log.debug("findAnnotatedMethod({}, {})", object, annotationClass);</span>
<span class="nc bnc" id="L263" title="All 2 branches missed."> if (object != null)</span>
{
<span class="nc bnc" id="L265" title="All 2 branches missed."> for (final Method method : object.getClass().getDeclaredMethods())</span>
{
<span class="nc bnc" id="L267" title="All 2 branches missed."> if (method.getAnnotation(annotationClass) != null)</span>
{
<span class="nc" id="L269"> log.debug(">>>> found {} annotated method on {}", annotationClass, object);</span>
<span class="nc" id="L270"> method.setAccessible(true);</span>
<span class="nc" id="L271"> return method; </span>
}
}
}
<span class="nc" id="L276"> return null;</span>
}
/*******************************************************************************************************************
*
* Starts a "slide in/out" animation moving an old {@link Node} out and a new {@link Node} in, with a given
* direction.
*
* @param newNode the {@code Node} to move in
* @param oldNode the {@code Node} to move out
* @param direction +1 for "forward" direction, -1 for "backward" direction
*
******************************************************************************************************************/
private void slide (@Nonnull final Node newNode, @Nonnull final Node oldNode, final int direction)
{
<span class="nc" id="L291"> contentPane.getChildren().add(newNode);</span>
<span class="nc" id="L292"> final double height = contentPane.getHeight();</span>
<span class="nc" id="L293"> final KeyFrame start = new KeyFrame(Duration.ZERO,</span>
<span class="nc" id="L294"> new KeyValue(newNode.translateYProperty(), height * direction),</span>
<span class="nc" id="L295"> new KeyValue(oldNode.translateYProperty(), 0));</span>
<span class="nc" id="L296"> final KeyFrame end = new KeyFrame(Duration.millis(200),</span>
<span class="nc" id="L297"> new KeyValue(newNode.translateYProperty(), 0),</span>
<span class="nc" id="L298"> new KeyValue(oldNode.translateYProperty(), -height * direction));</span>
<span class="nc" id="L299"> final Timeline slideAnimation = new Timeline(start, end);</span>
<span class="nc" id="L300"> slideAnimation.setOnFinished(event -> contentPane.getChildren().remove(oldNode));</span>
<span class="nc" id="L301"> slideAnimation.play();</span>
<span class="nc" id="L302"> }</span>
}
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.7.202105040129</span></div></body></html>