/*
* This is part of Geomajas, a GIS framework, http://www.geomajas.org/.
*
* Copyright 2008-2015 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.widget.searchandfilter.client.widget.search;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import org.geomajas.annotation.Api;
import org.geomajas.gwt.client.gfx.paintable.GfxGeometry;
import org.geomajas.gwt.client.gfx.style.ShapeStyle;
import org.geomajas.gwt.client.map.layer.VectorLayer;
import org.geomajas.gwt.client.spatial.geometry.Geometry;
import org.geomajas.gwt.client.util.GeometryConverter;
import org.geomajas.gwt.client.util.WidgetLayout;
import org.geomajas.gwt.client.widget.MapWidget;
import org.geomajas.widget.searchandfilter.client.SearchAndFilterMessages;
import org.geomajas.widget.searchandfilter.client.util.GsfLayout;
import org.geomajas.widget.searchandfilter.client.widget.search.SearchWidget.SaveRequestEvent;
import org.geomajas.widget.searchandfilter.client.widget.search.SearchWidget.SaveRequestHandler;
import org.geomajas.widget.searchandfilter.search.dto.AndCriterion;
import org.geomajas.widget.searchandfilter.search.dto.Criterion;
import org.geomajas.widget.searchandfilter.search.dto.GeometryCriterion;
import org.geomajas.widget.searchandfilter.search.dto.OrCriterion;
import com.google.gwt.core.client.GWT;
import com.smartgwt.client.types.Alignment;
import com.smartgwt.client.types.ListGridFieldType;
import com.smartgwt.client.types.Overflow;
import com.smartgwt.client.util.SC;
import com.smartgwt.client.widgets.Canvas;
import com.smartgwt.client.widgets.form.DynamicForm;
import com.smartgwt.client.widgets.form.fields.RadioGroupItem;
import com.smartgwt.client.widgets.form.fields.SelectItem;
import com.smartgwt.client.widgets.form.fields.events.ChangeEvent;
import com.smartgwt.client.widgets.form.fields.events.ChangeHandler;
import com.smartgwt.client.widgets.grid.HoverCustomizer;
import com.smartgwt.client.widgets.grid.ListGrid;
import com.smartgwt.client.widgets.grid.ListGridField;
import com.smartgwt.client.widgets.grid.ListGridRecord;
import com.smartgwt.client.widgets.grid.events.RecordClickEvent;
import com.smartgwt.client.widgets.grid.events.RecordClickHandler;
import com.smartgwt.client.widgets.layout.HLayout;
import com.smartgwt.client.widgets.layout.VLayout;
/**
* Panel for editing search criteria for a combined search. This handles the combination of the criteria. The actual
* criteria are handled by other {@link AbstractSearchPanel} implementations.
*
* @author Kristof Heirwegh
* @since 1.0.0
*/
@Api
public class CombinedSearchPanel extends AbstractSearchPanel {
private static final String GEOMETRY_IDENTIFIER = "CombinedSearchPanelGeometry";
private final SearchAndFilterMessages messages = GWT.create(SearchAndFilterMessages.class);
private DynamicForm operatorForm;
private RadioGroupItem type;
private ListGrid searchItems;
private SelectItem selectSearch;
private List<SearchWidget> searchWidgets = new ArrayList<SearchWidget>();
private boolean alwaysShowElements = true;
private boolean hideButtonsWhenAdding;
private List<GfxGeometry> geometries = new ArrayList<GfxGeometry>();
private final ShapeStyle selectionStyle;
/**
* Create a combined search panel for a specific map widget.
*
* @param mapWidget map widget to search for
*/
@Api
public CombinedSearchPanel(final MapWidget mapWidget) {
super(mapWidget);
selectionStyle = new ShapeStyle();
selectionStyle.setFillColor("#FFFF00");
selectionStyle.setFillOpacity(0.3f);
selectionStyle.setStrokeColor("#B45F04");
selectionStyle.setStrokeOpacity(0.9f);
selectionStyle.setStrokeWidth(2f);
VLayout layout = new VLayout(10);
layout.setWidth(GsfLayout.combinedSearchPanelWidth);
layout.setHeight(1);
// -- operator type --
operatorForm = new DynamicForm();
operatorForm.setWidth100();
operatorForm.setAlign(Alignment.LEFT);
type = new RadioGroupItem();
type.setTitle(messages.combinedSearchWidgetType());
type.setValueMap(messages.combinedSearchWidgetOr(), messages.combinedSearchWidgetAnd());
type.setValue(messages.combinedSearchWidgetAnd());
type.setVertical(false);
operatorForm.setFields(type);
layout.addMember(operatorForm);
// -- criteria grid --
searchItems = new ListGrid();
searchItems.setWidth100();
searchItems.setHeight(1);
searchItems.setShowAllRecords(true);
searchItems.setBodyOverflow(Overflow.VISIBLE);
searchItems.setOverflow(Overflow.VISIBLE);
searchItems.setLeaveScrollbarGap(false);
searchItems.setShowHeader(false);
searchItems.setShowRecordComponents(true);
searchItems.setShowRecordComponentsByCell(true);
ListGridField nameField = new ListGridField(SearchListRecord.NAME_FIELD);
nameField.setType(ListGridFieldType.TEXT);
ListGridField removeField = new ListGridField(SearchListRecord.REMOVE_FIELD);
removeField.setType(ListGridFieldType.IMAGE);
removeField.setShowHover(true);
removeField.setWidth(24);
removeField.setHoverCustomizer(new HoverCustomizer() {
public String hoverHTML(Object value, ListGridRecord record, int rowNum, int colNum) {
return messages.combinedSearchWidgetRemove();
}
});
removeField.setWidth(20);
removeField.addRecordClickHandler(new RecordClickHandler() {
public void onRecordClick(RecordClickEvent event) {
searchItems.removeData(event.getRecord());
updateDisplay();
}
});
searchItems.setFields(nameField, removeField);
HLayout addBar = new HLayout(0);
addBar.setAlign(Alignment.LEFT);
addBar.setHeight(20);
DynamicForm searchForm = new DynamicForm();
selectSearch = new SelectItem();
selectSearch.setTitle("");
selectSearch.setWidth(200);
selectSearch.setTooltip(messages.combinedSearchWidgetSelectSearch());
selectSearch.addChangeHandler(new ChangeHandler() {
public void onChange(ChangeEvent event) {
if (null != event.getValue() && !"".equals(event.getValue())) {
if (hideButtonsWhenAdding) {
Canvas parent = getParentElement();
if (parent instanceof SearchWidget) {
((SearchWidget) parent).hideSearchButtons();
}
}
final SearchWidget sw = getSearchWidget((String) event.getValue());
sw.showForSave(new SaveRequestHandler() {
public void onSaveRequested(SaveRequestEvent event) {
searchItems.addData(new SearchListRecord(sw.getName(), event.getCriterion()));
update();
}
public void onCancelRequested(SaveRequestEvent event) {
update();
}
private void update() {
updateDisplay();
if (hideButtonsWhenAdding) {
Canvas parent = getParentElement();
if (parent instanceof SearchWidget) {
((SearchWidget) parent).showSearchButtons();
}
selectSearch.setValue("");
}
}
});
}
if (!hideButtonsWhenAdding) {
event.cancel();
}
}
});
searchForm.setFields(selectSearch);
addBar.addMember(searchForm);
layout.addMember(searchItems);
layout.addMember(addBar);
addChild(layout);
updateDisplay();
}
/**
* Set whether all elements should always be displayed or only when significant.
* <p/>
* This affects the and/or choice and the list of criteria.
* <p/>
* The default value is true.
*
* @param alwaysShowElements should all elements always be visible?
*/
public void setAlwaysShowElements(boolean alwaysShowElements) {
this.alwaysShowElements = alwaysShowElements;
updateDisplay();
}
/**
* Set whether the search and reset buttons should be hidden while adding a criterion.
* <p/>
* The default value is false.
*
* @param hideButtonsWhenAdding should buttons be hidden while adding?
*/
public void setHideButtonsWhenAdding(boolean hideButtonsWhenAdding) {
this.hideButtonsWhenAdding = hideButtonsWhenAdding;
}
@Override
public void show() {
updateDisplay();
super.show();
}
public void updateDisplay() {
int recordCount = searchItems.getRecords().length;
if (alwaysShowElements || recordCount >= 1) {
searchItems.show();
} else {
searchItems.hide();
}
if (alwaysShowElements || recordCount >= 2) {
operatorForm.show();
} else {
operatorForm.hide();
}
updateGeometriesOnMap();
}
private void updateGeometriesOnMap() {
// make list of geometries from search items
List<Geometry> searchGeoms = new ArrayList<Geometry>();
for (ListGridRecord record : searchItems.getRecords()) {
SearchListRecord slr = (SearchListRecord) record;
if (slr.getCritter() instanceof GeometryCriterion) {
Geometry geometry = GeometryConverter.toGwt(((GeometryCriterion) slr.getCritter()).getGeometry());
searchGeoms.add(geometry);
}
}
// (re)draw the geometries
for (int i = 0; i < searchGeoms.size(); i++) {
GfxGeometry worldPaintable = null;
if (i < geometries.size()) {
worldPaintable = geometries.get(i);
mapWidget.unregisterWorldPaintable(worldPaintable);
} else {
worldPaintable = new GfxGeometry(GEOMETRY_IDENTIFIER + "_" + (i + 1));
worldPaintable.setStyle(selectionStyle);
geometries.add(worldPaintable);
}
worldPaintable.setGeometry(searchGeoms.get(i));
mapWidget.registerWorldPaintable(worldPaintable);
}
// unregister superfluous geometries
for (int i = searchGeoms.size(); i < geometries.size(); i++) {
mapWidget.unregisterWorldPaintable(geometries.get(i));
}
}
private SearchWidget getSearchWidget(String value) {
for (SearchWidget searchWidget : searchWidgets) {
if (value.equals(searchWidget.getSearchWidgetId())) {
return searchWidget;
}
}
return SearchWidgetRegistry.getSearchWidgetInstance(value);
}
@Override
public boolean validate() {
if (searchItems.getRecords().length > 0) {
return true;
} else {
SC.say(messages.combinedSearchWidgetNoValue());
return false;
}
}
@Override
public Criterion getFeatureSearchCriterion() {
List<Criterion> criteria = new ArrayList<Criterion>();
for (ListGridRecord record : searchItems.getRecords()) {
criteria.add(((SearchListRecord) record).getCritter());
}
if (messages.combinedSearchWidgetAnd().equals(type.getValue())) {
AndCriterion crit = new AndCriterion();
crit.setCriteria(criteria);
return crit;
} else {
OrCriterion crit = new OrCriterion();
crit.setCriteria(criteria);
return crit;
}
}
@Override
public VectorLayer getFeatureSearchVectorLayer() {
return null; // not relevant for combined search
}
@Override
public void reset() {
operatorForm.reset();
searchItems.getDataAsRecordList().removeList(searchItems.getRecords());
updateDisplay();
}
public void onDestroy() {
searchItems.clear();
updateGeometriesOnMap();
}
/**
* Which searches can you use to combine?
* <p>
* Use this method if you do not use the SearchWidgetRegistry or want to
* limit the number of widgets.
*
* @param searchWidgets search widgets
*/
@Api
public void initializeList(Collection<SearchWidget> searchWidgets) {
LinkedHashMap<String, String> values = new LinkedHashMap<String, String>();
for (SearchWidget searchWidget : searchWidgets) {
values.put(searchWidget.getSearchWidgetId(), searchWidget.getName());
}
selectSearch.setValueMap(values);
this.searchWidgets.addAll(searchWidgets);
}
/**
* Which searches can you use to combine?
* <p>
* Use this method if you use the {@link SearchWidgetRegistry} and want to use all
* searches in it (do not forget to add them to the registry first).
*/
public void initializeListUseAll() {
selectSearch.setValueMap(SearchWidgetRegistry.getSearchWidgetMapping());
}
@Override
public void initialize(Criterion featureSearch) {
List<Criterion> critters;
if (featureSearch instanceof AndCriterion) {
critters = ((AndCriterion) featureSearch).getCriteria();
type.setValue(messages.combinedSearchWidgetAnd());
} else if (featureSearch instanceof OrCriterion) {
critters = ((OrCriterion) featureSearch).getCriteria();
type.setValue(messages.combinedSearchWidgetOr());
} else {
return;
} // not supported
if (critters != null && critters.size() > 0) {
for (Criterion cr : critters) {
searchItems.addData(new SearchListRecord(SearchWidgetRegistry.getI18nTypeName(cr), cr));
updateDisplay();
}
}
}
public static boolean canHandle(Criterion criterion) {
return criterion != null && (criterion instanceof OrCriterion || criterion instanceof AndCriterion);
}
// ----------------------------------------------------------
/**
* Record representation for grid.
*/
private static class SearchListRecord extends ListGridRecord {
public static final String NAME_FIELD = "nameField";
public static final String REMOVE_FIELD = "removeField";
// private String name;
private Criterion critter;
public SearchListRecord(String name, Criterion criterion) {
setSearchWidget(name, criterion);
}
public void setSearchWidget(String name, Criterion criterion) {
this.critter = criterion;
setAttribute(NAME_FIELD, name + ", " + criterion.getDisplayText());
setAttribute(REMOVE_FIELD, WidgetLayout.iconRemove);
}
public Criterion getCritter() {
return critter;
}
}
}