/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is NetBeans. The Initial Developer of the Original
* Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun
* Microsystems, Inc. All Rights Reserved.
*/
package org.openide.explorer.propertysheet;
import java.awt.Component;
import javax.swing.event.ChangeListener;
import javax.swing.event.ChangeEvent;
import java.beans.*;
import java.util.*;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import org.openide.ErrorManager;
import org.openide.nodes.*;
import org.openide.util.Utilities;
import org.openide.util.NbBundle;
import org.openide.util.WeakListener;
import org.openide.util.actions.SystemAction;
import org.openide.util.datatransfer.NewType;
/**
* This class encapsulates working with indexed properties.
*/
class IndexedPropertyEditor extends Object implements ExPropertyEditor {
// -----------------------------------------------------------------------------
// Private variables
private Object[] array;
private PropertyEnv env;
private PropertyChangeSupport propertySupport = new PropertyChangeSupport (this);
private Node.IndexedProperty indexedProperty = null;
private IndexedEditorPanel currentEditorPanel;
// -----------------------------------------------------------------------------
// init
public IndexedPropertyEditor() {
}
// -----------------------------------------------------------------------------
// ExPropertyEditor implementation
public void attachEnv (PropertyEnv env) {
this.env = env;
FeatureDescriptor details = env.getFeatureDescriptor();
if (details instanceof Node.IndexedProperty) {
indexedProperty = (Node.IndexedProperty)details;
} else {
throw new IllegalStateException("This is not an array: " + details); // NOI18N
}
}
// -----------------------------------------------------------------------------
// PropertyEditor implementation
public void setValue(Object value) {
if (value == null) {
array = null;
firePropertyChange();
return;
}
if (!value.getClass ().isArray ()) {
throw new IllegalArgumentException(env != null ? "Property which value is not an array " + env.getFeatureDescriptor().getName() : "Unknown property - not attached yet."); //NOI18N
}
if (value.getClass().getComponentType().isPrimitive()) {
array = Utilities.toObjectArray (value);
} else {
array = (Object[])Array.newInstance(
value.getClass().getComponentType(),
((Object[])value).length);
System.arraycopy(value, 0, array, 0, array.length);
}
firePropertyChange();
}
public Object getValue() {
if (array == null) {
return null;
}
if (indexedProperty.getElementType().isPrimitive()) {
return Utilities.toPrimitiveArray(array);
}
return array;
}
public boolean isPaintable() {
return false;
}
public void paintValue(java.awt.Graphics gfx, java.awt.Rectangle box) {
}
public String getJavaInitializationString(int index) {
if (array[index] == null) return "null"; // NOI18N
try {
indexedProperty.getIndexedPropertyEditor().setValue(array [index]);
return indexedProperty.getIndexedPropertyEditor().getJavaInitializationString();
} catch (NullPointerException e) {
return "null"; // NOI18N
}
}
public String getJavaInitializationString() {
if (array == null) return ""; // NOI18N
StringBuffer buf = new StringBuffer ("new "); // NOI18N
buf.append (indexedProperty.getElementType().getName ());
// empty array
if (array.length == 0) {
buf.append ("[0]"); // NOI18N
} else
// non-empty array
{
buf.append ("[] {\n\t"); // NOI18N
for (int i = 0; i < array.length; i++) {
try {
indexedProperty.getIndexedPropertyEditor().setValue (array[i]);
buf.append (indexedProperty.getIndexedPropertyEditor().getJavaInitializationString());
} catch (NullPointerException e) {
buf.append ("null"); // NOI18N
}
if (i != array.length - 1)
buf.append (",\n\t"); // NOI18N
else
buf.append ("\n"); // NOI18N
}
buf.append ("}"); // NOI18N
}
return buf.toString ();
}
public String getAsText() {
if (array == null) return "null"; // NOI18N
StringBuffer buf = new StringBuffer ("["); // NOI18N
PropertyEditor p = null;
if (indexedProperty != null) {
p = indexedProperty.getIndexedPropertyEditor();
}
for (int i = 0; i < array.length; i++) {
if (p != null) {
p.setValue (array[i]);
// bugfix #27361, append property's value as text instead of java initialization string
buf.append (p.getAsText());
} else {
buf.append ("null"); // NOI18N
}
if (i != array.length - 1)
buf.append (", "); // NOI18N
}
buf.append ("]"); // NOI18N
return buf.toString ();
}
public void setAsText(String text) throws java.lang.IllegalArgumentException {
if (text.equals("null")) { // NOI18N
setValue(null);
return;
}
if (text.equals("[]")) { // NOI18N
setValue(Array.newInstance(indexedProperty.getElementType(), 0));
return;
}
int i1 = text.indexOf('[');
if ((i1 <0) || (i1+1 >= text.length()) ) {
i1 = 0;
} else {
i1++;
}
int i2 = text.lastIndexOf(']');
if (i2 < 0) {
i2 = text.length();
}
if ((i2 < i1) || (i2 > text.length()) ) {
return;
} else {
}
try {
text = text.substring(i1, i2);
StringTokenizer tok = new StringTokenizer(text, ","); // NOI18N
java.util.List list = new LinkedList();
while (tok.hasMoreTokens()) {
String s = tok.nextToken();
PropertyEditor p = indexedProperty.getIndexedPropertyEditor();
p.setAsText(s.trim());
list.add(p.getValue());
}
Object [] a = list.toArray((Object[])Array.newInstance(getConvertedType(), list.size()));
setValue(a);
} catch (Exception x) {
IllegalArgumentException iae = new IllegalArgumentException();
ErrorManager.getDefault().annotate(iae, ErrorManager.USER,
getString("EXC_ErrorInIndexedSetter"),
getString("EXC_ErrorInIndexedSetter"), x, new java.util.Date());
throw iae;
}
}
public String[] getTags() {
return null;
}
public Component getCustomEditor() {
if (array == null) {
array = (Object[])Array.newInstance(getConvertedType(), 0);
firePropertyChange();
}
Node dummy = new DisplayIndexedNode(0);
// beware - this will function only if the DisplayIndexedNode has
// one property on the first sheet and the property is of type
// ValueProp
Node.Property prop = dummy.getPropertySets()[0].getProperties()[0];
Node.Property []np = new Node.Property[] { prop };
currentEditorPanel = new IndexedEditorPanel(createRootNode(), np);
return currentEditorPanel;
}
public boolean supportsCustomEditor() {
return true;
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
propertySupport.addPropertyChangeListener (listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
propertySupport.removePropertyChangeListener (listener);
}
// other methods ........................................................................
private Node createRootNode() {
DisplayIndexedNode[]n = new DisplayIndexedNode[array.length];
for (int i = 0; i < n.length; i++) {
n[i] = new DisplayIndexedNode(i);
}
MyIndexedRootNode idr = new MyIndexedRootNode(n);
Index ind = (Index)idr.getCookie(Index.class);
for (int i = 0; i < n.length; i++) {
ind.addChangeListener(WeakListener.change(n[i], ind));
}
return idr;
}
/**
* Converts indexedProperty.getElementType() class
*/
private Class getConvertedType() {
Class type = indexedProperty.getElementType();
if (type.isPrimitive()) {
type = Utilities.getObjectType(type);
}
return type;
}
void firePropertyChange () {
propertySupport.firePropertyChange ("value", null, null); // NOI18N
}
private static String getString(String key) {
return NbBundle.getBundle(IndexedPropertyEditor.class).getString(key);
}
/**
* Makes an attempt to create new value of the property. Usefull
* especially when enlarging the array.
*/
private Object defaultValue() {
Object value = null;
if (indexedProperty.getElementType().isPrimitive()) {
if (getConvertedType().equals(Integer.class)) {
value = new Integer(0);
}
if (getConvertedType().equals(Boolean.class)) {
value = Boolean.FALSE;
}
if (getConvertedType().equals(Byte.class)) {
value = new Byte((byte)0);
}
if (getConvertedType().equals(Character.class)) {
value = new Character('\u0000');
}
if (getConvertedType().equals(Double.class)) {
value = new Double(0d);
}
if (getConvertedType().equals(Float.class)) {
value = new Float(0f);
}
if (getConvertedType().equals(Long.class)) {
value = new Long(0l);
}
if (getConvertedType().equals(Short.class)) {
value = new Short((short)0);
}
} else {
try {
value = getConvertedType().newInstance();
} catch (Exception x) {
// ignore any exception - if this fails just
// leave null as the value
}
}
return value;
}
/**
* Node displayed in TableSheetView. Encapsulates a value of
* one element in the array.
*/
class DisplayIndexedNode extends AbstractNode implements ChangeListener {
private int index;
public DisplayIndexedNode(int index) {
super (Children.LEAF);
this.index = index;
setName(Integer.toString(index));
setDisplayName(Integer.toString(index));
}
protected SystemAction[] createActions() {
try {
return new SystemAction[] {
SystemAction.get(Class.forName("org.openide.actions.MoveUpAction")), // NOI18N
SystemAction.get(Class.forName("org.openide.actions.MoveDownAction")) // NOI18N
};
} catch (ClassNotFoundException cnfe) {
// silently ignore in case we are in standalone library
// without these actions
}
return null;
}
// Create a property sheet:
protected Sheet createSheet () {
Sheet sheet = super.createSheet ();
Sheet.Set props = sheet.get (Sheet.PROPERTIES);
if (props == null) {
props = Sheet.createPropertiesSet ();
sheet.put (props);
}
props.put (new ValueProp());
return sheet;
}
/**
* Destroy doesn't do regular destroy here
* but changes the whole array and recreates
* the node hierarchy. Does *not* even call super.destroy()!
*/
public void destroy () throws java.io.IOException {
Object []newArray = (Object[])Array.newInstance(getConvertedType(), array.length-1);
System.arraycopy(array, 0, newArray, 0, index);
System.arraycopy(array, index+1, newArray, index, array.length-index-1);
// throw away the old array!
array = newArray;
IndexedPropertyEditor.this.firePropertyChange();
if (currentEditorPanel != null) {
currentEditorPanel.getExplorerManager().setRootContext(
createRootNode());
}
}
/**
* Listens on parent node. Assumes that parent node is
* Indexed node !
*/
public void stateChanged(ChangeEvent e) {
Node parent = getParentNode();
Index i = (Index)parent.getCookie(Index.class);
if (i != null) {
int currentIndex = i.indexOf(this);
if (currentIndex != index) {
if (currentIndex > index) {
// first swap the values in array
// the condition is there in order react by swapping
// the values only on one of the two nodes
Object tmp = array[index];
array[index] = array[currentIndex];
array[currentIndex] = tmp;
}
// update the index variable and change the name
index = currentIndex;
DisplayIndexedNode.this.firePropertyChange(null, null, null);
setDisplayName(Integer.toString(index));
IndexedPropertyEditor.this.firePropertyChange();
}
}
}
private class ValueProp extends PropertySupport
{
public ValueProp () {
super(
indexedProperty.getName(),
indexedProperty.getElementType(),
indexedProperty.getDisplayName(),
indexedProperty.getShortDescription(),
indexedProperty.canRead(),
indexedProperty.canWrite());
}
public Object getValue () {
if (index < array.length) {
return array[index];
} else {
return null;
}
}
public void setValue (Object value) throws IllegalArgumentException,
IllegalAccessException, InvocationTargetException
{
Object oldVal = array[index];
array[index] = value;
DisplayIndexedNode.this.firePropertyChange (this.getName(), oldVal, value);
IndexedPropertyEditor.this.firePropertyChange();
}
public PropertyEditor getPropertyEditor ()
{
return indexedProperty.getIndexedPropertyEditor();
}
}
}
/** Root node in the TableSheetView. This node should
* not be displayed.
*/
private class MyIndexedRootNode extends IndexedNode {
public MyIndexedRootNode(Node[] ch) {
getChildren().add(ch);
setName("IndexedRoot"); // NOI18N
setDisplayName(NbBundle.getBundle(IndexedPropertyEditor.class).getString("CTL_Index"));
}
public NewType[] getNewTypes() {
NewType nt = new NewType() {
public void create() {
if (array != null) {
Object []newArray = (Object[])Array.newInstance(getConvertedType(), array.length+1);
System.arraycopy(array, 0, newArray, 0, array.length);
// throw away the old array!
array = newArray;
array[array.length-1] = defaultValue();
} else {
// throw away the old array!
array = (Object[])Array.newInstance(getConvertedType(), 1);
array[0] = defaultValue();
}
IndexedPropertyEditor.this.firePropertyChange();
DisplayIndexedNode din = new DisplayIndexedNode(array.length-1);
getChildren().add(new Node[] { din });
Index i = (Index)getCookie(Index.class);
i.addChangeListener(WeakListener.change(din, i));
}
};
return new NewType[] { nt };
}
}
}