/*
* 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 Jun 7, 2010.
*/
package com.scriptographer.ai;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import com.scratchdisk.script.ArgumentReader;
import com.scratchdisk.util.ArrayList;
/**
* {@code ItemAttributes} objects are used to describe attributes of items to
* query for when using the {@link Document#getItems(ItemAttributes)} function.
* It serves as a description of a filter applied to the queried items, defined
* by various fields that can either be set to {@code true}, {@code false}, or
* {@code null}.
*
* Sample code:
* <code>
* // All selected paths and rasters contained in the document.
* var selectedItems = document.getItems({
* type: [Path, Raster],
* selected: true
* });
*
* // All visible Paths contained in the document.
* var visibleItems = document.getItems({
* type: Path,
* hidden: false
* });
* </code>
*
* @author lehni
*/
public class ItemAttributes {
private Class[] types;
private HashMap<ItemAttribute, Boolean> attributes =
new HashMap<ItemAttribute, Boolean>();
public ItemAttributes() {
}
/**
* This constructor is here so the scripting layer knows which version of
* Document#getItems() to choose from. It performs nothing more than setting
* all properties on the newly produced script wrapper.
*
* @jshide
*/
public ItemAttributes(ArgumentReader reader) {
reader.setProperties(this);
}
/**
* The method that performs the actual work, called from
* {@link Document#getItems(ItemAttributes)}
*
* @param document
* @return
*/
protected ItemList getItems(Document document) {
// Convert the attributes list to a new HashMap containing only
// integer -> boolean pairs.
int whichAttrs = 0, attrs = 0;
for (Map.Entry<ItemAttribute, Boolean> entry : attributes.entrySet()) {
ItemAttribute attribute = entry.getKey();
Boolean value = entry.getValue();
if (value != null) {
whichAttrs |= attribute.value;
if (value)
attrs |= attribute.value;
}
}
ItemList items = new ItemList();
// If no class was specified, match them all through Item.
Class[] types = this.types != null
? this.types : new Class[] { Item.class };
for (int i = 0; i < types.length; i++) {
Class type = types[i];
// Expand PathItem -> Path / CompoundPath
if (PathItem.class.equals(type)) {
items.addAll(document.getMatchingItems(Path.class,
whichAttrs, attrs));
items.addAll(document.getMatchingItems(CompoundPath.class,
whichAttrs, attrs));
} else {
ItemList list = document.getMatchingItems(type,
whichAttrs, attrs);
// Filter out TextItems that do not match the given type.
// This is needed since nativeGetMatchingItems returns all
// TextItems...
// TODO: Move this to the native side maybe?
if (TextItem.class.isAssignableFrom(type))
for (Item item : list)
if (!type.isInstance(item))
list.remove(item);
items.addAll(list);
}
}
// Filter out matched children when the parent matches too
for (int i = items.size() - 1; i >= 0; i--) {
Item item = items.get(i);
if (items.contains(item.getParent()))
items.remove(i);
}
return items;
}
/**
* The type of items to search for. It can be set to any of the {@link Item}
* classes, a {@code String} describing the name of such a class, or an
* {@code Array} of either, describing a row of {@link Item} classes to
* match.
*
* You can use {@link PathItem} to match both {@link Path} and
* {@link CompoundPath}, and {@link TextItem} to match all three types of
* text items ( {@link PointText}, {@link AreaText} and {@link PathText}).
*/
public Class[] getType() {
return types;
}
public void setType(Class[] types) {
if (types == null) {
this.types = null;
} else {
ArrayList<Class> classes =
new ArrayList<Class>(Arrays.asList(types));
// Filter out classes again that are not inheriting Item.
for (int i = classes.size() - 1; i >= 0; i--)
if (!Item.class.isAssignableFrom((classes.get(i))))
classes.remove(i);
this.types = classes.toArray(new Class[classes.size()]);
}
}
public void setType(String[] types) {
if (types == null) {
this.types = null;
} else {
ArrayList<Class> classes = new ArrayList<Class>();
for (String type : types) {
// Try loading class from String name.
try {
classes.add(Class.forName(Item.class.getPackage().getName()
+ "." + type));
} catch (ClassNotFoundException e) {
}
}
setType(classes.toArray(new Class[classes.size()]));
}
}
public void setType(Class type) {
setType(type != null ? new Class[] { type } : null);
}
public void setType(String type) {
setType(type != null ? type.split("\\s*,\\s*") : null);
}
private Boolean get(ItemAttribute attribute) {
return attributes.get(attribute);
}
private void set(ItemAttribute attribute, Boolean value) {
attributes.put(attribute, value);
}
/**
* Filters selected items when set to {@code true}, ignores selected items
* when set to {@code false}.
*
* Sample code:
* <code>
* var selectedItems = document.getItems({
* selected: true
* });
*
* var unselectedItems = document.getItems({
* selected: false
* });
* </code>
*/
public Boolean getSelected() {
return get(ItemAttribute.SELECTED);
}
public void setSelected(Boolean selected) {
set(ItemAttribute.SELECTED, selected);
}
/**
* Filters locked items when set to {@code true}, ignores locked items
* when set to {@code false}.
*
* Sample code:
* <code>
* var lockedItems = document.getItems({
* locked: true
* });
*
* var unlockedItems = document.getItems({
* locked: false
* });
* </code>
*/
public Boolean getLocked() {
return get(ItemAttribute.LOCKED);
}
public void setLocked(Boolean locked) {
set(ItemAttribute.LOCKED, locked);
}
/**
* Filters hidden items when set to {@code true}, ignores hidden items
* when set to {@code false}.
*
* Sample code:
* <code>
* var hiddenItems = document.getItems({
* hidden: true
* });
*
* var visibleItems = document.getItems({
* hidden: false
* });
* </code>
*/
public Boolean getHidden() {
return get(ItemAttribute.HIDDEN);
}
public void setHidden(Boolean hidden) {
set(ItemAttribute.HIDDEN, hidden);
}
public Boolean getFullySelected() {
return get(ItemAttribute.FULLY_SELECTED);
}
public void setFullySelected(Boolean fullySelected) {
set(ItemAttribute.FULLY_SELECTED, fullySelected);
}
/**
* Filters path items that define a clip mask when set to {@code true},
* ignores them when set to {@code false}.
*
* Sample code:
* <code>
* var clipMaskItems = document.getItems({
* clipMask: true
* });
*
* var otherItems = document.getItems({
* clipMask: false
* });
* </code>
*/
public Boolean getClipMask() {
return get(ItemAttribute.CLIP_MASK);
}
public void setClipMask(Boolean clipMask) {
set(ItemAttribute.CLIP_MASK, clipMask);
}
/**
* Filters items that are targeted when set to {@code true}, ignores
* targeted items when set to {@code false}.
*
* Sample code:
* <code>
* var targetedItems = document.getItems({
* targeted: true
* });
*
* var otherItems = document.getItems({
* targeted: false
* });
* </code>
*/
public Boolean getTargeted() {
return get(ItemAttribute.TARGETED);
}
public void setTargeted(Boolean targeted) {
set(ItemAttribute.TARGETED, targeted);
}
}