/*
* This is part of Geomajas, a GIS framework, http://www.geomajas.org/.
*
* Copyright 2008-2011 Geosparc nv, http://www.geosparc.com/, Belgium.
*
* The program is available in open source according to the GNU Affero
* General Public License. All contributions in this program are covered
* by the Geomajas Contributors License Agreement. For full licensing
* details, see LICENSE.txt in the project root.
*/
package org.geomajas.gwt.client.widget;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.geomajas.configuration.AssociationAttributeInfo;
import org.geomajas.configuration.AttributeInfo;
import org.geomajas.configuration.FeatureInfo;
import org.geomajas.configuration.PrimitiveAttributeInfo;
import org.geomajas.configuration.PrimitiveType;
import org.geomajas.gwt.client.util.StringUtil;
import org.geomajas.layer.feature.Attribute;
import org.geomajas.layer.feature.attribute.AssociationValue;
import org.geomajas.layer.feature.attribute.ManyToOneAttribute;
import org.geomajas.layer.feature.attribute.OneToManyAttribute;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.smartgwt.client.types.Alignment;
import com.smartgwt.client.types.ListGridFieldType;
import com.smartgwt.client.widgets.Img;
import com.smartgwt.client.widgets.grid.ListGrid;
import com.smartgwt.client.widgets.grid.ListGridField;
import com.smartgwt.client.widgets.grid.ListGridFieldIfFunction;
import com.smartgwt.client.widgets.grid.ListGridRecord;
import com.smartgwt.client.widgets.grid.events.CellClickEvent;
import com.smartgwt.client.widgets.grid.events.CellClickHandler;
import com.smartgwt.client.widgets.grid.events.CellOverEvent;
import com.smartgwt.client.widgets.grid.events.CellOverHandler;
/**
* List grid that shows association attributes as records.
*
* @author Jan De Moerloose
*/
public class AttributeListGrid extends ListGrid {
/**
* Feature information
*/
private FeatureInfo featureInfo;
/**
* Show all attributes (true) or only the 'identifying' attributes (false)?
*/
private boolean allAttributesDisplayed;
/**
* Show the feature's ID in the table. This is false by default, and should not really be necessary.
*/
private boolean idInTable;
/**
* When hovering over image attributes, should they be shown in floating panels or not?
*/
private boolean showImageAttributeOnHover;
/**
* List of values in the grid
*/
private Map<AssociationValue, String> idByValue = new HashMap<AssociationValue, String>();
private int newRows;
private static final String NEW_PREFIX = "_new_";
private static final String VALUE_HOLDER_RECORD_ATTRIBUTE = "_value_";
private static final String ID_NAME = "attributeId";
// -------------------------------------------------------------------------
// Constructors:
// -------------------------------------------------------------------------
public AttributeListGrid(FeatureInfo featureInfo) {
this.featureInfo = featureInfo;
setShowEmptyMessage(true);
setIdInTable(false);
}
// -------------------------------------------------------------------------
// Class specific methods:
// -------------------------------------------------------------------------
/**
* Empty the grid, thereby removing all rows. It does not clear the header though.
*/
public void clearValues() {
setData(new ListGridRecord[] {});
idByValue.clear();
newRows = 0;
}
/**
* Adds a new value to the list.
*
* @param associationValue The value to be added to the list.
* @return Returns true in case of update, and false if the value is already in the list or the value is null
*/
public boolean saveOrUpdateValue(AssociationValue associationValue) {
if (idByValue.containsKey(associationValue)) {
updateValue(associationValue);
setData(getRecords());
return true;
} else {
// Calculate id
Object idObject = associationValue.getId().getValue();
String id;
if (idObject != null) {
id = idObject.toString();
} else {
// fake id
id = NEW_PREFIX + "." + (newRows++);
}
// Feature checks out, add it to the grid:
ListGridRecord record = new ListGridRecord();
record.setAttribute(ID_NAME, id);
updateAttributes(associationValue, record);
idByValue.put(associationValue, id);
addData(record);
return false;
}
}
/**
* Removes a value to the list.
*
* @param associationValue The value to be removed from the list.
* @return Returns true in case of successful removal.
*/
public boolean deleteValue(AssociationValue associationValue) {
if (idByValue.containsKey(associationValue)) {
for (ListGridRecord record : getRecords()) {
if (record.getAttributeAsObject(VALUE_HOLDER_RECORD_ATTRIBUTE) == associationValue) {
removeData(record);
idByValue.remove(associationValue);
return true;
}
}
}
return false;
}
/**
* Returns true if the specified value has been added to the list.
*
* @param associationValue value to check
* @return true if added, false otherwise.
*/
public boolean containsValue(AssociationValue associationValue) {
return idByValue.containsKey(associationValue);
}
private void updateAttributes(AssociationValue associationValue, ListGridRecord record) {
for (AttributeInfo attributeInfo : featureInfo.getAttributes()) {
Attribute<?> attr = associationValue.getAllAttributes().get(attributeInfo.getName());
if (attr.isPrimitive()) {
Object value = attr.getValue();
if (value != null) {
if (value instanceof Boolean) {
record.setAttribute(attributeInfo.getName(), (Boolean) value); // "false" != false
} else {
record.setAttribute(attributeInfo.getName(), value.toString());
}
} else {
record.setAttribute(attributeInfo.getName(), "");
}
} else {
AssociationAttributeInfo associationAttributeInfo = (AssociationAttributeInfo) attributeInfo;
String displayName = associationAttributeInfo.getFeature().getDisplayAttributeName();
Object value = attr.getValue();
if (value != null) {
if (displayName == null) {
displayName = associationAttributeInfo.getFeature().getAttributes().get(0).getName();
}
switch (associationAttributeInfo.getType()) {
case MANY_TO_ONE:
ManyToOneAttribute manyToOneAttribute = (ManyToOneAttribute) attr;
Object displayValue = manyToOneAttribute.getValue().getAllAttributes().get(displayName)
.getValue();
if (displayValue != null) {
record.setAttribute(attributeInfo.getName(), displayValue.toString());
} else {
record.setAttribute(attributeInfo.getName(), "");
}
break;
case ONE_TO_MANY:
OneToManyAttribute oneToManyAttribute = (OneToManyAttribute) attr;
List<String> values = new ArrayList<String>();
for (AssociationValue assoc : oneToManyAttribute.getValue()) {
Object o = assoc.getAllAttributes().get(displayName).getValue();
if (o != null) {
values.add(o.toString());
}
}
record.setAttribute(attributeInfo.getName(), StringUtil.join(values, ","));
break;
}
} else {
record.setAttribute(attributeInfo.getName(), "");
}
}
}
record.setAttribute(VALUE_HOLDER_RECORD_ATTRIBUTE, associationValue);
}
public AssociationValue getSelectedValue() {
ListGridRecord record = getSelectedRecord();
if (record != null) {
return (AssociationValue) record.getAttributeAsObject(VALUE_HOLDER_RECORD_ATTRIBUTE);
} else {
return null;
}
}
public void selectValue(AssociationValue value) {
for (ListGridRecord record : getRecords()) {
if (record.getAttributeAsObject(VALUE_HOLDER_RECORD_ATTRIBUTE) == value) {
selectRecord(record);
}
}
}
public void updateValue(AssociationValue value) {
for (ListGridRecord record : getRecords()) {
if (record.getAttributeAsObject(VALUE_HOLDER_RECORD_ATTRIBUTE) == value) {
updateAttributes(value, record);
}
}
}
public List<AssociationValue> getValues() {
List<AssociationValue> values = new ArrayList<AssociationValue>();
for (ListGridRecord record : getRecords()) {
values.add((AssociationValue) record.getAttributeAsObject(VALUE_HOLDER_RECORD_ATTRIBUTE));
}
return values;
}
/**
* Is the grid currently displaying all attributes, instead of only the 'identifying' ones?
*
* @return are all attributes displayed
*/
public boolean isAllAttributesDisplayed() {
return allAttributesDisplayed;
}
/**
* Determine if all attributes of a layer should be shown, or only the 'identifying' ones. Changing this value will
* not change the layout of the grid. So set this value in advance.
*
* @param allAttributesDisplayed should all attributes be displayed
*/
public void setAllAttributesDisplayed(boolean allAttributesDisplayed) {
this.allAttributesDisplayed = allAttributesDisplayed;
updateFields();
}
/**
* Return whether or not the feature's ID's are currently drawn in the grid.
*
* @return is feature id displayed
*/
public boolean isIdInTable() {
return idInTable;
}
/**
* Determine whether or not the feature's ID should be displayed in the grid. This method will immediately update
* the entire grid.
*
* @param idInTable should id be displayed
*/
public void setIdInTable(boolean idInTable) {
this.idInTable = idInTable;
updateFields();
}
public boolean isShowImageAttributeOnHover() {
return showImageAttributeOnHover;
}
public void setShowImageAttributeOnHover(boolean showImageAttributeOnHover) {
this.showImageAttributeOnHover = showImageAttributeOnHover;
}
// -------------------------------------------------------------------------
// Private methods:
// -------------------------------------------------------------------------
/**
* Actually create or update the fields.
*/
private void updateFields() {
if (featureInfo != null) {
// Create a header field for each attribute definition:
List<ListGridField> fields = new ArrayList<ListGridField>();
if (idInTable) {
ListGridField gridField = new ListGridField(ID_NAME, "ID");
gridField.setAlign(Alignment.LEFT);
gridField.setCanEdit(false);
fields.add(gridField);
}
for (AttributeInfo attributeInfo : featureInfo.getAttributes()) {
if (!attributeInfo.isHidden() && (attributeInfo.isIdentifying() || allAttributesDisplayed)) {
fields.add(createAttributeGridField(attributeInfo));
}
}
setFields(fields.toArray(new ListGridField[fields.size()]));
setCanResizeFields(true);
}
}
/**
* Create a single field definition from a attribute definition.
*
* @param attributeInfo attribute info
* @return field for grid
*/
private ListGridField createAttributeGridField(final AttributeInfo attributeInfo) {
ListGridField gridField = new ListGridField(attributeInfo.getName(), attributeInfo.getLabel());
gridField.setAlign(Alignment.LEFT);
gridField.setCanEdit(false);
gridField.setShowIfCondition(new IdentifyingListGridFieldIfFunction(attributeInfo.isIdentifying()));
if (attributeInfo instanceof PrimitiveAttributeInfo) {
PrimitiveAttributeInfo info = (PrimitiveAttributeInfo) attributeInfo;
if (info.getType().equals(PrimitiveType.BOOLEAN)) {
gridField.setType(ListGridFieldType.BOOLEAN);
} else if (info.getType().equals(PrimitiveType.STRING)) {
gridField.setType(ListGridFieldType.TEXT);
} else if (info.getType().equals(PrimitiveType.DATE)) {
gridField.setType(ListGridFieldType.DATE);
} else if (info.getType().equals(PrimitiveType.SHORT)) {
gridField.setType(ListGridFieldType.INTEGER);
} else if (info.getType().equals(PrimitiveType.INTEGER)) {
gridField.setType(ListGridFieldType.INTEGER);
} else if (info.getType().equals(PrimitiveType.LONG)) {
gridField.setType(ListGridFieldType.INTEGER);
} else if (info.getType().equals(PrimitiveType.FLOAT)) {
gridField.setType(ListGridFieldType.FLOAT);
} else if (info.getType().equals(PrimitiveType.DOUBLE)) {
gridField.setType(ListGridFieldType.FLOAT);
} else if (info.getType().equals(PrimitiveType.IMGURL)) {
gridField.setType(ListGridFieldType.IMAGE);
if (showImageAttributeOnHover) {
addCellOverHandler(new ImageCellHandler(attributeInfo));
}
} else if (info.getType().equals(PrimitiveType.CURRENCY)) {
gridField.setType(ListGridFieldType.TEXT);
} else if (info.getType().equals(PrimitiveType.URL)) {
gridField.setType(ListGridFieldType.TEXT);
gridField.setAttribute("text-decoration", "underline");
addCellClickHandler(new UrlCellHandler(attributeInfo));
}
} else if (attributeInfo instanceof AssociationAttributeInfo) {
gridField.setType(ListGridFieldType.TEXT);
}
return gridField;
}
/**
* Private class, implementing the {@link ListGridFieldIfFunction} interface that determines the visibility of a
* grid field, based upon the attribute definition's identifying value, and the {@link FeatureListGrid}'s
* <code>allAttributesDisplayed</code> value.
*
* @author Pieter De Graef
*/
private class IdentifyingListGridFieldIfFunction implements ListGridFieldIfFunction {
private boolean identifying;
public IdentifyingListGridFieldIfFunction(boolean identifying) {
this.identifying = identifying;
}
public boolean execute(ListGrid grid, ListGridField field, int fieldNum) {
if (identifying) {
return true;
}
if (grid instanceof AttributeListGrid) {
AttributeListGrid table = (AttributeListGrid) grid;
if (table.isAllAttributesDisplayed()) {
return true;
}
}
return false;
}
}
/**
* Display the actual image of an image-cell when the mouse goes over it. The image self-destructs after 3 seconds.
*
* @author Pieter De Graef
*/
private class ImageCellHandler implements CellOverHandler {
private Timer killTimer;
private Img img;
private int row = -1;
private AttributeInfo attributeInfo;
ImageCellHandler(AttributeInfo attributeInfo) {
this.attributeInfo = attributeInfo;
}
public void onCellOver(CellOverEvent event) {
ListGridField gridField = AttributeListGrid.this.getField(event.getColNum());
if (gridField.getName().equals(attributeInfo.getName())) {
ListGridRecord record = event.getRecord();
String value = record.getAttribute(attributeInfo.getName());
if (event.getRowNum() != row) {
if (img != null) {
cleanup();
}
img = new Img(value);
img.setMaxWidth(300);
img.setMaxHeight(300);
img.setKeepInParentRect(true);
img.setShowEdges(true);
img.setLeft(AttributeListGrid.this.getAbsoluteLeft() + 10);
img.setTop(AttributeListGrid.this.getAbsoluteTop() + 10);
img.draw();
killTimer = new Timer() {
public void run() {
img.destroy();
}
};
killTimer.schedule(Math.round(3000));
row = event.getRowNum();
}
}
}
private void cleanup() {
killTimer.cancel();
img.destroy();
img = null;
}
}
/**
* Open the url in a separate browser window
*
* @author Jan De Moerloose
*/
private class UrlCellHandler implements CellClickHandler {
private AttributeInfo attributeInfo;
UrlCellHandler(AttributeInfo attributeInfo) {
this.attributeInfo = attributeInfo;
}
public void onCellClick(CellClickEvent event) {
ListGridField gridField = AttributeListGrid.this.getField(event.getColNum());
if (gridField.getName().equals(attributeInfo.getName())) {
ListGridRecord record = event.getRecord();
String value = record.getAttribute(attributeInfo.getName());
Window.open(value, "urlWindow", null);
}
}
}
}