<commit_before>/*******************************************************************************
 * Copyright (c) 2015-2016 Oak Ridge National Laboratory.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 ******************************************************************************/
package org.csstudio.javafx.rtplot.internal;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.IllegalPathStateException;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import org.csstudio.display.builder.util.undo.UndoableActionManager;
import org.csstudio.javafx.BufferUtil;
import org.csstudio.javafx.DoubleBuffer;
import org.csstudio.javafx.rtplot.util.RTPlotUpdateThrottle;

import javafx.application.Platform;
import javafx.geometry.Point2D;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelFormat;
import javafx.scene.image.WritableImage;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;

/** Base for plots
 *
 *  <p>Based on an {@link ImageView}.
 *  Container needs to call <code>setSize</code>.
 *
 *  @author <NAME>
 */
@SuppressWarnings("nls")
abstract class PlotCanvasBase extends ImageView
{
    // Implementation used to be based on JFX Canvas,
    // drawing the plot's image and then adding cursor mode feedback.
    // JFX canvas, however, can queue up rendering requests,
    // including multiple copies of to-be-rendered images,
    // which quickly exhausts memory for large plots.
    //
    // Using reflection to check the Canvas.current and its internal
    // buffer one can warn about this.
    // Calling canvas.getGraphicsContext2D().clearRect(0, 0, width, height)
    // will help to shrink the rendering queue IF it's being processed
    // ( http://stackoverflow.com/questions/18097404/how-can-i-free-canvas-memory ).
    //
    // Overall, however, ImageView avoids memory issues because it
    // only holds a reference to the current image.

    protected static final int ARROW_SIZE = 8;

    protected static final double ZOOM_FACTOR = 1.2;

    /** When using 'rubberband' to zoom in, need to select a region
     *  at least this wide resp. high.
     *  Smaller regions are likely the result of an accidental
     *  click-with-jerk, which would result into a huge zoom step.
     */
    protected static final int ZOOM_PIXEL_THRESHOLD = 20;

    /** Strokes used for mouse feedback */
    protected static final BasicStroke MOUSE_FEEDBACK_BACK = new BasicStroke(3, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER),
                                       MOUSE_FEEDBACK_FRONT = new BasicStroke(1);

    /** Support for un-do and re-do */
    protected final UndoableActionManager undo = new UndoableActionManager(50);

    /** Area of this plot */
    protected volatile Rectangle area = new Rectangle(0, 0, 0, 0);

    /** Suppress updates triggered by axis changes from layout or autoscale
     *
     *  Calling updateImageBuffer can trigger axis changes because of layout
     *  or autoscale, which call the plot_part_listener.
     */
    private volatile boolean in_update = false;

    /** Does layout need to be re-computed? */
    protected final AtomicBoolean need_layout = new AtomicBoolean(true);

    /** Does plot image to be re-created? */
    protected final AtomicBoolean need_update = new AtomicBoolean(true);

    /** Throttle updates, enforcing a 'dormant' period */
    private final RTPlotUpdateThrottle update_throttle;

    /** Buffer for image and color bar
     *
     *  <p>UpdateThrottle calls updateImageBuffer() to set the image
     *  in its thread, then redrawn in UI thread.
     */
    private volatile BufferedImage plot_image = null;

    /** Listener to {@link PlotPart}s, triggering refresh of plot */
    protected final PlotPartListener plot_part_listener = new PlotPartListener()
    {
        @Override
        public void layoutPlotPart(final PlotPart plotPart)
        {
            need_layout.set(true);
        }

        @Override
        public void refreshPlotPart(final PlotPart plotPart)
        {
            if (! in_update)
                requestUpdate();
        }
    };

    /** (Double) buffer used to combined the plot with mouse feedback overlays */
    private final DoubleBuffer buffers = new DoubleBuffer();

