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 :: CEC :: Navigation Adapter</a> &gt; <a href="../index.html" class="el_bundle">it-tidalwave-bluemarine2-ui-javafx</a> &gt; <a href="index.source.html" class="el_package">it.tidalwave.bluemarine2.ui.commons.flowcontroller.impl.javafx</a> &gt; <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 &quot;License&quot;); 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 &quot;AS IS&quot; 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.
this implementation keeps all the history in the stack, thus wasting some memory.
<span class="nc" id="L82"> private final Stack&lt;NodeAndControl&gt; presentationStack = new Stack&lt;&gt;();</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(&quot;showPresentationImpl({}, {})&quot;, 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(() -&gt;</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(&quot;dismissCurrentPresentation()&quot;);</span> <span class="nc bnc" id="L146" title="All 2 branches missed."> if (presentationStack.size() &lt; 2)</span> { // TODO: should ask for user confirmation <span class="nc" id="L149"> powerOff();</span> } else { <span class="nc" id="L153"> Platform.runLater(() -&gt;</span> { <span class="nc" id="L155"> log.debug(&quot;&gt;&gt;&gt;&gt; presentationStack: {}&quot;, 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(&quot;tryToDismissCurrentPresentation()&quot;);</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(&quot;&quot;, 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(&quot;Shutting down...&quot;);</span> // TODO: 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(&quot;notifyActivated({})&quot;, 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(&quot;&quot;, 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(&quot;canDeactivate({})&quot;, 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&lt;? extends Annotation&gt; annotationClass) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { <span class="nc" id="L261"> log.debug(&quot;findAnnotatedMethod({}, {})&quot;, 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(&quot;&gt;&gt;&gt;&gt; found {} annotated method on {}&quot;, 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 &quot;slide in/out&quot; 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 &quot;forward&quot; direction, -1 for &quot;backward&quot; 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 -&gt; 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>