View Javadoc

1   /**********************************************
2    * Copyright (C) 2011 Lukas Laag
3    * This file is part of svgreal.
4    * 
5    * svgreal is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    * 
10   * svgreal is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   * 
15   * You should have received a copy of the GNU General Public License
16   * along with svgreal.  If not, see http://www.gnu.org/licenses/
17   **********************************************/
18  package org.vectomatic.svg.edit.client.command.edit;
19  
20  import java.util.ArrayList;
21  import java.util.HashMap;
22  import java.util.HashSet;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Set;
26  
27  import org.vectomatic.dom.svg.OMSVGElement;
28  import org.vectomatic.dom.svg.OMSVGGElement;
29  import org.vectomatic.dom.svg.OMSVGMatrix;
30  import org.vectomatic.dom.svg.OMSVGPathElement;
31  import org.vectomatic.dom.svg.OMSVGPathSeg;
32  import org.vectomatic.dom.svg.OMSVGPathSegArcAbs;
33  import org.vectomatic.dom.svg.OMSVGPathSegCurvetoCubicAbs;
34  import org.vectomatic.dom.svg.OMSVGPathSegCurvetoQuadraticAbs;
35  import org.vectomatic.dom.svg.OMSVGPathSegLinetoAbs;
36  import org.vectomatic.dom.svg.OMSVGPathSegList;
37  import org.vectomatic.dom.svg.OMSVGPathSegMovetoAbs;
38  import org.vectomatic.dom.svg.OMSVGPoint;
39  import org.vectomatic.dom.svg.OMSVGRectElement;
40  import org.vectomatic.dom.svg.OMSVGSVGElement;
41  import org.vectomatic.dom.svg.utils.SVGConstants;
42  import org.vectomatic.svg.edit.client.AppBundle;
43  import org.vectomatic.svg.edit.client.command.path.IPathRepOwner;
44  import org.vectomatic.svg.edit.client.command.path.SVGCubicSegRep;
45  import org.vectomatic.svg.edit.client.command.path.SVGLineSegRep;
46  import org.vectomatic.svg.edit.client.command.path.SVGMoveSegRep;
47  import org.vectomatic.svg.edit.client.command.path.SVGQuadraticSegRep;
48  import org.vectomatic.svg.edit.client.command.path.SVGSegRep;
49  import org.vectomatic.svg.edit.client.engine.SVGModel;
50  import org.vectomatic.svg.edit.client.event.ScalingEvent;
51  import org.vectomatic.svg.edit.client.event.ScalingHandler;
52  import org.vectomatic.svg.edit.client.event.SelectionChangedProcessor;
53  import org.vectomatic.svg.edit.client.event.SelectionChangedProxy;
54  import org.vectomatic.svg.edit.client.model.svg.SVGElementModel;
55  import org.vectomatic.svg.edit.client.model.svg.SVGPathElementModel;
56  import org.vectomatic.svg.edit.client.model.svg.path.SVGSegModel;
57  import org.vectomatic.svg.edit.client.model.svg.path.SVGSegStore;
58  
59  import com.extjs.gxt.ui.client.data.ChangeEvent;
60  import com.extjs.gxt.ui.client.event.SelectionChangedEvent;
61  import com.extjs.gxt.ui.client.store.Record;
62  import com.extjs.gxt.ui.client.widget.grid.GridSelectionModel;
63  import com.google.gwt.core.client.GWT;
64  import com.google.gwt.dom.client.Element;
65  import com.google.gwt.event.dom.client.MouseDownEvent;
66  import com.google.gwt.event.dom.client.MouseMoveEvent;
67  import com.google.gwt.event.dom.client.MouseUpEvent;
68  import com.google.gwt.event.shared.HandlerRegistration;
69  
70  /**
71   * 2D manipulator class to edit path geometry.
72   */
73  public class EditPathGeometryManipulator extends EditManipulatorBase implements SelectionChangedProcessor<SVGSegModel>, IPathRepOwner, ScalingHandler {
74  	/**
75  	 * The path representation (differs from the actual svg path
76  	 * in the model so that the end user can see the difference
77  	 * between the actual model and its changes as they update the
78  	 * representation interactively).
79  	 */
80  	protected OMSVGPathElement path;
81  	/**
82  	 * The path segments
83  	 */
84  	protected List<SVGSegRep> segments;
85  	/**
86  	 * Map segment representations to their model
87  	 */
88  	protected Map<SVGSegRep, SVGSegModel> repToModel;
89  	/**
90  	 * Map model to their segment representations
91  	 */
92  	protected Map<SVGSegModel, SVGSegRep> modelToRep;
93  	/**
94  	 * A group for elements representing tangents
95  	 */
96  	protected OMSVGGElement tangentGroup;
97  	/**
98  	 * A group for elements representing vertices
99  	 */
100 	protected OMSVGGElement vertexGroup;
101 	/**
102 	 * Maps element representing a segment endpoint vertex
103 	 * the segment object
104 	 */
105 	private Map<Element, SVGSegRep> vertexToSeg;
106 	/**
107 	 * Maps element representing a segment tangent to
108 	 * the segment object
109 	 */
110 	private Map<Element, SVGSegRep> tangentToSeg;
111 	/**
112 	 * The mousedown target
113 	 */
114 	protected Element target;
115 	/**
116 	 * The mousedown point in user space
117 	 */
118 	protected OMSVGPoint p0;
119 	/**
120 	 * The transform from screen coordinates to
121 	 * manipulator coordinates when a mousedown event occurs
122 	 */
123 	protected OMSVGMatrix m;
124 	/**
125 	 * To catch selection changes
126 	 */
127 	protected SelectionChangedProxy<SVGSegModel> selChangeProxy = new SelectionChangedProxy<SVGSegModel>(this);
128 	/**
129 	 * Event registration for the scaling handler
130 	 */
131 	protected HandlerRegistration scalingHandlerReg;
132 
133 	public EditPathGeometryManipulator() {
134 		segments = new ArrayList<SVGSegRep>();
135 		repToModel = new HashMap<SVGSegRep, SVGSegModel>();
136 		modelToRep = new HashMap<SVGSegModel, SVGSegRep>();
137 		vertexToSeg = new HashMap<Element, SVGSegRep>();
138 		tangentToSeg = new HashMap<Element, SVGSegRep>();
139 	}
140 	
141 	@Override
142 	public OMSVGElement bind(Record record) {
143 		this.record = record;
144 		SVGPathElementModel model = (SVGPathElementModel) record.getModel();
145 		scalingHandlerReg = model.getOwner().addScalingHandler(this);
146 		svg = model.getOwner().getSvgElement();
147 
148 		path = new OMSVGPathElement();
149 		g = new OMSVGGElement();
150 		Mode.VERTEX.write(g);
151 		g.setClassNameBaseVal(AppBundle.INSTANCE.css().pathGeometryManipulator());
152 		tangentGroup = new OMSVGGElement();
153 		vertexGroup = new OMSVGGElement();
154 		g.appendChild(path);
155 		g.appendChild(tangentGroup);
156 		g.appendChild(vertexGroup);
157 		
158 		monitorModel = true;
159 		model.addChangeListener(this);
160 		getSelectionModel().addSelectionChangedListener(selChangeProxy);
161 		scheduleInit();
162 		return g;
163 	}
164 	
165 
166 	@Override
167 	public void unbind() {
168 		if (g != null) {
169 			Element parent = g.getElement().getParentElement();
170 			if (parent != null) {
171 				parent.removeChild(g.getElement());
172 			}
173 			g = null;
174 			tangentGroup = null;
175 			vertexGroup = null;
176 			target = null;
177 			p0 = null;
178 			m = null;
179 			
180 			SVGPathElementModel model = (SVGPathElementModel) record.getModel();
181 			model.removeChangeListener(this);
182 			getSelectionModel().removeSelectionListener(selChangeProxy);
183 			record = null;
184 			repToModel.clear();
185 			modelToRep.clear();
186 			vertexToSeg.clear();
187 			tangentToSeg.clear();
188 			scalingHandlerReg.removeHandler();
189 		}
190 	}
191 
192 	@Override
193 	public void modelChanged(ChangeEvent event) {
194 		GWT.log("SVGPathManipulator.modelChanged(" + monitorModel + "," + toString() + ")");
195 		if (monitorModel && record != null) {
196 			SVGPathElementModel model = (SVGPathElementModel) record.getModel();
197 			super.modelChanged(event);
198 			
199 			// Clear segment list
200 			path.setAttribute(SVGConstants.SVG_D_ATTRIBUTE, model.getElement().getAttribute(SVGConstants.SVG_D_ATTRIBUTE));
201 			segments.clear();
202 			
203 			// Clear mapping tables
204 			repToModel.clear();
205 			modelToRep.clear();
206 			vertexToSeg.clear();
207 			tangentToSeg.clear();
208 			
209 			// Clear DOM
210 			while (vertexGroup.hasChildNodes()) {
211 				vertexGroup.removeChild(vertexGroup.getLastChild());
212 			};
213 			while (tangentGroup.hasChildNodes()) {
214 				tangentGroup.removeChild(tangentGroup.getLastChild());
215 			};
216 
217 			// Build segment list
218 			SVGSegStore store = model.getSegStore();
219 			List<SVGSegModel> segModels = store.getModels();
220 			OMSVGPathSegList segs = path.getPathSegList();
221 			for (int i = 0, size = segModels.size(); i < size; i++) {
222 				SVGSegModel segModel = segModels.get(i);
223 				OMSVGPathSeg seg = segs.getItem(i);
224 				switch(seg.getPathSegType()) {
225 				    case OMSVGPathSeg.PATHSEG_CLOSEPATH:
226 					  	{
227 					  	}
228 				  		break;
229 				    case OMSVGPathSeg.PATHSEG_MOVETO_ABS:
230 					  	{
231 					  		appendSegment(segModel, new SVGMoveSegRep(this, (OMSVGPathSegMovetoAbs)seg));
232 					  	}
233 				  		break;
234 				    case OMSVGPathSeg.PATHSEG_LINETO_ABS:
235 					  	{
236 							appendSegment(segModel, new SVGLineSegRep(this, (OMSVGPathSegLinetoAbs)seg));
237 					  	}
238 				  		break;
239 				    case OMSVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS:
240 					  	{
241 							appendSegment(segModel, new SVGCubicSegRep(this, (OMSVGPathSegCurvetoCubicAbs)seg));
242 					  	}
243 				  		break;
244 				    case OMSVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS:
245 					  	{
246 							appendSegment(segModel, new SVGQuadraticSegRep(this, (OMSVGPathSegCurvetoQuadraticAbs)seg));
247 					  	}
248 				  		break;
249 				    case OMSVGPathSeg.PATHSEG_ARC_ABS:
250 					  	{
251 							OMSVGPathSegArcAbs arcAbs = (OMSVGPathSegArcAbs)seg;
252 					  	}
253 				  		break;
254 				  	case OMSVGPathSeg.PATHSEG_UNKNOWN:
255 				    case OMSVGPathSeg.PATHSEG_MOVETO_REL:
256 				    case OMSVGPathSeg.PATHSEG_LINETO_REL:
257 				    case OMSVGPathSeg.PATHSEG_CURVETO_CUBIC_REL:
258 				    case OMSVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL:
259 				    case OMSVGPathSeg.PATHSEG_ARC_REL:
260 				    case OMSVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS:
261 				    case OMSVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL:
262 				    case OMSVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS:
263 				    case OMSVGPathSeg.PATHSEG_LINETO_VERTICAL_REL:
264 				    case OMSVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS:
265 				    case OMSVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL:
266 				    case OMSVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS:
267 				    case OMSVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL:
268 				    	GWT.log("Invalid segment type:" + seg.getPathSegTypeAsLetter());
269 				    	assert(false);
270 				  		break;
271 				}
272 			}
273 			
274 			// Connect the segments together
275 			for (int i = 0, size = segments.size(); i < size - 1; i++) {
276 				SVGSegRep seg = segments.get(i);
277 				SVGSegRep nextSeg = segments.get(i + 1);
278 				seg.setNext(nextSeg);
279 				nextSeg.setPrevious(seg);
280 			}
281 			
282 			update(SVGModel.getVertexSize((SVGElementModel) record.getModel()));
283 			processSelectionChanged(new SelectionChangedEvent<SVGSegModel>(getSelectionModel(), getSelectionModel().getSelectedItems()));
284 		}
285 		
286 	}
287 	
288 	public SVGSegModel getModel(SVGSegRep rep) {
289 		SVGSegModel model = repToModel.get(rep);
290 		assert model != null;
291 		return model;
292 	}
293 	public SVGSegRep getRepresentation(SVGSegModel model) {
294 		SVGSegRep rep = modelToRep.get(model);
295 		assert rep != null;
296 		return rep;	
297 	}
298 
299 	@Override
300 	public OMSVGSVGElement getSvg() {
301 		return svg;
302 	}
303 	
304 	public OMSVGGElement getRootGroup() {
305 		return g;
306 	}
307 	
308 	public void appendSegment(SVGSegModel modelSeg, SVGSegRep repSeg) {
309 		segments.add(repSeg);
310 		
311 		OMSVGRectElement vertex = repSeg.getVertex();
312 		vertexGroup.appendChild(vertex);
313 		vertexToSeg.put(vertex.getElement(), repSeg);
314 		
315 		OMSVGGElement tangents = repSeg.getTangents();
316 		tangentGroup.appendChild(tangents);
317 		for (OMSVGElement element : tangents.<OMSVGElement>getChildNodes()) {
318 			tangentToSeg.put(element.getElement(), repSeg);
319 		}
320 		
321 		modelToRep.put(modelSeg, repSeg);
322 		repToModel.put(repSeg, modelSeg);
323 	}
324 	
325 	public SVGSegStore getSegStore() {
326 		return ((SVGPathElementModel) record.getModel()).getSegStore();
327 	}
328 	
329 	public GridSelectionModel<SVGSegModel> getSelectionModel() {
330 		return getSegStore().getSelectionModel();
331 	}
332 	
333 	public void selectSegment(SVGSegRep segment, boolean shiftDown, boolean ctrlDown) {
334 		GridSelectionModel<SVGSegModel> selectionModel = getSelectionModel();
335 		boolean selected = segment.getState() == VertexState.SELECTED;
336 		if (shiftDown) {
337 			List<SVGSegModel> selectedSegs = selectionModel.getSelectedItems();
338 			if (selectedSegs.size() > 0) {
339 				// Select all the segments in the range defined by the
340 				// last selected segment and this segment
341 				int ix1 = segments.indexOf(getRepresentation(selectedSegs.get(selectedSegs.size() - 1)));
342 				int ix2 = segments.indexOf(segment);
343 				for (int i = Math.min(ix1, ix2); i <= Math.max(ix1, ix2); i++) {
344 					SVGSegRep s = segments.get(i);
345 					if (s.getState() == VertexState.NONE) {
346 						selectionModel.select(i, true);
347 					}
348 				}
349 			} else {
350 				// Select segment
351 				selectionModel.select(getModel(segment), true);
352 			}
353 		} else if (ctrlDown) {
354 			// Toggle selection
355 			if (selected) {
356 				selectionModel.deselect(getModel(segment));
357 			} else {
358 				selectionModel.select(getModel(segment), true);
359 			}
360 		} else {
361 			// Clear previous selection
362 			selectionModel.deselectAll();
363 			
364 			// Select segment
365 			selectionModel.select(getModel(segment), false);
366 		}
367 	}
368 
369 	@Override
370 	public boolean processSelectionChanged(SelectionChangedEvent<SVGSegModel> se) {
371 		List<SVGSegModel> selection = (se == null) ? null : se.getSelection();
372 		Set<SVGSegModel> selected = (selection != null) ? new HashSet<SVGSegModel>(selection) : new HashSet<SVGSegModel>();
373 		// Clear rep selection
374 		for (SVGSegRep rep : segments) {
375 			rep.setState(selected.contains(getModel(rep)) ? VertexState.SELECTED : VertexState.NONE);
376 		}
377 		return true;
378 	}
379 
380 	@Override
381 	public boolean processMouseDown(MouseDownEvent event) {
382 		GWT.log("SVGPathManipulator.processMouseDown");
383 		Element eventTarget = event.getNativeEvent().getEventTarget().cast();
384 		if (SVGConstants.SVG_SVG_TAG.equals(eventTarget.getTagName())) {
385 			// Clear the selection
386 			getSelectionModel().deselectAll();
387 		} else if (path.getElement() == eventTarget) {
388 			Mode state = getMode();
389 			if (state == Mode.VERTEX) {
390 				Mode.TANGENT.write(g);
391 				update(SVGModel.getVertexSize((SVGElementModel) record.getModel()));
392 			} else if (state == Mode.TANGENT) {
393 				Mode.VERTEX.write(g);
394 				update(SVGModel.getVertexSize((SVGElementModel) record.getModel()));
395 			} else {
396 				assert false;
397 			}
398 		} else {
399 			m = path.getScreenCTM().inverse();
400 			p0 = getCoordinates(event, m);
401 			SVGSegRep segment = vertexToSeg.get(eventTarget);
402 			if (segment != null) {
403 				if (event.isControlKeyDown() || segment.getState() == VertexState.NONE) {
404 					selectSegment(segment, event.isShiftKeyDown(), event.isControlKeyDown());
405 				}
406 				if (segment.getState() == VertexState.SELECTED) {
407 					target = eventTarget;
408 				}
409 			} else if (tangentToSeg.containsKey(eventTarget)) {
410 				target = eventTarget;
411 			}
412 		}
413 		event.preventDefault();
414 		event.stopPropagation();
415 		return true;
416 	}
417 
418 	@Override
419 	public boolean processMouseMove(MouseMoveEvent event) {
420 		if (target != null) {
421 //			GWT.log("SVGPathManipulator.processMouseMove");
422 			OMSVGPoint p1 = getCoordinates(event, m);
423 			OMSVGPoint delta = p1.substract(p0, svg.createSVGPoint());
424 			p1.assignTo(p0);
425 			
426 			SVGSegRep segment = tangentToSeg.get(target);
427 			float hs = SVGModel.getVertexSize((SVGElementModel) record.getModel());
428 			if (segment != null) {
429 				segment.processMouseMove(delta, target, hs, event.isControlKeyDown());
430 			} else {
431 				GridSelectionModel<SVGSegModel> selectionModel = getSelectionModel();
432 				for (SVGSegModel model : selectionModel.getSelectedItems()) {
433 					getRepresentation(model).processMouseMove(delta, null, hs, false);
434 				}
435 			}
436 			event.preventDefault();
437 			event.stopPropagation();
438 		}
439 		return true;
440 	}
441 
442 	@Override
443 	public boolean processMouseUp(MouseUpEvent event) {
444 		if (target != null) {
445 			GWT.log("SVGPathManipulator.processMouseUp");
446 //			monitorModel = false;
447 			record.beginEdit();
448 			record.set(SVGConstants.SVG_D_ATTRIBUTE, path.getAttribute(SVGConstants.SVG_D_ATTRIBUTE));
449 			record.endEdit();
450 			record.commit(false);
451 //			monitorModel = true;
452 			target = null;
453 		}
454 		event.preventDefault();
455 		event.stopPropagation();
456 		return true;
457 	}
458 	
459 	@Override
460 	public Mode getMode() {
461 		return Mode.read(g);
462 	}
463 	
464 	public void update(float hs) {
465 		for (int i = 0; i < segments.size(); i++) {
466 			segments.get(i).update(hs);
467 		}
468 	}
469 
470 	@Override
471 	public List<SVGSegRep> getSegments() {
472 		return segments;
473 	}
474 
475 	@Override
476 	public void onScale(ScalingEvent event) {
477 		update(SVGModel.getVertexSize((SVGElementModel) record.getModel()));
478 	}
479 }