/*
* 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 2, 2010.
*/
package com.scriptographer.adm.ui;
import java.util.LinkedHashMap;
import com.scratchdisk.script.ScriptEngine;
import com.scratchdisk.util.ConversionUtils;
import com.scriptographer.adm.Border;
import com.scriptographer.adm.Button;
import com.scriptographer.adm.CheckBox;
import com.scriptographer.adm.ColorButton;
import com.scriptographer.adm.Dialog;
import com.scriptographer.adm.FontPopupList;
import com.scriptographer.adm.FontPopupListOption;
import com.scriptographer.adm.Frame;
import com.scriptographer.adm.FrameStyle;
import com.scriptographer.adm.Item;
import com.scriptographer.adm.ItemGroup;
import com.scriptographer.adm.ListEntry;
import com.scriptographer.adm.PopupList;
import com.scriptographer.adm.PopupMenu;
import com.scriptographer.adm.Size;
import com.scriptographer.adm.Slider;
import com.scriptographer.adm.SpinEdit;
import com.scriptographer.adm.TextEdit;
import com.scriptographer.adm.TextEditItem;
import com.scriptographer.adm.TextOption;
import com.scriptographer.adm.TextPane;
import com.scriptographer.adm.TextValueItem;
import com.scriptographer.adm.ToggleItem;
import com.scriptographer.adm.ValueItem;
import com.scriptographer.adm.layout.TableLayout;
import com.scriptographer.ai.Color;
import com.scriptographer.ai.FontWeight;
import com.scriptographer.ai.RGBColor;
import com.scriptographer.ui.Component;
import com.scriptographer.ui.ComponentProxy;
import com.scriptographer.ui.ComponentType;
import com.scriptographer.ui.TextUnits;
/**
* @author lehni
*
*/
public class AdmComponentProxy extends ComponentProxy {
// Used for scaling slider values
private double factor = 1;
private Integer selectedIndex;
// Native items / entries
private Item item;
private ListEntry entry;
public AdmComponentProxy(Component component) {
super(component);
// Set scaling factor for Slider to allow fractional digits
if (component.getType() == ComponentType.SLIDER)
factor = 1000;
}
protected Item createItem(Dialog dialog) {
// Item:
item = null;
ComponentType type = component.getType();
switch (type) {
case NUMBER: {
Boolean steppers = component.getSteppers();
if (steppers != null && steppers) {
item = new SpinEdit(dialog) {
protected void onChange() {
AdmComponentProxy.this.onChange(true);
}
};
} else {
TextEditItem textItem = new TextEdit(dialog) {
protected void onChange() {
AdmComponentProxy.this.onChange(true);
}
};
textItem.setAllowMath(true);
textItem.setAllowUnits(true);
item = textItem;
}
}
break;
case STRING: {
Boolean multiline = component.getMultiline();
TextOption[] options = multiline != null && multiline
? new TextOption[] { TextOption.MULTILINE }
: null;
item = new TextEdit(dialog, options) {
protected void onChange() {
AdmComponentProxy.this.onChange(true);
}
};
}
break;
case TEXT: {
item = new TextPane(dialog);
// Space a bit more to the top, to compensate the descender space.
item.setMargin(new Border(1, 0, 0, 0));
}
break;
case RULER: {
// If the ruler has a label, add it to the left of it, using an
// ItemGroup and a TableLayout. The ItemGroup needs to be created
// before the frame, otherwise layouting issues arise...
// Ideally this should be resolved, but as ADM is on its way out,
// just work around it for now.
ItemGroup group;
String label = component.getLabel();
if (label != null && !label.equals("")) {
group = new ItemGroup(dialog);
TextPane labelItem = new TextPane(dialog);
labelItem.setText(label);
// Use 3 rows, so the center one with the ruler gets centered,
// then span the label across all three.
double[][] sizes = {
new double[] { TableLayout.PREFERRED, TableLayout.FILL },
new double[] { TableLayout.FILL, TableLayout.PREFERRED,
TableLayout.FILL }
};
group.setLayout(new TableLayout(sizes));
group.add(labelItem, "0, 0, 0, 2");
group.setMarginTop(2);
} else {
group = null;
}
Frame frame = new Frame(dialog);
frame.setStyle(FrameStyle.SUNKEN);
// Margin needs to be set before changing size...
// TODO: Fix this in UI package?
int top = label != null ? 2 : 4, bottom = 4;
frame.setMargin(top, 0, bottom, 0);
// Margin is included inside size, not added. This is different
// to how things works with CSS...
// TODO: Fix this in UI package?
frame.setHeight(2 + top + bottom);
// Now finish setting up the layout group and label
if (group != null) {
group.add(frame, "1, 1, full, center");
item = group;
} else {
item = frame;
}
}
break;
case SLIDER: {
item = new Slider(dialog) {
protected void onChange() {
AdmComponentProxy.this.onChange(true);
}
};
}
break;
case BOOLEAN: {
item = new CheckBox(dialog) {
protected void onClick() {
AdmComponentProxy.this.onChange(true);
}
};
}
break;
case LIST: {
item = new PopupList(dialog, true) {
protected void onChange() {
selectedIndex = this.getSelectedEntry().getIndex();
AdmComponentProxy.this.onChange(true);
}
};
}
break;
case BUTTON: {
item = new Button(dialog) {
protected void onClick() {
AdmComponentProxy.this.onClick();
}
};
}
break;
case COLOR: {
item = new ColorButton(dialog) {
protected void onClick() {
AdmComponentProxy.this.onChange(true);
}
};
}
break;
case FONT: {
item = new FontPopupList(dialog, new FontPopupListOption[] {
FontPopupListOption.EDITABLE,
FontPopupListOption.VERTICAL
}) {
protected void onChange() {
AdmComponentProxy.this.onChange(true);
}
};
}
break;
case MENU_ENTRY:
case MENU_SEPARATOR: {
PopupMenu menu = dialog.getPopupMenu();
if (menu != null) {
entry = new ListEntry(menu) {
protected void onSelect() {
AdmComponentProxy.this.onSelect();
}
};
if (type == ComponentType.MENU_SEPARATOR)
entry.setSeparator(true);
}
}
break;
}
initialize();
return item;
}
protected int addToContent(Dialog dialog,
LinkedHashMap<String, com.scriptographer.adm.Component> content,
int column, int row) {
Item valueItem = createItem(dialog);
String label = component.getLabel();
boolean isRuler = component.getType() == ComponentType.RULER;
boolean hasLabel = !isRuler && label != null && !"".equals(label);
if (hasLabel) {
TextPane labelItem = new TextPane(dialog);
labelItem.setText(label + ":");
// Adjust top margin of label to reflect the native margin
// in the value item.
Item marginItem = valueItem;
// If this is an item group, use the first item in it instead
// This is only needed for FontPopupList so far.
if (marginItem instanceof ItemGroup)
marginItem = (Item) ((ItemGroup) marginItem).getContent().get(0);
Border margin = marginItem.getVisualMargin();
// Also take into account any margins the component might have set
if (valueItem != marginItem)
margin = margin.add(valueItem.getMargin());
labelItem.setMargin(margin.top + 3, 4, 0, 0);
content.put(column + ", " + row + ", right, top", labelItem);
}
boolean fullSize = component.getFullSize();
String justification = isRuler || component.getFullSize()
? "full, center" : "left, center";
content.put(isRuler || !hasLabel && fullSize
? column + ", " + row + ", " + (column + 1) + ", " + row + ", "
+ justification
: (column + 1) + ", " + row + ", " + justification,
valueItem);
return row + 1;
}
protected Size getSize() {
if (item != null) {
Size size;
Boolean multiline = component.getMultiline();
Integer columns = component.getColumns();
Integer rows = component.getRows();
if (multiline != null && multiline && columns != null
&& rows != null) {
// Base width on an average wide character, such as H
size = item.getTextSize("H");
size = new Size(
size.width * columns + 8,
size.height * rows + 8
);
} else {
// Use preferred size instead of best size for ruler, as we want
// to take into account items of which the size was already set.
size = component.getType() == ComponentType.RULER
? item.getPreferredSize()
: item.getBestSize();
Integer length = component.getLength();
if (length != null)
size.width = item.getTextSize("H").width * length;
}
Integer width = component.getWidth();
if (width != null)
size.width = width;
Integer height = component.getHeight();
if (height != null)
size.height = height;
return size;
}
return null;
}
public void updateSize() {
if (item != null) {
Size size = getSize();
if (size != null && !size.equals(item.getSize())) {
item.setSize(size);
onSizeChanged();
}
}
}
public Object getValue() {
if (item == null)
return component.getDefaultValue();
switch (component.getType()) {
case STRING:
case TEXT:
return ((TextValueItem) item).getText();
case BUTTON:
return ((Button) item).getText();
case NUMBER:
return ((ValueItem) item).getValue();
case SLIDER:
double value = ((ValueItem) item).getValue() / factor;
Double inc = component.getIncrement();
if (inc != null) {
double pre = value;
value = Math.round(value / inc) * inc;
if (pre != value)
((ValueItem) item).setValue((float) (value * factor));
}
return value;
case BOOLEAN:
return ((ToggleItem) item).isChecked();
case LIST:
ListEntry selected = ((PopupList) item).getSelectedEntry();
if (selected != null)
return component.getOption(selected.getIndex());
break;
case COLOR:
return ((ColorButton) item).getColor();
case FONT:
return ((FontPopupList) item).getFontWeight();
}
return null;
}
public boolean setValue(Object value) {
if (item == null && entry == null)
return false;
boolean callOnChange = true;
switch (component.getType()) {
case STRING:
case TEXT: {
String text = ConversionUtils.toString(value);
Integer maxLength = component.getMaxLength();
if (maxLength != null && text != null
&& text.length() > maxLength)
text = text.substring(0, maxLength);
((TextValueItem) item).setText(text);
updateSize();
}
break;
case BUTTON: {
((Button) item).setText(ConversionUtils.toString(value));
updateSize();
}
break;
case NUMBER:
case SLIDER: {
((ValueItem) item).setValue(
(float) (ConversionUtils.toDouble(value) * factor));
}
break;
case BOOLEAN: {
((CheckBox) item).setChecked(ConversionUtils.toBoolean(value));
}
break;
case LIST: {
PopupList list = (PopupList) item;
int index = selectedIndex != null ? selectedIndex : 0;
for (int i = 0, l = list.size(); i < l; i++) {
Object option = component.getOption(i);
if (ConversionUtils.equals(value, option)) {
index = i;
break;
}
}
setSelectedIndex(index, false);
// No need to call onChange, as setSelectionIndex already does:
callOnChange = false;
}
break;
case COLOR: {
Color color = ScriptEngine.convertToJava(value, Color.class);
if (color == null)
color = new RGBColor(0, 0, 0);
((ColorButton) item).setColor(color);
}
break;
case FONT: {
FontWeight weight = ScriptEngine.convertToJava(value,
FontWeight.class);
if (weight != null)
((FontPopupList) item).setFontWeight(weight);
}
break;
case MENU_ENTRY: {
entry.setText(ConversionUtils.toString(value));
}
break;
}
// Update palette's value object too
if (callOnChange)
onChange(false);
return true;
}
public Integer getSelectedIndex() {
return selectedIndex;
}
public boolean setSelectedIndex(Integer index, boolean callback) {
selectedIndex = index;
if (item == null)
return false;
// TODO: Handle index == null ?
PopupList list = (PopupList) item;
list.setSelectedEntry(list.get(index));
return true;
}
public boolean setRange(Double min, Double max) {
if (item == null)
return false;
((ValueItem) item).setRange(
(float) (min != null ? min * factor : Integer.MIN_VALUE),
(float) (max != null ? max * factor : Integer.MAX_VALUE));
return true;
}
public void setIncrement(double increment) {
if (item != null)
((ValueItem) item).setIncrements((float) (increment * factor));
}
public void setFractionDigits(Integer fractionDigits) {
if (item != null)
((TextEditItem) item).setFractionDigits(fractionDigits);
}
public void setUnits(TextUnits units) {
if (item != null) {
TextEditItem textItem = (TextEditItem) item;
textItem.setUnits(units);
textItem.setShowUnits(units != TextUnits.NONE);
}
}
public void setSteppers(Boolean steppers) {
// No support to change this at runtime for now
}
public void setVisible(boolean visible) {
if (item != null)
item.setVisible(visible);
}
public void setEnabled(boolean enabled) {
if (item != null)
item.setEnabled(enabled);
}
public void setMaxLength(Integer maxLength) {
if (item != null)
((TextEditItem) item).setMaxLength(maxLength != null
? maxLength : -1);
}
public void setOptions(Object[] options, Object current) {
if (item == null)
return;
PopupList list = (PopupList) item;
list.removeAll();
if (options != null && options.length > 0) {
int index = selectedIndex != null ? selectedIndex : 0;
for (int i = 0; i < options.length; i++) {
Object option = options[i];
if (option.equals(current))
index = i;
ListEntry entry = null;
if (option instanceof ListEntry) {
entry = (ListEntry) option;
entry = list.add(entry);
} else {
entry = new ListEntry(list);
// Simplify names for art items
String name = option instanceof com.scriptographer.ai.Item
? ((com.scriptographer.ai.Item) option).getName()
: null;
entry.setText(name != null ? name : option.toString());
}
}
if (index < 0)
index = 0;
else if (index >= options.length)
index = options.length - 1;
// We're changing options, not value, so cause onChange
// callback for value
setSelectedIndex(index, true);
updateSize();
}
}
}