org.eclipse.swt.examples.paint.ContinuousPaintSession.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.swt.examples.paint.ContinuousPaintSession.java

Source

/*******************************************************************************
 * Copyright (c) 2000, 2015 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.swt.examples.paint;

import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Display;

/**
 * The superclass for paint tools that draw continuously along the path
 * traced by the mouse's movement while the button is depressed
 */
public abstract class ContinuousPaintSession extends BasicPaintSession {
    /**
     * True if a click-drag is in progress.
     */
    private boolean dragInProgress = false;

    /**
     * A cached Point array for drawing.
     */
    private Point[] points = new Point[] { new Point(-1, -1), new Point(-1, -1) };

    /**
     * The time to wait between retriggers in milliseconds.
     */
    private int retriggerInterval = 0;

    /**
     * The currently valid RetriggerHandler
     */
    protected Runnable retriggerHandler = null;

    /**
     * Constructs a ContinuousPaintSession.
     *
     * @param paintSurface the drawing surface to use
     */
    protected ContinuousPaintSession(PaintSurface paintSurface) {
        super(paintSurface);
    }

    /**
     * Sets the retrigger timer.
     * <p>
     * After the timer elapses, if the mouse is still hovering over the same point with the
     * drag button pressed, a new render order is issued and the timer is restarted.
     * </p>
     * @param interval the time in milliseconds to wait between retriggers, 0 to disable
     */
    public void setRetriggerTimer(int interval) {
        retriggerInterval = interval;
    }

    /**
     * Activates the tool.
     */
    @Override
    public void beginSession() {
        getPaintSurface().setStatusMessage(PaintExample.getResourceString("session.ContinuousPaint.message"));
        dragInProgress = false;
    }

    /**
     * Deactivates the tool.
     */
    @Override
    public void endSession() {
        abortRetrigger();
    }

    /**
     * Aborts the current operation.
     */
    @Override
    public void resetSession() {
        abortRetrigger();
    }

    /**
     * Handles a mouseDown event.
     *
     * @param event the mouse event detail information
     */
    @Override
    public final void mouseDown(MouseEvent event) {
        if (event.button != 1)
            return;
        if (dragInProgress)
            return; // spurious event
        dragInProgress = true;

        points[0].x = event.x;
        points[0].y = event.y;
        render(points[0]);
        prepareRetrigger();
    }

    /**
     * Handles a mouseDoubleClick event.
     *
     * @param event the mouse event detail information
     */
    @Override
    public final void mouseDoubleClick(MouseEvent event) {
    }

    /**
     * Handles a mouseUp event.
     *
     * @param event the mouse event detail information
     */
    @Override
    public final void mouseUp(MouseEvent event) {
        if (event.button != 1)
            return;
        if (!dragInProgress)
            return; // spurious event
        abortRetrigger();
        mouseSegmentFinished(event);
        dragInProgress = false;
    }

    /**
     * Handles a mouseMove event.
     *
     * @param event the mouse event detail information
     */
    @Override
    public final void mouseMove(MouseEvent event) {
        final PaintSurface ps = getPaintSurface();
        ps.setStatusCoord(ps.getCurrentPosition());
        if (!dragInProgress)
            return;
        mouseSegmentFinished(event);
        prepareRetrigger();
    }

    /**
     * Handle a rendering segment
     *
     * @param event the mouse event detail information
     */
    private final void mouseSegmentFinished(MouseEvent event) {
        if (points[0].x == -1)
            return; // spurious event
        if (points[0].x != event.x || points[0].y != event.y) {
            // draw new segment
            points[1].x = event.x;
            points[1].y = event.y;
            renderContinuousSegment();
        }
    }

    /**
     * Draws a continuous segment from points[0] to points[1].
     * Assumes points[0] has been drawn already.
     *
     * @post points[0] will refer to the same point as points[1]
     */
    protected void renderContinuousSegment() {
        /* A lazy but effective line drawing algorithm */
        final int dX = points[1].x - points[0].x;
        final int dY = points[1].y - points[0].y;
        int absdX = Math.abs(dX);
        int absdY = Math.abs(dY);

        if ((dX == 0) && (dY == 0))
            return;

        if (absdY > absdX) {
            final int incfpX = (dX << 16) / absdY;
            final int incY = (dY > 0) ? 1 : -1;
            int fpX = points[0].x << 16; // X in fixedpoint format

            while (--absdY >= 0) {
                points[0].y += incY;
                points[0].x = (fpX += incfpX) >> 16;
                render(points[0]);
            }
            if (points[0].x == points[1].x)
                return;
            points[0].x = points[1].x;
        } else {
            final int incfpY = (dY << 16) / absdX;
            final int incX = (dX > 0) ? 1 : -1;
            int fpY = points[0].y << 16; // Y in fixedpoint format

            while (--absdX >= 0) {
                points[0].x += incX;
                points[0].y = (fpY += incfpY) >> 16;
                render(points[0]);
            }
            if (points[0].y == points[1].y)
                return;
            points[0].y = points[1].y;
        }
        render(points[0]);
    }

    /**
     * Prepare the retrigger timer
     */
    private final void prepareRetrigger() {
        if (retriggerInterval > 0) {
            /*
             * timerExec() provides a lightweight mechanism for running code at intervals from within
             * the event loop when timing accuracy is not important.
             *
             * Since it is not possible to cancel a timerExec(), we remember the Runnable that is
             * active in order to distinguish the valid one from the stale ones.  In practice,
             * if the interval is 1/100th of a second, then creating a few hundred new RetriggerHandlers
             * each second will not cause a significant performance hit.
             */
            Display display = getPaintSurface().getDisplay();
            retriggerHandler = new Runnable() {
                @Override
                public void run() {
                    if (retriggerHandler == this) {
                        render(points[0]);
                        prepareRetrigger();
                    }
                }
            };
            display.timerExec(retriggerInterval, retriggerHandler);
        }
    }

    /**
     * Aborts the retrigger timer
     */
    private final void abortRetrigger() {
        retriggerHandler = null;
    }

    /**
     * Template method: Renders a point.
     * @param point, the point to render
     */
    protected abstract void render(Point point);
}