/* * 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 21.12.2004. */ package com.scriptographer.ai; import java.util.ArrayList; import java.util.EnumSet; import com.scratchdisk.util.IntMap; import com.scratchdisk.util.IntegerEnumUtils; import com.scriptographer.ScriptographerException; import com.scriptographer.adm.Image; import com.scriptographer.sg.CoordinateSystem; /** * The Tool object refers to the Scriptographer tool in the Illustrator tool * palette and can be accessed through the global {@code tool} variable. All its * properties are also available in the global scope. * * The global {@code tool} variable only exists in scripts that contain mouse * handler functions ({@link #getOnMouseDown()}, {@link #getOnMouseDrag()}, * {@link #getOnMouseUp()}), which are automatically associated with the tool * button on execution. * * Sample code: * <code> * var path; * * // Only execute onMouseDrag when the mouse * // has moved at least 10 points: * tool.distanceThreshold = 10; * * function onMouseDown(event) { * // Create a new path every time the mouse is clicked * path = new Path(); * } * * function onMouseDrag(event) { * // Add a point to the path every time the mouse is dragged * path.lineTo(event.point); * } * </code> * * @author lehni */ public class Tool extends ToolHandler { // TODO: implement a way to set cursors? private int cursor = 128; /** * tools maps tool handles to their wrappers. */ private static IntMap<Tool> tools = new IntMap<Tool>(); private static ArrayList<Tool> unusedTools = null; private Image image; private Image rolloverImage; private Image defaultImage; private String name; private String defaultTooltip; /** * @jshide */ public Tool(String name, Image image, EnumSet<ToolOption> options, Tool groupTool, Tool toolsetTool) { this.name = name; defaultImage = image; ArrayList<Tool> unusedTools = getUnusedTools(); // Now see first whether there is an unusedEffect already that fits this // description int index = unusedTools.indexOf(this); if (index >= 0) { // Found one, let's reuse it's handle and remove the old effect from // the list: Tool tool = unusedTools.get(index); handle = tool.handle; tool.handle = 0; unusedTools.remove(index); } else { // No previously existing effect found, create a new one: handle = nativeCreate(name, image != null ? image.createIconHandle() : 0, IntegerEnumUtils.getFlags(options), groupTool != null ? groupTool.handle : 0, toolsetTool != null ? toolsetTool.handle : 0); } if (handle == 0) throw new ScriptographerException("Unable to create Tool."); reset(); tools.put(handle, this); } public Tool(String name, Image image, EnumSet<ToolOption> options, Tool groupTool) { this(name, image, options, groupTool, null); } public Tool(String name, Image image, EnumSet<ToolOption> options) { this(name, image, options, null, null); } public Tool(String name, Image image) { this(name, image, null, null, null); } public Tool(String name) { this(name, null, null, null, null); } private native int nativeCreate(String name, int iconHandle, int options, int groupHandle, int toolsetHandle); protected Tool(int handle, String name) { super(handle); // See resourceIds.h: this.name = name; } public void initialize() { super.initialize(); setEventInterval(-1); } public void reset() { initialize(); if (defaultImage != null) setImage(defaultImage); if (defaultTooltip != null) setTooltip(defaultTooltip); } /** * Checks whether the input device of the user has a pressure feature (i.e. * a drawing tablet) */ public native boolean hasPressure(); /** * Sets the fixed time delay between each call to the {@link #onMouseDrag} * event. Setting this to an interval means the {@link #onMouseDrag} event * is called repeatedly after the initial {@link #onMouseDown} until the * user releases the mouse. * * Sample code: * <code> * // Fire the onMouseDrag event once a second, * // while the mouse button is down * tool.eventInterval = 1000; * </code> * * @return the interval time in milliseconds */ public native int getEventInterval(); public native void setEventInterval(int interval); /** * @deprecated use Tool#setEventInterval instead. * * @jshide */ public void setIdleEventInterval(int interval) { setEventInterval(interval); } /** * @jshide */ public native String getTitle(); /** * @jshide */ public native void setTitle(String title); /** * {@grouptitle Tool Button Styling} * * The tooltip as seen when you hold the cursor over the tool button. */ public native String getTooltip(); private native void nativeSetTooltip(String text); public void setTooltip(String text) { if (defaultTooltip == null) defaultTooltip = text; nativeSetTooltip(text); } private native int nativeGetOptions(); private native void nativeSetOptions(int options); /** * @jshide */ public native boolean getSelected(); /** * @jshide */ public native void setSelected(boolean selected); /** * @jshide */ public EnumSet<ToolOption> getOptions() { return IntegerEnumUtils.getSet(ToolOption.class, nativeGetOptions()); } /** * @jshide */ public void setOptions(EnumSet<ToolOption> options) { nativeSetOptions(IntegerEnumUtils.getFlags(options)); } public Image getImage() { return image; } private native void nativeSetImage(int iconHandle); public void setImage(Image image) { nativeSetImage(image != null ? image.createIconHandle() : 0); this.image = image; if (defaultImage == null) defaultImage = image; } public Image getRolloverImage() { return rolloverImage; } private native void nativeSetRolloverImage(int iconHandle); public void setRolloverImage(Image image) { nativeSetRolloverImage(image != null ? image.createIconHandle() : 0); this.rolloverImage = image; } public void onHandleEvent(ToolEventType type, Point pt, int pressure, int modifiers) { if (type == ToolEventType.MOUSEDOWN) { try { Item.collectCreatedItems(); super.onHandleEvent(type, pt, pressure, modifiers); if (Item.hasCreatedItems()) Document.getActiveDocument().redraw(); } finally { Item.clearCreatedItems(); } } else { super.onHandleEvent(type, pt, pressure, modifiers); } } /** * To be called from the native environment. Returns the cursor * id to be set, if any. */ private static int onHandleEvent(int handle, String selector, double x, double y, int pressure, int modifiers) { Tool tool = getTool(handle); ToolEventType type = ToolEventType.get(selector); if (tool != null && type != null) { // Make sure we use the right coordinate system to convert the // point. It is a bit a shame we have to introduce a local // convertPoint(topDown, x, y) function for this, but that's the // only easy way to use the native side's coordinate system handling // on the point and respect the tools / script's context correctly // before actually calling Document.beginExecution() Point point = convertPoint( CoordinateSystem.TOP_DOWN == (tool.script != null ? tool.script.getCoordinateSystem() : null), // Activate the underlying Artboard if the mouse is pressed, // as Ai only activates on mouse-up. type == ToolEventType.MOUSEDOWN, // Update coordinates system when tool is selected, mouse // is clicked or released, and when the we're not in a drag // movement. During a drag movement, the coordinates won't // change and therefore do not need an update. type == ToolEventType.SELECT || type == ToolEventType.MOUSEDOWN || type == ToolEventType.MOUSEUP || type == ToolEventType.MOUSEMOVE, x, y); tool.onHandleEvent(type, point, pressure, modifiers); } // Tell the native side to update the cursor return tool.cursor; } /* * See the comment above in onHandleEvent */ private static native Point convertPoint(boolean topDownCoordinates, boolean activateArtboard, boolean updateCoordinates, double x, double y); private static Tool getTool(int handle) { return tools.get(handle); } public boolean equals(Object obj) { if (obj instanceof Tool) { Tool tool = (Tool) obj; return name.equals(tool.name); } return false; } private static ArrayList<Tool> getUnusedTools() { if (unusedTools == null) unusedTools = nativeGetTools(); return unusedTools; } private static native ArrayList<Tool> nativeGetTools(); }