/*
* 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.attribute;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.geomajas.configuration.AttributeInfo;
import org.geomajas.configuration.PrimitiveAttributeInfo;
import org.geomajas.configuration.PrimitiveType;
import org.geomajas.gwt.client.i18n.I18nProvider;
import org.geomajas.gwt.client.map.layer.VectorLayer;
import org.geomajas.layer.feature.SearchCriterion;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.smartgwt.client.widgets.Canvas;
import com.smartgwt.client.widgets.form.DynamicForm;
import com.smartgwt.client.widgets.form.fields.BlurbItem;
import com.smartgwt.client.widgets.form.fields.CanvasItem;
import com.smartgwt.client.widgets.form.fields.FormItem;
import com.smartgwt.client.widgets.form.fields.SelectItem;
import com.smartgwt.client.widgets.form.fields.events.ChangedEvent;
import com.smartgwt.client.widgets.form.fields.events.ChangedHandler;
/**
* <p>
* A canvas that represents the dynamic form for a single attribute search criterion. Since such a criterion exists of 3
* items (attribute-name, operator, value), this form will also display 3 <code>FormItem</code>s for the user to fill.
* The first is an attribute select, where the user select on what attribute to search. The second is an operator select
* that automatically updates it's list of available operators, in accordance to the selected attribute. The third
* <code>FormItem</code> will display an {@link AttributeFormItem}. This is a <code>FormItem</code> that is tailored to
* handle any kind of attribute definition, by displaying the correct delegate <code>FormItem</code>.
* </p>
* <p>
* On top of just displaying a form to create a criterion, it is also possible to retrieve the actual
* {@link SearchCriterion} object.
* </p>
*
* @author Pieter De Graef
*/
public class AttributeCriterionPane extends Canvas {
private static final String CQL_WILDCARD = "%";
private static final String CQL_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
private DynamicForm form;
private SelectItem attributeSelect;
private SelectItem operatorSelect;
private AttributeFormItem valueItem;
private VectorLayer layer;
private AttributeInfo selectedAttribute;
// -------------------------------------------------------------------------
// Constructors:
// -------------------------------------------------------------------------
/**
* Create a search criterion pane, for the given vector layer. The layer is required, as it's list of attribute
* definitions are a vital part of the search criteria.
*
* @param layer layer to create criterion for
*/
public AttributeCriterionPane(VectorLayer layer) {
super();
this.layer = layer;
buildUI();
}
// -------------------------------------------------------------------------
// Public methods:
// -------------------------------------------------------------------------
/**
* Validate the value that the user filled in. If it is not valid, don't ask for the SearchCriterion.
*
* @return true when user entered invalid value
*/
public boolean hasErrors() {
return valueItem.getForm().hasErrors();
}
/**
* Return the actual search criterion object, or null if not all fields have been properly filled.
*
* @return search criterion
*/
public SearchCriterion getSearchCriterion() {
Object operator = operatorSelect.getValue();
Object value = valueItem.getValue();
if (selectedAttribute != null && operator != null) {
String operatorString = getOperatorCodeFromLabel(operator.toString());
String valueString = "";
if (value != null) {
valueString = value.toString();
}
// CQL does not recognize "contains", so change to "like":
if ("contains".equals(operatorString)) {
operatorString = "LIKE";
valueString = CQL_WILDCARD + valueString + CQL_WILDCARD;
}
// If value was null, and no "contains" operator, return null:
if (valueString == null || valueString.length() == 0) {
return null;
}
if (selectedAttribute instanceof PrimitiveAttributeInfo) {
PrimitiveAttributeInfo attr = (PrimitiveAttributeInfo) selectedAttribute;
if (attr.getType().equals(PrimitiveType.STRING) || attr.getType().equals(PrimitiveType.IMGURL)
|| attr.getType().equals(PrimitiveType.URL)) {
// In case of a string, add quotes:
valueString = "'" + valueString + "'";
} else if (attr.getType().equals(PrimitiveType.DATE)) {
if (value instanceof Date) {
// In case of a date, parse correctly for CQL: 2006-11-30T01:30:00Z
DateTimeFormat format = DateTimeFormat.getFormat(CQL_TIME_FORMAT);
if ("=".equals(operatorString)) {
// Date equals not supported by CQL, so we use the DURING operator instead:
operatorString = "DURING";
Date date1 = (Date) value;
Date date2 = new Date(date1.getTime() + 86400000); // total milliseconds in a day
valueString = format.format(date1) + "/" + format.format(date2);
} else {
// Simply format the date:
valueString = format.format((Date) value);
}
}
}
}
// Now create the criterion:
SearchCriterion criterion = new SearchCriterion();
criterion.setAttributeName(selectedAttribute.getName());
criterion.setOperator(operatorString);
criterion.setValue(valueString);
return criterion;
}
return null;
}
/**
* Static method that return an array of available operator labels for the given attribute definition. An operator
* label is something like "is equal to". Don't confuse this with the operator code (something like "=").
*
* @param attributeInfo
* The attribute definition for which to return possible operators.
*/
public static String[] getOperatorsForAttributeType(AttributeInfo attributeInfo) {
if (attributeInfo != null && attributeInfo instanceof PrimitiveAttributeInfo) {
PrimitiveAttributeInfo primitive = (PrimitiveAttributeInfo) attributeInfo;
switch (primitive.getType()) {
case SHORT:
case INTEGER:
case LONG:
case FLOAT:
case DOUBLE:
case CURRENCY:
return new String[] { I18nProvider.getSearch().operatorEquals(),
I18nProvider.getSearch().operatorNotEquals(), I18nProvider.getSearch().operatorST(),
I18nProvider.getSearch().operatorSE(), I18nProvider.getSearch().operatorBT(),
I18nProvider.getSearch().operatorBE() };
case DATE:
return new String[] { I18nProvider.getSearch().operatorEquals(),
I18nProvider.getSearch().operatorBefore(), I18nProvider.getSearch().operatorAfter() };
case STRING:
case URL:
case IMGURL:
return new String[] { I18nProvider.getSearch().operatorContains(),
I18nProvider.getSearch().operatorEquals(), I18nProvider.getSearch().operatorNotEquals() };
case BOOLEAN:
default:
return new String[] { I18nProvider.getSearch().operatorEquals(),
I18nProvider.getSearch().operatorNotEquals() };
}
}
return new String[] { I18nProvider.getSearch().operatorEquals(), I18nProvider.getSearch().operatorNotEquals() };
}
/**
* Return the operator code from an operator label. The difference is this: an operator label is a string like
* "is equal to", while it's code is "=".
*
* @param label
* The operator label.
*/
public static String getOperatorCodeFromLabel(String label) {
if (label != null) {
if (I18nProvider.getSearch().operatorEquals().equals(label)) {
return "=";
} else if (I18nProvider.getSearch().operatorNotEquals().equals(label)) {
return "<>";
} else if (I18nProvider.getSearch().operatorST().equals(label)) {
return "<";
} else if (I18nProvider.getSearch().operatorSE().equals(label)) {
return "<=";
} else if (I18nProvider.getSearch().operatorBT().equals(label)) {
return ">";
} else if (I18nProvider.getSearch().operatorBE().equals(label)) {
return ">=";
} else if (I18nProvider.getSearch().operatorContains().equals(label)) {
return "contains";
} else if (I18nProvider.getSearch().operatorBefore().equals(label)) {
return "BEFORE";
} else if (I18nProvider.getSearch().operatorAfter().equals(label)) {
return "AFTER";
}
}
return label;
}
// -------------------------------------------------------------------------
// Private methods:
// -------------------------------------------------------------------------
private void buildUI() {
// Attribute select:
attributeSelect = new SelectItem("attributeItem");
attributeSelect.setWidth(140);
attributeSelect.setShowTitle(false);
List<String> labels = new ArrayList<String>();
for (AttributeInfo attribute : layer.getLayerInfo().getFeatureInfo().getAttributes()) {
labels.add(attribute.getLabel());
}
attributeSelect.setValueMap(labels.toArray(new String[labels.size()]));
attributeSelect.setHint(I18nProvider.getSearch().gridChooseAttribute());
attributeSelect.setShowHintInField(true);
attributeSelect.setValidateOnChange(true);
attributeSelect.setShowErrorStyle(true);
attributeSelect.setRequired(true);
// Operator select:
operatorSelect = new SelectItem("operatorItem");
operatorSelect.setDisabled(true);
operatorSelect.setWidth(140);
operatorSelect.setShowTitle(false);
operatorSelect.setValidateOnChange(true);
operatorSelect.setShowErrorStyle(true);
operatorSelect.setRequired(true);
// Value form item:
valueItem = new AttributeFormItem("valueItem");
valueItem.setShowTitle(false);
valueItem.setDisabled(true);
valueItem.setWidth(150);
// Mechanisms:
attributeSelect.addChangedHandler(new ChangedHandler() {
public void onChanged(ChangedEvent event) {
selectedAttribute = getSelectedAttribute();
if (selectedAttribute != null) {
// Adjust operator value map and enabled:
operatorSelect.setDisabled(false);
String[] operators = getOperatorsForAttributeType(selectedAttribute);
operatorSelect.setValueMap(operators);
operatorSelect.setValue(operators[0]);
// Adjust value form item and enable:
valueItem.setAttributeInfo(selectedAttribute.getName(), selectedAttribute);
valueItem.setDisabled(false);
valueItem.setWidth(form.getWidth() - 290);
}
}
});
// Finalize:
form = new DynamicForm();
form.setNumCols(6);
form.setHeight(26);
form.setWidth100();
form.setFields(attributeSelect, operatorSelect, valueItem);
addChild(form);
}
private AttributeInfo getSelectedAttribute() {
Object value = attributeSelect.getValue();
if (value != null) {
for (AttributeInfo attributeInfo : layer.getLayerInfo().getFeatureInfo().getAttributes()) {
if (attributeInfo.getLabel().equals((String) value)) {
return attributeInfo;
}
}
}
return null;
}
/**
* <p>
* Editable form item implementation that can edit any kind of feature attribute. It starts by using a default
* <code>TextItem</code> as <code>FormItem</code> representative. Every time the <code>setAttributeInfo</code>
* method is called, a new internal <code>FormItem</code> will be created and shown in the place of the
* <code>TextItem</code>. In order to create the correct representation for each kind of attribute, a
* {@link AttributeFormFieldRegistry} is used.
* </p>
*
* @author Pieter De Graef
*/
private class AttributeFormItem extends CanvasItem {
private DynamicForm form;
private FormItem formItem;
// -------------------------------------------------------------------------
// Constructors:
// -------------------------------------------------------------------------
/**
* Create the form item with the given name. An internal form will already be created, and in that form a
* <code>TextItem</code> will be shown.
*
* @param name form item name
*/
public AttributeFormItem(String name) {
super(name);
form = new DynamicForm();
form.setHeight(26);
formItem = new BlurbItem();
formItem.setShowTitle(false);
formItem.setValue("...................");
form.setFields(formItem);
setCanvas(form);
}
// -------------------------------------------------------------------------
// Public methods:
// -------------------------------------------------------------------------
/**
* Set a new attribute information object. This will alter the internal form, to display a new
* <code>FormItem</code> for the new type of attribute. In order to accomplish this, a
* {@link AttributeFormFieldRegistry} is used.
*
* @param attributeName The new attribute name, possibly recursive.
* @param attributeInfo The new attribute definition for whom to display the correct <code>FormItem</code>.
*/
public void setAttributeInfo(String attributeName, AttributeInfo attributeInfo) {
formItem = AttributeFormFieldRegistry.createFormItem(attributeInfo, new DefaultAttributeProvider(layer
.getLayerInfo().getServerLayerId()));
if (formItem != null) {
formItem.setDisabled(false);
formItem.setShowTitle(false);
form.setFields(formItem);
form.setDisabled(false);
form.setCanFocus(true);
}
}
/** Set a new width on this instance. Delegates to the internal form. */
public void setWidth(int width) {
form.setWidth(width);
if (formItem != null) {
formItem.setWidth(width);
}
}
/** Get the current value form the internal <code>FormItem</code>. */
public Object getValue() {
if (formItem != null) {
return formItem.getValue();
}
return null;
}
/** Return the form for the inner FormItem. On the returned form, validation will work. */
public DynamicForm getForm() {
return form;
}
}
}