/*
* 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 23.10.2005.
*/
package com.scriptographer.ui;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import com.scratchdisk.list.ExtendedArrayList;
import com.scratchdisk.list.Lists;
import com.scratchdisk.list.ReadOnlyList;
import com.scratchdisk.script.ArgumentReader;
import com.scratchdisk.script.Callable;
import com.scratchdisk.script.ChangeEmitter;
import com.scratchdisk.script.ChangeReceiver;
import com.scratchdisk.script.MapArgumentReader;
import com.scratchdisk.script.ScriptEngine;
import com.scratchdisk.util.ConversionUtils;
import com.scriptographer.ScriptographerEngine;
/**
* @author lehni
*/
public class Component implements ChangeReceiver {
private String label;
private String name; // for preferences
private ComponentType type;
private Object defaultValue;
private Object options[];
private boolean visible = true;
private boolean enabled = true;
private boolean fullSize = false;
private Integer width;
private Integer height;
private Integer length;
private Integer maxLength;
private Boolean multiline;
private Integer rows;
private Integer columns;
private Double min;
private Double max;
private Double increment;
private Integer fractionDigits;
private TextUnits units;
private Boolean steppers;
protected ComponentProxy proxy;
protected Palette palette;
private boolean initialized;
/**
* A reference to the original object that defined this componenet, to
* be able to replace it again in components at the end of execution.
* (Needed by LiveEffect scope restauration).
*/
private Map<String, Object> definition;
/**
* @jshide
*/
public Component(ArgumentReader reader)
throws IllegalArgumentException {
if (reader.isMap()) {
type = reader.readEnum("type", ComponentType.class);
defaultValue = reader.readObject("value");
if (type == null) {
// Determine type form options and value
if (reader.has("options"))
type = ComponentType.LIST;
else if (reader.has("onClick"))
type = ComponentType.BUTTON;
else if (reader.has("onSelect"))
type = ComponentType.MENU_ENTRY;
else if (defaultValue instanceof Number)
type = ComponentType.NUMBER;
else if (defaultValue instanceof Boolean)
type = ComponentType.BOOLEAN;
else if (defaultValue instanceof String)
type = ComponentType.STRING;
}
if (type != null) {
setType(type);
// Call setMultiline to set default value for length
setMultiline(false);
// Tell the framework to set the properties from the map on the
// object after creating through ArgumentReader
reader.setProperties(this);
// Turn on steppers for number components with units by default
if (steppers == null && type == ComponentType.NUMBER
&& units != null && units != TextUnits.NONE)
setSteppers(true);
}
}
if (type == null)
throw new IllegalArgumentException();
}
protected void initialize() {
// Now set all the values again, so the item reflects them:
if (!visible)
proxy.setVisible(false);
if (!enabled)
proxy.setEnabled(false);
// Calculate a default range for sliders of none was defined
if (min == null && max == null && type == ComponentType.SLIDER) {
min = 0d;
max = defaultValue == null ? 1d
: ConversionUtils.toDouble(defaultValue);
}
// Set these values not straight through proxy but through the public
// methods here, since they perform type filtering for us.
setRange(min, max);
setFractionDigits(fractionDigits);
// Setting range internally updates increments, so no need to set it
// again here.
setMaxLength(maxLength);
setOptions(options);
setValue(defaultValue);
setUnits(units);
// Now update the size of the item.
updateSize();
initialized = true;
}
/**
* @jshide
*/
public Component(ComponentType type, String label,
Object value) {
this.label = label;
setType(type);
this.defaultValue = value;
}
/**
* Creates a STRING Item
*
* @jshide
*/
public Component(String description, String value) {
this(ComponentType.STRING, description, value);
}
/**
* Creates a NUMBER Item
*
* @jshide
*/
public Component(String description, double value) {
this(ComponentType.NUMBER, description, value);
}
/**
* Creates a BOOLEAN Item
*
* @jshide
*/
public Component(String description, boolean value) {
this(ComponentType.BOOLEAN, description, value);
}
/**
* Creates a RANGE Item
*
* @jshide
*/
public Component(String description, Number value, double min,
double max, double step) {
this(ComponentType.SLIDER, description, value);
this.setRange(min, max);
this.increment = step;
}
/**
* Creates a LIST Item
*
* @jshide
*/
public Component(String description, Object value, Object[] options) {
this(ComponentType.LIST, description, value);
this.options = options;
}
// TODO: make constructors for other types
/**
* Resets the value of the component to {@link #defaultValue}.
*/
public void reset() {
setValue(defaultValue);
}
protected void updateSize() {
if (proxy != null)
proxy.updateSize();
}
protected void onSizeChanged() {
if (initialized && palette != null)
palette.onSizeChanged();
}
/*
* Beans
*/
/**
* The component type.
*/
public ComponentType getType() {
return type;
}
@SuppressWarnings("deprecation")
public void setType(ComponentType type) {
if (proxy != null)
throw new UnsupportedOperationException(
"The component type cannot be changed once it is created");
switch (type) {
case CHECKBOX:
// Convert deprecated type CHECKBOX to BOOLEAN:
type = ComponentType.BOOLEAN;
break;
case RANGE:
// Convert deprecated type RANGE to SLIDER:
type = ComponentType.SLIDER;
break;
}
this.type = type;
// Update definition as well, so LiveEffect scope restoration works
if (definition != null)
definition.put("type", type);
}
/**
* The value of the component.
*/
public Object getValue() {
return proxy != null ? proxy.getValue() : defaultValue;
}
public void setValue(Object value) {
if (proxy == null || !proxy.setValue(value))
defaultValue = value;
// Update definition as well, so LiveEffect scope restoration works
if (definition != null)
definition.put("value", value);
}
/**
* The first {@link #getValue()} that was set.
*
* Sample code:
* <code>
* var values = {
* number: 30
* };
* var components = {
* number: {
* type: 'number'
* }
* };
* var palette = new Palette('Text', components, values);
* values.number = 60;
* print(components.number.value) // 60
* print(components.number.defaultValue) // 30
* </code>
*/
public Object getDefaultValue() {
return defaultValue;
}
/**
* The name of the component as it is referenced in
* {@link Palette#getComponents}.
*/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
// Update definition as well, so LiveEffect scope restoration works
if (definition != null)
definition.put("name", name);
}
/**
* Text label that appears on the left hand side of the component in the
* palette.
*/
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
// Update definition as well, so LiveEffect scope restoration works
if (definition != null)
definition.put("label", label);
}
/**
* @deprecated
*/
public String getDescription() {
return getLabel();
}
/**
* @deprecated
*/
public void setDescription(String description) {
setLabel(description);
}
/**
* Specifies whether the component is visible.
*
* Sample code:
* <code>
* var values = {
* number: 10
* };
* var components = {
* showNumber: {
* type: 'checkbox', label: 'Show',
* onChange: function(value) {
* components.number.visible = value;
* }
* },
* number: {
* type: 'number', label: 'Number',
* visible: false
* }
* };
* var palette = new Palette('Show / Hide', components, values);
* </code>
* @return {@true if the component is visible}
*/
public boolean isVisible() {
return visible;
}
public void setVisible(boolean visible) {
this.visible = visible;
if (proxy != null)
proxy.setVisible(visible);
// Update definition as well, so LiveEffect scope restoration works
if (definition != null)
definition.put("visible", visible);
}
/**
* Specifies whether the component is enabled. When set to {@code false},
* the component is grayed out and does not allow user interaction.
*
* Sample code:
* <code>
* var values = {
* canEdit: false,
* text: 'Can you edit me?'
* }
* var components = {
* canEdit: {
* type: 'checkbox', label: 'Allow Editing',
* onChange: function(value) {
* components.text.enabled = value;
* }
* },
* text: {
* type: 'string',
* enabled: false
* }
* };
* var palette = new Palette('Text', components, values);
* </code>
*
* @return {@true if the component is enabled}
*/
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
if (proxy != null)
proxy.setEnabled(enabled);
// Update definition as well, so LiveEffect scope restoration works
if (definition != null)
definition.put("enabled", enabled);
}
/**
* {@grouptitle Size}
*
* When set to {@code true}, the component is stretched over the width of
* the palette. When no {@link #getLabel()} is set, it also eliminates the
* margin on the left hand side.
*/
public boolean getFullSize() {
return fullSize;
}
public void setFullSize(boolean fullSize) {
this.fullSize = fullSize;
if (fullSize) {
width = null;
length = null;
}
// Update definition as well, so LiveEffect scope restoration works
if (definition != null)
definition.put("fullSize", fullSize);
}
/**
* The width of the input field in pixels.
*/
public Integer getWidth() {
return width;
}
public void setWidth(Integer width) {
this.width = width;
// Clear columns and length when setting width and vice versa.
fullSize = false;
columns = null;
length = null;
updateSize();
// Update definition as well, so LiveEffect scope restoration works
if (definition != null)
definition.put("width", width);
}
/**
* The height of the input field in pixels.
*/
public Integer getHeight() {
return height;
}
public void setHeight(Integer height) {
this.height = height;
// Clear rows when setting height and vice versa.
rows = null;
updateSize();
// Update definition as well, so LiveEffect scope restoration works
if (definition != null)
definition.put("height", height);
}
/**
* {@grouptitle Number / Slider Properties}
*
* The range for the numeric value as an array in the form: [min, max]. The
* first element in the array defines the allowed minimum amount, the second
* the maximum amount, both are included in the range.
*
* Sample code:
* <code>
* var values = {
* percentage: 50,
* angle: 180
* };
* var components = {
* percentage: {
* type: 'slider', label: 'Percentage',
* range: [0, 100]
* },
* angle: {
* type: 'number', label: 'Angle',
* range: [0, 360]
* },
* };
*
* var palette = new Palette('Range Examples', components, values);
* </code>
*/
public double[] getRange() {
return hasRange() ? new double[] {
min, max
} : null;
}
public void setRange(double[] range) {
if (range == null)
setRange(null, null);
else
setRange(range[0], range[1]);
// Update definition as well, so LiveEffect scope restoration works
if (definition != null)
definition.put("range", range);
}
/**
* @jshide
*/
public void setRange(Double min, Double max) {
if (type == ComponentType.NUMBER
|| type == ComponentType.SLIDER) {
this.min = min;
this.max = max;
if (proxy != null && proxy.setRange(min, max)) {
// Setting range sets increment again as well, as it will
// be dynamically calculated based on range in case it was
// not set on a fixed value.
setIncrement(increment);
}
}
}
/**
* @jshide
*/
public boolean hasRange() {
return min != null && max != null;
}
/**
* The minimum amount allowed.
*
* Sample code:
* <code>
* var values = {
* size: 5
* };
* var components = {
* size: {
* type: 'number', label: 'Size',
* min: 0, steppers: true
* }
* };
* var palette = new Palette('Minimum Value', components, values);
* </code>
*/
public Double getMin() {
return min;
}
public void setMin(Double min) {
setRange(min, max);
// Update definition as well, so LiveEffect scope restoration works
if (definition != null)
definition.put("min", min);
}
/**
* The maximum amount allowed.
*
* Sample code:
* <code>
* var values = {
* size: 5
* };
* var components = {
* size: {
* type: 'number', label: 'Size',
* max: 10, steppers: true
* }
* };
* var palette = new Palette('Maximum Value', components, values);
* </code>
*/
public Double getMax() {
return max;
}
public void setMax(Double max) {
setRange(min, max);
// Update definition as well, so LiveEffect scope restoration works
if (definition != null)
definition.put("max", max);
}
/**
* The amount the steppers increase / decrease the value. Even when steppers
* are not activated, the user can still use the up/down arrow keys to step
* by the amount defined by increment.
*
* Sample code:
* <code>
* var values = {
* percentage: 50
* };
* var components = {
* percentage: {
* type: 'number',
* range: [0, 100],
* steppers: true, increment: 10
* }
* };
* var palette = new Palette('Increment', components, values);
* </code>
*/
public Double getIncrement() {
if (type == ComponentType.NUMBER
|| type == ComponentType.SLIDER) {
// If no increment is defined, calculate a default value, based on
// the defined range.
if (increment == null) {
if (min != null && max != null) {
double range = max - min;
if (range == 0)
range = 1.0;
int numDigits = Math.max(0, 2 - (int) Math.ceil(Math.log10(range)));
double inc = 1.0 / Math.pow(10, numDigits);
return inc > 1.0 ? 1.0 : inc;
}
return 1.0;
}
return increment;
}
return null;
}
public void setIncrement(Double increment) {
if (type == ComponentType.NUMBER
|| type == ComponentType.SLIDER) {
this.increment = increment;
// If no increment is defined, use a default value, as calculated
// by getIncrement.
if (proxy != null)
proxy.setIncrement(getIncrement());
// Update definition as well, so LiveEffect scope restoration works
if (definition != null)
definition.put("increment", increment);
}
}
/**
* The amount of digits after the decimal point. If finer grained values are
* entered, they are rounded to the next allowed number. The default is 3.
*/
public Integer getFractionDigits() {
return fractionDigits;
}
public void setFractionDigits(Integer fractionDigits) {
if (type == ComponentType.NUMBER) {
if (fractionDigits == null)
fractionDigits = 3;
this.fractionDigits = fractionDigits;
if (proxy != null)
proxy.setFractionDigits(fractionDigits);
// Update definition as well, so LiveEffect scope restoration works
if (definition != null)
definition.put("fractionDigits", fractionDigits);
}
}
/**
* The units to be displayed behind the value.
*
* Sample code:
* <code>
* var values = {
* width: 10,
* percentage: 50,
* angle: 180
* }
*
* var components = {
* width: {
* type: 'number', label: 'Width',
* units: 'point'
* },
* percentage: {
* type: 'number', label: 'Percentage',
* units: 'percent'
* },
* angle: {
* type: 'number', label: 'Angle',
* units: 'degree'
* }
* }
*
* var palette = new Palette('Units Examples', components, values);
* </code>
*/
public TextUnits getUnits() {
return units;
}
public void setUnits(TextUnits units) {
if (type == ComponentType.NUMBER) {
if (units == null)
units = TextUnits.NONE;
this.units = units;
if (proxy != null)
proxy.setUnits(units);
// Update definition as well, so LiveEffect scope restoration works
if (definition != null)
definition.put("units", units);
}
}
/**
* Activates little stepping arrows on the side of the input field when set
* to true.
*/
public Boolean getSteppers() {
return steppers;
}
public void setSteppers(Boolean steppers) {
// No support to change this at runtime for now
if (type == ComponentType.NUMBER) {
this.steppers = steppers;
if (proxy != null)
proxy.setSteppers(steppers);
// Update definition as well, so LiveEffect scope restoration works
if (definition != null)
definition.put("steppers", steppers);
}
}
/**
* OptionList is a normal ExtendedArrayList that implements ChangeEmitter so
* changes on it get propagated to the Palette object which is a
* ChangeReceiver automatically through mechanisms in the scripting engine.
*/
private static class OptionList extends ExtendedArrayList<Object> implements
ChangeEmitter {
public OptionList(Object[] options) {
super(options);
}
}
/**
* {@grouptitle List Properties}
*
* The options that the user can choose from in the list component.
*
* Sample code:
* <code>
* var values = {
* fruit: 'Orange'
* };
* var components = {
* fruit: {
* type: 'list', label: 'Fruit',
* options: ['Orange', 'Apple', 'Banana', 'Kiwi']
* }
* };
* var palette = new Palette('List Example', components, values);
* </code>
*/
public com.scratchdisk.list.List<Object> getOptions() {
return new OptionList(options);
}
public void setOptions(ReadOnlyList<Object> options) {
setOptions(options != null ? Lists.toArray(options) : new Object[0]);
}
public void setOptions(Object[] options) {
if (type == ComponentType.LIST) {
// Retrieve current value before setting new options,
// as options is used inside getValue().
Object current = getValue();
this.options = options;
if (proxy != null)
proxy.setOptions(options, current);
// Update definition as well, so LiveEffect scope restoration works
if (definition != null)
definition.put("options", options);
}
}
/**
* @jshide
*/
public Object getOption(int index) {
if (options != null && index >= 0 && index < options.length)
return options[index];
return null;
}
/**
* The index of the selected value in the {@link #getOptions} array.
*
* Sample code:
* <code>
* var values = {
* fruit: 'Apple'
* };
* var components = {
* fruit: {
* type: 'list', label: 'Fruit',
* options: ['Orange', 'Apple', 'Banana', 'Kiwi']
* }
* };
* var palette = new Palette('List Example', components, values);
* print(components.fruit.selectedIndex) // 1
* </code>
*/
public Integer getSelectedIndex() {
return proxy != null ? proxy.getSelectedIndex() : null;
}
public void setSelectedIndex(Integer index) {
// We're changing index, not value, so cause onChange callback for value
setSelectedIndex(index, true);
}
protected void setSelectedIndex(Integer index, boolean callback) {
if (type == ComponentType.LIST && index != null && index >= 0
&& (options == null || index < options.length)) {
if (proxy != null && proxy.setSelectedIndex(index, callback))
onChange(callback);
// Update definition as well, so LiveEffect scope restoration works
if (definition != null)
definition.put("selectedIndex", index);
}
}
/**
* {@grouptitle Text and String Properties}
*
* The width of the text field in average amount of characters.
*/
public Integer getLength() {
return length;
}
public void setLength(Integer length) {
if (type == ComponentType.STRING ||
type == ComponentType.NUMBER) {
this.multiline = false;
this.length = length;
// Clear width and columns when setting length and vice versa.
fullSize = false;
width = null;
columns = null;
updateSize();
// Update definition as well, so LiveEffect scope restoration works
if (definition != null)
definition.put("length", length);
}
}
/**
* The maximum amount of characters that the text field may contain.
*
* Sample code:
* <code>
* var values = {
* name: ''
* };
* var components = {
* name: {
* type: 'string', label: 'Name',
* editable: true, maxLength: 3
* }
* };
* var palette = new Palette('Max Length', components, values);
* values.name = '123456';
* print(values.name) // '123'
* </code>
*/
public Integer getMaxLength() {
return maxLength;
}
public void setMaxLength(Integer maxLength) {
if (type == ComponentType.STRING ||
type == ComponentType.NUMBER) {
this.maxLength = maxLength;
if (proxy != null)
proxy.setMaxLength(maxLength);
// Update definition as well, so LiveEffect scope restoration works
if (definition != null)
definition.put("maxLength", maxLength);
}
}
/**
* Specifies whether the field shows multiple lines of text.
*
* Sample code:
* <code>
* var components = {
* text: {
* type: 'text', label: 'Text',
* value: 'This is a text\nwith multiple lines',
* multiline: true
* }
* };
* var palette = new Palette('Text', components);
* </code>
*/
public Boolean getMultiline() {
return multiline;
}
public void setMultiline(Boolean multiline) {
if (type == ComponentType.STRING ||
type == ComponentType.NUMBER) {
multiline = multiline && type == ComponentType.STRING;
// Set default values for length / columns, rows
if (multiline) {
if (rows == null)
rows = 6;
if (columns == null)
columns = 32;
length = null;
} else {
if (length == null)
length = type == ComponentType.STRING ? 16 : 8;
columns = null;
rows = null;
}
this.multiline = multiline;
// Update definition as well, so LiveEffect scope restoration works
if (definition != null)
definition.put("multiline", multiline);
}
}
/**
* The average amount of characters per line visible in the text area.
*
* Sample code:
* <code>
* var components = {
* text: {
* type: 'string',
* value: 'This is a string component\nwith 6 rows and 30 columns',
* rows: 6, columns: 32
* }
* };
* var palette = new Palette('Text', components);
* </code>
*/
public Integer getColumns() {
return columns;
}
public void setColumns(Integer columns) {
if (type == ComponentType.STRING) {
this.setMultiline(true);
this.columns = columns;
// Clear width and length when setting columns and vice versa.
fullSize = false;
width = null;
length = null;
updateSize();
// Update definition as well, so LiveEffect scope restoration works
if (definition != null)
definition.put("columns", columns);
}
}
/**
* The amount of visible lines of text in the text area. Due to a bug in
* Illustrator's GUI, values below 6 cause problems with scrollbars on
* Macintosh. The default is 6.
*
* Sample code:
* <code>
* var components = {
* text: {
* type: 'string',
* value: 'This is a string component\nwith 6 rows and 30 columns',
* rows: 6, columns: 30
* }
* };
* var palette = new Palette('Text', components);
* </code>
*/
public Integer getRows() {
return rows;
}
public void setRows(Integer rows) {
if (type == ComponentType.STRING) {
this.setMultiline(rows > 1);
this.rows = rows;
// Clear height when setting rows and vice versa.
height = null;
updateSize();
// Update definition as well, so LiveEffect scope restoration works
if (definition != null)
definition.put("rows", rows);
}
}
private Callable onChange;
/**
* {@grouptitle Callback handlers}
*
* The function that is called whenever the value of the component changes.
* The function receives the new value as an argument.
*
* Sample code:
* <code>
* var components = {
* threshold: {
* type: 'number', label: 'Distance Threshold',
* units: 'point',
* onChange: function(value) {
* print('Threshold was changed to', value);
* tool.distanceThreshold = value;
* }
* }
* };
*
* var palette = new Palette('title', components);
* </code>
*/
public Callable getOnChange() {
return onChange;
}
public void setOnChange(Callable onChange) {
this.onChange = onChange;
// Update definition as well, so LiveEffect scope restoration works
if (definition != null)
definition.put("onChange", onChange);
}
protected void onChange(boolean callback) {
Object value = getValue();
// First call onChange on Palette, so values get updated
if (palette != null)
palette.onChange(this, name, value, callback);
// And now call onChange on the item. values will contain the same
// new value now too.
if (callback && onChange != null)
ScriptographerEngine.invoke(onChange, this, value);
}
private Callable onClick;
/**
* The function that is called when a button component is clicked.
*
* Sample code:
* <code>
* var components = {
* button: {
* type: 'button',
* value:'Click Me', label: 'Button',
* onClick: function() {
* print('You clicked me!');
* }
* }
* };
* var palette = new Palette('Button Component', components);
* </code>
*/
public Callable getOnClick() {
return onClick;
}
public void setOnClick(Callable onClick) {
this.onClick = onClick;
// Update definition as well, so LiveEffect scope restoration works
if (definition != null)
definition.put("onClick", onClick);
}
protected void onClick() {
if (onClick != null)
ScriptographerEngine.invoke(onClick, this);
}
private Callable onSelect;
/**
* The function that is called when a popup menu entry is selected.
*
* Sample code:
* <code>
* var components = {
* menuEntry: {
* type: 'menu-entry',
* value:'Select Me',
* onSelect: function() {
* print('You selected me!');
* }
* }
* };
* var values = new Palette('Menu Entry', components);
* </code>
*/
public Callable getOnSelect() {
return onSelect;
}
public void setOnSelect(Callable onSelect) {
this.onSelect = onSelect;
// Update definition as well, so LiveEffect scope restoration works
if (definition != null)
definition.put("onSelect", onSelect);
}
protected void onSelect() {
if (onSelect != null)
ScriptographerEngine.invoke(onSelect, this);
}
@SuppressWarnings("unchecked")
private static Component getComponent(Object object, String name,
Object value) {
if (object != null) {
if (object instanceof Component) {
return (Component) object;
} else if (object instanceof Map) {
try {
ArgumentReader reader =
ScriptEngine.convertToArgumentReader(object);
if (name != null || value != null) {
// We need to create a new map that contains all the
// values from map, but also name and value, so the
// ArgumentReader constructor can read these from there.
// Just clone it.
Map<Object, Object> clone =
new HashMap<Object, Object>((Map) object);
if (name != null)
clone.put("name", name);
if (value != null)
clone.put("value", value);
// Make a new ArgumentReader that inherits its converter
// from the current reader, which was returned by
// ScriptEngine.convertToArgumentReader. So for example
// if map actually is a NativeObject, our new reader will
// inherit all converter functionality from it.
reader = new MapArgumentReader(reader, clone);
}
Component component = new Component(reader);
component.definition = (Map) object;
return component;
} catch (IllegalArgumentException e) {
}
}
}
return null;
}
/**
* @jshide
*/
public static Component[] getComponents(
Map<String, Object> components, Map<String, Object> values) {
ArrayList<Component> comps =
new ArrayList<Component>();
for (Map.Entry<String, Object> entry : components.entrySet()) {
String name = entry.getKey();
Object object = entry.getValue();
Object value = values != null ? values.get(name) : null;
Component component = getComponent(object, name, value);
if (component != null) {
comps.add(component);
// Make sure we're putting the produced components back into the
// passed comonents Map, so they can be accessed directly from
// the script (e.g. to toggle the enabled flag).
components.put(name, component);
}
}
return comps.toArray(new Component[comps.size()]);
}
/**
* @jshide
*/
public static void restoreComponentDefinitions(
Map<String, Object> components) {
for (Map.Entry<String, Object> entry : components.entrySet()) {
String name = entry.getKey();
Object object = entry.getValue();
if (object instanceof Component) {
Component component = (Component) object;
if (component.definition != null)
components.put(name, component.definition);
}
}
}
}