    /** Redraw the plot on UI thread by painting the 'plot_image' */
    private final Runnable redraw_runnable = () ->
    {
        final BufferedImage copy = plot_image;
        if (copy != null)
        {
            // Create copy of basic plot
            if (copy.getType() != BufferedImage.TYPE_INT_ARGB)
                throw new IllegalPathStateException("Need TYPE_INT_ARGB for direct buffer access, not " + copy.getType());
            final int width = copy.getWidth(), height = copy.getHeight();
            final BufferUtil buffer = buffers.getBufferedImage(width, height);
            final BufferedImage combined = buffer.getImage();
            final int[] src  = ((DataBufferInt)     copy.getRaster().getDataBuffer()).getData();
            final int[] dest = ((DataBufferInt) combined.getRaster().getDataBuffer()).getData();
            System.arraycopy(src, 0, dest, 0, width * height);

            // Add mouse mode feedback
            final Graphics2D gc = buffer.getGraphics();
            gc.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            gc.setColor(Color.BLACK);
            drawMouseModeFeedback(gc);

            // Convert to JFX image and show
            final WritableImage image = new WritableImage(width, height);
            // SwingFXUtils.toFXImage(combined, image);
            image.getPixelWriter().setPixels(0, 0, width, height, PixelFormat.getIntArgbInstance(), dest, 0, width);

            setImage(image);
        }
    };

    protected MouseMode mouse_mode = MouseMode.NONE;
    protected Optional<Point2D> mouse_start = Optional.empty();
    protected volatile Optional<Point2D> mouse_current = Optional.empty();

    /** Constructor
     *  @param active Active mode where plot reacts to mouse/keyboard?
     */
    protected PlotCanvasBase(final boolean active)
    {
        // 50ms = 20Hz default throttle
        update_throttle = new RTPlotUpdateThrottle(50, TimeUnit.MILLISECONDS, () ->
        {
            if (need_update.getAndSet(false))
            {
                in_update = true;
                final BufferedImage latest = updateImageBuffer();
                in_update = false;
                if (latest == null)
                    // Update failed, request another
                    requestUpdate();
                else
                    plot_image = latest;
            }
            Platform.runLater(redraw_runnable);
        });

        if (active)
        {
            setOnMouseEntered(this::mouseEntered);
            setOnScroll(this::wheelZoom);
        }
    }

