View Javadoc

1   /**********************************************
2    * Copyright (C) 2009 Lukas Laag
3    * This file is part of Vectomatic.
4    * 
5    * Vectomatic 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   * Vectomatic 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 Vectomatic.  If not, see http://www.gnu.org/licenses/
17   **********************************************/
18  package org.vectomatic.client.rep.controller;
19  
20  import java.util.ArrayList;
21  import java.util.List;
22  
23  import org.vectomatic.client.rep.RepApplication;
24  import org.vectomatic.client.rep.command.NewShapeCommand;
25  import org.vectomatic.client.rep.view.Cursor;
26  import org.vectomatic.client.rep.view.DrawingView;
27  import org.vectomatic.common.model.Attribute;
28  import org.vectomatic.common.model.geometry.BezierSegment;
29  import org.vectomatic.common.model.geometry.LineSegment;
30  import org.vectomatic.common.model.geometry.Path;
31  import org.vectomatic.common.model.geometry.Point;
32  import org.vectomatic.common.model.geometry.Segment;
33  import org.vectomatic.common.model.style.Color;
34  
35  /**
36   * Controller to respond to new path requests and turn
37   * them into NewShapeCommand
38   */
39  public class NewPathController extends ControllerBase {
40  	private static final int VERTEX_SIZE = 3;
41  	private static final int CONTROL_POINT_SIZE = 3;
42  	private static final float T = 0.5f;
43  	private enum State {
44  		S0 { 
45  			@Override
46  			State processActivate(NewPathController controller, DrawingView view)  {
47  				view.setCursor(Cursor.CURSOR_CROSSHAIR);
48  				controller._segments.clear();
49  				return S1;
50  			}
51  		},
52  		S1 { 
53  			@Override
54  			State processMouseDown(NewPathController controller, DrawingView view, Point p, int modifiers) {
55  				view.toModelCoordinates(p, controller._p0);
56  				controller._p0.copyTo(controller._p1);
57  				return S2;
58  			} 
59  		},
60  		S2 {
61  			@Override
62  			State processMouseMove(NewPathController controller, DrawingView view, Point p, int modifiers) {
63  				view.toModelCoordinates(p, controller._p1);
64  				return this;
65  			}		
66  			@Override
67  			State processMouseUp(NewPathController controller, DrawingView view, Point p, int modifiers) {
68  				view.toModelCoordinates(p, controller._p1);
69  				if (controller._p1.distance(controller._p0) > T) {
70  					controller._p1.copyTo(controller._p2);
71  					controller._p1.copyTo(controller._p3);
72  					// The user wants to draw a spline tangent
73  					return S3;
74  				}
75  				// The user wants to draw a line segment
76  				return S4;
77  			}
78  		},
79  		S3 {
80  			@Override
81  			State processMouseMove(NewPathController controller, DrawingView view, Point p, int modifiers) {
82  				view.toModelCoordinates(p, controller._p2);
83  				controller._p2.copyTo(controller._p3);
84  				if (controller._segments.size() > 0) {
85  					float tol = view.convertToReferenceLength(VERTEX_SIZE * 2);
86  					if (controller._p2.distance(controller._p0) < tol) {
87  						view.setCursor(Cursor.CURSOR_OPEN_POLYLINE);
88  					} else if (controller._p2.distance(controller._segments.get(0).getStartPoint()) < tol) {
89  						view.setCursor(Cursor.CURSOR_CLOSED_POLYLINE);
90  					} else {
91  						view.setCursor(Cursor.CURSOR_CROSSHAIR);
92  					}
93  				}
94  				return S3;
95  			}
96  			@Override
97  			State processMouseDown(NewPathController controller, DrawingView view, Point p, int modifiers) {
98  				return S7;
99  			}
100 		},
101 		S4 {
102 			@Override
103 			State processMouseMove(NewPathController controller, DrawingView view, Point p, int modifiers) {
104 				view.toModelCoordinates(p, controller._p1);
105 				if (controller._segments.size() > 0) {
106 					float tol = view.convertToReferenceLength(VERTEX_SIZE * 2);
107 					if (controller._p1.distance(controller._p0) < tol) {
108 						view.setCursor(Cursor.CURSOR_OPEN_POLYLINE);
109 					} else if (controller._p1.distance(controller._segments.get(0).getStartPoint()) < tol) {
110 						view.setCursor(Cursor.CURSOR_CLOSED_POLYLINE);
111 					} else {
112 						view.setCursor(Cursor.CURSOR_CROSSHAIR);
113 					}
114 				}
115 				return this;
116 			}		
117 			@Override
118 			State processMouseDown(NewPathController controller, DrawingView view, Point p, int modifiers) {
119 				view.toModelCoordinates(p, controller._p2);
120 				controller._p2.copyTo(controller._p3);
121 				return S5;
122 			}
123 		},
124 		S5 {
125 			@Override
126 			State processMouseMove(NewPathController controller, DrawingView view, Point p, int modifiers) {
127 				view.toModelCoordinates(p, controller._p2);
128 				if (controller._p2.distance(controller._p3) > T) {
129 					return S6;
130 				}
131 				return S5;
132 			}		
133 			@Override
134 			State processMouseUp(NewPathController controller, DrawingView view, Point p, int modifiers) {
135 				float tol = view.convertToReferenceLength(VERTEX_SIZE * 2);
136 				boolean pathHasSegments = controller._segments.size() > 0;
137 				boolean done = false;
138 				if (pathHasSegments && controller._p1.distance(controller._p0) < tol) {
139 					// Open path
140 					done = true;
141 				} else {
142 					if (controller._p1.distance(controller._p0) > tol) {
143 						if (pathHasSegments && controller._p1.distance(controller._segments.get(0).getStartPoint()) < tol) {
144 							// Closed path
145 							controller._segments.add(new LineSegment(new Point[]{controller._p0, controller._segments.get(0).getStartPoint()}));
146 							done = true;
147 						} else {
148 							// Point is viable
149 							controller._segments.add(new LineSegment(new Point[]{controller._p0, controller._p1}));
150 						}
151 					}
152 				}
153 				if (done) {
154 					return S8.processDeactivate(controller, view);
155 				}
156 				controller._p1.copyTo(controller._p0);
157 				controller._p2.copyTo(controller._p1);
158 				return S4;
159 			}
160 		},
161 		S6 {
162 			@Override
163 			State processMouseMove(NewPathController controller, DrawingView view, Point p, int modifiers) {
164 				view.toModelCoordinates(p).symetricPoint(controller._p3, controller._p2);
165 				if (p.distance(controller._p3) > T) {
166 					return S6;
167 				}
168 				return S5;
169 			}		
170 			@Override
171 			State processMouseUp(NewPathController controller, DrawingView view, Point p, int modifiers) {
172 				return S7.processMouseUp(controller, view, p, modifiers);
173 			}
174 		},
175 		S7 {
176 			@Override
177 			State processMouseMove(NewPathController controller, DrawingView view, Point p, int modifiers) {
178 				view.toModelCoordinates(p).symetricPoint(controller._p3, controller._p2);
179 				return S7;
180 			}		
181 			@Override
182 			State processMouseUp(NewPathController controller, DrawingView view, Point p, int modifiers) {
183 				float tol = view.convertToReferenceLength(VERTEX_SIZE * 2);
184 				boolean pathHasSegments = controller._segments.size() > 0;
185 				boolean done = false;
186 				if (pathHasSegments && controller._p2.distance(controller._p0) < tol) {
187 					// Open path
188 					done = true;
189 				} else {
190 					if (controller._p2.distance(controller._p0) > tol) {
191 						if (pathHasSegments && (controller._p2.distance(controller._segments.get(0).getStartPoint()) < tol || controller._p3.distance(controller._segments.get(0).getStartPoint()) < tol)) {
192 							// Closed path
193 							controller._segments.add(new BezierSegment(new Point[]{controller._p0, controller._p1, controller._p2, controller._segments.get(0).getStartPoint()}));
194 							done = true;
195 						} else {
196 							// Point is viable
197 							controller._segments.add(new BezierSegment(new Point[]{controller._p0, controller._p1, controller._p2, controller._p3}));
198 						}
199 					}
200 				}
201 				if (done) {
202 					return S8.processDeactivate(controller, view);
203 				}
204 				controller._p3.copyTo(controller._p0);
205 				controller._p2.symetricPoint(controller._p3, controller._p1);
206 				return controller._p2.equals(controller._p3) ? S4 : S3;
207 			}
208 		},
209 		S8 {
210 			State processDeactivate(NewPathController controller, DrawingView view) {
211 				if (controller._segments.size() > 0) {
212 					Path path = new Path(controller._segments);
213 					path.setAttribute(Attribute.LINE_STYLE, controller._app.getLineStyleController().getStyle());
214 					path.setAttribute(Attribute.LINE_OPACITY, controller._app.getLineStyleController().getOpacity());
215 					path.setAttribute(Attribute.FILL_STYLE, controller._app.getFillStyleController().getStyle());
216 					path.setAttribute(Attribute.FILL_OPACITY, controller._app.getFillStyleController().getOpacity());
217 					path.setAttribute(Attribute.LINE_WIDTH, controller._app.getLineWidthController().getLineWidth());
218 					NewShapeCommand command = new NewShapeCommand(controller._app, path);
219 					command.execute();
220 					controller._app.getHistory().addCommand(command);
221 				}
222 				return S0.processActivate(controller, view);
223 			}
224 		};
225 		State processActivate(NewPathController controller, DrawingView view) {
226 			throw new IllegalStateException("activate");
227 		}
228 		State processMouseDown(NewPathController controller, DrawingView view, Point p, int modifiers) {
229 			return this;
230 		}
231 		State processMouseUp(NewPathController controller, DrawingView view, Point p, int modifiers) {
232 			return this;
233 		}
234 		State processMouseMove(NewPathController controller, DrawingView view, Point p, int modifiers) {
235 			return this;
236 		}
237 		State processKeyDown(NewPathController controller, int key, int modifiers) {
238 			return this;
239 		}
240 		State processDeactivate(NewPathController controller, DrawingView view) {
241 			throw new IllegalStateException("deactivate");
242 		}
243 	};
244 	private Point _p0, _p1, _p2, _p3, _pTmp;
245 	private State _state;
246 	private List<Segment> _segments;
247 	private MouseControllerButton _button;
248 
249 	public NewPathController(RepApplication app) {
250 		super(app);
251 		_p0 = new Point();
252 		_p1 = new Point();
253 		_p2 = new Point();
254 		_p3 = new Point();
255 		_pTmp = new Point();
256 		_segments = new ArrayList<Segment>();
257 		_button = new MouseControllerButton(_app.getIcons().pathIcon().createImage(), _app.getConstants().newPathCommand(), this);
258 	}
259 	
260 	@Override
261 	public void mouseDown(DrawingView view, Point p, int modifiers) {
262 		_state = _state.processMouseDown(this, view, p, modifiers);
263 	}
264 	@Override
265 	public void mouseUp(DrawingView view, Point p, int modifiers) {
266 		_state = _state.processMouseUp(this, view, p, modifiers);
267 	}
268 	@Override
269 	public void mouseMove(DrawingView view, Point p, int modifiers) {
270 		_state = _state.processMouseMove(this, view, p, modifiers);
271 	}
272 	@Override
273 	public void keyDown(DrawingView view, char keyCode, int modifiers) {
274 		_state = _state.processKeyDown(this, keyCode, modifiers);
275 	}
276 	@Override
277 	public void activate(DrawingView view) {
278 		_state = State.S0;
279 		_state = _state.processActivate(this, view);
280 	}
281 	@Override
282 	public String toString() {
283 		return _state.toString();
284 	}
285 	
286 	@Override
287 	public void render(DrawingView view) {
288 		//RepApplication.app.debugArea.setText(_state.toString());
289 		_app.getLineStyleController().getStyle().acceptVisitor(view.getStrokeStyleVisitor());
290 		for (int i = 0, size = _segments.size(); i < size; i++) {
291 			Segment segment = _segments.get(i);
292 			if (i == 0) {
293 				view.beginPath();
294 				view.moveTo(segment.getStartPoint().x, segment.getStartPoint().y);
295 			}
296 			if (segment instanceof LineSegment) {
297 				view.lineTo(segment.getEndPoint().x, segment.getEndPoint().y);
298 			} else {
299 				BezierSegment bezierSegment = (BezierSegment)segment;
300 				view.bezierCurveTo(
301 						bezierSegment.getStartControlPoint().x, bezierSegment.getStartControlPoint().y, 
302 						bezierSegment.getEndControlPoint().x, bezierSegment.getEndControlPoint().y, 
303 						bezierSegment.getEndPoint().x, bezierSegment.getEndPoint().y);
304 			}
305 		}
306 		view.stroke();
307 		Color.BLACK.acceptVisitor(view.getStrokeStyleVisitor());
308 		Color.BLACK.acceptVisitor(view.getFillStyleVisitor());
309 		view.setLineWidth(1f);
310 		switch(_state) {
311 			case S2:
312 			case S4:
313 			case S5:
314 				view.moveTo(_p0.x, _p0.y);
315 				view.lineTo(_p1.x, _p1.y);
316 				view.stroke();
317 				Color.WHITE.acceptVisitor(view.getFillStyleVisitor());
318 				view.fillRect(_p0.x - VERTEX_SIZE, _p0.y - VERTEX_SIZE, VERTEX_SIZE * 2, VERTEX_SIZE * 2);
319 				Color.BLACK.acceptVisitor(view.getStrokeStyleVisitor());
320 				view.strokeRect(_p0.x - VERTEX_SIZE, _p0.y - VERTEX_SIZE, VERTEX_SIZE * 2, VERTEX_SIZE * 2);
321 				view.beginPath();
322 				view.moveTo(_p1.x - VERTEX_SIZE, _p1.y);
323 				view.lineTo(_p1.x + VERTEX_SIZE, _p1.y);
324 				view.moveTo(_p1.x, _p1.y - VERTEX_SIZE);
325 				view.lineTo(_p1.x, _p1.y + VERTEX_SIZE);
326 				view.stroke();
327 				break;
328 			case S3:
329 				view.moveTo(_p0.x, _p0.y);
330 				view.lineTo(_p1.x, _p1.y);			
331 				view.moveTo(_p0.x, _p0.y);
332 				view.bezierCurveTo(_p1.x, _p1.y, _p2.x, _p2.y, _p3.x, _p3.y);
333 				view.stroke();
334 				view.beginPath();
335 				view.arc(_p1.x, _p1.y, CONTROL_POINT_SIZE, 0, (float)(2 * Math.PI), true);
336 				view.fill();
337 				Color.WHITE.acceptVisitor(view.getFillStyleVisitor());
338 				view.fillRect(_p0.x - VERTEX_SIZE, _p0.y - VERTEX_SIZE, VERTEX_SIZE * 2, VERTEX_SIZE * 2);
339 				Color.BLACK.acceptVisitor(view.getStrokeStyleVisitor());
340 				view.strokeRect(_p0.x - VERTEX_SIZE, _p0.y - VERTEX_SIZE, VERTEX_SIZE * 2, VERTEX_SIZE * 2);
341 				break;
342 			case S6:
343 			case S7:
344 				view.moveTo(_p0.x, _p0.y);
345 				view.bezierCurveTo(_p1.x, _p1.y, _p2.x, _p2.y, _p3.x, _p3.y);
346 				view.moveTo(_p2.x, _p2.y);
347 				_p2.symetricPoint(_p3, _pTmp);
348 				view.lineTo(_pTmp.x, _pTmp.y);
349 				view.stroke();
350 				view.beginPath();
351 				view.arc(_p2.x, _p2.y, CONTROL_POINT_SIZE, 0, (float)(2 * Math.PI), true);
352 				view.arc(_pTmp.x, _pTmp.y, CONTROL_POINT_SIZE, 0, (float)(2 * Math.PI), true);
353 				view.fill();
354 				break;
355 		}
356 	}
357 	
358 	@Override
359 	public void deactivate(DrawingView view) {
360 		_state = State.S8.processDeactivate(this, view);
361 	}
362 
363 	public MouseControllerButton getButton() {
364 		return _button;
365 	}
366 }