001: /* uDig - User Friendly Desktop Internet GIS client
002: * http://udig.refractions.net
003: * (C) 2004, Refractions Research Inc.
004: *
005: * This library is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Lesser General Public
007: * License as published by the Free Software Foundation;
008: * version 2.1 of the License.
009: *
010: * This library is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * Lesser General Public License for more details.
014: */
015: package net.refractions.udig.tools.edit.behaviour;
016:
017: import java.util.ArrayList;
018: import java.util.List;
019:
020: import net.refractions.udig.project.ILayer;
021: import net.refractions.udig.project.command.UndoableComposite;
022: import net.refractions.udig.project.command.UndoableMapCommand;
023: import net.refractions.udig.project.ui.render.displayAdapter.MapMouseEvent;
024: import net.refractions.udig.tools.edit.EditPlugin;
025: import net.refractions.udig.tools.edit.EditState;
026: import net.refractions.udig.tools.edit.EditToolHandler;
027: import net.refractions.udig.tools.edit.EventBehaviour;
028: import net.refractions.udig.tools.edit.EventType;
029: import net.refractions.udig.tools.edit.commands.AddMissingGeomsCommand;
030: import net.refractions.udig.tools.edit.commands.RemoveEditGeomCommand;
031: import net.refractions.udig.tools.edit.commands.SelectGeometryCommand;
032: import net.refractions.udig.tools.edit.commands.SetCurrentGeomCommand;
033: import net.refractions.udig.tools.edit.commands.SetEditFeatureCommand;
034: import net.refractions.udig.tools.edit.preferences.PreferenceUtil;
035: import net.refractions.udig.tools.edit.support.ClosestEdge;
036: import net.refractions.udig.tools.edit.support.EditBlackboard;
037: import net.refractions.udig.tools.edit.support.EditGeom;
038: import net.refractions.udig.tools.edit.support.EditUtils;
039: import net.refractions.udig.tools.edit.support.Point;
040: import net.refractions.udig.tools.edit.support.PrimitiveShape;
041: import net.refractions.udig.tools.edit.support.PrimitiveShapeIterator;
042: import net.refractions.udig.tools.edit.support.ShapeType;
043:
044: import org.geotools.filter.FilterType;
045:
046: import com.vividsolutions.jts.geom.Geometry;
047: import com.vividsolutions.jts.geom.MultiPolygon;
048: import com.vividsolutions.jts.geom.Polygon;
049:
050: /**
051: * <p>
052: * Requirements:
053: * <ul>
054: * <li>state==MODIFYING or NONE</li>
055: * <li>event type == RELEASED</li>
056: * <li>only a single modifier may be down</li>
057: * <li>shift and ctrl are only legal modifiers</li>
058: * <li>button1 must be the button that was released</li>
059: * <li>no buttons may be down</li>
060: * <li>Not over currently selected geometry</li>
061: * </ul>
062: * </p>
063: * <p>
064: * Action:
065: * <ul>
066: * <li>If no currently selected geometry && mouse is over feature then:
067: * <ul>
068: * <li>If the feature's geometry isn't of the type specified in constructor return or:</li>
069: * <li>Clear Editblackboard and set feature's geometry on blackboard.</li>
070: * <li>set the current geom to be the geometry selected</li>
071: * <li>set the current Edit feature to be selected feature</li>
072: * <li>set mode to be Modified?</li>
073: * </ul>
074: * </li>
075: * <li>If there is a currently selected geometry && mouse is over a different feature:
076: * <ul>
077: * <li>If no modifiers select feature as described above</li>
078: * <li>if control is down add the feature to the Editblackboard or remove it if it is already on
079: * the EditBlackboard</li>
080: * <li>if shift is down add the feature to the Editblackboard or do nothing if it is already on the
081: * EditBlackboard</li>
082: * </ul>
083: * <li>If there is a currently selected geometry && mouse is not over a different feature:
084: * <ul>
085: * <li>Run acceptBehaviours</li>
086: * <li>deselect EditFeature</li>
087: * <li>deselect current EditGeom</li>
088: * <li>clear Editblackboard</li>
089: * <li>set current state to NONE</li>
090: * </ul>
091: * </li>
092: * </ul>
093: * </p>
094: *
095: * @author jones
096: * @since 1.1.0
097: */
098: public class SelectGeometryBehaviour implements EventBehaviour {
099:
100: private Class[] acceptableClasses;
101: private boolean treatUnknownGeomsAsPolygon;
102: private short filterType;
103: private boolean permitClear;
104: private boolean onlyAdd;
105: private ShapeType toCreate;
106:
107: /**
108: * Create instance
109: *
110: * @param acceptableClasses used to determine if a feature can be selected. If point is not in
111: * array then point geometries can not be selected.
112: * @param filterType one of the constants in {@link FilterType} that start with GEOMETRY_
113: */
114: public SelectGeometryBehaviour(Class[] acceptableClasses,
115: short filterType) {
116: Class[] c = new Class[0];
117: if (acceptableClasses != null) {
118: c = new Class[acceptableClasses.length];
119: System.arraycopy(acceptableClasses, 0, c, 0, c.length);
120: }
121:
122: this .acceptableClasses = c;
123: this .treatUnknownGeomsAsPolygon = false;
124: for (Class<Geometry> class1 : c) {
125: if (class1.isAssignableFrom(Polygon.class)
126: || class1.isAssignableFrom(MultiPolygon.class)) {
127: treatUnknownGeomsAsPolygon = true;
128: break;
129: }
130: }
131: this .filterType = filterType;
132: onlyAdd = false;
133: permitClear = true;
134: }
135:
136: public void setCreateGeomOnNoneSelect(ShapeType toCreate2) {
137: this .toCreate = toCreate2;
138: }
139:
140: public boolean isValid(EditToolHandler handler, MapMouseEvent e,
141: EventType eventType) {
142: boolean legalState = handler.getCurrentState() == EditState.NONE
143: || handler.getCurrentState() == EditState.MODIFYING;
144: boolean releaseButtonState = eventType == EventType.RELEASED;
145: boolean twoModifiersDown = e.isShiftDown()
146: && e.isModifierDown(MapMouseEvent.MOD1_DOWN_MASK);
147: boolean singleModifierDown = !(twoModifiersDown);
148: boolean altUp = !e.isAltDown();
149: boolean legalButton = e.button == MapMouseEvent.BUTTON1;
150: boolean noPressedButtons = e.buttons == MapMouseEvent.NONE;
151: if (!(legalState && releaseButtonState && altUp
152: && singleModifierDown && legalButton && noPressedButtons))
153: return false;
154:
155: if (e.isModifierDown(MapMouseEvent.MOD1_DOWN_MASK))
156: return true;
157:
158: if (handler.getCurrentGeom() == null)
159: return true;
160:
161: if (!handler.getCurrentShape().contains(
162: Point.valueOf(e.x, e.y), treatUnknownGeomsAsPolygon))
163: return true;
164:
165: return countOnBlackboard(handler, e) > 1; //click was within the current blackboard selection
166: }
167:
168: public UndoableMapCommand getCommand(final EditToolHandler handler,
169: final MapMouseEvent e, EventType eventType) {
170: if (!isValid(handler, e, eventType))
171: throw new IllegalArgumentException(
172: "Behaviour is not valid for the current state"); //$NON-NLS-1$
173: EditBlackboard editBlackboard = handler
174: .getEditBlackboard(handler.getEditLayer());
175: List<EditGeom> intersectingGeoms = EditUtils.instance
176: .getIntersectingGeom(editBlackboard, Point.valueOf(e.x,
177: e.y), treatUnknownGeomsAsPolygon);
178: if (e.isModifierDown(MapMouseEvent.MOD1_DOWN_MASK)
179: && !intersectingGeoms.isEmpty()) {
180: return new RemoveEditGeomCommand(handler, intersectingGeoms);
181: } else if (e.isShiftDown() && !intersectingGeoms.isEmpty()) {
182: return null;
183: }
184:
185: PrimitiveShape newShape = findOnBlackboard(handler, e);
186:
187: if (newShape != null && newShape != handler.getCurrentShape()) {
188: List<UndoableMapCommand> commands = new ArrayList<UndoableMapCommand>();
189: commands.add(new AddMissingGeomsCommand(handler, e,
190: acceptableClasses, filterType, onlyAdd));
191: commands.add(new SetCurrentGeomCommand(handler, newShape));
192: commands
193: .add(new SetEditFeatureCommand(handler, e, newShape));
194: UndoableComposite undoableComposite = new UndoableComposite(
195: commands);
196: return undoableComposite;
197: }
198:
199: SelectGeometryCommand selectGeometryCommand = new SelectGeometryCommand(
200: handler, e, acceptableClasses, filterType, permitClear,
201: onlyAdd);
202: selectGeometryCommand.setCreateOnNoSelect(toCreate);
203: return selectGeometryCommand;
204: }
205:
206: private PrimitiveShape findOnBlackboard(EditToolHandler handler,
207: MapMouseEvent e) {
208: //for overlapping geometries, select a different one on each click
209: boolean cycleGeom = false;
210: //set when the currently selected feature has been found
211: boolean selectedFound = false;
212: //for returning to the first match when we have the last match selected and click once more
213: PrimitiveShape firstMatch = null;
214: if (handler.getCurrentShape() != null
215: && handler.getCurrentShape().contains(e.x, e.y)
216: && countOnBlackboard(handler, e) > 1) {
217: cycleGeom = true;
218: }
219:
220: ILayer editLayer = handler.getEditLayer();
221: List<EditGeom> geoms = handler.getEditBlackboard(editLayer)
222: .getGeoms();
223: for (EditGeom geom : geoms) {
224: PrimitiveShapeIterator iter = PrimitiveShapeIterator
225: .getPathIterator(geom.getShell());
226: if (iter.toShape().contains(e.x, e.y)) {
227: if (cycleGeom) {
228: if (selectedFound) { //first match after the currently selected one
229: return geom.getShell();
230: }
231: if (geom == handler.getCurrentGeom()) { //currently selected geom
232: selectedFound = true;
233: }
234: if (firstMatch == null) { //first matching geom
235: firstMatch = geom.getShell();
236: }
237: } else {
238: return geom.getShell();
239: }
240: }
241: if (!cycleGeom) {
242: Class type = editLayer.getSchema().getDefaultGeometry()
243: .getType();
244: boolean polygonLayer = Polygon.class
245: .isAssignableFrom(type)
246: || MultiPolygon.class.isAssignableFrom(type);
247: ClosestEdge edge = geom.getShell().getClosestEdge(
248: Point.valueOf(e.x, e.y), polygonLayer);
249: if (edge != null
250: && edge.getDistanceToEdge() <= PreferenceUtil
251: .instance().getVertexRadius()) {
252: return geom.getShell();
253: }
254: }
255: }
256: if (cycleGeom) //selected geom was the last one, therefore select the first match
257: return firstMatch;
258: else
259: return null;
260: }
261:
262: private int countOnBlackboard(EditToolHandler handler,
263: MapMouseEvent e) {
264: List<EditGeom> geoms = handler.getEditBlackboard(
265: handler.getEditLayer()).getGeoms();
266: int count = 0;
267: for (EditGeom geom : geoms) {
268: PrimitiveShapeIterator iter = PrimitiveShapeIterator
269: .getPathIterator(geom.getShell());
270: if (iter.toShape().contains(e.x, e.y)) {
271: count++;
272: }
273: }
274: return count;
275: }
276:
277: public void handleError(EditToolHandler handler, Throwable error,
278: UndoableMapCommand command) {
279: EditPlugin.log("", error); //$NON-NLS-1$
280: }
281:
282: /**
283: * @return Returns the acceptableClasses.
284: */
285: public Class[] getAcceptableClasses() {
286: Class[] c = new Class[acceptableClasses.length];
287: System.arraycopy(acceptableClasses, 0, c, 0, c.length);
288: return c;
289: }
290:
291: /**
292: * @param acceptableClasses The acceptableClasses to set.
293: */
294: public void setAcceptableClasses(Class[] acceptableClasses) {
295: Class[] c = new Class[0];
296: if (acceptableClasses != null) {
297: c = new Class[acceptableClasses.length];
298: System.arraycopy(acceptableClasses, 0, c, 0, c.length);
299: }
300: this .acceptableClasses = c;
301: }
302:
303: /**
304: * @return Returns the filterType.
305: */
306: public short getFilterType() {
307: return this .filterType;
308: }
309:
310: /**
311: * @param filterType The filterType to set.
312: */
313: public void setFilterType(short filterType) {
314: this .filterType = filterType;
315: }
316:
317: /**
318: * @return Returns the onlyAdd.
319: */
320: public boolean isOnlyAdd() {
321: return this .onlyAdd;
322: }
323:
324: /**
325: * @param onlyAdd The onlyAdd to set.
326: */
327: public void setOnlyAdd(boolean onlyAdd) {
328: this .onlyAdd = onlyAdd;
329: }
330:
331: /**
332: * @return Returns the permitClear.
333: */
334: public boolean isPermitClear() {
335: return this .permitClear;
336: }
337:
338: /**
339: * @param permitClear The permitClear to set.
340: */
341: public void setPermitClear(boolean permitClear) {
342: this .permitClear = permitClear;
343: }
344:
345: /**
346: * @return Returns the treatUnkownGeomsAsPolygon.
347: */
348: public boolean isTreatUnknownGeomsAsPolygon() {
349: return this .treatUnknownGeomsAsPolygon;
350: }
351:
352: /**
353: * @param treatUnknownGeomsAsPolygon The treatUnknownGeomsAsPolygon to set.
354: */
355: public void setTreatUnknownGeomsAsPolygon(
356: boolean treatUnknownGeomsAsPolygon) {
357: this.treatUnknownGeomsAsPolygon = treatUnknownGeomsAsPolygon;
358: }
359:
360: }
|