    /** Call to update size of plot
     *
     *  @param width
     *  @param height
     */
    public void setSize(final double width, final double height)
    {
      area = new Rectangle((int)width, (int)height);<commit_msg>RTPlot: Prevent call to runLater when pending; Default throttle to 5Hz<commit_after>/*******************************************************************************
 * Copyright (c) 2015-2016 Oak Ridge National Laboratory.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 ******************************************************************************/
package org.csstudio.javafx.rtplot.internal;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.IllegalPathStateException;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import org.csstudio.display.builder.util.undo.UndoableActionManager;
import org.csstudio.javafx.BufferUtil;
import org.csstudio.javafx.DoubleBuffer;
import org.csstudio.javafx.rtplot.util.RTPlotUpdateThrottle;

import javafx.application.Platform;
import javafx.geometry.Point2D;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelFormat;
import javafx.scene.image.WritableImage;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;

/** Base for plots
 *
 *  <p>Based on an {@link ImageView}.
 *  Container needs to call <code>setSize</code>.
 *
 *  @author <NAME>
 */
@SuppressWarnings("nls")
abstract class PlotCanvasBase extends ImageView
{
    // Implementation used to be based on JFX Canvas,
    // drawing the plot's image and then adding cursor mode feedback.
    // JFX canvas, however, can queue up rendering requests,
    // including multiple copies of to-be-rendered images,
    // which quickly exhausts memory for large plots.
    //
    // Using reflection to check the Canvas.current and its internal
    // buffer one can warn about this.
    // Calling canvas.getGraphicsContext2D().clearRect(0, 0, width, height)
    // will help to shrink the rendering queue IF it's being processed
    // ( http://stackoverflow.com/questions/18097404/how-can-i-free-canvas-memory ).
    //
    // Overall, however, ImageView avoids memory issues because it
    // only holds a reference to the current image.

    protected static final int ARROW_SIZE = 8;

    protected static final double ZOOM_FACTOR = 1.2;

    /** When using 'rubberband' to zoom in, need to select a region
     *  at least this wide resp. high.
     *  Smaller regions are likely the result of an accidental
     *  click-with-jerk, which would result into a huge zoom step.
     */
    protected static final int ZOOM_PIXEL_THRESHOLD = 20;

    /** Strokes used for mouse feedback */
    protected static final BasicStroke MOUSE_FEEDBACK_BACK = new BasicStroke(3, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER),
                                       MOUSE_FEEDBACK_FRONT = new BasicStroke(1);

    /** Support for un-do and re-do */
    protected final UndoableActionManager undo = new UndoableActionManager(50);

    /** Area of this plot */
    protected volatile Rectangle area = new Rectangle(0, 0, 0, 0);

    /** Suppress updates triggered by axis changes from layout or autoscale
     *
     *  Calling updateImageBuffer can trigger axis changes because of layout
     *  or autoscale, which call the plot_part_listener.
     */
    private volatile boolean in_update = false;

    /** Does layout need to be re-computed? */
    protected final AtomicBoolean need_layout = new AtomicBoolean(true);

    /** Does plot image to be re-created? */
    protected final AtomicBoolean need_update = new AtomicBoolean(true);

    /** Throttle updates, enforcing a 'dormant' period */
    private final RTPlotUpdateThrottle update_throttle;

    /** Buffer for image and color bar
     *
     *  <p>UpdateThrottle calls updateImageBuffer() to set the image
     *  in its thread, then redrawn in UI thread.
     */
    private volatile BufferedImage plot_image = null;

    /** Listener to {@link PlotPart}s, triggering refresh of plot */
    protected final PlotPartListener plot_part_listener = new PlotPartListener()
    {
        @Override
        public void layoutPlotPart(final PlotPart plotPart)
        {
            need_layout.set(true);
        }

        @Override
        public void refreshPlotPart(final PlotPart plotPart)
        {
            if (! in_update)
                requestUpdate();
        }
    };

    /** (Double) buffer used to combined the plot with mouse feedback overlays */
    private final DoubleBuffer buffers = new DoubleBuffer();

    /** Has a call to redraw_runnable already been queued?
     *  Cleared when redraw_runnable is executed
     */
    private final AtomicBoolean pending_redraw = new AtomicBoolean();

    /** Redraw the plot on UI thread by painting the 'plot_image' */
    private final Runnable redraw_runnable = () ->
    {
        // Indicate that a redraw has occurred
        pending_redraw.set(false);
        final BufferedImage copy = plot_image;
        if (copy != null)
        {
            // Create copy of basic plot
            if (copy.getType() != BufferedImage.TYPE_INT_ARGB)
                throw new IllegalPathStateException("Need TYPE_INT_ARGB for direct buffer access, not " + copy.getType());
            final int width = copy.getWidth(), height = copy.getHeight();
            final BufferUtil buffer = buffers.getBufferedImage(width, height);
            final BufferedImage combined = buffer.getImage();
            final int[] src  = ((DataBufferInt)     copy.getRaster().getDataBuffer()).getData();
            final int[] dest = ((DataBufferInt) combined.getRaster().getDataBuffer()).getData();
            System.arraycopy(src, 0, dest, 0, width * height);

            // Add mouse mode feedback
            final Graphics2D gc = buffer.getGraphics();
            gc.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            gc.setColor(Color.BLACK);
            drawMouseModeFeedback(gc);

            // Convert to JFX image and show
            final WritableImage image = new WritableImage(width, height);
            // SwingFXUtils.toFXImage(combined, image);
            image.getPixelWriter().setPixels(0, 0, width, height, PixelFormat.getIntArgbInstance(), dest, 0, width);

            setImage(image);
        }
    };

    protected MouseMode mouse_mode = MouseMode.NONE;
    protected Optional<Point2D> mouse_start = Optional.empty();
    protected volatile Optional<Point2D> mouse_current = Optional.empty();

    /** Constructor
     *  @param active Active mode where plot reacts to mouse/keyboard?
     */
    protected PlotCanvasBase(final boolean active)
    {
        // 200ms = 5Hz default throttle
        update_throttle = new RTPlotUpdateThrottle(200, TimeUnit.MILLISECONDS, () ->
        {
            if (need_update.getAndSet(false))
            {
                in_update = true;
                final BufferedImage latest = updateImageBuffer();
                in_update = false;
                if (latest == null)
                    // Update failed, request another
                    requestUpdate();
                else
                    plot_image = latest;
            }
            if (!pending_redraw.getAndSet(true))
                Platform.runLater(redraw_runnable);
        });

        if (active)
        {
            setOnMouseEntered(this::mouseEntered);
            setOnScroll(this::wheelZoom);
        }
    }

    /** Call to update size of plot
     *
     *  @param width
     *  @param height
     */
    public void setSize(final double width, final double height)
    {
      area = new Rectangle((int)width, (int)height);
{'fraction_non_alphanumeric': 0.10769330397616889, 'fraction_numerical': 0.006864395803652377, 'mean_word_length': 2.733800773694391, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1120514', 'n_tokens_mistral': 4323, 'n_tokens_neox': 4123, 'n_words': 1259}