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.add;
19  
20  import java.util.ArrayList;
21  import java.util.List;
22  
23  import org.vectomatic.dom.svg.OMSVGElement;
24  import org.vectomatic.dom.svg.OMSVGGElement;
25  import org.vectomatic.dom.svg.OMSVGPathElement;
26  import org.vectomatic.dom.svg.OMSVGPathSeg;
27  import org.vectomatic.dom.svg.OMSVGPathSegList;
28  import org.vectomatic.dom.svg.OMSVGPoint;
29  import org.vectomatic.dom.svg.OMSVGSVGElement;
30  import org.vectomatic.dom.svg.impl.SVGElement;
31  import org.vectomatic.dom.svg.utils.SVGConstants;
32  import org.vectomatic.svg.edit.client.AppBundle;
33  import org.vectomatic.svg.edit.client.AppConstants;
34  import org.vectomatic.svg.edit.client.SvgrealApp;
35  import org.vectomatic.svg.edit.client.command.FactoryInstantiatorBase;
36  import org.vectomatic.svg.edit.client.command.IFactoryInstantiator;
37  import org.vectomatic.svg.edit.client.command.path.IPathRepOwner;
38  import org.vectomatic.svg.edit.client.command.path.SVGCloseSegRep;
39  import org.vectomatic.svg.edit.client.command.path.SVGCubicSegRep;
40  import org.vectomatic.svg.edit.client.command.path.SVGLineSegRep;
41  import org.vectomatic.svg.edit.client.command.path.SVGMoveSegRep;
42  import org.vectomatic.svg.edit.client.command.path.SVGQuadraticSegRep;
43  import org.vectomatic.svg.edit.client.command.path.SVGSegRep;
44  import org.vectomatic.svg.edit.client.engine.SVGModel;
45  import org.vectomatic.svg.edit.client.event.MouseDownProcessor;
46  import org.vectomatic.svg.edit.client.event.MouseMoveProcessor;
47  import org.vectomatic.svg.edit.client.event.MouseUpProcessor;
48  import org.vectomatic.svg.edit.client.event.ScalingEvent;
49  import org.vectomatic.svg.edit.client.event.ScalingHandler;
50  import org.vectomatic.svg.edit.client.model.ModelConstants;
51  import org.vectomatic.svg.edit.client.model.svg.SVGPathSegType;
52  
53  import com.extjs.gxt.ui.client.Style.IconAlign;
54  import com.extjs.gxt.ui.client.event.ButtonEvent;
55  import com.extjs.gxt.ui.client.event.SelectionListener;
56  import com.extjs.gxt.ui.client.util.Margins;
57  import com.extjs.gxt.ui.client.util.Padding;
58  import com.extjs.gxt.ui.client.util.Rectangle;
59  import com.extjs.gxt.ui.client.widget.LayoutContainer;
60  import com.extjs.gxt.ui.client.widget.Window;
61  import com.extjs.gxt.ui.client.widget.button.Button;
62  import com.extjs.gxt.ui.client.widget.button.ToggleButton;
63  import com.extjs.gxt.ui.client.widget.layout.FitLayout;
64  import com.extjs.gxt.ui.client.widget.layout.HBoxLayout;
65  import com.extjs.gxt.ui.client.widget.layout.HBoxLayout.HBoxLayoutAlign;
66  import com.extjs.gxt.ui.client.widget.layout.HBoxLayoutData;
67  import com.extjs.gxt.ui.client.widget.layout.VBoxLayout;
68  import com.extjs.gxt.ui.client.widget.layout.VBoxLayout.VBoxLayoutAlign;
69  import com.extjs.gxt.ui.client.widget.layout.VBoxLayoutData;
70  import com.google.gwt.core.client.GWT;
71  import com.google.gwt.event.dom.client.MouseDownEvent;
72  import com.google.gwt.event.dom.client.MouseMoveEvent;
73  import com.google.gwt.event.dom.client.MouseUpEvent;
74  import com.google.gwt.event.shared.HandlerRegistration;
75  import com.google.gwt.user.client.ui.AbstractImagePrototype;
76  
77  /**
78   * Command factory to add new paths to the the SVG model.
79   * @author laaglu
80   */
81  public class AddPathCommandFactory extends AddCommandFactoryBase implements IPathRepOwner, MouseDownProcessor, MouseMoveProcessor, MouseUpProcessor, ScalingHandler {
82  	@SuppressWarnings("serial")
83  	public static final IFactoryInstantiator<AddPathCommandFactory> INSTANTIATOR = new FactoryInstantiatorBase<AddPathCommandFactory>(ModelConstants.INSTANCE.addPathCmdFactory(), ModelConstants.INSTANCE.addPathCmdFactoryDesc()) {
84  		@Override
85  		public AddPathCommandFactory create() {
86  			return new AddPathCommandFactory();
87  		}
88  	};
89  
90  	@Override
91  	public IFactoryInstantiator<?> getInstantiator() {
92  		return INSTANTIATOR;
93  	}
94  
95  	class AddPathToolBar extends Window {
96  		private ToggleButton[] pathButtons;
97  		private Button undoButton, redoButton, commitButton;
98  		private SelectionListener<ButtonEvent> segTypeListener = new SelectionListener<ButtonEvent>() {
99  			@Override
100 			public void componentSelected(ButtonEvent ce) {
101 				ToggleButton button = (ToggleButton)ce.getButton();
102 				if (button.isPressed()) {
103 					setSegmentType(button.getToolTip().getToolTipConfig().getText());
104 				}
105 			}
106 		};
107 		public AddPathToolBar() {
108 			ModelConstants constants = ModelConstants.INSTANCE;
109 			AbstractImagePrototype icons[] = {
110 				AbstractImagePrototype.create(AppBundle.INSTANCE.pathMove()),
111 				AbstractImagePrototype.create(AppBundle.INSTANCE.pathLine()),
112 				AbstractImagePrototype.create(AppBundle.INSTANCE.pathQuadratic()),
113 				AbstractImagePrototype.create(AppBundle.INSTANCE.pathCubic())
114 			};
115 			String[] toolTips = {
116 					constants.segMoveTo(),
117 					constants.segLineTo(),
118 					constants.segQuadraticTo(),
119 					constants.segCubicTo()
120 			};
121 			LayoutContainer pathContainer = new LayoutContainer();
122 	        HBoxLayout hboxLayout = new HBoxLayout();  
123 	        hboxLayout.setHBoxLayoutAlign(HBoxLayoutAlign.MIDDLE);  
124 	        pathContainer.setLayout(hboxLayout);
125 	        HBoxLayoutData flex = new HBoxLayoutData(new Margins(0, 5, 0, 0));  
126 	        flex.setFlex(1);  
127 			pathButtons = new ToggleButton[icons.length];
128 			for (int i = 0; i < icons.length; i++) {
129 				pathButtons[i] = new ToggleButton();
130 				pathButtons[i].setIcon(icons[i]);
131 				pathButtons[i].setIconAlign(IconAlign.TOP);
132 				pathButtons[i].setToolTip(toolTips[i]);
133 				pathButtons[i].addSelectionListener(segTypeListener);
134 				pathButtons[i].setToggleGroup("path");
135 				pathButtons[i].setSize(20, 24);
136 		        pathContainer.add(pathButtons[i], flex);
137 			}
138 			Button closePathButton = new Button();
139 			closePathButton.setIcon(AbstractImagePrototype.create(AppBundle.INSTANCE.pathClose()));
140 			closePathButton.setIconAlign(IconAlign.TOP);
141 			closePathButton.setToolTip(constants.segClose());
142 			closePathButton.addSelectionListener(new SelectionListener<ButtonEvent>() {
143 				@Override
144 				public void componentSelected(ButtonEvent ce) {
145 					removeDanglingSegment();
146 					appendSegment(new SVGCloseSegRep(AddPathCommandFactory.this, path.createSVGPathSegClosePath()));
147 					validateSegment(SVGModel.getVertexSize(path));
148 					setSegmentType(segType);
149 				}
150 			});
151 			closePathButton.setSize(20, 24);
152 	        HBoxLayoutData flex2 = new HBoxLayoutData(new Margins(0));  
153 	        flex2.setFlex(1);  
154 	        pathContainer.add(closePathButton, flex2);
155 			
156 
157 			undoButton = new Button();
158 			undoButton.setIcon(AbstractImagePrototype.create(AppBundle.INSTANCE.undo()));
159 			undoButton.setIconAlign(IconAlign.TOP);
160 			undoButton.addSelectionListener(new SelectionListener<ButtonEvent>() {
161 				@Override
162 				public void componentSelected(ButtonEvent ce) {
163 					undoSegment();
164 				}
165 			});
166 			redoButton = new Button();
167 			redoButton.setIcon(AbstractImagePrototype.create(AppBundle.INSTANCE.redo()));
168 			redoButton.setIconAlign(IconAlign.TOP);
169 			redoButton.addSelectionListener(new SelectionListener<ButtonEvent>() {
170 				@Override
171 				public void componentSelected(ButtonEvent ce) {
172 					redoSegment();
173 				}
174 			});
175 			LayoutContainer urContainer = new LayoutContainer();
176 	        HBoxLayout urHboxLayout = new HBoxLayout();  
177 	        urHboxLayout.setHBoxLayoutAlign(HBoxLayoutAlign.MIDDLE);  
178 	        urContainer.setLayout(urHboxLayout);
179 	        urContainer.add(undoButton, flex);
180 			urContainer.add(redoButton, flex2);
181 
182 			commitButton = new Button();
183 			commitButton.setText(AppConstants.INSTANCE.commitButton());
184 			commitButton.addSelectionListener(new SelectionListener<ButtonEvent>() {
185 				@Override
186 				public void componentSelected(ButtonEvent ce) {
187 					commit();
188 				}
189 			});
190 
191 	        LayoutContainer container = new LayoutContainer();
192 	        VBoxLayout layout = new VBoxLayout();  
193 	        layout.setPadding(new Padding(5));  
194 	        layout.setVBoxLayoutAlign(VBoxLayoutAlign.STRETCH);
195 	        VBoxLayoutData vbl1 = new VBoxLayoutData(new Margins(0, 0, 5, 0));
196 	        VBoxLayoutData vbl2 = new VBoxLayoutData(new Margins(0));
197 	        container.setLayout(layout);
198 	        container.add(pathContainer, vbl1);
199 	        container.add(urContainer, vbl1);
200 	        container.add(commitButton, vbl2);
201 	        
202 	        int w = 175, h = 120;
203 	        setMinWidth(w);
204 	        setMinHeight(h);
205 	        setWidth(w);
206 	        setHeight(h);
207 	        setResizable(false);
208 	        setLayout(new FitLayout());
209 	        add(container);
210 			setClosable(false);
211 			setBorders(true);
212 			
213 	        Rectangle rect = SvgrealApp.getApp().getRectangle();
214 			setPagePosition(rect.x + rect.width - w -5, rect.y + rect.height - h - 5);
215 		}
216 		public ToggleButton getPathButton(String segType) {
217 			for (ToggleButton button : pathButtons) {
218 				if (segType.equals(button.getToolTip().getToolTipConfig().getText())) {
219 					return button;
220 				}
221 			}
222 			return null;
223 		}
224 	}
225 
226 	enum State {
227 		MOVE {
228 			boolean processMouseDown(AddPathCommandFactory factory, OMSVGPoint p, float hs) {
229 				factory.appendSegment(new SVGMoveSegRep(factory, factory.path.createSVGPathSegMovetoAbs(p.getX(), p.getY())));
230 				factory.validateSegment(hs);
231 				return true;
232 			}
233 		},
234 		LINE {
235 			boolean processMouseDown(AddPathCommandFactory factory, OMSVGPoint p, float hs) {
236 				factory.updateStatus(ModelConstants.INSTANCE.addPathCmdFactoryLineP2());
237 				if (factory.getSegCount() == 0) {
238 					factory.appendSegment(new SVGMoveSegRep(factory, factory.path.createSVGPathSegMovetoAbs(p.getX(), p.getY())));
239 					factory.validateSegment(hs);
240 				}
241 				if (factory.danglingSegment() != null) {
242 					factory.validateSegment(hs);
243 				}
244 				factory.appendSegment(new SVGLineSegRep(factory, factory.path.createSVGPathSegLinetoAbs(p.getX(), p.getY())));
245 				return true;
246 			}
247 			boolean processMouseMove(AddPathCommandFactory factory, OMSVGPoint delta, float hs) {
248 				SVGSegRep segRep = factory.danglingSegment();
249 				if (segRep != null) {
250 					// Update the second point
251 					segRep.updateEnd(delta, hs);
252 				}
253 				return true;
254 			}
255 		},
256 		QUADRATIC {
257 			@Override
258 			boolean processMouseDown(AddPathCommandFactory factory, OMSVGPoint p, float hs) {
259 				// Create the first point and first tangent
260 				if (factory.getSegCount() == 0) {
261 					factory.appendSegment(new SVGMoveSegRep(factory, factory.path.createSVGPathSegMovetoAbs(p.getX(), p.getY())));
262 					factory.validateSegment(hs);
263 				}
264 				if (factory.danglingSegment() != null) {
265 					factory.validateSegment(hs);
266 				}
267 				factory.updateStatus(ModelConstants.INSTANCE.addPathCmdFactoryQuadraticCp1b());
268 				SVGSegRep lastSegRep = factory.lastSegment();
269 				SVGSegRep segRep = factory.appendSegment(new SVGQuadraticSegRep(factory, factory.path.createSVGPathSegCurvetoQuadraticAbs(lastSegRep.getX(), lastSegRep.getY(), p.getX(), p.getY())));
270 				segRep.setCp1(p, hs);
271 				if (factory.ctrlPressed) {
272 					SVGSegRep prevSegRep = factory.previousCurve();
273 					if (prevSegRep != null) {
274 						prevSegRep.setCp2(factory.owner.getSvgElement().createSVGPoint(2 * segRep.getX() - p.getX(), 2 * segRep.getY() -p.getY()), hs);
275 					}
276 				}
277 				return true;
278 			}
279 			
280 			@Override
281 			boolean processMouseMove(AddPathCommandFactory factory, OMSVGPoint delta, float hs) {
282 				SVGSegRep segRep = factory.danglingSegment();
283 				if (segRep != null) {
284 					if (factory.mousePressed) {
285 						// Update the first tangent
286 						segRep.processMouseMove(delta, segRep.getCp1(), hs, false);
287 						if (factory.ctrlPressed) {
288 							SVGSegRep prevSegRep = factory.previousCurve();
289 							if (prevSegRep != null) {
290 								prevSegRep.processMouseMove(factory.owner.getSvgElement().createSVGPoint(-delta.getX(), -delta.getY()), prevSegRep.getCp2(), hs, false);
291 							}
292 						}
293 					} else {
294 						// Update the endpoint
295 						segRep.setX(segRep.getX() + delta.getX());
296 						segRep.setY(segRep.getY() + delta.getY());
297 						segRep.update(hs);
298 					}
299 				}
300 				return true;
301 			}
302 			
303 			@Override
304 			boolean processMouseUp(AddPathCommandFactory factory, OMSVGPoint p, float hs) {
305 				factory.updateStatus(ModelConstants.INSTANCE.addPathCmdFactoryQuadraticP2());
306 				SVGSegRep segRep = factory.danglingSegment();
307 				factory.p0.setX(segRep.getX());
308 				factory.p0.setY(segRep.getY());
309 				return true;
310 			}
311 		},
312 		CUBIC1 {
313 			@Override
314 			boolean processMouseDown(AddPathCommandFactory factory, OMSVGPoint p, float hs) {
315 				factory.updateStatus(ModelConstants.INSTANCE.addPathCmdFactoryCubicCp1b());
316 				// Create the first point and first tangent
317 				if (factory.getSegCount() == 0) {
318 					factory.appendSegment(new SVGMoveSegRep(factory, factory.path.createSVGPathSegMovetoAbs(p.getX(), p.getY())));
319 					factory.validateSegment(hs);
320 				}
321 				SVGSegRep segRep = factory.danglingSegment();
322 				if (segRep == null) {
323 					SVGSegRep lastSegRep = factory.lastSegment();
324 					segRep = factory.appendSegment(new SVGCubicSegRep(factory, factory.path.createSVGPathSegCurvetoCubicAbs(lastSegRep.getX(), lastSegRep.getY(), lastSegRep.getX(), lastSegRep.getY(), lastSegRep.getX(), lastSegRep.getY())));
325 				}
326 				segRep.setCp1(p, hs);
327 				if (factory.ctrlPressed) {
328 					SVGSegRep prevSegRep = factory.previousCurve();
329 					if (prevSegRep != null) {
330 						prevSegRep.setCp2(factory.owner.getSvgElement().createSVGPoint(2 * segRep.getX() - p.getX(), 2 * segRep.getY() - p.getY()), hs);
331 					}
332 				}
333 				return true;
334 			}
335 			
336 			@Override
337 			boolean processMouseMove(AddPathCommandFactory factory, OMSVGPoint delta, float hs) {
338 				if (factory.mousePressed) {
339 					// Update the first tangent
340 					SVGSegRep segRep = factory.danglingSegment();
341 					segRep.processMouseMove(delta, segRep.getCp1(), hs, false);
342 					if (factory.ctrlPressed) {
343 						SVGSegRep prevSegRep = factory.previousCurve();
344 						if (prevSegRep != null) {
345 							prevSegRep.processMouseMove(factory.owner.getSvgElement().createSVGPoint(-delta.getX(), -delta.getY()), prevSegRep.getCp2(), hs, false);
346 						}
347 					}
348 				}
349 				return true;
350 			}
351 			
352 			@Override
353 			boolean processMouseUp(AddPathCommandFactory factory, OMSVGPoint p, float hs) {
354 				factory.updateStatus(ModelConstants.INSTANCE.addPathCmdFactoryCubicCp2a());
355 				factory.setState(State.CUBIC2);
356 				SVGSegRep segRep = factory.danglingSegment();
357 				factory.p0.setX(segRep.getX());
358 				factory.p0.setY(segRep.getY());
359 				return true;
360 			}
361 		},
362 		CUBIC2 {
363 			@Override
364 			boolean processMouseDown(AddPathCommandFactory factory, OMSVGPoint p, float hs) {
365 				factory.updateStatus(ModelConstants.INSTANCE.addPathCmdFactoryCubicCp2b());
366 				if (factory.ctrlPressed) {
367 					// Continuity modef
368 					SVGSegRep segRep = factory.danglingSegment();
369 					factory.validateSegment(hs);
370 					factory.appendSegment(new SVGCubicSegRep(factory, factory.path.createSVGPathSegCurvetoCubicAbs(segRep.getX(), segRep.getY(), segRep.getX(), segRep.getY(), segRep.getX(), segRep.getY())));
371 				}
372 				return true;
373 			}
374 		
375 			@Override
376 			boolean processMouseMove(AddPathCommandFactory factory, OMSVGPoint delta, float hs) {
377 				SVGSegRep segRep = factory.danglingSegment();
378 				if (factory.ctrlPressed) {
379 					// Update the first tangent and the second tangent of the previous segment
380 					segRep.processMouseMove(delta, segRep.getCp1(), hs, false);
381 					SVGSegRep prevSegRep = factory.previousCurve();
382 					if (prevSegRep != null) {
383 						prevSegRep.processMouseMove(factory.owner.getSvgElement().createSVGPoint(-delta.getX(), -delta.getY()), prevSegRep.getCp2(), hs, false);
384 					}
385 				} else if (factory.mousePressed) {
386 					// Update the second tangent
387 					segRep.processMouseMove(delta, segRep.getCp2(), hs, false);
388 				} else {
389 					// Update the second point
390 					segRep.updateEnd(delta, hs);
391 				}
392 				return true;
393 			}
394 
395 			@Override
396 			boolean processMouseUp(AddPathCommandFactory factory, OMSVGPoint p, float hs) {
397 				SVGSegRep segRep = factory.danglingSegment();
398 				if (factory.ctrlPressed) {
399 					factory.updateStatus(ModelConstants.INSTANCE.addPathCmdFactoryCubicCp2a());
400 					factory.p0.setX(segRep.getX());
401 					factory.p0.setY(segRep.getY());
402 				} else {
403 					factory.updateStatus(ModelConstants.INSTANCE.addPathCmdFactoryCubicCp1a());
404 					// Create the next cubic segment
405 					factory.validateSegment(hs);
406 					factory.appendSegment(new SVGCubicSegRep(factory, factory.path.createSVGPathSegCurvetoCubicAbs(segRep.getX(), segRep.getY(), segRep.getX(), segRep.getY(), segRep.getX(), segRep.getY())));
407 					factory.setState(CUBIC1);
408 				}
409 				return true;
410 			}
411 		};
412 		boolean processMouseDown(AddPathCommandFactory factory, OMSVGPoint p, float hs) {
413 			return false;
414 		}
415 		boolean processMouseMove(AddPathCommandFactory factory, OMSVGPoint delta, float hs) {
416 			return false;
417 		}
418 		boolean processMouseUp(AddPathCommandFactory factory, OMSVGPoint p, float hs) {
419 			return false;
420 		}
421 	}
422 
423 	/**
424 	 * A toolbar to choose the type of the current segment
425 	 */
426 	private AddPathToolBar toolBar;
427 	/**
428 	 * The group where SVG elements representing this
429 	 * manipulator are nested
430 	 */
431 	protected OMSVGGElement g;
432 	/**
433 	 * A group for elements representing tangents
434 	 */
435 	protected OMSVGGElement tangentGroup;
436 	/**
437 	 * A group for elements representing vertices
438 	 */
439 	protected OMSVGGElement vertexGroup;
440 	/**
441 	 * The path representation
442 	 */
443 	protected OMSVGPathElement path;
444 	/**
445 	 * The path segment list
446 	 */
447 	OMSVGPathSegList segList;
448 	/**
449 	 * The path segment representations
450 	 */
451 	protected List<SVGSegRep> segments;
452 	/**
453 	 * The current mode
454 	 */
455 	protected State state;
456 	/**
457 	 * True if the mouse button is pressed, false otherwise
458 	 */
459 	protected boolean mousePressed;
460 	/**
461 	 * True if the ctrl key is pressed, false otherwise
462 	 */
463 	protected boolean ctrlPressed;
464 	/**
465 	 * The type of segment about to be added to the path
466 	 */
467 	protected String segType;
468 	/**
469 	 * The mousedown point in user space
470 	 */
471 	protected OMSVGPoint p0;
472 	/**
473 	 * The segment current being edited by the end-user,
474 	 * but not yet finished
475 	 */
476 	protected SVGSegRep danglingSegment;
477 	/**
478 	 * Index of the last visible segment in the segment stack
479 	 */
480 	protected int last;
481 	/**
482 	 * Event registration for the scaling handler
483 	 */
484 	protected HandlerRegistration scalingHandlerReg;
485 
486 	public AddPathCommandFactory() {
487 //		setMode(Mode.CUBIC1);
488 		segments = new ArrayList<SVGSegRep>();
489 		toolBar = new AddPathToolBar();
490 	}
491 
492 	@Override
493 	public void start(Object requester) {
494 		GWT.log("AddPathCommandFactory.start(" + requester + ")");
495 		super.start(requester);
496 		toolBar.getPathButton(ModelConstants.INSTANCE.segLineTo()).toggle(true);
497 		setSegmentType(ModelConstants.INSTANCE.segLineTo());
498 		toolBar.show();
499 	}
500 
501 	@Override
502 	public void stop() {
503 		GWT.log("AddPathCommandFactory.stop()");
504 		clear();
505 		toolBar.hide();
506 		super.stop();
507 	}
508 
509 	public void setSegmentType(String segType) {
510 		GWT.log("AddPathCommandFactory.setSegmentType(" + segType + ")");
511 		this.segType = segType;
512 		removeDanglingSegment();
513 		ModelConstants constants = ModelConstants.INSTANCE;
514 		switch(SVGPathSegType.INSTANCE.fromName(segType)) {
515 			case OMSVGPathSeg.PATHSEG_MOVETO_ABS:
516 				setState(State.MOVE);
517 				updateStatus(constants.addPathCmdFactoryMove());
518 				break;
519 			case OMSVGPathSeg.PATHSEG_LINETO_ABS: 
520 				setState(State.LINE);
521 				if (last == 0) {
522 					updateStatus(constants.addPathCmdFactoryLineFirst());
523 				} else {
524 					updateStatus(constants.addPathCmdFactoryLineP2());
525 					SVGSegRep segRep = lastSegment();
526 					appendSegment(new SVGLineSegRep(this, path.createSVGPathSegLinetoAbs(segRep.getX(), segRep.getY())));
527 					p0.setX(segRep.getX());
528 					p0.setY(segRep.getY());
529 				}
530 				break;
531 			case OMSVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS: 
532 				setState(State.QUADRATIC);
533 				updateStatus(last == 0 ? constants.addPathCmdFactoryQuadraticCp1First() : constants.addPathCmdFactoryQuadraticCp1a());
534 				break;
535 			case OMSVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS: 
536 				setState(State.CUBIC1);
537 				updateStatus(last == 0 ? constants.addPathCmdFactoryCubicCp1First() : constants.addPathCmdFactoryCubicCp1a());
538 				break;
539 		}
540 	}
541 
542 	public void update() {
543 		toolBar.undoButton.setEnabled(last > 0);
544 		toolBar.redoButton.setEnabled(last < segments.size());
545 		toolBar.commitButton.setEnabled(last > 0);
546 	}
547 	
548 	public void undoSegment() {
549 		GWT.log("AddPathCommandFactory.undoSegment()");
550 		removeDanglingSegment();
551 		SVGSegRep segRep = lastSegment();
552 		last--;
553 		segList.removeItem(last);
554 		vertexGroup.removeChild(segRep.getVertex());
555 		tangentGroup.removeChild(segRep.getTangents());
556 		update();
557 		setSegmentType(segType);
558 	}
559 	
560 	public void redoSegment() {
561 		GWT.log("AddPathCommandFactory.redoSegment()");
562 		removeDanglingSegment();
563 		last++;
564 		SVGSegRep segRep = lastSegment();
565 		segList.appendItem(segRep.getElement());
566 		vertexGroup.appendChild(segRep.getVertex());
567 		tangentGroup.appendChild(segRep.getTangents());
568 		update();
569 		setSegmentType(segType);
570 	}
571 
572 	public void commit() {
573 		GWT.log("AddPathCommandFactory.commit()");
574 		if (owner != null) {
575 			removeDanglingSegment();
576 			g.removeChild(path);
577 			owner.getTwinGroup().appendChild(path);
578 			applyCssContextStyle((SVGElement) path.getElement().cast());
579 //			path.getStyle().setSVGProperty(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE);
580 //			path.getStyle().setSVGProperty(SVGConstants.CSS_STROKE_PROPERTY, SVGConstants.CSS_BLACK_VALUE);
581 			path.getStyle().setSVGProperty(SVGConstants.CSS_VISIBILITY_PROPERTY, SVGConstants.CSS_VISIBLE_VALUE);
582 			createCommand(path);
583 			clear();
584 			setSegmentType(segType);
585 		}
586 	}
587 
588 	public void clear() {
589 		if (owner != null) {
590 			scalingHandlerReg.removeHandler();
591 			OMSVGElement parent = owner.getTwinGroup();
592 			parent.removeChild(g);
593 			owner = null;
594 			g = null;
595 			path = null;
596 			tangentGroup = null;
597 			vertexGroup = null;
598 			segList = null;
599 			segments.clear();
600 			danglingSegment = null;
601 			last = 0;
602 			update();
603 		}
604 	}
605 
606 	@Override
607 	public boolean processMouseDown(MouseDownEvent event) {
608 //		GWT.log("AddPathCommandFactory.processMouseDown(" + state + ")");
609 		if (owner == null) {
610 			owner = SvgrealApp.getApp().getActiveModel();
611 			scalingHandlerReg = owner.addScalingHandler(this);
612 			OMSVGElement parent = owner.getTwinGroup();
613 			path = new OMSVGPathElement();
614 			segList = path.getPathSegList();
615 			g = new OMSVGGElement();
616 			Mode.TANGENT.write(g);
617 			g.setClassNameBaseVal(AppBundle.INSTANCE.css().pathGeometryManipulator());
618 			tangentGroup = new OMSVGGElement();
619 			vertexGroup = new OMSVGGElement();
620 			g.appendChild(path);
621 			g.appendChild(tangentGroup);
622 			g.appendChild(vertexGroup);
623 			parent.appendChild(g);
624 
625 		}
626 		
627 		mousePressed = true;
628 		ctrlPressed = event.isControlKeyDown();
629 		p0 = owner.getCoordinates(event, true);
630 		
631 		boolean processed = state.processMouseDown(this, owner.getCoordinates(event, true), SVGModel.getVertexSize(path));
632 		if (processed) {
633 			event.stopPropagation();
634 			event.preventDefault();
635 		}
636 		return processed;
637 	}
638 
639 	@Override
640 	public boolean processMouseMove(MouseMoveEvent event) {
641 		if (p0 == null) {
642 			return false;
643 		}
644 //		GWT.log("AddPathCommandFactory.processMouseMove(" + state + ")");
645 		OMSVGPoint p1 = owner.getCoordinates(event, true);
646 		OMSVGPoint delta = p1.substract(p0, owner.getSvgElement().createSVGPoint());
647 		p1.assignTo(p0);
648 		float hs = SVGModel.getVertexSize(path);
649 		boolean processed = state.processMouseMove(this, delta, hs);
650 		if (processed) {
651 			event.stopPropagation();
652 			event.preventDefault();
653 		}
654 		if (danglingSegment != null) {
655 			if (danglingAtOrigin(hs)) {
656 				danglingSegment.setState(VertexState.CLOSING);
657 			} else {
658 				danglingSegment.setState(VertexState.NONE);
659 			}
660 		}
661 		return processed;
662 	}
663 	
664 	private boolean danglingAtOrigin(float hs) {
665 		float x = danglingSegment.getX() - segments.get(0).getX();
666 		float y = danglingSegment.getY() - segments.get(0).getY();
667 		return x*x + y*y < hs*hs;
668 	}
669 	
670 	@Override
671 	public boolean processMouseUp(MouseUpEvent event) {
672 //		GWT.log("AddPathCommandFactory.processMouseUp(" + state + ")");
673 		boolean processed = state.processMouseUp(this, owner.getCoordinates(event, true), SVGModel.getVertexSize(path));
674 		if (processed) {
675 			event.stopPropagation();
676 			event.preventDefault();
677 		}
678 		mousePressed = false;
679 		ctrlPressed = false;
680 		return processed;
681 	}
682 
683 	public void setState(State state) {
684 		this.state = state;
685 	}
686 	
687 	@Override
688 	public OMSVGSVGElement getSvg() {
689 		return owner.getSvgElement();
690 	}
691 
692 	@Override
693 	public List<SVGSegRep> getSegments() {
694 		return segments;
695 	}
696 	
697 	public int getSegCount() {
698 		return last;
699 	}
700 	
701 	public SVGSegRep danglingSegment() {
702 		return danglingSegment;
703 	}
704 
705 	public SVGSegRep lastSegment() {
706 		return last > 0 ? segments.get(last - 1) : null;
707 	}
708 
709 	public SVGSegRep previousCurve() {
710 		SVGSegRep segRep = lastSegment();
711 		if (segRep instanceof SVGQuadraticSegRep || segRep instanceof SVGCubicSegRep) {
712 			return segRep;
713 		}
714 		return null;
715 	}
716 
717 	@Override
718 	public Mode getMode() {
719 		return Mode.TANGENT;
720 	}
721 
722 	/**
723 	 * Appends a new segment to the list. The
724 	 * new segments is qualified as 'dangling', as
725 	 * it is still being edited by the end user and not
726 	 * yet permanently added to the path
727 	 * @param segRep The segment to add
728 	 * @return The added segment
729 	 */
730 	public SVGSegRep appendSegment(SVGSegRep segRep) {
731 		SVGSegRep prevSeg = lastSegment();
732 		segList.appendItem(segRep.getElement());
733 		if (prevSeg != null) {
734 			prevSeg.setNext(segRep);
735 			segRep.setPrevious(prevSeg);
736 		}
737 		vertexGroup.appendChild(segRep.getVertex());
738 		tangentGroup.appendChild(segRep.getTangents());
739 		danglingSegment = segRep;
740 		segRep.update(SVGModel.getVertexSize(path));
741 		return segRep;
742 	}
743 	
744 	/**
745 	 * Makes the dangling segment permanent
746 	 * @param hs The vertex size
747 	 */
748 	public void validateSegment(float hs) {
749 		// Invalidate segments in the redo stack
750 		for (int i = segments.size() - 1; i >= last; i--) {
751 			segments.remove(i);
752 		}
753 
754 		if (last > 0 && danglingAtOrigin(hs) && path.getTotalLength() > hs) {
755 			// Auto-fix the segment endpoint if:
756 			// + the segment is not the initial moveto segment
757 			// + the segment terminates at the path origin
758 			// + the path length is not null
759 			danglingSegment.setX(segments.get(0).getX());
760 			danglingSegment.setY(segments.get(0).getY());
761 			danglingSegment.update(hs);
762 		}
763 
764 		segments.add(danglingSegment);
765 		danglingSegment.setState(VertexState.SELECTED);
766 		
767 		danglingSegment = null;
768 		last++;
769 		update();
770 	}
771 	
772 	public void removeDanglingSegment() {
773 		if (danglingSegment != null) {
774 			int index = segList.getNumberOfItems() - 1;
775 			segList.removeItem(index);
776 			vertexGroup.removeChild(danglingSegment.getVertex());
777 			tangentGroup.removeChild(danglingSegment.getTangents());
778 			danglingSegment = null;
779 		}		
780 	}
781 
782 	@Override
783 	public void onScale(ScalingEvent event) {
784 		if (path != null) {
785 			float size = SVGModel.getVertexSize(path);
786 			for (SVGSegRep rep : segments) {
787 				rep.update(size);
788 			}
789 			if (danglingSegment != null) {
790 				danglingSegment.update(size);
791 			}
792 		}
793 	}
794 }