/******************************************************************************* * Copyright (c) 2000, 2005 IBM Corporation and others. * 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 * * 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); } /** * Activates the tool. */ public void beginSession() { getPaintSurface().setStatusMessage(PaintExample.getResourceString("session.ContinuousPaint.message")); dragInProgress = false; } /** * Deactivates the tool. */ public void endSession() { abortRetrigger(); } /** * Handles a mouseDoubleClick event. * * @param event * the mouse event detail information */ public final void mouseDoubleClick(MouseEvent event) { } /** * Handles a mouseDown event. * * @param event * the mouse event detail information */ 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 mouseMove event. * * @param event * the mouse event detail information */ public final void mouseMove(MouseEvent event) { final PaintSurface ps = getPaintSurface(); ps.setStatusCoord(ps.getCurrentPosition()); if (!dragInProgress) return; mouseSegmentFinished(event); prepareRetrigger(); } /** * Handles a mouseUp event. * * @param event * the mouse event detail information */ public final void mouseUp(MouseEvent event) { if (event.button != 1) return; if (!dragInProgress) return; // spurious event abortRetrigger(); mouseSegmentFinished(event); dragInProgress = false; } /** * Aborts the current operation. */ public void resetSession() { abortRetrigger(); } /** * 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; } /** * Aborts the retrigger timer */ private final void abortRetrigger() { retriggerHandler = null; } /** * 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(); } } /** * 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() { public void run() { if (retriggerHandler == this) { render(points[0]); prepareRetrigger(); } } }; display.timerExec(retriggerInterval, retriggerHandler); } } /** * Template method: Renders a point. * * @param point, * the point to render */ protected abstract void render(Point point); /** * 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]); } }