View Javadoc

1   /**********************************************
2    * Copyright (C) 2010 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.engine;
19  
20  import org.vectomatic.dom.svg.OMSVGDefsElement;
21  import org.vectomatic.dom.svg.OMSVGDocument;
22  import org.vectomatic.dom.svg.OMSVGGElement;
23  import org.vectomatic.dom.svg.OMSVGLength;
24  import org.vectomatic.dom.svg.OMSVGPathElement;
25  import org.vectomatic.dom.svg.OMSVGPathSegList;
26  import org.vectomatic.dom.svg.OMSVGPatternElement;
27  import org.vectomatic.dom.svg.OMSVGPoint;
28  import org.vectomatic.dom.svg.OMSVGRectElement;
29  import org.vectomatic.dom.svg.OMSVGSVGElement;
30  import org.vectomatic.dom.svg.impl.SVGSVGElement;
31  import org.vectomatic.dom.svg.utils.DOMHelper;
32  import org.vectomatic.dom.svg.utils.SVGConstants;
33  import org.vectomatic.svg.edit.client.AppBundle;
34  import org.vectomatic.svg.edit.client.AppCss;
35  import org.vectomatic.svg.edit.client.model.svg.SVGViewBoxElementModel;
36  
37  import com.google.gwt.core.client.GWT;
38  import com.google.gwt.dom.client.Style.Unit;
39  import com.google.gwt.dom.client.Style.Visibility;
40  import com.google.gwt.event.dom.client.MouseMoveEvent;
41  import com.google.gwt.event.dom.client.MouseMoveHandler;
42  
43  /**
44   * Class to represent grids.
45   * The grid has the following SVG structure:
46   * <pre>
47   * <defs>
48   *  <pattern id="docid-grid">
49   *   <path> // fine grid
50   *   <path> // coarse grid
51   *  </pattern>
52   *  <pattern id="docid-vrule"> // vrule
53   *   <rect> // ruler background
54   *   <path> // ruler gradations
55   *  </pattern>
56   *  <pattern id="docid-rule"> // hrule
57   *   <rect> // ruler background
58   *   <path> // ruler gradations
59   *  </pattern>
60   * </defs>
61   * 
62   * <g>
63   *	<rect style="fill:url('#docid-grid');stroke:black;"/> // grid
64   * 
65   *  <g>	// hruler
66   *   <rect style="fill:url('#docid-hrule');"/>
67   *	 <g>
68   * 	  <text x="0" y="-10">0</text>
69   * 	  ...
70   * 	  <text x="200" y="-10">N</text>
71   * 	 </g>
72   *   <path transform='translate(x,y)'> // hmarker
73   *  </g>
74   *  
75   *  <g> // vruler
76   *	 <rect style="fill:url('#docid-vrule');"/> 
77   * 	 <g>
78   * 	  <text x="-15" y="0">0</text>
79   * 	  <text x="-15" y="300">300</text>
80   * 	 </g>
81   *   <path transform='translate(x,y)'> // hmarker
82   *  </g>
83   * </g>
84   * </pre>
85   * @author laaglu
86   */
87  public class Grid {
88  	/**
89  	 * True if the grid and rulers are visible
90  	 */
91  	private boolean showsGrid;
92  	/**
93  	 * True if the coordinates and guides are visible
94  	 */
95  	private boolean showsGuides;
96  	/**
97  	 * True if the mouse input is rounded to the grid
98  	 */
99  	private boolean snapsToGrid;
100 	/**
101 	 * The grid root defs
102 	 */
103 	private OMSVGDefsElement defs;
104 	/**
105 	 * The grid root element
106 	 */
107 	private OMSVGGElement root;
108 	/**
109 	 * The grid element
110 	 */
111 	private OMSVGRectElement grid;
112 	/**
113 	 * The horizontal ruler element
114 	 */
115 	private OMSVGGElement hruler;
116 	/**
117 	 * The vertical ruler element
118 	 */
119 	private OMSVGGElement vruler;
120 	/**
121 	 * The horizontal position marker
122 	 */
123 	private OMSVGPathElement hmarker;
124 	/**
125 	 * The vertical position marker
126 	 */
127 	private OMSVGPathElement vmarker;	
128 	/**
129 	 * To update position markers
130 	 */
131 	private MouseMoveHandler moveHandler;
132 	/**
133 	 * The svg model to which the grid is attached
134 	 */
135 	private SVGModel svgModel;
136 	/**
137 	 * The grid horizontal spacing
138 	 */
139 	private float dx;
140 	/**
141 	 * The grid vertical spacing
142 	 */
143 	private float dy;
144 	
145 	public Grid() {
146 		moveHandler = new MouseMoveHandler() {
147 			@Override
148 			public void onMouseMove(MouseMoveEvent event) {
149 				if (showsGuides && isAttached()) {
150 					OMSVGPoint p = svgModel.getCoordinates(event, true);
151 					setHMarkerPosition(p.getX());
152 					setVMarkerPosition(p.getY());
153 				}
154 			}		
155 		};
156 	}
157 	
158 	public boolean showsGrid() {
159 		return showsGrid;
160 	}
161 	
162 	public void setShowsGrid(boolean showsGrid) {
163 		GWT.log("Grid.setShowsGrid(" + showsGrid + ")");
164 		this.showsGrid = showsGrid;
165 		grid.getStyle().setVisibility(showsGrid ? Visibility.VISIBLE : Visibility.HIDDEN);
166 	}
167 	
168 	public boolean showsGuides() {
169 		return showsGuides;
170 	}
171 	
172 	public void setShowsGuides(boolean showsGuides) {
173 		GWT.log("Grid.setShowsGuides(" + showsGuides + ")");
174 		this.showsGuides = showsGuides;
175 		hruler.getStyle().setVisibility(showsGuides ? Visibility.VISIBLE : Visibility.HIDDEN);
176 		vruler.getStyle().setVisibility(showsGuides ? Visibility.VISIBLE : Visibility.HIDDEN);
177 	}
178 	
179 	public boolean snapsToGrid() {
180 		return snapsToGrid;
181 	}
182 	
183 	public void setSnapsToGrid(boolean snapsToGrid) {
184 		GWT.log("Grid.setSnapsToGrid(" + snapsToGrid + ")");
185 		this.snapsToGrid = snapsToGrid;
186 	}
187 	
188 	public OMSVGDefsElement getDefs() {
189 		return defs;
190 	}
191 	
192 	public OMSVGGElement getRoot() {
193 		return root;
194 	}
195 	
196 	public MouseMoveHandler getMouseMoveHandler() {
197 		return moveHandler;
198 	}
199 	
200 	/**
201 	 * Snaps the specified point to the grid
202 	 * @param p A point in the model coordinate system
203 	 * @return A point snapped to grid
204 	 */
205 	public OMSVGPoint snap(OMSVGPoint p) {
206 		SVGViewBoxElementModel viewBox = svgModel.getViewBox();
207 		OMSVGSVGElement svg = svgModel.getSvgElement();
208 		OMSVGPoint p0 = svg.createSVGPoint(viewBox.<Float>get(SVGConstants.SVG_X_ATTRIBUTE), viewBox.<Float>get(SVGConstants.SVG_Y_ATTRIBUTE));
209 		OMSVGPoint p1 = p.substract(p0, svg.createSVGPoint());
210 		p1.setX(dx * (Math.round(p1.getX() / dx)));
211 		p1.setY(dy * (Math.round(p1.getY() / dy)));
212 		return p1.add(p0);
213 	}
214 	
215 	public void setHMarkerPosition(float xPos) {
216 		if (isAttached()) {
217 			// Clamp xPos
218 			SVGViewBoxElementModel viewBox = svgModel.getViewBox();
219 			float x = viewBox.<Float>get(SVGConstants.SVG_X_ATTRIBUTE);
220 			float width = viewBox.<Float>get(SVGConstants.SVG_WIDTH_ATTRIBUTE);
221 			xPos = Math.min(Math.max(xPos, x), x + width);
222 			
223 			hmarker.getTransform().getBaseVal().getItem(0).setTranslate(xPos, 0);
224 		}
225 	}
226 
227 	public void setVMarkerPosition(float yPos) {
228 		if (isAttached()) {
229 			// Clamp yPos
230 			SVGViewBoxElementModel viewBox = svgModel.getViewBox();
231 			float y = viewBox.<Float>get(SVGConstants.SVG_Y_ATTRIBUTE);
232 			float height = viewBox.<Float>get(SVGConstants.SVG_HEIGHT_ATTRIBUTE);
233 			yPos = Math.min(Math.max(yPos, y), y + height);
234 			vmarker.getTransform().getBaseVal().getItem(0).setTranslate(0, yPos);
235 		}
236 	}
237 	
238 	public boolean isAttached() {
239 		return svgModel != null;
240 	}
241 
242 	public void attach(SVGModel svgModel) {
243 		this.svgModel = svgModel;
244 		AppCss css = AppBundle.INSTANCE.css();
245 		OMSVGSVGElement svg = svgModel.getSvgElement();
246 		OMSVGDocument doc = (OMSVGDocument) svg.getOwnerDocument();
247 		String modelId = svg.getId();
248 		
249 		// Build the grid pattern
250 		dx = dy = 5;
251 		OMSVGPathElement gridPath1 = new OMSVGPathElement();
252 		OMSVGPathSegList gridSegs1 = gridPath1.getPathSegList();
253 		for (int i = 1; i < 5; i++) {
254 			gridSegs1.appendItem(gridPath1.createSVGPathSegMovetoAbs(0, i * 5));
255 			gridSegs1.appendItem(gridPath1.createSVGPathSegLinetoHorizontalAbs(25));
256 		}
257 		for (int i = 1; i < 5; i++) {
258 			gridSegs1.appendItem(gridPath1.createSVGPathSegMovetoAbs(i * 5, 0));
259 			gridSegs1.appendItem(gridPath1.createSVGPathSegLinetoVerticalAbs(25));
260 		}
261 		gridPath1.setClassNameBaseVal(css.grid1());
262 		
263 		OMSVGPathElement gridPath2 = new OMSVGPathElement();
264 		OMSVGPathSegList gridSegs2 = gridPath2.getPathSegList();
265 		gridSegs2.appendItem(gridPath2.createSVGPathSegMovetoAbs(0, 0));
266 		gridSegs2.appendItem(gridPath1.createSVGPathSegLinetoHorizontalAbs(25));
267 		gridSegs2.appendItem(gridPath2.createSVGPathSegMovetoAbs(0, 0));
268 		gridSegs2.appendItem(gridPath1.createSVGPathSegLinetoVerticalAbs(25));
269 		OMSVGPatternElement gridPattern = createPattern(0, 0, 25, 25);
270 		gridPath2.setClassNameBaseVal(css.grid2());
271 		String gridPatternId = modelId + "-grid";
272 		gridPattern.setId(gridPatternId);
273 		gridPattern.appendChild(gridPath1);
274 		gridPattern.appendChild(gridPath2);
275 		
276 		// Build the horizontal ruler pattern
277 		OMSVGRectElement hrulerPatternRect = doc.createSVGRectElement(0, 0, 100, 20, 0, 0);
278 		OMSVGPathElement hrulerPatternPath = new OMSVGPathElement();
279 		OMSVGPathSegList hrulerPatternSegs = hrulerPatternPath.getPathSegList();
280 		for (int i = 0; i < 10; i++) {
281 			hrulerPatternSegs.appendItem(hrulerPatternPath.createSVGPathSegMovetoAbs(i * 10, 20));
282 			hrulerPatternSegs.appendItem(hrulerPatternPath.createSVGPathSegLinetoVerticalRel(i == 0 ? -18 : (i % 2 == 1 ? -5 : -10)));
283 		}
284 		OMSVGPatternElement hrulerPattern = createPattern(0, 0, 100, 20);
285 		hrulerPattern.setClassNameBaseVal(css.hrulerPattern());
286 		String hrulerPatternId = modelId + "-hruler";
287 		hrulerPattern.setId(hrulerPatternId);
288 		hrulerPattern.appendChild(hrulerPatternRect);
289 		hrulerPattern.appendChild(hrulerPatternPath);
290 		
291 		// Build the vertical ruler pattern
292 		OMSVGRectElement vrulerPatternRect = doc.createSVGRectElement(0, 0, 20, 100, 0, 0);
293 		OMSVGPathElement vrulerPatternPath = new OMSVGPathElement();
294 		OMSVGPathSegList vrulerSegs = vrulerPatternPath.getPathSegList();
295 		for (int i = 0; i < 10; i++) {
296 			vrulerSegs.appendItem(vrulerPatternPath.createSVGPathSegMovetoAbs(20, i * 10));
297 			vrulerSegs.appendItem(vrulerPatternPath.createSVGPathSegLinetoHorizontalRel(i == 0 ? -18 : (i % 2 == 1 ? -5 : -10)));
298 		}
299 		OMSVGPatternElement vrulerPattern = createPattern(0, 0, 20, 100);
300 		vrulerPattern.setClassNameBaseVal(css.vrulerPattern());
301 		String vrulerPatternId = modelId + "-vruler";
302 		vrulerPattern.setId(vrulerPatternId);
303 		vrulerPattern.appendChild(vrulerPatternRect);
304 		vrulerPattern.appendChild(vrulerPatternPath);
305 		
306 		// Create the definitions
307 		defs = new OMSVGDefsElement();
308 		defs.appendChild(gridPattern);
309 		defs.appendChild(hrulerPattern);
310 		defs.appendChild(vrulerPattern);
311 		
312 		// Create the grid
313 		SVGViewBoxElementModel viewBox = svgModel.getViewBox();
314 		float x = viewBox.<Float>get(SVGConstants.SVG_X_ATTRIBUTE);
315 		float y = viewBox.<Float>get(SVGConstants.SVG_Y_ATTRIBUTE);
316 		float width = viewBox.<Float>get(SVGConstants.SVG_WIDTH_ATTRIBUTE);
317 		float height = viewBox.<Float>get(SVGConstants.SVG_HEIGHT_ATTRIBUTE);
318 		grid = doc.createSVGRectElement(x, y, width, height, 0, 0);
319 		grid.getStyle().setProperty(SVGConstants.CSS_FILL_PROPERTY, DOMHelper.toUrl(gridPatternId));
320 		grid.getStyle().setProperty(SVGConstants.CSS_STROKE_PROPERTY, SVGConstants.CSS_BLACK_VALUE);
321 		
322 		// Create the horizontal ruler
323 		OMSVGRectElement hrulerRect = doc.createSVGRectElement(x, y - 20, width, 20, 0, 0);
324 		hrulerRect.getStyle().setProperty(SVGConstants.CSS_FILL_PROPERTY, DOMHelper.toUrl(hrulerPatternId));
325 		OMSVGGElement hGradations = new OMSVGGElement();
326 		for (int i = (((int)x)/100)*100; i < width; i+=100) {
327 			hGradations.appendChild(doc.createSVGTextElement(i, -10, OMSVGLength.SVG_LENGTHTYPE_PX, Integer.toString(i)));
328 		}
329 		
330 		// Create the horizontal marker
331 		hmarker = doc.createSVGPathElement();
332 		OMSVGPathSegList hmarkerSegs = hmarker.getPathSegList();
333 		hmarkerSegs.appendItem(hmarker.createSVGPathSegMovetoAbs(0, 0));
334 		hmarkerSegs.appendItem(hmarker.createSVGPathSegLinetoRel(-4, -7));
335 		hmarkerSegs.appendItem(hmarker.createSVGPathSegLinetoHorizontalRel(8));
336 		hmarkerSegs.appendItem(hmarker.createSVGPathSegClosePath());
337 		hmarker.getTransform().getBaseVal().appendItem(svg.createSVGTransform());
338 		hmarker.setClassNameBaseVal(css.gridMarker());
339 
340 		hruler = new OMSVGGElement();
341 		hruler.setClassNameBaseVal(css.hruler());
342 		hruler.appendChild(hrulerRect);
343 		hruler.appendChild(hGradations);
344 		hruler.appendChild(hmarker);
345 
346 		// Create the vertical ruler
347 		OMSVGRectElement vrulerRect = doc.createSVGRectElement(x - 20, y, 20, height, 0, 0);
348 		vrulerRect.getStyle().setProperty(SVGConstants.CSS_FILL_PROPERTY, DOMHelper.toUrl(vrulerPatternId));
349 		OMSVGGElement vGradations = new OMSVGGElement();
350 		for (int i = (((int)y)/100)*100; i < height; i+=100) {
351 			vGradations.appendChild(doc.createSVGTextElement(-15, i, OMSVGLength.SVG_LENGTHTYPE_PX, Integer.toString(i)));
352 		}
353 
354 		// Create the vertical marker
355 		vmarker = doc.createSVGPathElement();
356 		OMSVGPathSegList vmarkerSegs = vmarker.getPathSegList();
357 		vmarkerSegs.appendItem(vmarker.createSVGPathSegMovetoAbs(0, 0));
358 		vmarkerSegs.appendItem(vmarker.createSVGPathSegLinetoRel(-7, -4));
359 		vmarkerSegs.appendItem(vmarker.createSVGPathSegLinetoVerticalRel(8));
360 		vmarkerSegs.appendItem(vmarker.createSVGPathSegClosePath());
361 		vmarker.getTransform().getBaseVal().appendItem(svg.createSVGTransform());
362 		vmarker.setClassNameBaseVal(css.gridMarker());
363 
364 		vruler = new OMSVGGElement();
365 		vruler.setClassNameBaseVal(css.vruler());
366 		vruler.appendChild(vrulerRect);
367 		vruler.appendChild(vGradations);
368 		vruler.appendChild(vmarker);
369 
370 		root = new OMSVGGElement();
371 		root.appendChild(grid);
372 		root.appendChild(hruler);
373 		root.appendChild(vruler);
374 		setShowsGrid(false);
375 		setShowsGuides(false);
376 	}
377 	
378 	private static OMSVGPatternElement createPattern(float x, float y, float width, float height) {
379 		OMSVGPatternElement pattern = new OMSVGPatternElement();
380 		pattern.getX().getBaseVal().newValueSpecifiedUnits(Unit.PX, x);
381 		pattern.getY().getBaseVal().newValueSpecifiedUnits(Unit.PX, y);
382 		pattern.getWidth().getBaseVal().newValueSpecifiedUnits(Unit.PX, width);
383 		pattern.getHeight().getBaseVal().newValueSpecifiedUnits(Unit.PX, height);
384 		if (!pattern.hasAttribute(SVGConstants.SVG_VIEW_BOX_ATTRIBUTE)) {
385 			StringBuilder builder = new StringBuilder();
386 			builder.append(x);
387 			builder.append(" ");
388 			builder.append(y);
389 			builder.append(" ");
390 			builder.append(width);
391 			builder.append(" ");
392 			builder.append(height);
393 			pattern.setAttribute(SVGConstants.SVG_VIEW_BOX_ATTRIBUTE, builder.toString());
394 		} else {
395 			pattern.getViewBox().getBaseVal().setX(x);
396 			pattern.getViewBox().getBaseVal().setY(y);
397 			pattern.getViewBox().getBaseVal().setWidth(width);
398 			pattern.getViewBox().getBaseVal().setHeight(height);			
399 		}
400 		pattern.setAttribute(SVGConstants.SVG_PATTERN_UNITS_ATTRIBUTE, SVGConstants.SVG_USER_SPACE_ON_USE_VALUE);
401 		return pattern;
402 	}
403 }