/*
* 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.gwt.client.widget;
import java.util.ArrayList;
import java.util.List;
import com.smartgwt.client.types.Overflow;
import org.geomajas.annotation.Api;
import org.geomajas.global.GeomajasConstant;
import org.geomajas.gwt.client.action.menu.SaveEditingAction;
import org.geomajas.gwt.client.i18n.I18nProvider;
import org.geomajas.gwt.client.map.MapModel;
import org.geomajas.gwt.client.map.MapView.ZoomOption;
import org.geomajas.gwt.client.map.event.FeatureTransactionEvent;
import org.geomajas.gwt.client.map.event.FeatureTransactionHandler;
import org.geomajas.gwt.client.map.feature.Feature;
import org.geomajas.gwt.client.map.feature.LazyLoadCallback;
import org.geomajas.gwt.client.map.feature.LazyLoader;
import org.geomajas.gwt.client.map.layer.VectorLayer;
import org.geomajas.gwt.client.spatial.Bbox;
import org.geomajas.gwt.client.util.WidgetLayout;
import org.geomajas.gwt.client.widget.attribute.FeatureFormFactory;
import org.geomajas.gwt.client.widget.attribute.DefaultFeatureFormFactory;
import com.smartgwt.client.types.Alignment;
import com.smartgwt.client.widgets.IButton;
import com.smartgwt.client.widgets.events.ClickEvent;
import com.smartgwt.client.widgets.events.DrawEvent;
import com.smartgwt.client.widgets.events.DrawHandler;
import com.smartgwt.client.widgets.form.events.ItemChangedEvent;
import com.smartgwt.client.widgets.form.events.ItemChangedHandler;
import com.smartgwt.client.widgets.layout.HLayout;
import com.smartgwt.client.widgets.layout.LayoutSpacer;
import com.smartgwt.client.widgets.layout.VLayout;
import com.smartgwt.client.widgets.toolbar.ToolStrip;
/**
* <p>
* The <code>FeatureAttributeWindow</code> is a floating window that uses a
* {@link org.geomajas.gwt.client.widget.FeatureAttributeEditor} to change the alpha-numerical attributes of a feature
* and persist these changes on the server. In essence, this widget is a
* {@link org.geomajas.gwt.client.widget.FeatureAttributeEditor} with some extra buttons. One of these extra buttons is
* a "save" button to actually save the widget. When setting a feature, the underlying
* {@link org.geomajas.gwt.client.widget.FeatureAttributeEditor} automatically creates a clone. That way you're not
* editing the feature directly, and changes are only applied when the save is clicked. This widget will also check
* whether or not all fields are valid, and will not allow saving when at least one of the fields is not valid.
* </p>
* <p>
* On top of that, this widget has a few options regarding the editing of a feature's attributes:
* <ul>
* <li><b>editingAllowed</b>: Is editing allowed? This must be set BEFORE the widget is actually drawn,
* because afterwards it won't have any effect anymore.</li>
* <li><b>editingEnabled</b>: Is editing currently enabled or not? This widget can toggle this value on the fly. When
* editing is enabled, it will display an editable attribute form with save, cancel and reset buttons. When editing is
* not enabled, these buttons will disappear, and a simple attribute form is shown that displays the attribute values,
* but does not allow for editing.</li>
* </ul>
* </p>
*
* @author Pieter De Graef
* @since 1.6.0
*/
@Api
public class FeatureAttributeWindow extends KeepInScreenWindow {
/**
* The ToolStrip that contains the different buttons (save, reset, ...).
*/
private ToolStrip toolStrip;
/**
* The editor instance that actually does the attribute editing and validation.
*/
private FeatureAttributeEditor attributeTable;
private FeatureFormFactory<?> factory;
private boolean editingAllowed;
private boolean editingEnabled;
/**
* Reference to the MapModel. Needed when we want to create a
* {@link org.geomajas.gwt.client.map.feature.FeatureTransaction} to actually save changes.
*/
private MapModel mapModel;
private IButton saveButton;
private IButton editButton;
private HLayout savePanel;
// -------------------------------------------------------------------------
// Constructor:
// -------------------------------------------------------------------------
/**
* Create an instance and immediately apply a feature onto it. Also specify whether or not editing is allowed. By
* default this constructor will use a {@link DefaultFeatureFormFactory} to create the attribute form. If you want
* to have some influence on how the feature attribute form should look, than use the other constructor.
*
* @param feature The feature instance to display and/or edit.
* @param editingAllowed This must be set before the widget is actually drawn, so it makes sense to set it at
* construction time.
* @since 1.6.0
*/
@Api
public FeatureAttributeWindow(Feature feature, boolean editingAllowed) {
this(feature, editingAllowed, new DefaultFeatureFormFactory());
}
/**
* Create an instance and immediately apply a feature onto it. Also specify whether or not editing is allowed.
*
* @param feature The feature instance to display and/or edit.
* @param editingAllowed This must be set before the widget is actually drawn, so it makes sense to set it at
* construction time.
* @param factory A specific factory for creating the attribute forms within this widget.
* @since 1.9.0
*/
@Api
public FeatureAttributeWindow(Feature feature, boolean editingAllowed, FeatureFormFactory<?> factory) {
super();
if (factory == null) {
this.factory = new DefaultFeatureFormFactory();
} else {
this.factory = factory;
}
setAutoSize(true);
setWidth("*");
setHeight("*");
setKeepInParentRect(true);
if (null != WidgetLayout.featureAttributeWindowWidth) {
setWidth(WidgetLayout.featureAttributeWindowWidth);
setAutoSize(false);
}
if (null != WidgetLayout.featureAttributeWindowHeight) {
setHeight(WidgetLayout.featureAttributeWindowHeight);
setAutoSize(false);
}
setOverflow(Overflow.AUTO);
setEditingAllowed(editingAllowed);
if (feature != null) {
buildWidget(feature.getLayer());
mapModel.addFeatureTransactionHandler(new ApplyFeatureTransactionToWindow());
setFeature(feature);
}
}
// -------------------------------------------------------------------------
// Public methods:
// -------------------------------------------------------------------------
/**
* Validate the current attribute values on display. Returns true if all attribute values have been validated, false
* otherwise.
*
* @return true if all attributes are valid
*/
public boolean validate() {
return attributeTable != null && attributeTable.validate();
}
/**
* Get the feature with attribute values as they are displayed in this widget. When editing is enabled, it may be
* possible that this feature will hold values that have not been validated, so it is recommended to run the
* <code>validate</code> method first.
*
* @return feature
*/
public Feature getFeature() {
if (attributeTable != null) {
return attributeTable.getFeature();
}
return null;
}
/**
* Apply a new feature onto this widget, assuring the attributes are loaded.
*
* @param feature feature
*/
public void setFeature(Feature feature) {
List<Feature> features = new ArrayList<Feature>();
features.add(feature);
LazyLoader.lazyLoad(features, GeomajasConstant.FEATURE_INCLUDE_ATTRIBUTES, new LazyLoadCallback() {
public void execute(List<Feature> response) {
Feature feature = response.get(0);
if (attributeTable == null) {
buildWidget(feature.getLayer());
}
if (feature != null) {
setTitle(I18nProvider.getAttribute().getAttributeWindowTitle(feature.getLabel()));
} else {
setTitle(I18nProvider.getAttribute().getAttributeWindowTitle(""));
}
attributeTable.setFeature(feature);
}
});
}
/**
* Is editing allowed? This must be set BEFORE the widget is actually drawn, because afterwards it won't have any
* effect anymore.
*
* @return true when editing is allowed
*/
public boolean isEditingAllowed() {
return editingAllowed;
}
/**
* Is editing allowed? This must be set BEFORE the widget is actually drawn, because afterwards it won't have any
* effect anymore.
*
* @param editingAllowed editing allowed status
*/
public void setEditingAllowed(boolean editingAllowed) {
this.editingAllowed = editingAllowed;
}
/**
* Is editing currently enabled or not? This widget can toggle this value on the fly. When editing is enabled, it
* will display an editable attribute form with save, cancel and reset buttons. When editing is not enabled, these
* buttons will disappear, and a simple attribute form is shown that displays the attribute values, but does not
* allow for editing.
*
* @return true if editing is enabled
*/
public boolean isEditingEnabled() {
return editingEnabled;
}
/**
* Is editing currently enabled or not? This widget can toggle this value on the fly. When editing is enabled, it
* will display an editable attribute form with save, cancel and reset buttons. When editing is not enabled, these
* buttons will disappear, and a simple attribute form is shown that displays the attribute values, but does not
* allow for editing.
*
* @param editingEnabled editing enabled status
*/
public void setEditingEnabled(boolean editingEnabled) {
if (isEditingAllowed()) {
this.editingEnabled = editingEnabled;
if (editingEnabled) {
savePanel.setVisible(true);
if (attributeTable != null && attributeTable.isDisabled()) {
attributeTable.setDisabled(false);
}
} else {
savePanel.setVisible(false);
if (attributeTable != null && !attributeTable.isDisabled()) {
attributeTable.setDisabled(true);
}
}
}
}
/**
* The tool strip at the top which contains the zoom and save buttons.
*
* @return tool strip
*/
public ToolStrip getToolStrip() {
return toolStrip;
}
// -------------------------------------------------------------------------
// Private methods:
// -------------------------------------------------------------------------
/**
* Build the entire widget.
*
* @param layer layer
*/
private void buildWidget(VectorLayer layer) {
mapModel = layer.getMapModel();
setTitle(I18nProvider.getAttribute().getAttributeWindowTitle(""));
setCanDragReposition(true);
setCanDragResize(true);
attributeTable = new FeatureAttributeEditor(layer, true, factory);
toolStrip = new ToolStrip();
toolStrip.setWidth100();
toolStrip.setPadding(WidgetLayout.marginSmall);
toolStrip.addMember(new ZoomButton());
editButton = new EditButton();
LayoutSpacer spacer = new LayoutSpacer();
spacer.setWidth(2);
toolStrip.addMember(spacer);
if (editingAllowed) {
toolStrip.addMember(editButton);
}
savePanel = new HLayout(WidgetLayout.marginSmall);
saveButton = new SaveButton();
IButton resetButton = new ResetButton();
IButton cancelButton = new CancelButton();
savePanel.addMember(saveButton);
savePanel.addMember(resetButton);
savePanel.addMember(cancelButton);
savePanel.setVisible(false);
savePanel.setAlign(Alignment.CENTER);
savePanel.setPadding(WidgetLayout.marginSmall);
VLayout layout = new VLayout();
layout.addMember(toolStrip);
layout.addMember(attributeTable);
layout.addMember(savePanel);
layout.setWidth(WidgetLayout.featureAttributeWindowLayoutWidth);
addItem(layout);
// Set the save button as disabled at startup:
addDrawHandler(new DrawHandler() {
public void onDraw(DrawEvent event) {
saveButton.setDisabled(true);
}
});
}
// -------------------------------------------------------------------------
// Private class EditButton:
// -------------------------------------------------------------------------
/** Definition of the edit button, that switches on editing. (editingEnabled=true) */
private class EditButton extends IButton implements com.smartgwt.client.widgets.events.ClickHandler {
public EditButton() {
setIcon(WidgetLayout.iconEdit);
setShowDisabledIcon(false);
setTitle(I18nProvider.getAttribute().btnEditTitle());
setTooltip(I18nProvider.getAttribute().btnEditTooltip());
addClickHandler(this);
setWidth(80);
}
public void onClick(ClickEvent event) {
if (isEditingEnabled()) {
setEditingEnabled(false);
editButton.setDisabled(false);
} else {
setEditingEnabled(true);
editButton.setDisabled(true);
saveButton.setDisabled(true);
}
}
}
// -------------------------------------------------------------------------
// Private class ZoomButton:
// -------------------------------------------------------------------------
/** Definition of the zoom button that zooms to the feature on the map. */
private class ZoomButton extends IButton implements com.smartgwt.client.widgets.events.ClickHandler {
public ZoomButton() {
setIcon(WidgetLayout.iconZoomSelection);
setShowDisabledIcon(false);
setTitle(I18nProvider.getAttribute().btnZoomFeature());
setTooltip(I18nProvider.getAttribute().btnZoomTooltip());
addClickHandler(this);
setWidth(150);
}
public void onClick(ClickEvent event) {
Bbox bounds = getFeature().getGeometry().getBounds();
mapModel.getMapView().applyBounds(bounds, ZoomOption.LEVEL_FIT);
}
}
// -------------------------------------------------------------------------
// Private class SaveButton:
// -------------------------------------------------------------------------
/** Definition of the Save button. */
private class SaveButton extends IButton implements com.smartgwt.client.widgets.events.ClickHandler {
public SaveButton() {
setIcon(WidgetLayout.iconSave);
setShowDisabledIcon(false);
setTitle(I18nProvider.getAttribute().btnSaveTitle());
setTooltip(I18nProvider.getAttribute().btnSaveTooltip());
attributeTable.addItemChangedHandler(new ItemChangedHandler() {
public void onItemChanged(ItemChangedEvent event) {
if (attributeTable.validate()) {
setDisabled(false);
} else {
setDisabled(true);
}
}
});
addClickHandler(this);
}
@Override
public void onClick(ClickEvent event) {
mapModel.getFeatureEditor().startEditing(new Feature[] { getFeature() }, new Feature[] { getFeature() });
SaveEditingAction action = new SaveEditingAction(mapModel);
action.onClick(null);
setEditingEnabled(false);
editButton.setDisabled(false);
}
}
// -------------------------------------------------------------------------
// Private class ResetButton:
// -------------------------------------------------------------------------
/** Definition of the Reset button that resets the features original attribute values. */
private class ResetButton extends IButton implements com.smartgwt.client.widgets.events.ClickHandler {
public ResetButton() {
setIcon(WidgetLayout.iconUndo);
setShowDisabledIcon(false);
setTitle(I18nProvider.getAttribute().btnResetTitle());
setTooltip(I18nProvider.getAttribute().btnResetTooltip());
addClickHandler(this);
}
@Override
public void onClick(ClickEvent event) {
attributeTable.reset();
}
}
// -------------------------------------------------------------------------
// Private class CancelButton:
// -------------------------------------------------------------------------
/** Definition of the cancel button that cancels editing (editingEnabled=false). */
private class CancelButton extends IButton implements com.smartgwt.client.widgets.events.ClickHandler {
public CancelButton() {
setIcon(WidgetLayout.iconQuit);
setShowDisabledIcon(false);
setTitle(I18nProvider.getAttribute().btnCancelTitle());
setTooltip(I18nProvider.getAttribute().btnCancelTooltip());
addClickHandler(this);
}
@Override
public void onClick(ClickEvent event) {
attributeTable.reset();
setEditingEnabled(false);
editButton.setDisabled(false);
}
}
/**
* Applies feature transaction changes to this window.
*
* @author Jan De Moerloose
*/
private class ApplyFeatureTransactionToWindow implements FeatureTransactionHandler {
@Override
public void onTransactionSuccess(FeatureTransactionEvent event) {
Feature feature = attributeTable.getFeature();
if (feature != null) {
setFeature(feature);
} else {
setFeature(null);
}
}
}
}