/*
* Scriptographer
*
* This file is part of Scriptographer, a Scripting Plugin for Adobe Illustrator
* http://scriptographer.org/
*
* Copyright (c) 2002-2010, Juerg Lehni
* http://scratchdisk.com/
*
* All rights reserved. See LICENSE file for details.
*
* File created on May 29, 2010.
*/
package com.scriptographer.ai;
import com.scratchdisk.script.Callable;
import com.scratchdisk.util.ConversionUtils;
import com.scratchdisk.util.IntMap;
import com.scriptographer.ScriptographerEngine;
import com.scriptographer.ScriptographerException;
import com.scriptographer.sg.Script;
/**
* @author lehni
*
*/
public class Timer extends NativeObject {
private int period;
private boolean periodic;
private Script script = null;
private static IntMap<Timer> timers = new IntMap<Timer>();
/**
* Creates a timer object.
*
* @param period The timer's period in milliseconds.
* @param periodic Controls whether the timer is one-shop or periodic.
*/
public Timer(int period, boolean periodic) {
script = ScriptographerEngine.getCurrentScript();
this.period = period;
this.periodic = periodic;
handle = nativeCreate(period);
if (handle == 0)
throw new ScriptographerException("Unable to create Timer.");
timers.put(handle, this);
}
public Timer(int period) {
this(period, true);
}
public void abort() {
if (handle != 0) {
timers.remove(handle);
nativeAbort(handle);
handle = 0;
}
}
private native int nativeCreate(int period);
private native void nativeAbort(int handle);
public double getPeriod() {
return period;
}
public boolean isPeriodic() {
return periodic;
}
protected boolean canAbort(boolean ignoreKeepAlive) {
return script == null || script.canRemove(ignoreKeepAlive);
}
public static void abortAll(boolean ignoreKeepAlive, boolean force) {
// As abort() modifies the Map, using an iterator is not possible here:
Object[] timers = Timer.timers.values().toArray();
for (int i = 0; i < timers.length; i++) {
Timer timer = (Timer) timers[i];
if (force || timer.canAbort(ignoreKeepAlive))
timer.abort();
}
}
private Callable onExecute = null;
public void setOnExecute(Callable onExecute) {
this.onExecute = onExecute;
}
public Callable getOnExecute() {
return onExecute;
}
/**
* The timer callback.
*
* Return true if document should be redrawn automatically.
*/
protected boolean onExecute() {
if (onExecute != null) {
Object result = ScriptographerEngine.invoke(onExecute, this);
if (result != null)
return ConversionUtils.toBoolean(result);
}
return false;
}
/**
* To be called from the native environment:
*/
private static boolean onExecute(int handle) {
Timer timer = getTimer(handle);
if (timer != null) {
try {
Document document = Document.getActiveDocument();
// Produce a normal undo cycle if in the previous cycle we have
// created or removed items. Otherwise just merge the changes of
// this cycle with the previous one.
// It is important to create new cycles when such changes
// happen, as they affect the live span of items within the undo
// history, and if all cycles were merged, the history tracking
// code in Document would not be able to track their life span.
int undoType = document == null || document.hasCreatedState()
|| document.hasRemovedState()
? Document.UNDO_STANDARD
: Document.UNDO_MERGE;
// Clear changed states now to track for new changes in
// onExecute()
if (document != null)
document.clearChangedStates();
boolean changed = timer.onExecute()
|| document != null && document.hasChangedSates();
// Only change undo type if the document have actually changed,
// or if the type is MERGE, in which case we will not add new
// unto levels. This is actually needed to prevent a weird bug
// that occasionally happens where Ai keeps adding new levels.
// To not set STANDARD in all cases prevents issues with
// situations where Timers are used for ADM interface stuff,
// e.g. invokeLater().
if (document != null
&& (changed || undoType == Document.UNDO_MERGE)) {
document.setUndoType(undoType);
}
return changed;
} finally {
// Simulate one shot timers by aborting:
if (!timer.periodic)
timer.abort();
}
}
return false;
}
private static Timer getTimer(int handle) {
return timers.get(handle);
}
protected void finalize() {
abort();
}
public Object getId() {
// Return the integer handle as id instead of a formated string, as the
// script side wants to work with numeric timer ids.
return handle;
}
}