/* * 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 Mar 1, 2010. */ package com.scriptographer.ai; import com.scratchdisk.script.Callable; import com.scriptographer.ScriptographerEngine; import com.scriptographer.sg.Script; /** * @author lehni * * @jshide */ public class ToolHandler extends NativeObject { private Double minDistance; private Double maxDistance; private boolean firstMove; protected Point point; protected Point downPoint; protected Point lastPoint; protected int count; protected int downCount; protected double pressure; protected ToolHandler(int handle) { super(handle); } public ToolHandler() { } /** * Initializes the tool's settings, so a new tool can be assigned to it * * @jshide */ public void initialize() { minDistance = null; maxDistance = null; firstMove = true; downPoint = null; lastPoint = null; count = 0; downCount = 0; onEditOptions = null; onSelect = null; onDeselect = null; onReselect = null; onMouseDown = null; onMouseUp = null; onMouseDrag = null; onMouseMove = null; script = null; } /** * The minimum distance the mouse has to drag before firing the onMouseDrag * event, since the last onMouseDrag event. * * Sample code: * <code> * // Fire the onMouseDrag event after the user has dragged * // more then 5 points from the last onMouseDrag event: * tool.minDistance = 5; * </code> * * @param threshold */ public Double getMinDistance() { return minDistance; } public void setMinDistance(Double minDistance) { this.minDistance = minDistance; if (minDistance != null && maxDistance != null && minDistance > maxDistance) maxDistance = minDistance; } public Double getMaxDistance() { return maxDistance; } public void setMaxDistance(Double maxDistance) { this.maxDistance = maxDistance; if (minDistance != null && maxDistance != null && maxDistance < minDistance) minDistance = maxDistance; } public Double getFixedDistance() { if (minDistance != null && minDistance.equals(maxDistance)) return minDistance; return null; } public void setFixedDistance(Double distance) { minDistance = distance; maxDistance = distance; } /** * @deprecated */ public double getDistanceThreshold() { return minDistance != null ? minDistance : 0; } /** * @deprecated */ public void setDistanceThreshold(double threshold) { minDistance = threshold; } private Callable onMouseDown; /** * {@grouptitle Mouse Event Handlers} * * The function to be called when the mouse button is pushed down. The * function receives a {@link ToolEvent} object which contains information * about the mouse event. * * Sample code: * <code> * function onMouseDown(event) { * // the position of the mouse in document coordinates: * print(event.point); * } * </code> */ public Callable getOnMouseDown() { return onMouseDown; } public void setOnMouseDown(Callable onMouseDown) { this.onMouseDown = onMouseDown; } protected void onMouseDown(ToolEvent event) { if (onMouseDown != null) ScriptographerEngine.invoke(onMouseDown, this, event); } private Callable onMouseDrag; /** * The function to be called when the mouse position changes while the mouse * is being dragged. The function receives a {@link ToolEvent} object which * contains information about the mouse event. * * This function can also be called periodically while the mouse doesn't * move by setting the {@link Tool#getEventInterval()} * * Sample code: * <code> * function onMouseDrag(event) { * // the position of the mouse in document coordinates * print(event.point); * } * </code> */ public Callable getOnMouseDrag() { return onMouseDrag; } public void setOnMouseDrag(Callable onMouseDrag) { this.onMouseDrag = onMouseDrag; } protected void onMouseDrag(ToolEvent event) { if (onMouseDrag != null) ScriptographerEngine.invoke(onMouseDrag, this, event); } private Callable onMouseMove; /** * The function to be called when the tool is selected and the mouse moves * within the document. The function receives a {@link ToolEvent} object * which contains information about the mouse event. * * Sample code: * <code> * function onMouseMove(event) { * // the position of the mouse in document coordinates * print(event.point); * } * </code> */ public Callable getOnMouseMove() { return onMouseMove; } public void setOnMouseMove(Callable onMouseMove) { this.onMouseMove = onMouseMove; } protected void onMouseMove(ToolEvent event) { // Make sure the first move event initializes both delta and count. if (onMouseMove != null) ScriptographerEngine.invoke(onMouseMove, this, event); } private Callable onMouseUp; /** * The function to be called when the mouse button is released. The function * receives a {@link ToolEvent} object which contains information about the * mouse event. * * Sample code: * <code> * function onMouseUp(event) { * // the position of the mouse in document coordinates * print(event.point); * } * </code> */ public Callable getOnMouseUp() { return onMouseUp; } public void setOnMouseUp(Callable onMouseUp) { this.onMouseUp = onMouseUp; } protected void onMouseUp(ToolEvent event) { if (onMouseUp != null) ScriptographerEngine.invoke(onMouseUp, this, event); } /* * TODO: onOptions should be called onEditOptions, or both onOptions, * but at least the same. */ private Callable onEditOptions; /** * {@grouptitle Tool Button Event Handlers} * * The function to be called when the tool button is double clicked. This is * often used to present the users with a dialog containing options that * they can set. */ public Callable getOnEditOptions() { return onEditOptions; } public void setOnEditOptions(Callable onOptions) { this.onEditOptions = onOptions; } protected void onEditOptions() { if (onEditOptions != null) ScriptographerEngine.invoke(onEditOptions, this); } private Callable onSelect; /** * The function to be called when the tool button is selected. */ public Callable getOnSelect() { return onSelect; } public void setOnSelect(Callable onSelect) { this.onSelect = onSelect; } protected void onSelect() { if (onSelect != null) ScriptographerEngine.invoke(onSelect, this); } private Callable onDeselect; /** * The function to be called when the tool button is deselected. */ public Callable getOnDeselect() { return onDeselect; } public void setOnDeselect(Callable onDeselect) { this.onDeselect = onDeselect; } protected void onDeselect() { if (onDeselect != null) ScriptographerEngine.invoke(onDeselect, this); } private Callable onReselect; /** * The function to be called when the tool button has been reselected. */ public Callable getOnReselect() { return onReselect; } public void setOnReselect(Callable onReselect) { this.onReselect = onReselect; } protected void onReselect() { if (onReselect != null) ScriptographerEngine.invoke(onReselect, this); } private boolean updateEvent(ToolEventType type, Point pt, int pressure, Double minDistance, Double maxDistance, boolean start, boolean needsChange, boolean matchMaxDistance) { if (!start) { if (minDistance != null || maxDistance != null) { double minDist = minDistance != null ? minDistance : 0; Point vector = pt.subtract(point); double distance = vector.getLength(); if (distance < minDist) return false; // Produce a new point on the way to pt if pt is further away // than maxDistance double maxDist = maxDistance != null ? maxDistance : 0; if (maxDist != 0) { if (distance > maxDist) pt = point.add(vector.normalize(maxDist)); else if (matchMaxDistance) return false; } } if (needsChange && pt.equals(point)) return false; } // Make sure mousemove events have lastPoint set even for the first move // so event.delta is always defined for them. lastPoint = start && type == ToolEventType.MOUSEMOVE ? pt : point; point = pt; switch (type) { case MOUSEDOWN: lastPoint = downPoint; downPoint = point; downCount++; break; case MOUSEUP: // Mouse up events return the down point for last point, // so delta is spanning over the whole drag. lastPoint = downPoint; break; } if (start) { count = 0; } else { count++; } this.pressure = pressure / 255.0; return true; } public void onHandleEvent(ToolEventType type, Point pt, int pressure, int modifiers) { try { switch (type) { case MOUSEDOWN: updateEvent(type, pt, pressure, null, null, true, false, false); onMouseDown(new ToolEvent(this, ToolEventType.MOUSEDOWN, modifiers)); break; case MOUSEDRAG: // In order for idleInterval drag events to work, we need to // not check the first call for a change of position. // Subsequent calls required by min/maxDistance functionality // will require it, otherwise this might loop endlessly. boolean needsChange = false; // If the mouse is moving faster than maxDistance, do not // produce events for what is left after the first event is // generated in case it is shorter than maxDistance, as this // would produce weird results. matchMaxDistance controls this. boolean matchMaxDistance = false; while (updateEvent(type, pt, pressure, minDistance, maxDistance, false, needsChange, matchMaxDistance)) { try { onMouseDrag(new ToolEvent(this, ToolEventType.MOUSEDRAG, modifiers)); } catch (Exception e) { ScriptographerEngine.reportError(e); } needsChange = true; matchMaxDistance = true; } break; case MOUSEUP: // If the last mouse drag happened in a different place, call // mouse drag first, then mouse up. if ((point.x != pt.x || point.y != pt.y) && updateEvent( ToolEventType.MOUSEDRAG, pt, pressure, minDistance, maxDistance, false, false, false)) { try { onMouseDrag(new ToolEvent(this, ToolEventType.MOUSEDRAG, modifiers)); } catch (Exception e) { ScriptographerEngine.reportError(e); } } updateEvent(type, pt, pressure, null, maxDistance, false, false, false); try { onMouseUp(new ToolEvent(this, ToolEventType.MOUSEUP, modifiers)); } catch (Exception e) { ScriptographerEngine.reportError(e); } // Start with new values for TRACK_CURSOR updateEvent(type, pt, pressure, null, null, true, false, false); firstMove = true; break; case MOUSEMOVE: while (updateEvent(type, pt, pressure, minDistance, maxDistance, firstMove, true, false)) { try { onMouseMove(new ToolEvent(this, ToolEventType.MOUSEMOVE, modifiers)); } catch (Exception e) { ScriptographerEngine.reportError(e); } firstMove = false; } break; case EDIT_OPTIONS: onEditOptions(); break; case SELECT: onSelect(); break; case DESELECT: onDeselect(); break; case RESELECT: onReselect(); break; } } catch (Exception e) { ScriptographerEngine.reportError(e); } } public void onHandleEvent(ToolEventType type, Point pt) { onHandleEvent(type, pt, 128, 0); } protected Script script; public Script getScript() { return script; } public void setScript(Script script) { this.script = script; // Let the script know it's there for a tool. script.setToolHandler(this); } }