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 java.util.ArrayList;
21  import java.util.HashMap;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.Stack;
25  
26  import org.vectomatic.dom.svg.OMElement;
27  import org.vectomatic.dom.svg.OMSVGDocument;
28  import org.vectomatic.dom.svg.OMSVGElement;
29  import org.vectomatic.dom.svg.OMSVGGElement;
30  import org.vectomatic.dom.svg.OMSVGMatrix;
31  import org.vectomatic.dom.svg.OMSVGPoint;
32  import org.vectomatic.dom.svg.OMSVGRect;
33  import org.vectomatic.dom.svg.OMSVGRectElement;
34  import org.vectomatic.dom.svg.OMSVGSVGElement;
35  import org.vectomatic.dom.svg.OMSVGStyle;
36  import org.vectomatic.dom.svg.impl.SVGElement;
37  import org.vectomatic.dom.svg.impl.SVGGElement;
38  import org.vectomatic.dom.svg.impl.SVGRectElement;
39  import org.vectomatic.dom.svg.itf.ISVGLocatable;
40  import org.vectomatic.dom.svg.itf.ISVGTransformable;
41  import org.vectomatic.dom.svg.utils.DOMHelper;
42  import org.vectomatic.dom.svg.utils.OMSVGParser;
43  import org.vectomatic.dom.svg.utils.SVGConstants;
44  import org.vectomatic.dom.svg.utils.SVGPrefixResolver;
45  import org.vectomatic.svg.edit.client.SVGSelectionModel;
46  import org.vectomatic.svg.edit.client.SvgrealApp;
47  import org.vectomatic.svg.edit.client.command.CommandStore;
48  import org.vectomatic.svg.edit.client.command.ICommandFactory;
49  import org.vectomatic.svg.edit.client.command.RemoveElementsCommandFactory;
50  import org.vectomatic.svg.edit.client.command.ShowPropertiesCommandFactory;
51  import org.vectomatic.svg.edit.client.command.add.AddCircleCommandFactory;
52  import org.vectomatic.svg.edit.client.command.add.AddEllipseCommandFactory;
53  import org.vectomatic.svg.edit.client.command.add.AddGroupCommandFactory;
54  import org.vectomatic.svg.edit.client.command.add.AddLineCommandFactory;
55  import org.vectomatic.svg.edit.client.command.add.AddPathCommandFactory;
56  import org.vectomatic.svg.edit.client.command.add.AddPolygonCommandFactory;
57  import org.vectomatic.svg.edit.client.command.add.AddPolylineCommandFactory;
58  import org.vectomatic.svg.edit.client.command.add.AddRectCommandFactory;
59  import org.vectomatic.svg.edit.client.event.HasRotationHandlers;
60  import org.vectomatic.svg.edit.client.event.HasScalingHandlers;
61  import org.vectomatic.svg.edit.client.event.KeyPressProcessor;
62  import org.vectomatic.svg.edit.client.event.KeyUpProcessor;
63  import org.vectomatic.svg.edit.client.event.MouseDownProcessor;
64  import org.vectomatic.svg.edit.client.event.MouseMoveProcessor;
65  import org.vectomatic.svg.edit.client.event.MouseUpProcessor;
66  import org.vectomatic.svg.edit.client.event.RotationEvent;
67  import org.vectomatic.svg.edit.client.event.RotationHandler;
68  import org.vectomatic.svg.edit.client.event.ScalingEvent;
69  import org.vectomatic.svg.edit.client.event.ScalingHandler;
70  import org.vectomatic.svg.edit.client.event.StoreEventProcessor;
71  import org.vectomatic.svg.edit.client.gxt.widget.CommandFactoryMenuItem;
72  import org.vectomatic.svg.edit.client.gxt.widget.KeyNavExt;
73  import org.vectomatic.svg.edit.client.model.MetaModel;
74  import org.vectomatic.svg.edit.client.model.svg.SVGCircleElementModel;
75  import org.vectomatic.svg.edit.client.model.svg.SVGElementModel;
76  import org.vectomatic.svg.edit.client.model.svg.SVGEllipseElementModel;
77  import org.vectomatic.svg.edit.client.model.svg.SVGImageElementModel;
78  import org.vectomatic.svg.edit.client.model.svg.SVGLineElementModel;
79  import org.vectomatic.svg.edit.client.model.svg.SVGNamedElementModel;
80  import org.vectomatic.svg.edit.client.model.svg.SVGPathElementModel;
81  import org.vectomatic.svg.edit.client.model.svg.SVGPolygonElementModel;
82  import org.vectomatic.svg.edit.client.model.svg.SVGPolylineElementModel;
83  import org.vectomatic.svg.edit.client.model.svg.SVGRectElementModel;
84  import org.vectomatic.svg.edit.client.model.svg.SVGUseElementModel;
85  import org.vectomatic.svg.edit.client.model.svg.SVGViewBoxElementModel;
86  
87  import com.extjs.gxt.ui.client.event.ComponentEvent;
88  import com.extjs.gxt.ui.client.store.StoreEvent;
89  import com.extjs.gxt.ui.client.store.StoreListener;
90  import com.extjs.gxt.ui.client.store.TreeStore;
91  import com.extjs.gxt.ui.client.widget.menu.Item;
92  import com.extjs.gxt.ui.client.widget.menu.Menu;
93  import com.extjs.gxt.ui.client.widget.treepanel.TreePanelSelectionModel;
94  import com.google.gwt.core.client.GWT;
95  import com.google.gwt.dom.client.Element;
96  import com.google.gwt.dom.client.Node;
97  import com.google.gwt.dom.client.NodeList;
98  import com.google.gwt.dom.client.Style.Unit;
99  import com.google.gwt.event.dom.client.KeyCodes;
100 import com.google.gwt.event.dom.client.MouseDownEvent;
101 import com.google.gwt.event.dom.client.MouseDownHandler;
102 import com.google.gwt.event.dom.client.MouseEvent;
103 import com.google.gwt.event.dom.client.MouseMoveEvent;
104 import com.google.gwt.event.dom.client.MouseMoveHandler;
105 import com.google.gwt.event.dom.client.MouseUpEvent;
106 import com.google.gwt.event.dom.client.MouseUpHandler;
107 import com.google.gwt.event.shared.EventHandler;
108 import com.google.gwt.event.shared.GwtEvent;
109 import com.google.gwt.event.shared.HandlerRegistration;
110 
111 /**
112  * Model class for an SVG document edited by the application.
113  * The document has the following DOM structure:
114  * <pre>
115  * <svg>
116  *   <defs>
117  *   <g> <!-- xform group : xforms applies here -->
118  *     <g/> <!-- grid group -->
119  *     <g> <!-- element group : opacity applies here ; root of the tree view (element part of the modelGroup) -->
120  *       <title/>
121  *       <desc/>
122  *       <rect/> <!-- viewbox -->
123  *       <circle/> ... <!-- geometry starts here -->
124  *    </g>
125  *   </g>
126  *   <g> <!-- twin group : xforms applies here; visiblity applies here (twin part of the modelGroup) -->
127  *     <title/>
128  *     <desc/>
129  *     <circle/> ... <!-- geometry starts here -->
130  *   </g>
131  * </svg>
132  * </pre>
133  * <dl>
134  * <dt>elementGroup</dt><dd>Contains all the elements from the original SVG, id-normalized.</dd>
135  * <dt>twinGroup</dt><dd>Contains a visibility-hidden clone of the previous group. It is used to display the selection and implement highlighting.</dd>
136  * <dt>gridGroup</dt><dd>Contains all the helper elements required to display grids, rulers...</dd>
137  * <dt>geometryGroup</dt><dd>Used to applied opacity for highlighting operations</dd>
138  * </dl>
139  * @author laaglu
140  */
141 public class SVGModel implements MouseDownHandler, MouseMoveHandler, MouseUpHandler, HasScalingHandlers, HasRotationHandlers {
142 	/**
143 	 * To be able to identify the viewBox model
144 	 */
145 	private static final String ATTR_KIND = "kind";
146 	private static final String ATTR_KIND_VIEWBOX = "viewBox";
147 	/**
148 	 * Id Prefix extension for twins
149 	 */
150 	public static final String EXT_TWIN = "twin";
151 	/**
152 	 * The prefix used for all id attributes in this model
153 	 */
154 	protected String idPrefix;
155 	/**
156 	 * The root of the SVG document
157 	 */
158 	protected OMSVGSVGElement svg;
159 	/**
160 	 * A group used to apply a visualization transform change the display
161 	 * of the document.
162 	 */
163 	protected OMSVGGElement xformGroup;
164 	/**
165 	 * The root of the model tree.
166 	 */
167 	protected SVGElementModel modelGroup;
168 	/**
169 	 * The matrix transform to the xform group and twin group
170 	 */
171 	protected OMSVGMatrix m;
172 	/**
173 	 * The current scaling the xform group and twin group
174 	 */
175 	protected float angle;
176 	/**
177 	 * The current rotation of the xform group and twin group
178 	 */
179 	protected float scale;
180 	/**
181 	 * The selection model
182 	 */
183 	protected SVGSelectionModel selectionModel;
184 	/**
185 	 * The current mode (false = display mode, true = highlighting mode) 
186 	 */
187 	protected boolean highlightingMode;
188 	/**
189 	 * The highlighted model in highlighting mode
190 	 */
191 	protected SVGElementModel highlightedModel;
192 	/**
193 	 * A map used to generate node names for nodes
194 	 * which do not have a title element.
195 	 */
196 	protected Map<String, Integer> tagNameToTagCount;
197 	/**
198 	 * The Store which contains this model data
199 	 */
200 	protected TreeStore<SVGElementModel> store;
201 	/**
202 	 * The Store which contains this model commands
203 	 */
204 	protected CommandStore commandStore;
205 	/**
206 	 * Associates SVG elements with their model wrapper
207 	 */
208 	protected Map<SVGElement, SVGElementModel> elementToModel;
209 	/**
210 	 * Associates element ids with their model
211 	 */
212 	protected Map<String, Object> idToModel;
213 	/**
214 	 * The svg rect use to represent the viewBox
215 	 */
216 	protected SVGViewBoxElementModel viewBox;
217 	/**
218 	 * A rectangled defining the bounds of the GXT viewport
219 	 */
220 	protected OMSVGRect windowRect;
221 	/**
222 	 * The grid settings for this model
223 	 */
224 	protected Grid grid;
225 
226 	/*==========================================================
227 	 * 
228 	 * C O N S T R U C T O R 
229 	 * 
230 	 *==========================================================*/
231 
232 	public SVGModel() {
233 		elementToModel = new HashMap<SVGElement, SVGElementModel>();
234 		idToModel = new HashMap<String, Object>();
235 		tagNameToTagCount = new HashMap<String, Integer>();
236 		grid = new Grid();
237 	}
238 	
239 	/*==========================================================
240 	 * 
241 	 * M O D E L   C O N S T R U C T I O N 
242 	 * 
243 	 *==========================================================*/
244 
245 	/**
246 	 * Factory method. Creates a new SVG model from the supplied
247 	 * SVG root and title
248 	 * @param svg The svg root
249 	 * @param title The svg title
250 	 * @param idPrefix the id prefix for this model
251 	 * @return The new SVG document
252 	 */
253 	public static SVGModel newInstance(OMSVGSVGElement svg, String title, String idPrefix) {
254 		SVGModel model = GWT.create(SVGModel.class);
255 		model.setSvgElement(svg, title, idPrefix);
256 		return model;
257 	}
258 	
259 	static {
260 		initialize();
261 	}
262 	protected static Map<String, MetaModel<SVGElement>> tagNameToMetamodel;
263 	public static MetaModel<SVGElement> getMetamodel(SVGElement element) {
264 		if (tagNameToMetamodel == null) {
265 			tagNameToMetamodel = new HashMap<String, MetaModel<SVGElement>>();
266 			tagNameToMetamodel.put(SVGConstants.SVG_CIRCLE_TAG, SVGCircleElementModel.getCircleElementMetaModel());
267 			tagNameToMetamodel.put(SVGConstants.SVG_ELLIPSE_TAG, SVGEllipseElementModel.getEllipseElementMetaModel());
268 			tagNameToMetamodel.put(SVGConstants.SVG_LINE_TAG, SVGLineElementModel.getLineElementMetaModel());
269 			tagNameToMetamodel.put(SVGConstants.SVG_RECT_TAG, SVGRectElementModel.getRectElementMetaModel());
270 			tagNameToMetamodel.put(SVGConstants.SVG_POLYGON_TAG, SVGPolygonElementModel.getPolygonElementMetaModel());
271 			tagNameToMetamodel.put(SVGConstants.SVG_POLYLINE_TAG, SVGPolylineElementModel.getPolylineElementMetaModel());
272 			tagNameToMetamodel.put(SVGConstants.SVG_PATH_TAG, SVGPathElementModel.getPathElementMetaModel());
273 			tagNameToMetamodel.put(SVGConstants.SVG_IMAGE_TAG, SVGImageElementModel.getImageElementMetaModel());
274 			tagNameToMetamodel.put(SVGConstants.SVG_USE_TAG, SVGUseElementModel.getUseElementMetaModel());
275 		}
276 		return tagNameToMetamodel.get(element.getTagName());
277 	}
278 
279 	private static final native void initialize() /*-{
280 		if ($wnd.otToModel == null) {
281 	    	$wnd.otToModel = new Object();
282 	    }
283 		$wnd.otToModel["SVGCircleElement"] = function(owner, elem, twin) { return @org.vectomatic.svg.edit.client.model.svg.SVGCircleElementModel::new(Lorg/vectomatic/svg/edit/client/engine/SVGModel;Lorg/vectomatic/dom/svg/impl/SVGCircleElement;Lorg/vectomatic/dom/svg/impl/SVGCircleElement;)(owner, elem, twin); };
284 		$wnd.otToModel["SVGEllipseElement"] = function(owner, elem, twin) { return @org.vectomatic.svg.edit.client.model.svg.SVGEllipseElementModel::new(Lorg/vectomatic/svg/edit/client/engine/SVGModel;Lorg/vectomatic/dom/svg/impl/SVGEllipseElement;Lorg/vectomatic/dom/svg/impl/SVGEllipseElement;)(owner, elem, twin); };
285 		$wnd.otToModel["SVGLineElement"] = function(owner, elem, twin) { return @org.vectomatic.svg.edit.client.model.svg.SVGLineElementModel::new(Lorg/vectomatic/svg/edit/client/engine/SVGModel;Lorg/vectomatic/dom/svg/impl/SVGLineElement;Lorg/vectomatic/dom/svg/impl/SVGLineElement;)(owner, elem, twin); };
286 		$wnd.otToModel["SVGRectElement"] = function(owner, elem, twin) { return @org.vectomatic.svg.edit.client.model.svg.SVGRectElementModel::new(Lorg/vectomatic/svg/edit/client/engine/SVGModel;Lorg/vectomatic/dom/svg/impl/SVGRectElement;Lorg/vectomatic/dom/svg/impl/SVGRectElement;)(owner, elem, twin); };
287 		$wnd.otToModel["SVGPolygonElement"] = function(owner, elem, twin) { return @org.vectomatic.svg.edit.client.model.svg.SVGPolygonElementModel::new(Lorg/vectomatic/svg/edit/client/engine/SVGModel;Lorg/vectomatic/dom/svg/impl/SVGPolygonElement;Lorg/vectomatic/dom/svg/impl/SVGPolygonElement;)(owner, elem, twin); };
288 		$wnd.otToModel["SVGPolylineElement"] = function(owner, elem, twin) { return @org.vectomatic.svg.edit.client.model.svg.SVGPolylineElementModel::new(Lorg/vectomatic/svg/edit/client/engine/SVGModel;Lorg/vectomatic/dom/svg/impl/SVGPolylineElement;Lorg/vectomatic/dom/svg/impl/SVGPolylineElement;)(owner, elem, twin); };
289 		$wnd.otToModel["SVGPathElement"] = function(owner, elem, twin) { return @org.vectomatic.svg.edit.client.model.svg.SVGPathElementModel::new(Lorg/vectomatic/svg/edit/client/engine/SVGModel;Lorg/vectomatic/dom/svg/impl/SVGPathElement;Lorg/vectomatic/dom/svg/impl/SVGPathElement;)(owner, elem, twin); };
290 		$wnd.otToModel["SVGImageElement"] = function(owner, elem, twin) { return @org.vectomatic.svg.edit.client.model.svg.SVGImageElementModel::new(Lorg/vectomatic/svg/edit/client/engine/SVGModel;Lorg/vectomatic/dom/svg/impl/SVGImageElement;Lorg/vectomatic/dom/svg/impl/SVGImageElement;)(owner, elem, twin); };
291 		$wnd.otToModel["SVGUseElement"] = function(owner, elem, twin) { return @org.vectomatic.svg.edit.client.model.svg.SVGUseElementModel::new(Lorg/vectomatic/svg/edit/client/engine/SVGModel;Lorg/vectomatic/dom/svg/impl/SVGUseElement;Lorg/vectomatic/dom/svg/impl/SVGUseElement;)(owner, elem, twin); };
292 	}-*/;
293 	
294 	/**
295 	 * Generates a model around an overlay type node
296 	 * @param <T> the node type
297 	 * @param element The overlay type node
298 	 * @return The node model
299 	 */
300 	public SVGElementModel convert(SVGElement element) {
301 		SVGElementModel model = elementToModel.get(element);
302 //		assert(model != null);
303 		return model;
304 	}
305 	
306 	public OMSVGSVGElement getSvgElement() {
307 		return svg;
308 	}
309 	
310 	/**
311 	 * Binds this SVG model to the specified SVG 'svg' element
312 	 * @param svg an SVG 'svg' element
313 	 * @param title the name of the root element
314 	 * @param idPrefix the id prefix for this model
315 	 */
316 	public void setSvgElement(OMSVGSVGElement svg, String title, String idPrefix) {
317 		this.svg = svg;
318 		this.idPrefix = idPrefix;
319 		windowRect = svg.createSVGRect();
320 
321 		// Force the svg to have its size managed by CSS
322 		// (no width and height attributes). This size will
323 		// be the min (window size, bbox of the svg in
324 		// screen coordinates taking into account the
325 		// viewing transform).
326 		svg.removeAttribute(SVGConstants.SVG_WIDTH_ATTRIBUTE);
327 		svg.removeAttribute(SVGConstants.SVG_HEIGHT_ATTRIBUTE);
328 		
329 	    // Add event handlers. These event handlers will re-route
330 	    // events to the highlighter or the manipulators
331 	    svg.addMouseMoveHandler(this);
332 	    svg.addMouseDownHandler(this);
333 	    svg.addMouseUpHandler(this);
334 
335 		elementToModel.clear();
336 		tagNameToTagCount.clear();
337 
338 		// Normalize ids to support multi-docs
339 	    SVGProcessor.normalizeIds(svg, idPrefix);
340 	    
341 	    // Create the transform group
342 	    xformGroup = new OMSVGGElement();
343 	    xformGroup.getTransform().getBaseVal().appendItem(svg.createSVGTransform());
344 
345 	    // Build the geometry group (used to control the viewing
346 	    // transform)
347 	    OMSVGGElement elementGroup = new OMSVGGElement();
348 		SVGNamedElementModel.createTitleDesc(elementGroup.getElement().<SVGElement>cast(), title);
349 	    SVGProcessor.reparent(svg, elementGroup);
350 	    
351 	    // Create the selection group to handle highlighting
352 	    // of the selection and hovered elements.
353 	    OMSVGGElement twinGroup = (OMSVGGElement) elementGroup.cloneNode(true);
354 	    twinGroup.getTransform().getBaseVal().appendItem(svg.createSVGTransform());
355 	    SVGProcessor.normalizeIds(twinGroup, SVGProcessor.newPrefixExtension(idPrefix, EXT_TWIN));
356 	    twinGroup.getStyle().setSVGProperty(SVGConstants.CSS_VISIBILITY_PROPERTY, SVGConstants.CSS_HIDDEN_VALUE);
357 	    
358 	    modelGroup = create(elementGroup.getElement().<SVGElement>cast(), twinGroup.getElement().<SVGElement>cast());
359 	    xformGroup.appendChild(elementGroup);
360 	    svg.appendChild(xformGroup);
361 	    svg.appendChild(twinGroup);
362 	    setScale(1f);
363 	    
364 	    // Build the SVG tree store
365 	    store = new TreeStore<SVGElementModel>();
366 	    store.add(modelGroup, true);
367 	    
368 	    // From now on, listen to changes to the model, to translate
369 	    // them into commands
370 	    store.addStoreListener(new StoreListener<SVGElementModel>() {
371 			@Override
372 			public void storeUpdate(StoreEvent<SVGElementModel> se) {
373 				SVGModel.this.storeUpdate(se);
374 			}
375 		});
376 
377 		// Keep the viewBox if available for later computation of the viewBox model
378 		// on svg attach. The actual SVG viewBox is removed.
379 		OMSVGRect viewBoxRect = svg.getViewBox().getBaseVal();
380 		if (viewBoxRect.getWidth() != 0f && viewBoxRect.getHeight() != 0f) {
381 			createViewBox(viewBoxRect);
382 		}
383 		svg.removeAttribute(SVGConstants.SVG_VIEW_BOX_ATTRIBUTE);
384 
385 	    // Build the command store
386 	    commandStore = new CommandStore();
387 
388 	    // Build the selection model
389 	    selectionModel = new SVGSelectionModel();
390 	    
391 	}
392 	
393 	/**
394 	 * Return true if the specified SVG element is part of this model
395 	 * @param element the element to test
396 	 * @return true if the specified SVG element is part of this model
397 	 */
398 	public boolean contains(SVGElement element) {
399 		return elementToModel.containsKey(element);
400 	}
401 	
402 	protected void adopt(SVGElementModel model) {
403 		adopt(model, true);
404 	}
405 	protected void adopt(SVGElementModel model, boolean root) {
406 		model.setOwner(this);
407 		elementToModel.put(model.getElement(), model);
408 		elementToModel.put(model.getTwin(), model);
409 		
410 		// Do a DFS-preorder traversal of the DOM tree
411 		SVGElementModel firstChild = (SVGElementModel) model.getChild(0);
412 		if (firstChild != null) {
413 			adopt(firstChild, false);
414 		}
415 		if (!root) {
416 			SVGElementModel nextSibling = model.getNextSibling();
417 			if (nextSibling != null) {
418 				adopt(nextSibling, false);
419 			}
420 		}
421 	}
422 
423 	protected void orphan(SVGElementModel model) {
424 		orphan(model, true);
425 	}
426 	
427 	protected void orphan(SVGElementModel model, boolean root) {
428 		model.setOwner(null);
429 		elementToModel.remove(model.getElement());
430 		elementToModel.remove(model.getTwin());
431 		
432 		// Do a DFS-preorder traversal of the DOM tree
433 		SVGElementModel firstChild = (SVGElementModel) model.getChild(0);
434 		if (firstChild != null) {
435 			orphan(firstChild, false);
436 		}
437 		if (!root) {
438 			SVGElementModel nextSibling = model.getNextSibling();
439 			if (nextSibling != null) {
440 				orphan(nextSibling, false);
441 			}
442 		}
443 	}
444 
445 	public SVGElementModel create(Node modelNode, Node modelTwin) {
446 		Stack<Node> stack = new Stack<Node>();
447 		stack.push(modelNode);
448 		stack.push(modelTwin);
449 		while(!stack.empty()) {
450 			Node twin = stack.pop();
451 			Node node = stack.pop();
452 			if (SVGProcessor.isSvgElement(node)) {
453 				if (SVGProcessor.isTitleDescElement(node.<SVGElement>cast())) {
454 					continue;
455 				}
456 //				if (SVGProcessor.isDefinitionElement(node.<SVGElement>cast())) {
457 //					continue;
458 //				}
459 //				if (SVGProcessor.isGraphicalElement(node.<SVGElement>cast())) {
460 					SVGElementModel model = convert(node.<SVGElement>cast());
461 					if (model == null) {
462 						model = create(this, node, twin);
463 						adopt(model, false);
464 					}
465 					SVGElementModel parentModel = (SVGElementModel) model.getParent();
466 					if (parentModel == null) {
467 						parentModel = convert(node.getParentElement().<SVGElement>cast());
468 					}
469 					if (parentModel != null) {
470 						parentModel.add(model);
471 					}
472 //				}
473 			}
474 			NodeList<Node> nodeChildren = node.getChildNodes();
475 			NodeList<Node> twinChildren = twin.getChildNodes();
476 			for (int i = nodeChildren.getLength() - 1; i >= 0; i--) {
477 				stack.push(nodeChildren.getItem(i));
478 				stack.push(twinChildren.getItem(i));
479 			}
480 		}
481 		return convert(modelNode.<SVGElement>cast());
482 	}
483 	
484 	private final native SVGElementModel create(SVGModel owner, Node element, Node twin) /*-{
485 	    var type = @org.vectomatic.dom.svg.utils.DOMHelper::getType(Lcom/google/gwt/core/client/JavaScriptObject;)(element);
486 	    if (type) {
487 	    	var ctor = $wnd.otToModel[type];
488 	    	if (ctor != null) {
489 				return ctor(owner, element, twin);
490 	    	} else {
491 	    		if (@org.vectomatic.svg.edit.client.engine.SVGProcessor::isTransformable(Lcom/google/gwt/dom/client/Node;)(element)) {
492 	    			return @org.vectomatic.svg.edit.client.model.svg.SVGGenericTransformableModel::new(Lorg/vectomatic/svg/edit/client/engine/SVGModel;Lorg/vectomatic/dom/svg/impl/SVGElement;Lorg/vectomatic/dom/svg/impl/SVGElement;)(owner, element, twin);
493 	    		}
494 	    		return @org.vectomatic.svg.edit.client.model.svg.SVGGenericElementModel::new(Lorg/vectomatic/svg/edit/client/engine/SVGModel;Lorg/vectomatic/dom/svg/impl/SVGElement;Lorg/vectomatic/dom/svg/impl/SVGElement;)(owner, element, twin);
495 	    	}
496 	    }
497 	    return null;
498 	}-*/;
499 	
500 	/**
501 	 * Appends a model to the children of the specified model
502 	 * @param parentModel The parent model
503 	 * @param model The model to append
504 	 */
505 	public void add(SVGElementModel parentModel, SVGElementModel model) {
506 		insertBefore(parentModel, model, null);
507 	}
508 
509 	/**
510 	 * Insert a new model into this SVG model before the specified child model
511 	 * or the specified parent model.
512 	 * @param parentModel The parent model
513 	 * @param newModel The model to insert. If the model is not in this SVG model,
514 	 * it is removed from its previous SVG model before being inserted into this SVG model.
515 	 * @param refModel The reference model. If null, the new model is appended to
516 	 * the children of the parent model
517 	 */
518 	public void insertBefore(SVGElementModel parentModel, SVGElementModel newModel, SVGElementModel refModel) {
519 		// Sanity checks
520 		Element element = newModel.getElement();
521 		Element parentElement = parentModel.getElement();
522 		assert(parentElement != null);
523 		assert(contains(parentElement.<SVGElement>cast())) : parentModel.toString() + " element is not in this model";
524 		Element refElement = refModel != null ? refModel.getElement() : null;
525 		if (refElement != null) {
526 			assert(contains(refElement.<SVGElement>cast())) : refElement.toString() + " element is not in this model";
527 		}
528 		
529 		Element twin = newModel.getTwin();
530 		Element parentTwin = parentModel.getTwin();
531 		assert(parentTwin != null);
532 		assert(contains(parentTwin.<SVGElement>cast())) : parentModel.toString() + " twin is not in this model";
533 		Element refTwin = refModel != null ? refModel.getTwin() : null;
534 		if (refElement != null) {
535 			assert(contains(refTwin.<SVGElement>cast())) : refElement.toString() + " twin is not in this model";
536 		}
537 		
538 		// Update SVG models
539 		SVGModel owner = newModel.getOwner();
540 		if (owner != null) {
541 			owner.remove(newModel);
542 		}
543 		adopt(newModel);
544 
545 		// Update the DOM tree
546 		parentElement.insertBefore(element, refElement);
547 		// Update the twin DOM tree
548 		parentTwin.insertBefore(twin, refTwin);
549 		
550 		
551 		if (refModel == null) {
552 			// Update the model tree
553 			parentModel.add(newModel);
554 
555 			// Update the store
556 			store.add(parentModel, newModel, true);
557 		} else {
558 			int index = parentModel.indexOf(refModel);
559 			// Update the model tree
560 			parentModel.insert(newModel, index);
561 			
562 			// Update the store
563 			store.insert(parentModel, newModel, index, true);			
564 		}
565 	}
566 
567 	/**
568 	 * Removes the specified model
569 	 * @param model
570 	 */
571 	public void remove(SVGElementModel model) {
572 		if (model.getOwner() == this) {
573 			// Update the SVG model
574 			orphan(model);
575 
576 			// Update the DOM tree
577 			Element element = model.getElement();
578 			Element parentElement = element.getParentElement();
579 			if (parentElement != null) {
580 				parentElement.removeChild(element);
581 			}
582 			Element twin = model.getTwin();
583 			Element parentTwin = twin.getParentElement();
584 			if (parentTwin != null) {
585 				parentTwin.removeChild(twin);
586 			}
587 			
588 			// Update the model tree
589 			if (model.getParent() != null) {
590 				model.getParent().remove(model);
591 			}
592 			
593 			// Update the store
594 			store.removeAll(model);
595 			store.remove(model);
596 		}
597 	}
598 	
599 	/**
600 	 * Recursively clones the specified model.
601 	 * @param model
602 	 * @return the root of the cloned tree
603 	 */
604 	public SVGElementModel clone(SVGElementModel model, String name) {
605 		SVGElementModel clone = create(model.getElement().cloneNode(true).<SVGElement>cast(), model.getTwin().cloneNode(true).<SVGElement>cast());
606 		clone.set(SVGConstants.SVG_TITLE_TAG, name);
607 		return clone;
608 	}
609 	
610 	
611 	protected void storeUpdate(StoreEvent<SVGElementModel> se) {
612 		ICommandFactory factory = SvgrealApp.getApp().getCommandFactorySelector().getActiveFactory();
613 		if (factory instanceof StoreEventProcessor) {
614 			((StoreEventProcessor)factory).processStoreEvent(se);
615 		}
616 	}
617 
618 	public String getMarkup() {
619 		OMSVGSVGElement svg = new OMSVGSVGElement();
620 		svg.setAttribute(SVGConstants.XMLNS_PREFIX + ":" + SVGConstants.XLINK_PREFIX, SVGConstants.XLINK_NAMESPACE_URI);
621 		svg.setViewBox(
622 				viewBox.<Float>get(SVGConstants.SVG_X_ATTRIBUTE), 
623 				viewBox.<Float>get(SVGConstants.SVG_Y_ATTRIBUTE), 
624 				viewBox.<Float>get(SVGConstants.SVG_WIDTH_ATTRIBUTE),
625 				viewBox.<Float>get(SVGConstants.SVG_HEIGHT_ATTRIBUTE));
626 		SVGGElement g = modelGroup.getElement().cloneNode(true).cast();
627 		
628 		// Skip the element representing the viewbox
629 		Node viewBoxElement = DOMHelper.evaluateNodeXPath(g, "//svg:rect[@" + ATTR_KIND + "='" + ATTR_KIND_VIEWBOX + "']", SVGPrefixResolver.INSTANCE);
630 		
631 		Node node = null;
632 		while((node = g.getFirstChild()) != null) {
633 			Node child = g.removeChild(node);
634 			if (child != viewBoxElement) {
635 				svg.getElement().appendChild(child);
636 			}
637 		}
638 		return svg.getMarkup();
639 	}
640 	
641 	/**
642 	 * Returns the root node of this model
643 	 * @return
644 	 */
645 	public SVGElementModel getRoot() {
646 		return store.getRootItems().get(0);
647 	}
648 
649 	/*==========================================================
650 	 * 
651 	 * G E T T E R S 
652 	 * 
653 	 *==========================================================*/
654 
655 	/**
656 	 * Returns the Store which contains this model data
657 	 * @return
658 	 */
659 	public TreeStore<SVGElementModel> getStore() {
660 		return store;
661 	}
662 	/**
663 	 * Returns the CommandStore which contains this model commands
664 	 * @return
665 	 */
666 	public CommandStore getCommandStore() {
667 		return commandStore;
668 	}
669 
670 	/**
671 	 * Returns the selection model.
672 	 * @return the selection model.
673 	 */
674 	public TreePanelSelectionModel<SVGElementModel> getSelectionModel() {
675 		return selectionModel;
676 	}
677 
678 	/**
679 	 * Returns the model viewBox.
680 	 * @return the model viewBox.
681 	 */
682 	public SVGViewBoxElementModel getViewBox() {
683 		return viewBox;
684 	}
685 	
686 	/**
687 	 * Returns the document id prefix.
688 	 * @return the document id prefix.
689 	 */
690 	public String getIdPrefix() {
691 		return idPrefix;
692 	}
693 
694 	
695 	/*==========================================================
696 	 * 
697 	 * E V E N T   H A N D L I N G 
698 	 * 
699 	 *==========================================================*/
700 	
701 	@Override
702 	public void onMouseMove(MouseMoveEvent event) {
703 		// Forward to manipulator
704 		ICommandFactory commandFactory = SvgrealApp.getApp().getCommandFactorySelector().getActiveFactory();
705 		if (commandFactory instanceof MouseMoveProcessor) {
706 			if (((MouseMoveProcessor)commandFactory).processMouseMove(event)) {
707 				return;
708 			}
709 		}
710 
711 		// Highlighting
712 		if (highlightingMode) {
713 			SVGElementModel model = convert(event.getNativeEvent().getEventTarget().<SVGElement>cast());
714 			highlightModel(model);
715 		} 
716 	}
717 	
718 	@Override
719 	public void onMouseDown(MouseDownEvent event) {
720 		GWT.log("SVGModel.onMouseDown");
721 		SVGElement target = event.getNativeEvent().getEventTarget().cast();
722 		
723 		// Context menu
724 		if (event.getNativeButton() == 2) {
725 			SVGElementModel model = convert(target);
726 			if (model == null || SVGConstants.SVG_SVG_TAG.equals(target.getTagName())) {
727 				// Empty selection or unknown element
728 				selectionModel.deselectAll();
729 			} else {
730 				if (!selectionModel.isSelected(model)) {
731 					// mono selection
732 					selectionModel.select(model, false);
733 				} /*
734 				else {
735 					// mono or multiselection
736 				}
737 				*/
738 			}
739 			return;
740 		}
741 
742 		// Forward to manipulator
743 		ICommandFactory commandFactory = SvgrealApp.getApp().getCommandFactorySelector().getActiveFactory();
744 		if (commandFactory instanceof MouseDownProcessor) {
745 			if (((MouseDownProcessor)commandFactory).processMouseDown(event)) {
746 				return;
747 			}
748 		}
749 		
750 		// Selection
751 		if (SVGConstants.SVG_SVG_TAG.equals(target.getTagName())) {
752 			selectionModel.deselectAll();
753 		} else {
754 			SVGElementModel model = convert(target);			
755 			if (model != null) {
756 				if (selectionModel.isSelected(model)) {
757 					if (event.isControlKeyDown()) {
758 						// Toggle selection
759 						selectionModel.deselect(model);
760 					}
761 				} else if (event.isShiftKeyDown() | event.isControlKeyDown()) {
762 					// Add to selection
763 					selectionModel.select(model, true);
764 				} else {
765 					// New selection
766 					selectionModel.select(model, false);
767 				}
768 			}
769 		}
770 	}
771 
772 	@Override
773 	public void onMouseUp(MouseUpEvent event) {
774 		GWT.log("SVGModel.onMouseUp");
775 		ICommandFactory commandFactory = SvgrealApp.getApp().getCommandFactorySelector().getActiveFactory();
776 		if (commandFactory instanceof MouseUpProcessor) {
777 			((MouseUpProcessor)commandFactory).processMouseUp(event);
778 		}
779 	}
780 
781 	public void onKeyPress(ComponentEvent event) {
782 		int code = event.getKeyCode();
783 		GWT.log("SVGModel.onKeyPress: " + code);
784 		ICommandFactory commandFactory = SvgrealApp.getApp().getCommandFactorySelector().getActiveFactory();
785 		if (commandFactory instanceof KeyPressProcessor && ((KeyPressProcessor)commandFactory).processKeyPress(event)) {
786 			return;
787 		}
788 		if (code == KeyCodes.KEY_DELETE || code == KeyCodes.KEY_BACKSPACE) {
789 			RemoveElementsCommandFactory.INSTANTIATOR.create().start(this);
790 		}
791 		if (code == KeyNavExt.KEY_F2) {
792 			List<SVGElementModel> selectedItems = selectionModel.getSelectedItems();
793 			if (selectedItems.size() == 1) {
794 				SVGElementModel model = selectedItems.get(0);
795 				SvgrealApp.getApp().getWindow(model.getElement()).renameModel(model);
796 			}
797 		}
798 	}
799 	
800 	public void onKeyUp(ComponentEvent event) {
801 		int code = event.getKeyCode();
802 		GWT.log("SVGModel.onKeyUp: " + code);
803 		ICommandFactory commandFactory = SvgrealApp.getApp().getCommandFactorySelector().getActiveFactory();
804 		if (commandFactory instanceof KeyUpProcessor && ((KeyUpProcessor)commandFactory).processKeyUp(event)) {
805 			return;
806 		}
807 	}
808 	/**
809 	 * Updates the context menu based on the model selection
810 	 * @param contextMenu The context menu to update
811 	 */
812 	public void updateContextMenu(Menu contextMenu) {
813 		List<SVGElementModel> selectedModels = selectionModel.getSelectedItems();
814 		List<Item> items = new ArrayList<Item>();
815 		int size = selectedModels.size();
816 		if (size == 0) {
817 			// Empty selection
818 			items.add(new CommandFactoryMenuItem(AddLineCommandFactory.INSTANTIATOR));
819 			items.add(new CommandFactoryMenuItem(AddCircleCommandFactory.INSTANTIATOR));
820 			items.add(new CommandFactoryMenuItem(AddEllipseCommandFactory.INSTANTIATOR));
821 			items.add(new CommandFactoryMenuItem(AddRectCommandFactory.INSTANTIATOR));
822 			items.add(new CommandFactoryMenuItem(AddPolylineCommandFactory.INSTANTIATOR));
823 			items.add(new CommandFactoryMenuItem(AddPolygonCommandFactory.INSTANTIATOR));
824 			items.add(new CommandFactoryMenuItem(AddPathCommandFactory.INSTANTIATOR));
825 			items.add(new CommandFactoryMenuItem(AddGroupCommandFactory.INSTANTIATOR));
826 		} else if (size == 1) {
827 			// Mono selection
828 			MetaModel metaModel = selectedModels.get(0).getMetaModel();
829 			items.addAll(metaModel.getContextMenuItems());
830 			items.add(new CommandFactoryMenuItem(RemoveElementsCommandFactory.INSTANTIATOR));
831 		} else {
832 			// Multi selection
833 			items.add(new CommandFactoryMenuItem(RemoveElementsCommandFactory.INSTANTIATOR));
834 		}
835 		items.add(new CommandFactoryMenuItem(ShowPropertiesCommandFactory.INSTANTIATOR));
836 		contextMenu.removeAll();
837 		for (Item item : items) {
838 			contextMenu.add(item);			
839 		}
840 	}
841 
842     /**
843      * Returns the coordinates of a mouse event, converted
844      * to the coordinate system of the model
845      * @param e
846      * A mouse event
847      * @param snap
848      * True if the coordinate should be snapped to the grid when
849      * grid snapping is activated
850      * @return
851      * The coordinates of the mouse event, converted
852      * to the coordinate system of the specified matrix
853      */
854     public OMSVGPoint getCoordinates(MouseEvent<? extends EventHandler> e, boolean snap) {
855     	OMSVGMatrix m = modelGroup.getElement().<SVGGElement>cast().getScreenCTM().inverse();
856     	OMSVGPoint p = svg.createSVGPoint(e.getClientX(), e.getClientY()).matrixTransform(m);
857     	return grid.snapsToGrid() ? grid.snap(p) : p;
858     }
859 
860 	/*==========================================================
861 	 * 
862 	 * C A N V A S   S I Z I N G
863 	 * 
864 	 *==========================================================*/
865 	
866 	public void onAttach() {
867 		// Create a viewbox for model which do not define one.
868 		// The viewbox is created to be 10% larger that the bbox
869 		if (viewBox == null) {
870     		GWT.log(svg.getBBox().getDescription());
871     		createViewBox(svg.getBBox().inset(svg.createSVGRect(), -0.1f * svg.getBBox().getWidth(), -0.1f * svg.getBBox().getHeight()));
872 		}
873 		if (!grid.isAttached()) {
874 			grid.attach(this);
875 			svg.insertBefore(grid.getDefs(), xformGroup);
876 			xformGroup.getElement().insertBefore(grid.getRoot().getElement(), modelGroup.getElement());
877 		}
878 		// Validate the model
879 		modelGroup.computeSeverity();
880 	}
881 	
882 	private void createViewBox(OMSVGRect viewBoxRect) {
883 		OMSVGDocument document = (OMSVGDocument) svg.getOwnerDocument();
884 		OMSVGRectElement rect = document.createSVGRectElement(viewBoxRect);
885 		rect.getStyle().setSVGProperty(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE);
886 		rect.getStyle().setSVGProperty(SVGConstants.CSS_STROKE_PROPERTY, SVGConstants.CSS_BLACK_VALUE);
887 		rect.getStyle().setSVGProperty(SVGConstants.CSS_STROKE_DASHARRAY_PROPERTY, "4, 2");
888 		rect.setAttribute(ATTR_KIND, ATTR_KIND_VIEWBOX);
889 		SVGRectElement element = rect.getElement().cast();
890 		SVGRectElement twin = element.cloneNode(true).cast();
891 		viewBox = new SVGViewBoxElementModel(this, element, twin);
892 		adopt(viewBox);
893 		
894 		insertBefore(modelGroup, viewBox, (SVGElementModel) modelGroup.getChild(0) /* will return null if geometryGroup has not children*/);
895 	}
896 
897 	/**
898 	 * Returns the scaling of the SVG.
899 	 * @return The scale (1 means scale 1:1, 2 means scale 2:1)
900 	 */
901 	public float getScale() {
902 		return scale;
903 	}
904 
905 	/**
906 	 * Sets the scaling of the SVG.
907 	 * @param scale
908 	 * The scale (1 means scale 1:1, 2 means scale 2:1)
909 	 */
910 	public void setScale(float scale) {
911 		this.scale = scale;
912 		updateTransform();
913 		fireEvent(new ScalingEvent(scale));
914 	}
915 	
916 	public float getRotation() {
917 		return angle;
918 	}
919 	
920 	/**
921 	 * Sets the rotation of the SVG.
922 	 * @param angle
923 	 * The angle (in degrees)
924 	 */
925 	public void setRotation(float angle) {
926 		this.angle = angle;
927 		updateTransform();
928 		fireEvent(new RotationEvent(angle));
929 	}
930 	
931 	/**
932 	 * Returns this document grid
933 	 * @return
934 	 */
935 	public Grid getGrid() {
936 		return grid;
937 	}
938 
939 	@Override
940 	public void fireEvent(GwtEvent<?> event) {
941 		SvgrealApp.getApp().getEventBus().fireEventFromSource(event, this);
942 	}
943 	
944 	@Override
945 	public HandlerRegistration addRotationHandler(RotationHandler handler) {
946 		return SvgrealApp.getApp().getEventBus().addHandlerToSource(RotationEvent.getType(), this, handler);
947 	}
948 
949 	@Override
950 	public HandlerRegistration addScalingHandler(ScalingHandler handler) {
951 		return SvgrealApp.getApp().getEventBus().addHandlerToSource(ScalingEvent.getType(), this, handler);
952 	}
953 	
954 	/**
955 	 * Specifies the dimensions of the window viewport
956 	 * @param width width of the window viewport
957 	 * @param height height of the window viewport
958 	 */
959 	public void setWindowRect(int width, int height) {
960 		GWT.log("setWindowRect(" + width + ", " + height + ")");
961 		windowRect.setWidth(width);
962 		windowRect.setHeight(height);
963 		updateTransform();
964 	}
965 	public OMSVGRect getWindowRect() {
966 		return windowRect;
967 	}
968 	/**
969 	 * Updates the display group transform and changes the CSS size
970 	 * of the SVG accordingly
971 	 */
972 	public void updateTransform() {
973 		if (viewBox != null) {
974 			OMSVGRect bbox = ((SVGRectElement)viewBox.getElement()).getBBox();
975 //			GWT.log("bbox = " + bbox.getDescription());
976 			float d = (float)Math.sqrt((bbox.getWidth() * bbox.getWidth() + bbox.getHeight() * bbox.getHeight()) * 0.25) * scale * 2;
977 //			GWT.log("d = " + d);
978 		
979 			// Compute the actual canvas size. It is the max of the window rect
980 			// and the transformed bbox.
981 			float width = Math.max(d, windowRect.getWidth());
982 			float height = Math.max(d, windowRect.getHeight());
983 //			GWT.log("width = " + width);
984 //			GWT.log("height = " + height);
985 
986 			// Compute the display transform to center the image in the
987 			// canvas
988 			OMSVGMatrix m = svg.createSVGMatrix();
989 			float cx = bbox.getCenterX();
990 			float cy = bbox.getCenterY();
991 			m = m.translate(0.5f * (width - bbox.getWidth()) -bbox.getX(), 0.5f * (height - bbox.getHeight()) -bbox.getY())
992 			.translate(cx, cy)
993 			.rotate(angle)
994 			.scale(scale)
995 			.translate(-cx, -cy);
996 			((ISVGTransformable)xformGroup).getTransform().getBaseVal().getItem(0).setMatrix(m);
997 			((ISVGTransformable)modelGroup.getTwinWrapper()).getTransform().getBaseVal().getItem(0).setMatrix(m);
998 //			GWT.log("m=" + m.getDescription());
999 			svg.getStyle().setWidth(width, Unit.PX);
1000 			svg.getStyle().setHeight(height, Unit.PX);
1001 		}
1002 	}
1003 
1004 	/**
1005 	 * Computes the size of the vertex representation (it should be 1mm
1006 	 * whatever the scaling factor).
1007 	 * @return
1008 	 */
1009 	public static float getVertexSize(SVGElementModel model) {
1010 		return getVertexSize((ISVGLocatable)model.getElementWrapper());
1011 	}
1012 	
1013 	public static float getVertexSize(ISVGLocatable element) {
1014 		OMSVGMatrix m = element.getScreenCTM().inverse();
1015 		// 1mm = 3.543307px
1016 		float a = 3.543307f * m.getA();
1017 		float b = 3.543307f * m.getD();
1018 		return (float)Math.sqrt(a * a + b * b);
1019 	}
1020 
1021 	/*==========================================================
1022 	 * 
1023 	 * H I G H L I G H T I N G
1024 	 * 
1025 	 *==========================================================*/
1026 
1027 	
1028 	public OMSVGGElement getElementGroup() {
1029 		return (OMSVGGElement) modelGroup.getElementWrapper();
1030 	}
1031 	public OMSVGGElement getTwinGroup() {
1032 		return (OMSVGGElement) modelGroup.getTwinWrapper();
1033 	}
1034 
1035 	public boolean isHighlightingMode() {
1036 		return highlightingMode;
1037 	}
1038 	
1039 	public void setHighlightingMode(boolean highlightingMode) {
1040 		if (highlightingMode != this.highlightingMode) {
1041 //			GWT.log("setHighlightingMode(" + highlightingMode + ")");
1042 			this.highlightingMode = highlightingMode;
1043 			float opacity = this.highlightingMode ? 0.25f : 1f;
1044 			modelGroup.getElementWrapper().getStyle().setSVGProperty(SVGConstants.CSS_OPACITY_PROPERTY, Float.toString(opacity));
1045 
1046 			for (SVGElementModel model : selectionModel.getSelectedItems()) {
1047 				displayTwin(model, highlightingMode);
1048 			}
1049 			highlightModel(null);
1050 		}
1051 	}
1052 	
1053 	public void highlightModel(SVGElementModel model) {
1054 		if (model != highlightedModel) {
1055 //			GWT.log("highlightModel(" + (model != null ? model.getTwin() : null) + ")");
1056 			if (highlightedModel != null && !selectionModel.isSelected(highlightedModel)) {
1057 				displayTwin(highlightedModel, false);
1058 			}
1059 			if (model != null) {
1060 				displayTwin(model, true);
1061 			}
1062 			highlightedModel = model;
1063 		}
1064 	}
1065 	
1066 	public void displayTwin(SVGElementModel model, boolean value) {
1067 //		GWT.log("displayTwin(" + ((model == null) ? "null" : model.getTwin()) + " ==> " + value + ")");
1068 		if (model != modelGroup) {
1069 			OMSVGStyle style  = model.getTwin().getStyle().cast();
1070 			if (style != null) {
1071 				if (value) {
1072 					style.setSVGProperty(SVGConstants.CSS_VISIBILITY_PROPERTY, SVGConstants.CSS_VISIBLE_VALUE);
1073 //					style.setSVGProperty(SVGConstants.CSS_POINTER_EVENTS_PROPERTY, SVGConstants.CSS_NONE_VALUE);
1074 				} else {
1075 					style.clearSVGProperty(SVGConstants.CSS_VISIBILITY_PROPERTY);		
1076 				}
1077 			}
1078 		}
1079 	}
1080 	
1081 	/*==========================================================
1082 	 * 
1083 	 * E L E M E N T   N A M I N G
1084 	 * 
1085 	 *==========================================================*/
1086 
1087 	public String generateName(SVGElementModel model) {
1088 		String name = model.getMetaModel().getName();
1089 		if (name == null) {
1090 			name = DOMHelper.getLocalName(model.getElement());
1091 		}
1092 		Integer count = tagNameToTagCount.get(name);
1093 		if (count == null) {
1094 			count = 0;
1095 		}
1096 		tagNameToTagCount.put(name, count + 1);
1097 		return name + (count + 1);
1098 	}
1099 	
1100 	/*==========================================================
1101 	 * 
1102 	 * R E F E R E N C E   S O L V I N G
1103 	 * 
1104 	 *==========================================================*/
1105 	
1106 	public OMSVGElement dereference(String idref) {
1107 		if (idref.startsWith("#")) {
1108 			idref = idref.substring(1);
1109 		} else if (idref.startsWith("url(#")) {
1110 			idref = idref.substring(5, idref.length() - 1);
1111 		}
1112 		return OMSVGParser.currentDocument().getElementById(idref);
1113 	}
1114 }