/*
* 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 02.12.2004.
*/
package com.scriptographer.ai;
import java.awt.Graphics2D;
import java.lang.ref.SoftReference;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.Map;
import java.util.Stack;
import com.scratchdisk.list.Lists;
import com.scratchdisk.list.ReadOnlyList;
import com.scratchdisk.script.ChangeReceiver;
import com.scratchdisk.util.ArrayList;
import com.scratchdisk.util.IntegerEnumUtils;
import com.scratchdisk.util.SoftIntMap;
import com.scriptographer.CommitManager;
import com.scriptographer.ScriptographerEngine;
import com.scriptographer.ScriptographerException;
import com.scriptographer.adm.Image;
/**
* The Item type allows you to access and modify the artwork items in
* Illustrator documents. Its functionality is inherited by different document
* item types such as {@link Path}, {@link CompoundPath}, {@link Group},
* {@link Layer} and {@link Raster}. They each add a layer of functionality that
* is unique to their type, but share the underlying properties and functions
* that they inherit from Item.
*
* @author lehni
*
* @jsreference {@type field} {@name document} {@reference Item#document} {@after data}
*/
public class Item extends DocumentObject implements Style, ChangeReceiver {
/**
* The internal version. this is used for internally reflected data, such as
* segmentList, pathStyle, and so on. Every time an object gets modified,
* ScriptographerEngine.selectionChanged() gets fired that increases the
* version of all involved items. update-commit related code needs to check
* against this variable
*/
protected int version = 0;
/**
* Handle history, to keep track of version / handle pairs in a stack.
*/
protected Stack<HandleHistoryEntry> handleHistory = null;
/**
* The history version at the time of the creation of this item. This is
* used for isValid checks. If historyVersion is below creationVersion, the
* item does not exist anymore and is invalid (happens through undo's). Set
* to -1 if creation version is unknown, for existing items that are
* wrapped, rather than newly created. These version values include both
* branch and level information. See Document.
*/
protected long creationVersion;
/**
* The history version at which this item was removed. Set to an invalid
* value as long as it is not removed. This is used in isValid checks.
*/
protected long deletionVersion = Long.MAX_VALUE;
/**
* Then history version of the last modification of this item. Refetching is
* needed for any item for which the current historyVersion is between
* between creationVersion and modificationVersion. These version values
* include both branch and level information. See Document.
*/
protected long modificationVersion;
/**
* The handle for the dictionary that contains this item, if any
*/
protected int dictionaryHandle = 0;
/**
* The key under which this item was inserted into the dictionary
*/
protected int dictionaryKey = 0;
/**
* The art item's dictionary
*/
private Dictionary data = null;
/**
* Internal hash map that keeps track of already wrapped objects. defined
* as soft.
*/
private static SoftIntMap<Item> items = new SoftIntMap<Item>();
private PathStyle style = null;
/**
* For Document#currentStyleItem
*/
protected final static int HANDLE_CURRENT_STYLE = -1;
// from AIArt.h
// AIArtType
protected final static short
// The special type kAnyArt is never returned as an item type, but
// is used as a parameter to the Matching Item suite function
// GetMatchingArt.
TYPE_ANY = -1,
// The type kUnknownArt is reserved for objects that are not supported
// in the plug-in interface. You should anticipate unknown items
// and ignore them gracefully. For example graph objects return
// kUnkownType.
//
// If a plug-in written for an earlier version of the plug-in API calls
// GetArt- Type with an item of a type unknown in its version,
// this function will map the art type to either an appropriate type or
// to kUnknownArt.
TYPE_UNKNOWN = 0,
TYPE_GROUP = 1,
TYPE_PATH = 2,
TYPE_COMPOUNDPATH = 3,
// Pre-AI11 text art type. No longer supported but remains as a place
// holder so that the segmentValues for other art types remain the same.
TYPE_TEXT = 4,
// Pre-AI11 text art type. No longer supported but remains as a place
// holder so that the segmentValues for other art types remain the same.
TYPE_TEXTPATH = 5,
// Pre-AI11 text art type. No longer supported but remains as a place
// holder so that the segmentValues for other art types remain the same.
TYPE_TEXTRUN = 6,
TYPE_PLACED = 7,
// The special type kMysteryPathArt is never returned as an item
// type, it is an obsolete parameter to GetMatchingArt. It used to match
// paths inside text objects without matching the text objects
// themselves. In AI11 and later the kMatchTextPaths flag is used to
// indicate that text paths should be returned.
TYPE_MYSTERYPATH = 8,
TYPE_RASTER = 9,
TYPE_PLUGIN = 10,
TYPE_MESH = 11,
TYPE_TEXTFRAME = 12,
TYPE_SYMBOL = 13,
// A foreign object is a "black box" containing drawing commands.
// Construct using AIForeignObjectSuite::New(... rather than
// AIArtSuite::NewArt(.... See AIForeignObjectSuite.
TYPE_FOREIGN = 14,
// A text object read from a legacy file (AI10, AI9, AI8 ....
TYPE_LEGACYTEXT = 15,
// Lehni: self defined type for layer groups:
TYPE_LAYER = 100,
TYPE_TRACING = 101;
private static native int nativeCreate(short type);
/**
* Creates a wrapper for a AIArtHandle. Make sure the right constructor is
* used (Path, Raster). Use wrapArtHandle instead of directly calling this
* constructor (it is called from the anchestor's constructors).
*
* @param handle
*/
protected Item(int handle, Document doc, boolean created,
boolean unversioned) {
super(handle, doc);
if (document == null)
throw new ScriptographerException(
"Unable to create item. There is no open document.");
if (unversioned) {
creationVersion = 0;
} else if (created) {
// Set the creation level to the current level for this newly
// created item, so that isValid works right away. After the history
// cycle, these values are set one higher, since this items really
// belongs to the document.historyVersion + 1 cycle.
creationVersion = document.historyVersion;
// This item's versions need to be updated after the history cycle
// is finished.
document.addCreatedItem(this);
} else {
// This is an existing item of which the creation level is unknown.
// set levels to -1
creationVersion = -1;
// Since creationLevel for this item is not known, add it to
// the items to check on each undo.
document.checkItems.add(new SoftReference<Item>(this));
}
// Use the current history level for the modification level, to force
// updates bellow this level, since we do not know when exactly
// the item was created or last modified.
modificationVersion = document.historyVersion;
// Keep track of this object from now on, see wrapArtHandle
if (handle != 0)
items.put(handle, this);
// Collect new items. Used for effects
if (created && createdItems != null)
createdItems.add(this);
}
protected Item(int handle, int docHandle, boolean created) {
this(handle, Document.wrapHandle(docHandle), created, false);
}
/**
* Creates a new AIArtHandle of the specified type in the active document
* and wraps it in a item.
*
* @param type Item.TYPE_
*/
protected Item(short type) {
// Create with false handle, to get document pointer and have time to
// activate with forCreation = true, to make sure currentStyle gets
// committed, etc.
this(0, null, true, false);
document.activate(false, true);
// Now set the handle
handle = nativeCreate(type);
// Keep track of this object from now on, see wrapArtHandle
items.put(handle, this);
}
/**
* Creates a wrapper for newly created items in the active document.
* Do not use to wrap existing ones, since it passes created = true!
*/
protected Item(int handle) {
this(handle, null, true, false);
}
/**
* Wraps an AIArtHandle of given type (determined by
* sAIArt->GetType(artHandle)) by the correct Item ancestor class:
*
* @param artHandle
* @param type
* @return the wrapped item
*/
protected static Item wrapHandle(int artHandle, short type, int textType,
int docHandle, boolean wrapped, boolean created) {
// ScriptographerEngine.logConsole("wrapHandle: @" + Integer.toHexString(artHandle));
// First see whether the object was already wrapped before:
Item item = null;
// Only try to use the previous wrapper for this address if the object
// was marked wrapped otherwise we might get wrong wrappers for objects
// that reuse a previous address
if (wrapped) {
item = items.get(artHandle);
// Make sure this item is still valid. It might be a reused art
// handle too...
if (item != null && !item.isValid())
item = null;
}
// If it wasn't wrapped yet, do it now:
// TODO: Don't forget to add all types also to the native
// Item_getType function in com_scriptographer_ai_Item.cpp!
if (item == null) {
switch (type) {
case TYPE_PATH:
item = new Path(artHandle, docHandle, created);
break;
case TYPE_GROUP:
item = new Group(artHandle, docHandle, created);
break;
case TYPE_RASTER:
item = new Raster(artHandle, docHandle, created);
break;
case TYPE_PLACED:
item = new PlacedFile(artHandle, docHandle, created);
break;
case TYPE_LAYER:
item = new Layer(artHandle, docHandle, created);
break;
case TYPE_COMPOUNDPATH:
item = new CompoundPath(artHandle, docHandle, created);
break;
case TYPE_TEXTFRAME:
switch (textType) {
case TextItem.TEXTTYPE_POINT:
item = new PointText(artHandle, docHandle, created);
break;
case TextItem.TEXTTYPE_AREA:
item = new AreaText(artHandle, docHandle, created);
break;
case TextItem.TEXTTYPE_PATH:
item = new PathText(artHandle, docHandle, created);
break;
}
break;
case TYPE_TRACING:
item = new Tracing(artHandle, docHandle, created);
break;
case TYPE_SYMBOL:
item = new PlacedSymbol(artHandle, docHandle, created);
}
}
return item;
}
protected static native Item wrapHandle(int artHandle, int docHandle,
boolean created, boolean checkWrapped);
/**
* Returns the wrapper, if the object has one
*
* @param artHandle
* @return the wrapper for the artHandle
*/
protected static Item getIfWrapped(int artHandle) {
Item item = items.get(artHandle);
// Make sure this item is still valid. It might be a reused art handle
// too...
if (item != null && !item.isValid()) {
// Remove invalid ones so they dont come back, as their handle was
// now reused in the meantime by a new item. We can do this safely
// as getIfWrapped is only used for now valid items, so there is no
// way we could go back to the previous item through undoing, as
// Illustrator would not have given us the handle otherwise.
items.remove(artHandle);
item = null;
}
return item;
}
/**
* Increases the version of the items associated with artHandles, if
* there are any. It does not wrap the artHandles if they weren't already.
*
* Called from the native environment.
*/
protected static void updateIfWrapped(int[] artHandles) {
// Reuse item objects for lookups, instead of creating a new one
// for every artHandle
for (int i = 0; i < artHandles.length; i+=2) {
// artHandles contains two entries for every item:
// The current handle, and the initial handle that was stored
// in the item's dictionary when it was wrapped.
// See the native side for more explanations
// (ScriptographerEngine::wrapArtHandle,
// ScriptographerEngine::onSelectionChanged)
int curHandle = artHandles[i];
int prevHandle = artHandles[i + 1];
Item item = null;
// Only change the handle if it has changed.
// Items where the handle has not changed are also included,
// since their version numbers needs to increase.
if (prevHandle != 0 && prevHandle != curHandle) {
// In case there was already an item with the initial handle
// before, change its handle now:
item = items.get(prevHandle);
if (item != null)
item.changeHandle(curHandle, 0, false);
} else {
item = items.get(curHandle);
// Now update it if it was found
if (item != null)
item.version++;
}
}
}
/**
* @jshide
*/
public static void removeIfWrapped(int[] artHandles, boolean removeHandles) {
for (int i = 0; i < artHandles.length; i++) {
Item item = items.get(artHandles[i]);
if (item != null)
item.remove(removeHandles);
}
}
/**
* Simple helper class for handle history entries, to keep track of
* version / handle pairs in a stack.
*/
static class HandleHistoryEntry {
long version;
int handle;
HandleHistoryEntry(long version, int handle) {
this.version = version;
this.handle = handle;
}
}
protected void changeHandle(int newHandle, int docHandle,
boolean clearDictionary) {
// Remove the object at the old handle
if (handle != newHandle) {
// If there was no handle history yet, add the creation version to
// the history as a first element.
if (handleHistory == null) {
handleHistory = new Stack<HandleHistoryEntry>();
handleHistory.push(
new HandleHistoryEntry(creationVersion, handle));
}
items.remove(handle);
// Change the handles...
handle = newHandle;
// ...and insert it again
items.put(newHandle, this);
// Now add the new handle to the history.
handleHistory.push(
new HandleHistoryEntry(document.historyVersion, newHandle));
// Mark this item as modified. This will update modificationVersion
// after the cycle through #updateModified(), which will also adjust
// the version of the last HistoryEntry.
setModified();
}
if (docHandle != 0)
document = Document.wrapHandle(docHandle);
if (clearDictionary) {
dictionaryHandle = 0;
dictionaryKey = 0;
}
// Update
version++;
}
/**
* Call to update the item's modification level
*/
protected void setModified() {
modificationVersion = document.historyVersion;
// This item's modification date needs updating after the cycle.
document.addModifiedItem(this);
}
/**
* Called by document to update items in document.modifiedItems after cycle.
*/
protected void updateModified(long version) {
// Adjust last HistoryEntry as well if it was created in the current
// cycle.
if (handleHistory != null) {
HandleHistoryEntry last = handleHistory.peek();
if (last.version == modificationVersion) {
if (Document.reportUndoHistory)
ScriptographerEngine.logConsole("Updating handleHistory version "
+ Integer.toString(last.handle, 16) + ", " + last.version);
last.version = version;
}
}
modificationVersion = version;
}
/**
* Checks a cached version number against the internal version and decides
* if the cached data needs an update. Also takes history levels into
* account.
*/
protected boolean needsUpdate(int version) {
// Before checking if this item is valid or needs updates, check handle
// history, and swap back to previous handles if version went back
// bellow the last entry on the stack.
// TODO: Should this be moved to isValid() instead?
if (handleHistory != null) {
HandleHistoryEntry last = handleHistory.peek();
if (document.historyVersion < last.version) {
handleHistory.pop();
HandleHistoryEntry previous = handleHistory.peek();
// Just like in #changeHandle(), remove old handle...
items.remove(handle);
if (Document.reportUndoHistory)
ScriptographerEngine.logConsole("Switching back to handleHistory version for handle "
+ Integer.toString(handle, 16) + ": "
+ Integer.toString(previous.handle, 16) + ", " + previous.version);
// ...change the handle to the previous one...
handle = previous.handle;
// ...and add it again.
items.put(handle, this);
if (handleHistory.size() <= 1) {
// We've reached the end of the history,
// switch back to simple mode.
handleHistory = null;
}
}
}
return isValid() && (version != this.version
|| document.historyVersion < modificationVersion);
}
/**
* Returns true if this item needs an update regardless of the cached
* version, due to history changes.
*/
protected boolean needsUpdate() {
return this.needsUpdate(version);
}
/**
* Called by native methods through commitIfWrapped if all cached changes
* need to be committed before the objects are modified.
*
* The version is then increased to invalidate the cached values, as they
* were just changed.
*/
protected boolean commitAndInvalidate(boolean invalidate) {
boolean committed = CommitManager.commit(this);
// Increasing version by one causes refetching of cached data:
if (invalidate)
version++;
return committed;
}
/**
* Called by native methods if all cached changes need to be committed
* before the objects are modified.
*/
protected static boolean commitIfWrapped(int handle, boolean invalidate) {
Item item = getIfWrapped(handle);
if (item != null)
return item.commitAndInvalidate(invalidate);
return false;
}
private static native boolean nativeRemove(int handle, int docHandle,
int dictionaryHandle);
protected boolean remove(boolean removeHandle) {
if (handle != 0 && (!removeHandle || isValid()
&& nativeRemove(handle, document.handle, dictionaryHandle))) {
// Do not remove from items since undoing can bring them back, and
// we don't want to loose the undo history tracking ionformation for
// them: items.remove(handle);
deletionVersion = document.historyVersion;
// This item's versions need to be updated after the history cycle
// is finished.
document.addRemovedItem(this);
return true;
}
return false;
}
/**
* Removes the item from the document. If the item has children,
* they are also removed.
*
* @return {@true if the item was removed}
*/
public boolean remove() {
return remove(true);
}
/**
* Removes all the children items contained within the item.
*
* @return {@true if removing was successful}
*/
public boolean removeChildren() {
Item child = getFirstChild();
boolean removed = false;
while (child != null) {
Item next = child.getNextSibling();
child.remove();
child = next;
removed = true;
}
return removed;
}
protected native void finalize();
/**
* Copies the item to another document, or duplicates it within the
* same document.
*
* @param document the document to copy the item to
* @return the new copy of the item
*/
public native Item copyTo(Document document);
/**
* Copies the item into the specified item.
*
* @param item
*/
public native Item copyTo(Item item);
/**
* Clones the item within the same document.
*
* @return the newly cloned item
*/
public Object clone() {
Item parent = getParent();
// TODO: parent == null: dictionary item -> return valid parent?
Item clone = parent != null ? copyTo(parent) : copyTo(document);
clone.moveAbove(this);
return clone;
}
/**
* The name of the item as it appears in the layers palette.
*
* Sample code:
* <code>
* var layer = new Layer(); // a layer is an item
* print(layer.name); // null
* layer.name = 'A nice name';
* print(layer.name); // 'A nice name'
* </code>
*/
public native String getName();
public native void setName(String name);
/**
* The item's position within the art board. This is the
* {@link Rectangle#getCenter()} of the {@link Item#getBounds()} rectangle.
*
* Sample code:
* <code>
* // Create a circle at position { x: 10, y: 10 }
* var circle = new Path.Circle(new Point(10, 10), 10);
*
* // Move the circle to { x: 20, y: 20 }
* circle.position = new Point(20, 20);
*
* // Move the circle 10 points to the right
* circle.position += new Point(10, 0);
* print(circle.position); // { x: 30, y: 20 }
* </code>
*/
public native Point getPosition();
public void setPosition(Point pt) {
translate(pt.subtract(getPosition()));
}
/**
* @jshide
*/
public void setPosition(double x, double y) {
setPosition(new Point(x, y));
}
/**
* The path style of the item.
*
* Sample code:
* <code>
* var circle = new Path.Circle(new Point(10, 10), 10);
* circle.style = {
* fillColor: new RGBColor(1, 0, 0),
* strokeColor: new RGBColor(0, 1, 0),
* strokeWidth: 5
* };
* </code>
*/
public PathStyle getStyle() {
if (style == null) {
style = new PathStyle(this);
} else {
style.update();
}
return style;
}
public void setStyle(PathStyle style) {
// Make sure it's created and fetched
getStyle();
this.style.init(style);
this.style.markDirty();
}
/**
* A boolean value that specifies whether the center point of the item is
* visible.
*
* @jshide
*/
public native boolean isCenterVisible();
/**
* @jshide
*/
public native void setCenterVisible(boolean centerVisible);
private native int nativeGetAttributes(int attributes);
private native void nativeSetAttributes(int attributes, int values);
/*
* This can be used to retrieve all user attributes and restore them again
* after an operation that would change them.
*/
protected int getAttributes() {
return nativeGetAttributes(0xffffffff);
}
protected void setAttributes(int attributes) {
// Setting all attributes at once does not seem to work, so loop
// through attributes and set separately.
// nativeSetAttributes(0xffffffff, attributes);
for (ItemAttribute attribute : ItemAttribute.values())
nativeSetAttributes(attribute.value, attributes & attribute.value);
}
/**
* @jshide
*/
public boolean getAttribute(ItemAttribute attribute) {
if (attribute != null)
return (nativeGetAttributes(attribute.value) & attribute.value) != 0;
return false;
}
/**
* @jshide
*/
public void setAttribute(ItemAttribute attribute, boolean value) {
if (attribute != null) {
if (attribute == ItemAttribute.SELECTED
|| attribute == ItemAttribute.FULLY_SELECTED)
document.commitCurrentStyle();
nativeSetAttributes(attribute.value, value ? attribute.value : 0);
}
}
/**
* Specifies whether an item is selected.
*
* Sample code:
* <code>
* print(document.selectedItems.length); // 0
* var path = new Path.Circle(new Size(50, 50), 25);
* path.selected = true; // Select the path
* print(document.selectedItems.length) // 1
* </code>
*
* @return {true if the item is selected or partially selected (groups with
* some selected items/partially selected paths)}
*/
public boolean isSelected() {
return getAttribute(ItemAttribute.SELECTED);
}
public void setSelected(boolean selected) {
setAttribute(ItemAttribute.SELECTED, selected);
}
/**
* Specifies whether the item is fully selected. For paths this means that
* all segments are selected, for container items (groups/layers) all
* children are selected.
*
* @return {@true if the item is fully selected}
*/
public boolean isFullySelected() {
return getAttribute(ItemAttribute.FULLY_SELECTED);
}
public void setFullySelected(boolean selected) {
setAttribute(ItemAttribute.FULLY_SELECTED, selected);
}
/**
* Specifies whether the item is locked.
*
* Sample code:
* <code>
* var path = new Path();
* print(path.locked) // false
* path.locked = true; // Locks the path
* </code>
*
* @return {@true if the item is locked}
*/
public boolean isLocked() {
return getAttribute(ItemAttribute.LOCKED);
}
public void setLocked(boolean locked) {
setAttribute(ItemAttribute.LOCKED, locked);
}
/**
* Specifies whether the item is visible.
*
* Sample code:
* <code>
* var path = new Path();
* print(path.visible) // true
* path.visible = false; // Hides the path
* </code>
*
* @return {@true if the item is visible}
*/
public boolean isVisible() {
return !getAttribute(ItemAttribute.HIDDEN);
}
public void setVisible(boolean visible) {
setAttribute(ItemAttribute.HIDDEN, !visible);
}
/**
* Specifies whether the item is hidden.
*
* Sample code:
* <code>
* var path = new Path();
* print(path.hidden); // false
* path.hidden = true; // Hides the path
* </code>
*
* @return {@true if the item is hidden}
*
* @jshide
*/
public final boolean isHidden() {
return !isVisible();
}
/**
* @jshide
*/
public final void setHidden(boolean hidden) {
setVisible(!hidden);
}
/**
* Specifies whether the item defines a clip mask. This can only be set on
* paths, compound paths, and text frame objects, and only if the item is
* already contained within a clipping group.
*
* Sample code:
* <code>
* var group = new Group();
* group.appendChild(path);
* group.clipped = true;
* path.clipMask = true;
* </code>
*
* @return {@true if the item defines a clip mask}
*/
public boolean isClipMask() {
return getAttribute(ItemAttribute.CLIP_MASK);
}
@SuppressWarnings("deprecation")
public void setClipMask(boolean clipMask) {
setAttribute(ItemAttribute.CLIP_MASK, clipMask);
if (clipMask) {
PathStyle style = getStyle();
style.setFillColor(Color.NONE);
style.setStrokeColor(Color.NONE);
// We need to reflect the clip mask status in the deprectted clip
// property too, otherwise the delayed commmit() through the
// CommitManager would override whatever we set here.
style.clip = clipMask;
}
}
/**
* Specifies whether the item is targeted.
*
* Sample code:
* <code>
* var path = new Path();
* print(path.targeted) // false
* path.targeted = true; // Marks the the path as targeted
* </code>
*
* @return {@true if the item is locked}
*/
public boolean isTargeted() {
return getAttribute(ItemAttribute.TARGETED);
}
public void setTargeted(boolean targeted) {
setAttribute(ItemAttribute.TARGETED, targeted);
}
private native int nativeGetBlendMode();
private native void nativeSetBlendMode(int mode);
/**
* The blend mode of the item.
*
* Sample code:
* <code>
* var circle = new Path.Circle(new Point(50, 50), 10);
* print(circle.blendMode); // normal
*
* // Change the blend mode of the path item:
* circle.blendMode = 'multiply';
* </code>
*/
public BlendMode getBlendMode() {
return IntegerEnumUtils.get(BlendMode.class, nativeGetBlendMode());
}
public void setBlendMode(BlendMode blend) {
nativeSetBlendMode(blend.value);
}
/**
* The opacity of the item.
*
* Sample code:
* <code>
* // Create a circle at position { x: 10, y: 10 }
* var circle = new Path.Circle(new Point(10, 10), 10);
*
* // Change the opacity of the circle to 50%:
* circle.opacity = 0.5;
* </code>
*
* @return the opacity of the item as a value between 0 and 1.
*/
public native float getOpacity();
public native void setOpacity(float opacity);
public native boolean getIsolated();
public native void setIsolated(boolean isolated);
private native int nativeGetKnockout(boolean inherited);
private native void nativeSetKnockout(int knockout);
public Knockout getKnockout(boolean inherited) {
return IntegerEnumUtils.get(Knockout.class,
nativeGetKnockout(inherited));
}
public Knockout getKnockout() {
return getKnockout(false);
}
public void setKnockout(Knockout knockout) {
nativeSetKnockout(knockout.value);
}
public native boolean getAlphaIsShape();
public native void setAlphaIsShape(boolean isShape);
private native int nativeGetData();
/**
* @jshide
*/
public LiveEffectPosition getEffectPosition(LiveEffect effect,
Map<String, Object> parameters) {
if (effect != null)
return IntegerEnumUtils.get(LiveEffectPosition.class,
nativeGetEffectPosition(effect, parameters));
return null;
}
private native int nativeGetEffectPosition(LiveEffect effect,
Map<String, Object> parameters);
/**
* @jshide
*/
public boolean hasEffect(LiveEffect effect, Map<String, Object> parameters) {
return getEffectPosition(effect, parameters) != null;
}
/**
* @jshide
*/
public boolean addEffect(LiveEffect effect, Map<String, Object> parameters,
LiveEffectPosition position) {
if (effect != null) {
LiveEffectParameters params = parameters != null
? parameters instanceof LiveEffectParameters
? (LiveEffectParameters) parameters
: new LiveEffectParameters(parameters)
: new LiveEffectParameters();
// The moment we pass params to nativeAddEffect, Illustrator
// will take care of the releasing, so set release to false.
params.release = false;
return nativeAddEffect(effect, params, position != null
? position.value : effect.getPosition().value);
}
return false;
}
/**
* @jshide
*/
public boolean addEffect(LiveEffect effect, Map<String, Object> parameters) {
return addEffect(effect, parameters, effect.getPosition());
}
/**
* @jshide
*/
public boolean addEffect(LiveEffect effect) {
return addEffect(effect, null);
}
private native boolean nativeAddEffect(LiveEffect effect,
LiveEffectParameters data, int position);
/**
* @jshide
*/
public native boolean editEffect(LiveEffect effect,
Map<String, Object> parameters);
/**
* @jshide
*/
public native boolean removeEffect(LiveEffect effect,
Map<String, Object> parameters);
/**
* An object contained within the item which can be used to store data.
* The values in this object can be accessed even after the file has been
* closed and opened again. Since these values are stored in a native
* structure, only a limited amount of value types are supported: Number,
* String, Boolean, Item, Point, Matrix.
*
* Sample code:
* <code>
* var path = new Path.Circle(new Point(50, 50), 50);
* path.data.point = new Point(50, 50);
* print(path.data.point); // {x: 50, y: 50}
* </code>
*/
public Dictionary getData() {
// We need to check if existing data references are still valid,
// as Dictionary.releaseAll() is invalidating them after each
// history cycle. See Dictionary.releaseAll() for more explanations
if (data == null || !data.isValid())
data = Dictionary.wrapHandle(nativeGetData(), document, this);
return data;
}
public void setData(Map<String, Object> map) {
Dictionary data = getData();
if (map != data) {
data.clear();
data.putAll(map);
}
}
/**
* {@grouptitle Document Hierarchy}
*
* The document that the item belongs to.
*/
public Document getDocument() {
// This is only here for the API document.
// It does exactly the same as the definition in DocumentObject
return document;
}
/**
* The item's parent layer, if any.
*/
public native Layer getLayer();
/**
* The item that this item is contained within.
*
* Sample code:
* <code>
* var path = new Path();
* print(path.parent) // Layer (Layer 1)
*
* var group = new Group();
* group.appendTop(path);
* print(path.parent); // Group (@31fbbe00)
* </code>
*/
public native Item getParent();
/**
* The children items contained within this item.
*
* Sample code:
* <code>
* var group = new Group();
*
* // the group doesn't have any children yet
* print(group.children.length); // 0
*
* var path = new Path();
* path.name = 'pathName';
*
* // append the path in the group
* group.appendTop(path);
*
* print(group.children.length); // 1
*
* // access children by index:
* print(group.children[0]); // Path (pathName)
*
* // access children by name:
* print(group.children['pathName']); // Path (pathName)
* </code>
*/
public ItemList getChildren() {
// Don't implement this in native as the number of items is not known
// in advance and like this, a java ArrayList can be used:
// TODO: Cache the result. Invalidate cached version when version
// changes, or when appendChild / moveAbove / bellow affects this
// children list.
ItemList list = new ItemList();
Item child = getFirstChild();
while (child != null) {
list.add(child);
child = child.getNextSibling();
}
return list;
}
public void setChildren(ReadOnlyList<Item> children) {
removeChildren();
for (Item child : children)
appendBottom(child);
}
public void setChildren(Item[] children) {
setChildren(Lists.asList(children));
}
/**
* Reverses the order of this item's children
*/
public boolean reverseChildren() {
boolean changed = false;
for (Item child : getChildren())
changed = appendTop(child) | changed;
return changed;
}
/**
* The first item contained within this item.
*/
public native Item getFirstChild();
/**
* The last item contained within this item.
*/
public native Item getLastChild();
/**
* The next item on the same level as this item.
*/
public native Item getNextSibling();
/**
* The previous item on the same level as this item.
*/
public native Item getPreviousSibling();
/**
* The index of this item within the list of it's parent's children.
*/
public int getIndex() {
Item item = getPreviousSibling();
int i = 0;
while (item != null) {
item = item.getPreviousSibling();
i++;
}
return i;
}
/**
* {@grouptitle Bounding Rectangles}
*
* The bounding rectangle of the item excluding stroke width.
*/
public native Rectangle getBounds();
/**
* @jshide
*/
public void setBounds(double x, double y, double width, double height) {
Rectangle rect = getBounds();
Matrix matrix = new Matrix();
// Read this from bottom to top:
// Translate to new center:
matrix.translate(
x + width * 0.5f,
y + height * 0.5f);
// Scale to new Size, if size changes and avoid divisions by 0:
if (width != rect.width || height != rect.height)
matrix.scale(
rect.width != 0 ? width / rect.width : 1,
rect.height != 0 ? height / rect.height : 1);
// Translate to center:
matrix.translate(
-(rect.x + rect.width * 0.5f),
-(rect.y + rect.height * 0.5f));
// Now execute the transformation:
transform(matrix);
}
public void setBounds(Rectangle rect) {
setBounds(rect.x, rect.y, rect.width, rect.height);
}
/**
* The bounding rectangle of the item including stroke width.
*/
public native Rectangle getStrokeBounds();
/**
* The bounding rectangle of the item including stroke width and controls.
*/
public native Rectangle getControlBounds();
/*
* Stroke Styles
*/
/**
* @copy PathStyle#getStrokeColor()
*/
public Color getStrokeColor() {
return getStyle().getStrokeColor();
}
public void setStrokeColor(Color color) {
getStyle().setStrokeColor(color);
}
public void setStrokeColor(java.awt.Color color) {
getStyle().setStrokeColor(color);
}
/**
* @copy PathStyle#getStrokeWidth()
*/
public Float getStrokeWidth() {
return getStyle().getStrokeWidth();
}
public void setStrokeWidth(Float width) {
getStyle().setStrokeWidth(width);
}
/**
* @copy PathStyle#getStrokeCap()
*/
public StrokeCap getStrokeCap() {
return getStyle().getStrokeCap();
}
public void setStrokeCap(StrokeCap cap) {
getStyle().setStrokeCap(cap);
}
/**
* @copy PathStyle#getStrokeJoin()
*/
public StrokeJoin getStrokeJoin() {
return getStyle().getStrokeJoin();
}
public void setStrokeJoin(StrokeJoin join) {
getStyle().setStrokeJoin(join);
}
/**
* @copy PathStyle#getDashOffset()
*/
public Float getDashOffset() {
return getStyle().getDashOffset();
}
public void setDashOffset(Float offset) {
getStyle().setDashOffset(offset);
}
/**
* @copy PathStyle#getDashArray()
*/
public float[] getDashArray() {
return getStyle().getDashArray();
}
public void setDashArray(float[] array) {
getStyle().setDashArray(array);
}
/**
* @copy PathStyle#getMiterLimit()
*/
public Float getMiterLimit() {
return getStyle().getMiterLimit();
}
public void setMiterLimit(Float limit) {
getStyle().setMiterLimit(limit);
}
/**
* @copy PathStyle#getStrokeOverprint()
*/
public Boolean getStrokeOverprint() {
return getStyle().getStrokeOverprint();
}
public void setStrokeOverprint(Boolean overprint) {
getStyle().setStrokeOverprint(overprint);
}
/*
* Fill Style
*/
/**
* @copy PathStyle#getFillColor()
*/
public Color getFillColor() {
return getStyle().getFillColor();
}
public void setFillColor(Color color) {
getStyle().setFillColor(color);
}
public void setFillColor(java.awt.Color color) {
getStyle().setFillColor(color);
}
/**
* @copy PathStyle#getFillOverprint()
*/
public Boolean getFillOverprint() {
return getStyle().getFillOverprint();
}
public void setFillOverprint(Boolean overprint) {
getStyle().setFillOverprint(overprint);
}
/*
* Path Style
*/
/**
* {@grouptitle Path Style}
*
* @copy PathStyle#getWindingRule()
*/
public WindingRule getWindingRule() {
return getStyle().getWindingRule();
}
public void setWindingRule(WindingRule rule) {
getStyle().setWindingRule(rule);
}
/**
* @copy PathStyle#getResolution()
*/
public Float getResolution() {
return getStyle().getResolution();
}
public void setResolution(Float resolution) {
getStyle().setResolution(resolution);
}
/*
* End of Style
*/
public HitResult hitTest(Point point, HitRequest request, float tolerance) {
return document.hitTest(point, request, tolerance, this);
}
public HitResult hitTest(Point point, HitRequest request) {
return hitTest(point, request, HitResult.DEFAULT_TOLERANCE);
}
public HitResult hitTest(Point point) {
return hitTest(point, HitRequest.ALL, HitResult.DEFAULT_TOLERANCE);
}
public HitResult hitTest(Point point, float tolerance) {
return hitTest(point, HitRequest.ALL, tolerance);
}
private native Item nativeExpand(int flags, int steps);
/**
* Breaks artwork up into individual parts and works just like calling
* "expand" from the Object menu in Illustrator.
*
* It outlines stroked lines, text objects, gradients, patterns, etc.
*
* The item itself is removed, and the newly created item containing the
* expanded artwork is returned.
*
* @param flags
* @param steps the amount of steps for gradient, when the
* {@code 'gradient-to-paths'} flag is passed
* @return the newly created item containing the expanded artwork
*/
public Item expand(EnumSet<ExpandFlag> flags, int steps) {
return nativeExpand(IntegerEnumUtils.getFlags(flags), steps);
}
public Item expand(EnumSet<ExpandFlag> flags) {
return expand(flags, 0);
}
public Item expand(ExpandFlag[] flags, int steps) {
return expand(EnumSet.copyOf(Arrays.asList(flags)), steps);
}
public Item expand(ExpandFlag[] flags) {
return expand(flags, 0);
}
private static int defaultExpandFlags =
IntegerEnumUtils.getFlags(EnumSet.of(ExpandFlag.PLUGIN_ART,
ExpandFlag.TEXT, ExpandFlag.STROKE, ExpandFlag.PATTERN,
ExpandFlag.SYMBOL_INSTANCES));
/**
* Calls {@link #expand(int, int)} with these flags set:
* ExpandFlag#PLUGIN_ART, ExpandFlag#TEXT, ExpandFlag#STROKE,
* ExpandFlag#PATTERN, ExpandFlag#SYMBOL_INSTANCES
*
* @return the newly created item containing the expanded artwork
*/
public Item expand() {
return nativeExpand(defaultExpandFlags, 0);
}
private native Raster nativeRasterize(int type, float resolution,
int antialiasing, float width, float height);
/**
* Rasterizes the item into a newly created Raster object. The item itself
* is not removed after rasterization.
*
* @param type the color mode of the raster {@default same as document}
* @param resolution the resolution of the raster in dpi {@default 72}
* @param antialiasing the amount of anti-aliasing {@default 4}
* @param width {@default automatic}
* @param height {@default automatic}
* @return the newly created Raster item
*/
public Raster rasterize(ColorType type, float resolution, int antialiasing,
float width, float height) {
return nativeRasterize(type != null ? type.value : -1, resolution,
antialiasing, width, height);
}
public Raster rasterize(ColorType type, float resolution, int antialiasing) {
return rasterize(type, resolution, antialiasing, -1, -1);
}
public Raster rasterize(ColorType type, float resolution) {
return rasterize(type, resolution, 4, -1, -1);
}
public Raster rasterize(ColorType type) {
return rasterize(type, 72, 4, -1, -1);
}
public Raster rasterize() {
return rasterize(null, 72, 4, -1, -1);
}
private static native Raster nativeRasterize(Item[] items, int type,
float resolution, int antialiasing, float width, float height);
/**
* Rasterizes the passed items into a newly created Raster object. The items
* are not removed after rasterization.
*
* @param type the color mode of the raster {@default same as document}
* @param resolution the resolution of the raster in dpi {@default 72}
* @param antialiasing the amount of anti-aliasing {@default 4}
* @param width {@default automatic}
* @param height {@default automatic}
* @return the newly created Raster item
*/
public static Raster rasterize(Item[] items, ColorType type,
float resolution, int antialiasing, float width, float height) {
return nativeRasterize(items, type != null ? type.value : -1,
resolution, antialiasing, width, height);
}
public static Raster rasterize(Item[] items, ColorType type,
float resolution, int antialiasing) {
return rasterize(items, type, resolution, antialiasing, -1, -1);
}
public static Raster rasterize(Item[] items, ColorType type) {
return rasterize(items, type, 0, 4, -1, -1);
}
public static Raster rasterize(Item[] items) {
return rasterize(items, null, 0, 4, -1, -1);
}
private native void nativeDraw(Image image, int width, int height);
/**
* @jshide
*/
public void draw(Image image) {
nativeDraw(image, image.getWidth(), image.getHeight());
}
/**
* {@grouptitle Tests}
*
* Checks if the item contains any children items.
*
* @return {@true if it has one or more children}
*/
public boolean hasChildren() {
return getFirstChild() != null;
}
/**
* Checks whether the item is editable.
*
* @return {@true when neither the item, nor it's parents are locked or
* hidden}
*/
public native boolean isEditable();
/**
* Checks whether the item is valid, i.e. it hasn't been removed.
*
* Sample code:
* <code>
* var path = new Path();
* print(path.isValid()); // true
* path.remove();
* print(path.isValid()); // false
* </code>
*
* @return {@true if the item is valid}
*/
public boolean isValid() {
if (!Document.trackUndoHistory)
return Item.isValid(handle);
boolean valid = handle != 0
&& document.isValidVersion(creationVersion)
&& !document.isValidVersion(deletionVersion);
// Weird stacking of if statements, just so Eclipse does not warn about
// dead code
if (Document.reportUndoHistory)
if (!valid)
ScriptographerEngine.logConsole(getId() + " is invalid { branch: "
+ ((creationVersion >> 32) & 0xffffffffl) + ", level: "
+ (creationVersion & 0xffffffffl) + " }, isValid: "
+ Item.isValid(handle));
return valid;
}
protected void checkValid() {
if (!isValid())
throw new ScriptographerException("The item is no longer valid, either due to deletion or undoing. Use isValid() checks to avoid this error.");
}
private static native boolean[] nativeCheckItems(int[] values,
int length);
protected static void checkItems(Document document, long version) {
ArrayList<SoftReference<Item>> checkItems = document.checkItems;
if (!checkItems.isEmpty()) {
int[] values = new int[checkItems.size() * 3];
// Check all these handles in one go, for increased performance
// We need to pass dictionaryHandle and key as well, so these
// art items can be checked for validity differently.
int j = 0;
for (int i = 0, l = checkItems.size(); i < l; i++) {
Item item = checkItems.get(i).get();
if (item != null) {
values[j++] = item.handle;
values[j++] = item.dictionaryHandle;
values[j++] = item.dictionaryKey;
}
}
boolean[] valid = nativeCheckItems(values, j);
// Update historyVersion to one that is not valid anymore
for (int i = valid.length - 1; i >= 0; i--) {
if (!valid[i]) {
// Retrieve the item to update through its soft reference.
Item item = checkItems.get(i).get();
// Check for null as the soft reference might have been
// released
if (item != null) {
item.creationVersion = version;
if (Document.reportUndoHistory)
ScriptographerEngine.logConsole("Marking " + item
+ " as invalid before version: " + version);
}
// Remove it from the list
checkItems.remove(i);
}
}
}
}
/**
* {@grouptitle Hierarchy Operations}
*
* Inserts the specified item as a child of the item by appending it to the
* list of children and moving it above all other children. You can use this
* function for groups, compound paths and layers.
*
* Sample code:
* <code>
* var group = new Group();
* var path = new Path();
* group.appendTop(path);
* print(path.isDescendant(group)); // true
* </code>
*
* @param item The item that will be appended as a child
*/
public native boolean appendTop(Item item);
/*
public boolean appendTop(Item... items) {
boolean ok = true;
for (Item item : items) {
if (!appendTop(item))
ok = false;
}
return ok;
}
*/
/**
* Inserts the specified item as a child of this item by appending it to the
* list of children and moving it below all other children. You can use this
* function for groups, compound paths and layers.
*
* Sample code:
* <code>
* var group = new Group();
* var path = new Path();
* group.appendBottom(path);
* print(path.isDescendant(group)); // true
* </code>
*
* @param item The item that will be appended as a child
*/
public native boolean appendBottom(Item item);
/**
* A link to {@link #appendTop}
*
* @deprecated use {@link #appendTop} or {@link #appendBottom} instead.
*/
public boolean appendChild(Item item) {
return appendTop(item);
}
/**
* Moves this item above the specified item.
*
* Sample code:
* <code>
* var firstPath = new Path();
* var secondPath = new Path();
* print(firstPath.isAbove(secondPath)); // false
* firstPath.moveAbove(secondPath);
* print(firstPath.isAbove(secondPath)); // true
* </code>
*
* @param item The item above which it should be moved
* @return true if it was moved, false otherwise
*/
public native boolean moveAbove(Item item);
/**
* Moves the item below the specified item.
*
* Sample code:
* <code>
* var firstPath = new Path();
* var secondPath = new Path();
* print(secondPath.isBelow(firstPath)); // false
* secondPath.moveBelow(firstPath);
* print(secondPath.isBelow(firstPath)); // true
* </code>
*
* @param item the item below which it should be moved
* @return true if it was moved, false otherwise
*/
public native boolean moveBelow(Item item);
/**
* {@grouptitle Hierarchy Tests}
*
* Checks if this item is above the specified item in the stacking order of
* the document.
*
* Sample code:
* <code>
* var firstPath = new Path();
* var secondPath = new Path();
* print(secondPath.isAbove(firstPath)); // true
* </code>
*
* @param item The item to check against
* @return {@true if it is above the specified item}
*/
public native boolean isAbove(Item item);
/**
* Checks if the item is below the specified item in the stacking order of
* the document.
*
* Sample code:
* <code>
* var firstPath = new Path();
* var secondPath = new Path();
* print(firstPath.isBelow(secondPath)); // true
* </code>
*
* @param item The item to check against
* @return {@true if it is below the specified item}
*/
public native boolean isBelow(Item item);
public boolean isParent(Item item) {
return getParent() == item;
}
public boolean isChild(Item item) {
return item != null && item.getParent() == this;
}
/**
* Checks if the item is contained within the specified item.
*
* Sample code:
* <code>
* var group = new Group();
* var path = new Path();
* group.appendTop(path);
* print(path.isDescendant(group)); // true
* </code>
*
* @param item The item to check against
* @return {@true if it is inside the specified item}
*/
public native boolean isDescendant(Item item);
/**
* Checks if the item is an ancestor of the specified item.
*
* Sample code:
* <code>
* var group = new Group();
* var path = new Path();
* group.appendChild(path);
* print(group.isAncestor(path)); // true
* print(path.isAncestor(group)); // false
* </code>
*
* @param item the item to check against
* @return {@true if the item is an ancestor of the specified item}
*/
public native boolean isAncestor(Item item);
/**
* Checks whether the item is grouped with the specified item.
*
* @param item
* @return {@true if the items are grouped together}
*/
public boolean isGroupedWith(Item item) {
Item parent = getParent();
while (parent != null) {
// Find group parents
if ((parent instanceof Group || parent instanceof CompoundPath)
&& item.isDescendant(parent))
return true;
// Keep walking up otherwise
parent = parent.getParent();
}
return false;
}
/**
* {@grouptitle Transform Functions}
*
* Scales the item by the given values from its center point, or optionally
* by a supplied point.
*
* @param sx
* @param sy
* @param center {@default the center point of the item}
*
* @see Matrix#scale(double, double, Point center)
*/
public void scale(double sx, double sy, Point center) {
transform(new Matrix().scale(sx, sy, center));
}
public void scale(double sx, double sy) {
scale(sx, sy, getPosition());
}
/**
* Scales the item by the given value from its center point, or optionally
* by a supplied point.
*
* Sample code:
* <code>
* // Create a circle at position { x: 10, y: 10 }
* var circle = new Path.Circle(new Point(10, 10), 10);
* print(circle.bounds.width); // 20
*
* // Scale the path by 200% around its center point
* circle.scale(2);
*
* print(circle.bounds.width); // 40
* </code>
*
* <code>
* // Create a circle at position { x: 10, y: 10 }
* var circle = new Path.Circle(new Point(10, 10), 10);
*
* // Scale the path 200% from its bottom left corner
* circle.scale(2, circle.bounds.bottomLeft);
* </code>
*
* @param scale the scale factor
* @param center {@default the center point of the item}
* @see Matrix#scale(double, Point center)
*/
public void scale(double scale, Point center) {
scale(scale, scale, center);
}
public void scale(double scale) {
scale(scale, scale);
}
/**
* Translates (moves) the item by the given offset point.
*
* Sample code:
* <code>
* // Create a circle at position { x: 10, y: 10 }
* var circle = new Path.Circle(new Point(10, 10), 10);
* circle.translate(new Point(5, 10));
* print(circle.position); // {x: 15, y: 20}
* </code>
*
* Alternatively you can also add to the {@link #getPosition()} of the item:
* <code>
* // Create a circle at position { x: 10, y: 10 }
* var circle = new Path.Circle(new Point(10, 10), 10);
* circle.position += new Point(5, 10);
* print(circle.position); // {x: 15, y: 20}
* </code>
*
* @param t
*/
public void translate(Point t) {
transform(new Matrix().translate(t));
}
/**
* Rotates the item by a given angle around the given point.
*
* Angles are oriented clockwise and measured in degrees by default. Read
* more about angle units and orientation in the description of the
* {@link com.scriptographer.ai.Point#getAngle()} property.
*
* @param angle the rotation angle
* @see Matrix#rotate(double, Point)
*/
public void rotate(double angle, Point center) {
transform(new Matrix().rotate(angle, center));
}
/**
* Rotates the item by a given angle around its center point.
*
* Angles are oriented clockwise and measured in degrees by default. Read
* more about angle units and orientation in the description of the
* {@link com.scriptographer.ai.Point#getAngle()} property.
*
* @param angle the rotation angle
*/
public void rotate(double angle) {
rotate(angle, getPosition());
}
/**
* Shears the item with a given amount around its center point.
*
* @param shx
* @param shy
* @see Matrix#shear(double, double)
*/
public void shear(double shx, double shy) {
Point pos = getPosition();
Matrix matrix = new Matrix();
matrix.translate(pos.x, pos.y);
matrix.shear(shx, shy);
matrix.translate(-pos.x, -pos.y);
transform(matrix);
}
/* Matrix Transform */
private native void nativeTransform(Matrix matrix, int flags);
/**
* @jshide
*/
public void transform(Matrix matrix, EnumSet<TransformFlag> flags) {
nativeTransform(matrix, IntegerEnumUtils.getFlags(flags));
}
/**
* Transforms the item with custom flags to be set.
*
* @param matrix
* @param flags
*/
public void transform(Matrix matrix, TransformFlag[] flags) {
transform(matrix, EnumSet.copyOf(Arrays.asList(flags)));
}
private static int defaultTransformFlags =
IntegerEnumUtils.getFlags(EnumSet.of(TransformFlag.OBJECTS,
TransformFlag.CHILDREN));
/**
* Transforms the item with the flags TransformFlag.OBJECTS, and
* TransformFlag.CHILDREN set
*
* @param matrix
*/
public void transform(Matrix matrix) {
nativeTransform(matrix, defaultTransformFlags);
}
/* TODO:
{"equals", artEquals, 0},
{"hasEqualPath", artHasEqualPath, 1},
{"hasFill", artHasFill, 0},
{"hasStroke", artHasStroke, 0},
{"isClipping", artIsClipping, 0},
*/
/**
* @jshide
*/
public native int getItemType();
/**
* @jshide
*/
public static native int getItemType(Class cls);
public String toString() {
if (isValid()) {
String name = getName();
if (name != null)
return getClass().getSimpleName() + " '" + name + "'";
}
return super.toString();
}
private static ItemList createdItems = null;
/**
* @jshide
*/
public static void collectCreatedItems() {
createdItems = new ItemList();
}
/**
* @jshide
*/
public static void clearCreatedItems() {
// Clear right away
createdItems = null;
}
/**
* @jshide
*/
public static boolean hasCreatedItems() {
return createdItems.size() > 0;
}
/**
* @jshide
*/
public static ItemList retreiveCreatedItems() {
ItemList items = createdItems;
createdItems = null;
for (int i = items.size() - 1; i >= 0; i--) {
Item item = items.get(i);
// Make sure we're not returning any invalid new items.
if (!item.isValid())
items.remove(i);
}
return items;
}
/**
* @jshide
*/
public static boolean debug(Item item) {
return item.isValid();
}
/*
* This is just here for debugging the undo history code. It can be removed
* once that works well.
*/
protected static native boolean isValid(int handle);
/**
* Draws the item's content into a Graphics2D object. Useful for
* conversions.
*
* @jshide
*/
public void paint(Graphics2D graphics) {
ItemList children = getChildren();
for (int i = children.size() - 1; i >= 0; i--) {
Item child = children.get(i);
if (child.isVisible())
child.paint(graphics);
}
}
}