Newer
Older
piccolo2d.java / extras / src / main / java / edu / umd / cs / piccolox / swt / SWTTimer.java
/*
 * Copyright (c) 2008, Piccolo2D project, http://piccolo2d.org
 * Copyright (c) 1998-2008, University of Maryland
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided
 * that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this list of conditions
 * and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
 * and the following disclaimer in the documentation and/or other materials provided with the
 * distribution.
 *
 * None of the name of the University of Maryland, the name of the Piccolo2D project, or the names of its
 * contributors may be used to endorse or promote products derived from this software without specific
 * prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package edu.umd.cs.piccolox.swt;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.Timer;

import org.eclipse.swt.widgets.Display;

/**
 * @author Lance Good
 */
public class SWTTimer extends Timer {

    private boolean notify = false;

    int initialDelay, delay;
    boolean repeats = true, coalesce = true;

    Runnable doPostEvent = null;

    Display display = null;

    // These fields are maintained by TimerQueue.
    // eventQueued can also be reset by the TimerQueue, but will only ever
    // happen in applet case when TimerQueues thread is destroyed.
    long expirationTime;
    SWTTimer nextTimer;
    boolean running;

    /**
     * DoPostEvent is a runnable class that fires actionEvents to the listeners
     * on the EventDispatchThread, via invokeLater.
     * 
     * @see #post
     */
    class SWTDoPostEvent implements Runnable {
        public void run() {

            if (notify) {
                fireActionPerformed(new ActionEvent(SWTTimer.this, 0, null, System.currentTimeMillis(), 0));
                if (coalesce) {
                    cancelEventOverride();
                }
            }
        }

        SWTTimer getTimer() {
            return SWTTimer.this;
        }
    }

    /**
     * Constructor for SWTTimer.
     * 
     * @param delay
     * @param listener
     */
    public SWTTimer(Display display, int delay, ActionListener listener) {
        super(delay, listener);
        this.delay = delay;
        this.initialDelay = delay;

        doPostEvent = new SWTDoPostEvent();
        this.display = display;
    }

    /**
     * Notifies all listeners that have registered interest for notification on
     * this event type.
     * 
     * @param e the action event to fire
     */
    protected void fireActionPerformed(ActionEvent e) {
        // Guaranteed to return a non-null array
        Object[] listeners = listenerList.getListenerList();

        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = listeners.length - 2; i >= 0; i -= 2) {
            if (listeners[i] == ActionListener.class) {
                ((ActionListener) listeners[i + 1]).actionPerformed(e);
            }
        }
    }

    /**
     * Returns the timer queue.
     */
    SWTTimerQueue timerQueue() {
        return SWTTimerQueue.sharedInstance(display);
    }

    /**
     * Sets the <code>Timer</code>'s delay, the number of milliseconds between
     * successive action events.
     * 
     * @param delay the delay in milliseconds
     * @see #setInitialDelay
     */
    public void setDelay(int delay) {
        if (delay < 0) {
            throw new IllegalArgumentException("Invalid delay: " + delay);
        }
        else {
            this.delay = delay;
        }
    }

    /**
     * Returns the delay, in milliseconds, between firings of action events.
     * 
     * @see #setDelay
     * @see #getInitialDelay
     */
    public int getDelay() {
        return delay;
    }

    /**
     * Sets the <code>Timer</code>'s initial delay, which by default is the same
     * as the between-event delay. This is used only for the first action event.
     * Subsequent action events are spaced using the delay property.
     * 
     * @param initialDelay the delay, in milliseconds, between the invocation of
     *            the <code>start</code> method and the first action event fired
     *            by this timer
     * 
     * @see #setDelay
     */
    public void setInitialDelay(int initialDelay) {
        if (initialDelay < 0) {
            throw new IllegalArgumentException("Invalid initial delay: " + initialDelay);
        }
        else {
            this.initialDelay = initialDelay;
        }
    }

    /**
     * Returns the <code>Timer</code>'s initial delay.
     * 
     * @see #setInitialDelay
     * @see #setDelay
     */
    public int getInitialDelay() {
        return initialDelay;
    }

    /**
     * If <code>flag</code> is <code>false</code>, instructs the
     * <code>Timer</code> to send only one action event to its listeners.
     * 
     * @param flag specify <code>false</code> to make the timer stop after
     *            sending its first action event
     */
    public void setRepeats(boolean flag) {
        repeats = flag;
    }

    /**
     * Returns <code>true</code> (the default) if the <code>Timer</code> will
     * send an action event to its listeners multiple times.
     * 
     * @see #setRepeats
     */
    public boolean isRepeats() {
        return repeats;
    }

    /**
     * Sets whether the <code>Timer</code> coalesces multiple pending
     * <code>ActionEvent</code> firings. A busy application may not be able to
     * keep up with a <code>Timer</code>'s event generation, causing multiple
     * action events to be queued. When processed, the application sends these
     * events one after the other, causing the <code>Timer</code>'s listeners to
     * receive a sequence of events with no delay between them. Coalescing
     * avoids this situation by reducing multiple pending events to a single
     * event. <code>Timer</code>s coalesce events by default.
     * 
     * @param flag specify <code>false</code> to turn off coalescing
     */
    public void setCoalesce(boolean flag) {
        boolean old = coalesce;
        coalesce = flag;
        if (!old && coalesce) {
            // We must do this as otherwise if the Timer once notified
            // in !coalese mode notify will be stuck to true and never
            // become false.
            cancelEventOverride();
        }
    }

    /**
     * Returns <code>true</code> if the <code>Timer</code> coalesces multiple
     * pending action events.
     * 
     * @see #setCoalesce
     */
    public boolean isCoalesce() {
        return coalesce;
    }

    /**
     * Starts the <code>Timer</code>, causing it to start sending action events
     * to its listeners.
     * 
     * @see #stop
     */
    public void start() {
        timerQueue().addTimer(this, System.currentTimeMillis() + getInitialDelay());
    }

    /**
     * Returns <code>true</code> if the <code>Timer</code> is running.
     * 
     * @see #start
     */
    public boolean isRunning() {
        return timerQueue().containsTimer(this);
    }

    /**
     * Stops the <code>Timer</code>, causing it to stop sending action events to
     * its listeners.
     * 
     * @see #start
     */
    public void stop() {
        timerQueue().removeTimer(this);
        cancelEventOverride();
    }

    /**
     * Restarts the <code>Timer</code>, canceling any pending firings and
     * causing it to fire with its initial delay.
     */
    public void restart() {
        stop();
        start();
    }

    /**
     * Resets the internal state to indicate this Timer shouldn't notify any of
     * its listeners. This does not stop a repeatable Timer from firing again,
     * use <code>stop</code> for that.
     */
    synchronized void cancelEventOverride() {
        notify = false;
    }

    synchronized void postOverride() {
        if (notify == false || !coalesce) {
            notify = true;
            display.asyncExec(doPostEvent);
        }
    }

}