blob: b415351a0a0e5cba8d0a86aa43a5a6a22bb0d818 [file] [log] [blame]
/*******************************************************************************
* 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.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.widgets.*;
/**
* 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.
*/
public void beginSession() {
getPaintSurface().
setStatusMessage(PaintExample.getResourceString("session.ContinuousPaint.message"));
dragInProgress = false;
}
/**
* Deactivates the tool.
*/
public void endSession() {
abortRetrigger();
}
/**
* Aborts the current operation.
*/
public void resetSession() {
abortRetrigger();
}
/**
* 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 mouseDoubleClick event.
*
* @param event the mouse event detail information
*/
public final void mouseDoubleClick(MouseEvent event) {
}
/**
* 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;
}
/**
* 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();
}
/**
* 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() {
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);
}