/* * 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.controller.editing; import org.geomajas.gwt.client.controller.AbstractSnappingController; import org.geomajas.gwt.client.gfx.paintable.GfxGeometry; import org.geomajas.gwt.client.gfx.style.ShapeStyle; import org.geomajas.gwt.client.i18n.I18nProvider; import org.geomajas.gwt.client.map.feature.FeatureTransaction; import org.geomajas.gwt.client.map.feature.TransactionGeomIndex; import org.geomajas.gwt.client.map.layer.VectorLayer; import org.geomajas.gwt.client.spatial.Bbox; import org.geomajas.gwt.client.spatial.geometry.Geometry; import org.geomajas.gwt.client.spatial.geometry.GeometryFactory; import org.geomajas.gwt.client.spatial.geometry.LinearRing; import org.geomajas.gwt.client.spatial.geometry.Polygon; import org.geomajas.gwt.client.util.DistanceFormat; import org.geomajas.gwt.client.widget.MapWidget; import org.geomajas.gwt.client.widget.MapWidget.RenderGroup; import org.geomajas.gwt.client.widget.MapWidget.RenderStatus; import com.smartgwt.client.types.VerticalAlignment; import com.smartgwt.client.widgets.Label; import com.smartgwt.client.widgets.events.ClickEvent; import com.smartgwt.client.widgets.events.ClickHandler; import com.smartgwt.client.widgets.menu.Menu; /** * Basic template for a controller that handles the editing of certain types of geometries. * * @author Pieter De Graef */ public abstract class EditController extends AbstractSnappingController { /** * When editing geometries, 2 editing modes exist: one in which you constantly add new points to the geometry * (INSERT_MODE), and one where you are free to move points, add or remove points etc (DRAG_MODE). This is the * definition of both modes. */ public static enum EditMode { DRAG_MODE, INSERT_MODE } /** * Reference to a parent editing controller. Editing controllers support a hierarchical structure where a parent * delegates to the correct child EditController, depending on the circumstances. */ protected EditController parent; /** The currently active editing modus. */ private EditMode editMode; /** The current active context menu on the map. */ protected Menu menu; /** Definition of the label that displays geometric information of the geometry that's currently being edited. */ protected GeometricInfoLabel infoLabel; /** Option to show the maximum bounds wherein editing is allowed. */ private boolean maxBoundsDisplayed; /** Paintable for the maximum bounds wherein editing is allowed. */ private GfxGeometry maxExtent; // ------------------------------------------------------------------------- // Constructor // ------------------------------------------------------------------------- /** * Protected and only constructor. Extending classes must use this constructor. * * @param mapWidget * Reference to the map widget on which editing is in progress. * @param parent * The parent editing controller, or null if there is none. */ protected EditController(MapWidget mapWidget, EditController parent) { super(mapWidget); this.parent = parent; } // ------------------------------------------------------------------------- // Class specific functions: // ------------------------------------------------------------------------- /** * Return a context menu specific for this editing controller instance. */ public abstract Menu getContextMenu(); /** * When creating a new feature, the controller must specify through a <code>TransactionGeomIndex</code> object, * where to begin adding coordinates in the geometry. The contents of this index will depend on the type of * geometric object the edit controller supports. * * @return */ public abstract TransactionGeomIndex getGeometryIndex(); /** * Set a new index at which inserting of points should happen. * * @param geometryIndex */ public abstract void setGeometryIndex(TransactionGeomIndex geometryIndex); /** * Clean up all graphical stuff. */ public abstract void cleanup(); /** * True if this controller is busy. A busy controller should receive all subsequent mouse events until this method * returns false. * * @return true if busy */ public abstract boolean isBusy(); /** * Show an overview of geometric attributes of the geometry that's being edited. */ public void showGeometricInfo() { FeatureTransaction ft = getFeatureTransaction(); if (infoLabel == null && ft != null && ft.getNewFeatures() != null && ft.getNewFeatures().length > 0) { infoLabel = new GeometricInfoLabel(); infoLabel.addClickHandler(new DestroyLabelInfoOnClick()); infoLabel.setGeometry(ft.getNewFeatures()[0].getGeometry()); infoLabel.animateMove(mapWidget.getWidth() - 155, 10); } } /** * Update the overview of geometric attributes of the geometry that's being edited. */ public void updateGeometricInfo() { FeatureTransaction ft = getFeatureTransaction(); if (infoLabel != null && ft != null && ft.getNewFeatures() != null && ft.getNewFeatures().length > 0) { infoLabel.setGeometry(ft.getNewFeatures()[0].getGeometry()); } } /** * Hide the overview of geometric attributes. */ public void hideGeometricInfo() { if (infoLabel != null) { infoLabel.destroy(); infoLabel = null; } } // ------------------------------------------------------------------------- // Getters and setters: // ------------------------------------------------------------------------- /** * Reference to a parent editing controller. Editing controllers support a hierarchical structure where a parent * delegates to the correct child EditController, depending on the circumstances. */ public EditController getParent() { return parent; } /** The currently active editing modus. */ public EditMode getEditMode() { return editMode; } public void setEditMode(EditMode editMode) { this.editMode = editMode; } public GeometricInfoLabel getInfoLabel() { return infoLabel; } /** Should the maximum bounds wherein editing is allowed be rendered on the map or not? */ public boolean isMaxBoundsDisplayed() { return maxBoundsDisplayed; } /** * Determine whether or not the maximum bounds wherein editing is allowed be rendered on the map. * * @param maxBoundsDisplayed * The new value. */ public void setMaxBoundsDisplayed(boolean maxBoundsDisplayed) { this.maxBoundsDisplayed = maxBoundsDisplayed; } // ------------------------------------------------------------------------- // GraphicsController implementation: // ------------------------------------------------------------------------- public void onActivate() { menu = getContextMenu(); mapWidget.setContextMenu(menu); if (maxBoundsDisplayed) { VectorLayer layer = getFeatureTransaction().getLayer(); GeometryFactory factory = mapWidget.getMapModel().getGeometryFactory(); LinearRing hole = factory.createLinearRing(new Bbox(layer.getLayerInfo().getMaxExtent())); LinearRing shell = factory.createLinearRing(mapWidget.getMapModel().getMapView().getMaxBounds()); Polygon polygon = factory.createPolygon(shell, new LinearRing[] { hole }); maxExtent = new GfxGeometry("maxExtent"); maxExtent.setGeometry(polygon); maxExtent.setStyle(new ShapeStyle("#000000", .6f, "#990000", 1, 2)); mapWidget.registerWorldPaintable(maxExtent); } } public void onDeactivate() { if (maxExtent != null) { mapWidget.unregisterWorldPaintable(maxExtent); } if (getFeatureTransaction() != null) { mapWidget.render(getFeatureTransaction(), RenderGroup.VECTOR, RenderStatus.DELETE); mapWidget.getMapModel().getFeatureEditor().stopEditing(); } if (menu != null) { menu.destroy(); menu = null; mapWidget.setContextMenu(null); } hideGeometricInfo(); cleanup(); } // ------------------------------------------------------------------------- // Protected helper methods: // ------------------------------------------------------------------------- /** * Helper method to the FeatureTransaction. */ protected FeatureTransaction getFeatureTransaction() { return mapWidget.getMapModel().getFeatureEditor().getFeatureTransaction(); } // ------------------------------------------------------------------------- // Private inner classes: // ------------------------------------------------------------------------- /** * Label that displays some basic geometric information, such as length and area. * * @author Pieter De Graef */ private class GeometricInfoLabel extends Label { private Geometry geometry; // Constructors: protected GeometricInfoLabel() { setParentElement(mapWidget); setValign(VerticalAlignment.TOP); setShowEdges(true); setWidth(145); setPadding(3); setLeft(mapWidget.getWidth() - 155); setTop(-80); setBackgroundColor("#FFFFFF"); setAnimateTime(500); } // Getters and setters: public void setGeometry(Geometry geometry) { this.geometry = geometry; updateContents(); } // Private methods: private void updateContents() { String contents = I18nProvider.getMenu().editGeometricInfoLabelTitle(); if (geometry == null) { setContents(contents + I18nProvider.getMenu().editGeometricInfoLabelNoGeometry()); } else { String area = DistanceFormat.asMapArea(mapWidget, geometry.getArea()); String length = DistanceFormat.asMapLength(mapWidget, geometry.getLength()); setContents(contents + I18nProvider.getMenu().editGeometricInfoLabelInfo(area, length, geometry.getNumPoints())); } } } /** * @author Pieter De Graef */ private class DestroyLabelInfoOnClick implements ClickHandler { public void onClick(ClickEvent event) { infoLabel.destroy(); infoLabel = null; } } }