/*
Copyright 2008-2010 Gephi
Authors : Mathieu Bastian <mathieu.bastian@gephi.org>, Mathieu Jacomy, Julian Bilcke, Eduardo Ramos
Website : http://www.gephi.org
This file is part of Gephi.
Gephi is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
Gephi is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Gephi. If not, see <http://www.gnu.org/licenses/>.
*/
package org.gephi.ui.tools.plugin.edit;
import java.awt.Color;
import java.util.EnumSet;
import org.gephi.data.attributes.api.AttributeColumn;
import org.gephi.data.attributes.api.AttributeRow;
import org.gephi.data.attributes.api.AttributeType;
import org.gephi.data.attributes.api.AttributeValue;
import org.gephi.data.attributes.type.DynamicType;
import org.gephi.datalab.api.AttributeColumnsController;
import org.gephi.dynamic.api.DynamicController;
import org.gephi.dynamic.api.DynamicModel;
import org.gephi.dynamic.api.DynamicModel.TimeFormat;
import org.gephi.graph.api.Node;
import org.gephi.graph.api.NodeData;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.PropertySupport;
import org.openide.nodes.Sheet;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
/**
* PropertySheet that allows to edit one or more nodes.
* If multiple node edition mode is used at first all values will be shown as blank
* but will change with the editions and all nodes will be set the values that the user inputs.
* @author Mathieu Bastian
*/
public class EditNodes extends AbstractNode {
private PropertySet[] propertySets;
private Node[] nodes;
private boolean multipleNodes;
private TimeFormat currentTimeFormat=TimeFormat.DOUBLE;
/**
* Single node edition mode will always be enabled with this single node constructor
* @param node
*/
public EditNodes(Node node) {
super(Children.LEAF);
this.nodes = new Node[]{node};
setName(node.getNodeData().getLabel());
multipleNodes = false;
}
/**
* If the nodes array has more than one element, multiple nodes edition mode will be enabled.
* @param nodes
*/
public EditNodes(Node[] nodes) {
super(Children.LEAF);
this.nodes = nodes;
multipleNodes = nodes.length > 1;
if (multipleNodes) {
setName(NbBundle.getMessage(EditNodes.class, "EditNodes.multiple.elements"));
} else {
setName(nodes[0].getNodeData().getLabel());
}
}
@Override
public PropertySet[] getPropertySets() {
propertySets = new PropertySet[]{prepareNodesProperties(), prepareNodesAttributes()};
return propertySets;
}
/**
* Prepare set of attributes of the node(s).
* @return Set of these attributes
*/
private Sheet.Set prepareNodesAttributes() {
try {
DynamicModel dm=Lookup.getDefault().lookup(DynamicController.class).getModel();
if(dm!=null){
currentTimeFormat=dm.getTimeFormat();
}
AttributeColumnsController ac = Lookup.getDefault().lookup(AttributeColumnsController.class);
Sheet.Set set = new Sheet.Set();
set.setName("attributes");
if (nodes.length > 1) {
set.setDisplayName(NbBundle.getMessage(EditNodes.class, "EditNodes.attributes.text.multiple"));
} else {
set.setDisplayName(NbBundle.getMessage(EditNodes.class, "EditNodes.attributes.text", nodes[0].getNodeData().getLabel()));
}
AttributeRow row = (AttributeRow) nodes[0].getNodeData().getAttributes();
AttributeValueWrapper wrap;
for (AttributeValue value : row.getValues()) {
if (multipleNodes) {
wrap = new MultipleNodesAttributeValueWrapper(nodes, value.getColumn());
} else {
wrap = new SingleNodeAttributeValueWrapper(row, value.getColumn());
}
AttributeType type = value.getColumn().getType();
Property p;
if (ac.canChangeColumnData(value.getColumn())) {
//Editable column, provide "set" method:
if (!NotSupportedTypes.contains(type)) {//The AttributeType can be edited by default:
p = new PropertySupport.Reflection(wrap, type.getType(), "getValue" + type.getType().getSimpleName(), "setValue" + type.getType().getSimpleName());
} else {//Use the AttributeType as String:
p = new PropertySupport.Reflection(wrap, String.class, "getValueAsString", "setValueAsString");
}
} else {
//Not editable column, do not provide "set" method:
if (!NotSupportedTypes.contains(type)) {//The AttributeType can be edited by default:
p = new PropertySupport.Reflection(wrap, type.getType(), "getValue" + type.getType().getSimpleName(), null);
} else {//Use the AttributeType as String:
p = new PropertySupport.Reflection(wrap, String.class, "getValueAsString", null);
}
}
p.setDisplayName(value.getColumn().getTitle());
p.setName(value.getColumn().getId());
set.put(p);
}
return set;
} catch (Exception ex) {
Exceptions.printStackTrace(ex);
return null;
}
}
/**
* Prepare set of editable properties of the node(s): size, position.
* @return Set of these properties
*/
private Sheet.Set prepareNodesProperties() {
try {
if (multipleNodes) {
MultipleNodesPropertiesWrapper nodesWrapper = new MultipleNodesPropertiesWrapper(nodes);
Sheet.Set set = new Sheet.Set();
set.setName("properties");
set.setDisplayName(NbBundle.getMessage(EditNodes.class, "EditNodes.properties.text.multiple"));
Property p;
//Size:
p = new PropertySupport.Reflection(nodesWrapper, Float.class, "getNodesSize", "setNodesSize");
p.setDisplayName(NbBundle.getMessage(EditNodes.class, "EditNodes.size.text"));
p.setName("size");
set.put(p);
//All position coordinates:
set.put(buildMultipleNodesGeneralPositionProperty(nodesWrapper, "x"));
set.put(buildMultipleNodesGeneralPositionProperty(nodesWrapper, "y"));
set.put(buildMultipleNodesGeneralPositionProperty(nodesWrapper, "z"));
//Color:
p = new PropertySupport.Reflection(nodesWrapper, Color.class, "getNodesColor", "setNodesColor");
p.setDisplayName(NbBundle.getMessage(EditNodes.class, "EditNodes.color.text"));
p.setName("color");
set.put(p);
return set;
} else {
Node node = nodes[0];
Sheet.Set set = new Sheet.Set();
set.setName("properties");
set.setDisplayName(NbBundle.getMessage(EditNodes.class, "EditNodes.properties.text", node.getNodeData().getLabel()));
NodeData data = node.getNodeData();
Property p;
//Size:
p = new PropertySupport.Reflection(data, Float.TYPE, "getSize", "setSize");
p.setDisplayName(NbBundle.getMessage(EditNodes.class, "EditNodes.size.text"));
p.setName("size");
set.put(p);
//All position coordinates:
set.put(buildGeneralPositionProperty(data, "x"));
set.put(buildGeneralPositionProperty(data, "y"));
set.put(buildGeneralPositionProperty(data, "z"));
//Color:
SingleNodePropertiesWrapper nodeWrapper = new SingleNodePropertiesWrapper(node);
p = new PropertySupport.Reflection(nodeWrapper, Color.class, "getNodeColor", "setNodeColor");
p.setDisplayName(NbBundle.getMessage(EditNodes.class, "EditNodes.color.text"));
p.setName("color");
set.put(p);
return set;
}
} catch (Exception ex) {
Exceptions.printStackTrace(ex);
return null;
}
}
public class SingleNodePropertiesWrapper {
private Node node;
public SingleNodePropertiesWrapper(Node node) {
this.node = node;
}
public Color getNodeColor() {
NodeData data = node.getNodeData();
return new Color(data.r(), data.g(), data.b(), data.alpha());
}
public void setNodeColor(Color c) {
if (c != null) {
NodeData data = node.getNodeData();
data.setR(c.getRed() / 255f);
data.setG(c.getGreen() / 255f);
data.setB(c.getBlue() / 255f);
data.setAlpha(c.getAlpha() / 255f);
}
}
}
public class MultipleNodesPropertiesWrapper {
Node[] nodes;
public MultipleNodesPropertiesWrapper(Node[] nodes) {
this.nodes = nodes;
}
//Methods and fields for multiple nodes editing:
private Float nodesX = null;
private Float nodesY = null;
private Float nodesZ = null;
private Float nodesSize = null;
private Color nodesColor = null;
public Float getNodesX() {
return nodesX;
}
public void setNodesX(Float x) {
nodesX = x;
for (Node node : nodes) {
node.getNodeData().setX(x);
}
}
public Float getNodesY() {
return nodesY;
}
public void setNodesY(Float y) {
nodesY = y;
for (Node node : nodes) {
node.getNodeData().setY(y);
}
}
public Float getNodesZ() {
return nodesZ;
}
public void setNodesZ(Float z) {
nodesZ = z;
for (Node node : nodes) {
node.getNodeData().setZ(z);
}
}
public Color getNodesColor() {
return nodesColor;
}
public void setNodesColor(Color c) {
if (c != null) {
nodesColor = c;
NodeData data;
for (Node node : nodes) {
data = node.getNodeData();
data.setR(c.getRed() / 255f);
data.setG(c.getGreen() / 255f);
data.setB(c.getBlue() / 255f);
data.setAlpha(c.getAlpha() / 255f);
}
}
}
public Float getNodesSize() {
return nodesSize;
}
public void setNodesSize(Float size) {
nodesSize = size;
for (Node node : nodes) {
node.getNodeData().setSize(size);
}
}
}
/**
* Used to build property for each position coordinate (x,y,z) in the same way.
* @return Property for that coordinate
*/
private Property buildGeneralPositionProperty(NodeData data, String coordinate) throws NoSuchMethodException {
//Position:
Property p = new PropertySupport.Reflection(data, Float.TYPE, coordinate, "set" + coordinate.toUpperCase());
p.setDisplayName(NbBundle.getMessage(EditNodes.class, "EditNodes.position.text", coordinate));
p.setName(coordinate);
return p;
}
/**
* Used to build property for each position coordinate of various nodes (x,y,z) in the same way.
* @return Property for that coordinate
*/
private Property buildMultipleNodesGeneralPositionProperty(MultipleNodesPropertiesWrapper nodesWrapper, String coordinate) throws NoSuchMethodException {
//Position:
Property p = new PropertySupport.Reflection(nodesWrapper, Float.class, "getNodes" + coordinate.toUpperCase(), "setNodes" + coordinate.toUpperCase());
p.setDisplayName(NbBundle.getMessage(EditNodes.class, "EditNodes.position.text", coordinate));
p.setName(coordinate);
return p;
}
/**
* These AttributeTypes are not supported by default by netbeans property editor.
* We will use attributes of these types as Strings and parse them.
*/
private static EnumSet<AttributeType> NotSupportedTypes = EnumSet.of(
AttributeType.BIGINTEGER,
AttributeType.BIGDECIMAL,
AttributeType.LIST_BIGDECIMAL,
AttributeType.LIST_BIGINTEGER,
AttributeType.LIST_BOOLEAN,
AttributeType.LIST_BYTE,
AttributeType.LIST_CHARACTER,
AttributeType.LIST_DOUBLE,
AttributeType.LIST_FLOAT,
AttributeType.LIST_INTEGER,
AttributeType.LIST_LONG,
AttributeType.LIST_SHORT,
AttributeType.LIST_STRING,
AttributeType.TIME_INTERVAL,
AttributeType.DYNAMIC_BIGDECIMAL,
AttributeType.DYNAMIC_BIGINTEGER,
AttributeType.DYNAMIC_BOOLEAN,
AttributeType.DYNAMIC_BYTE,
AttributeType.DYNAMIC_CHAR,
AttributeType.DYNAMIC_DOUBLE,
AttributeType.DYNAMIC_FLOAT,
AttributeType.DYNAMIC_INT,
AttributeType.DYNAMIC_LONG,
AttributeType.DYNAMIC_SHORT,
AttributeType.DYNAMIC_STRING);
public interface AttributeValueWrapper {
public Byte getValueByte();
public void setValueByte(Byte object);
public Short getValueShort();
public void setValueShort(Short object);
public Character getValueCharacter();
public void setValueCharacter(Character object);
public String getValueString();
public void setValueString(String object);
public Double getValueDouble();
public void setValueDouble(Double object);
public Float getValueFloat();
public void setValueFloat(Float object);
public Integer getValueInteger();
public void setValueInteger(Integer object);
public Boolean getValueBoolean();
public void setValueBoolean(Boolean object);
public Long getValueLong();
public void setValueLong(Long object);
/****** Other types are not supported by property editors by default so they are used and parsed as Strings ******/
public String getValueAsString();
public void setValueAsString(String value);
}
public class SingleNodeAttributeValueWrapper implements AttributeValueWrapper {
private AttributeRow row;
private AttributeColumn column;
public SingleNodeAttributeValueWrapper(AttributeRow row, AttributeColumn column) {
this.row = row;
this.column = column;
}
private String convertToStringIfNotNull() {
Object value = row.getValue(column.getIndex());
if (value != null) {
if (value instanceof DynamicType) {
return ((DynamicType) value).toString(currentTimeFormat==TimeFormat.DOUBLE);
} else {
return value.toString();
}
} else {
return null;
}
}
public Byte getValueByte() {
return (Byte) row.getValue(column.getIndex());
}
public void setValueByte(Byte object) {
row.setValue(column.getIndex(), object);
}
public Short getValueShort() {
return (Short) row.getValue(column.getIndex());
}
public void setValueShort(Short object) {
row.setValue(column.getIndex(), object);
}
public Character getValueCharacter() {
return (Character) row.getValue(column.getIndex());
}
public void setValueCharacter(Character object) {
row.setValue(column.getIndex(), object);
}
public String getValueString() {
return (String) row.getValue(column.getIndex());
}
public void setValueString(String object) {
row.setValue(column.getIndex(), object);
}
public Double getValueDouble() {
return (Double) row.getValue(column.getIndex());
}
public void setValueDouble(Double object) {
row.setValue(column.getIndex(), object);
}
public Float getValueFloat() {
return (Float) row.getValue(column.getIndex());
}
public void setValueFloat(Float object) {
row.setValue(column.getIndex(), object);
}
public Integer getValueInteger() {
return (Integer) row.getValue(column.getIndex());
}
public void setValueInteger(Integer object) {
row.setValue(column.getIndex(), object);
}
public Boolean getValueBoolean() {
return (Boolean) row.getValue(column.getIndex());
}
public void setValueBoolean(Boolean object) {
row.setValue(column.getIndex(), object);
}
public Long getValueLong() {
return (Long) row.getValue(column.getIndex());
}
public void setValueLong(Long object) {
row.setValue(column.getIndex(), object);
}
public String getValueAsString() {
return convertToStringIfNotNull();
}
public void setValueAsString(String value) {
row.setValue(column.getIndex(), column.getType().parse(value));
}
}
public class MultipleNodesAttributeValueWrapper implements AttributeValueWrapper {
private Node[] nodes;
private AttributeColumn column;
private Object value;
public MultipleNodesAttributeValueWrapper(Node[] nodes, AttributeColumn column) {
this.nodes = nodes;
this.column = column;
this.value = null;
}
private String convertToStringIfNotNull() {
if (value != null) {
if (value instanceof DynamicType) {
return ((DynamicType) value).toString(currentTimeFormat==TimeFormat.DOUBLE);
} else {
return value.toString();
}
} else {
return null;
}
}
private void setValueToAllNodes(Object object) {
this.value = object;
for (Node node : nodes) {
node.getNodeData().getAttributes().setValue(column.getIndex(), value);
}
}
public Byte getValueByte() {
return (Byte) value;
}
public void setValueByte(Byte object) {
setValueToAllNodes(object);
}
public Short getValueShort() {
return (Short) value;
}
public void setValueShort(Short object) {
setValueToAllNodes(object);
}
public Character getValueCharacter() {
return (Character) value;
}
public void setValueCharacter(Character object) {
setValueToAllNodes(object);
}
public String getValueString() {
return (String) value;
}
public void setValueString(String object) {
setValueToAllNodes(object);
}
public Double getValueDouble() {
return (Double) value;
}
public void setValueDouble(Double object) {
setValueToAllNodes(object);
}
public Float getValueFloat() {
return (Float) value;
}
public void setValueFloat(Float object) {
setValueToAllNodes(object);
}
public Integer getValueInteger() {
return (Integer) value;
}
public void setValueInteger(Integer object) {
setValueToAllNodes(object);
}
public Boolean getValueBoolean() {
return (Boolean) value;
}
public void setValueBoolean(Boolean object) {
setValueToAllNodes(object);
}
public Long getValueLong() {
return (Long) value;
}
public void setValueLong(Long object) {
setValueToAllNodes(object);
}
public String getValueAsString() {
return convertToStringIfNotNull();
}
public void setValueAsString(String value) {
setValueToAllNodes(column.getType().parse(value));
}
}
}