View Javadoc

1   /**********************************************
2    * Copyright (C) 2010 Lukas Laag
3    * This file is part of vectomatic2.
4    * 
5    * vectomatic2 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   * vectomatic2 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 vectomatic2.  If not, see http://www.gnu.org/licenses/
17   **********************************************/
18  package org.vectomatic.svg.edit.client;
19  
20  import org.vectomatic.dom.svg.OMSVGGElement;
21  import org.vectomatic.dom.svg.OMSVGMatrix;
22  import org.vectomatic.dom.svg.OMSVGPoint;
23  import org.vectomatic.dom.svg.OMSVGRect;
24  import org.vectomatic.dom.svg.OMSVGSVGElement;
25  import org.vectomatic.dom.svg.OMSVGTransform;
26  import org.vectomatic.dom.svg.OMSVGTransformList;
27  import org.vectomatic.dom.svg.ui.SVGImage;
28  import org.vectomatic.dom.svg.utils.OMSVGParser;
29  import org.vectomatic.dom.svg.utils.SVGConstants;
30  import org.vectomatic.svg.edit.client.engine.SVGProcessor;
31  import org.vectomatic.svg.edit.client.event.RotationEvent;
32  import org.vectomatic.svg.edit.client.event.RotationHandler;
33  import org.vectomatic.svg.edit.client.gxt.AbsoluteLayerLayout;
34  import org.vectomatic.svg.edit.client.gxt.AbsoluteLayerLayoutData;
35  import org.vectomatic.svg.edit.client.widget.Compass;
36  
37  import com.extjs.gxt.ui.client.Style;
38  import com.extjs.gxt.ui.client.event.DragEvent;
39  import com.extjs.gxt.ui.client.event.Events;
40  import com.extjs.gxt.ui.client.event.Listener;
41  import com.extjs.gxt.ui.client.event.SliderEvent;
42  import com.extjs.gxt.ui.client.widget.LayoutContainer;
43  import com.extjs.gxt.ui.client.widget.Slider;
44  import com.extjs.gxt.ui.client.widget.Window;
45  import com.extjs.gxt.ui.client.widget.layout.FitData;
46  import com.extjs.gxt.ui.client.widget.layout.FitLayout;
47  import com.google.gwt.core.client.GWT;
48  import com.google.gwt.dom.client.Element;
49  import com.google.gwt.dom.client.Node;
50  import com.google.gwt.dom.client.Style.Unit;
51  
52  /**
53   * GXT window class dedicated to displaying and editing
54   * a single SVG image. The window has several layers: the
55   * bottom layer contains the SVG image itself and the top
56   * layer contains widgets to manipulate it (rotation compass
57   * and scale slider).
58   * @author laaglu
59   */
60  public class SVGWindow extends Window {
61  	/**
62  	 * The svg image to display
63  	 */
64  	protected OMSVGSVGElement svg;
65  	/**
66  	 * A group to apply a visualization transform to the svg
67  	 * It is the first and only child of the svg
68  	 */
69  	protected OMSVGGElement xformGroup;
70  	/**
71  	 * The current transform
72  	 */
73  	protected OMSVGTransform xform;
74  	/**
75  	 * The current scaling the the svg
76  	 */
77  	protected float angle;
78  	/**
79  	 * The current rotation of the svg
80  	 */
81  	protected float scale;
82  	/**
83  	 * The SVG rotation compass
84  	 */
85  	protected Compass compass;
86  	/**
87  	 * The SVG scale slider
88  	 */
89  	protected Slider scaleSlider;
90  	/**
91  	 * Specifies the SVG window to display
92  	 * @param svg
93  	 * The SVG image to display
94  	 */
95  	public void setSvg(final OMSVGSVGElement svg) {
96  		this.svg = svg;
97  		setPlain(true);
98  		setMaximizable(true);
99  		setSize(500, 300);
100 		setMinWidth(200);
101 		setMinHeight(170);
102 		
103 		// A CSS multi-layer container
104 		// The lower layer contains the image itself
105 		// The higher layer contains the control (compass and scale slider)
106 		LayoutContainer layersContainer = new LayoutContainer();
107 		GWT.log("borders: " + getBorders());
108 	    layersContainer.setLayout(new AbsoluteLayerLayout());
109 
110 	    /////////////////////////////////////////////////
111 	    // Populate the lower layer
112 	    /////////////////////////////////////////////////
113 		LayoutContainer svgContainer = new LayoutContainer();
114 	    svgContainer.setScrollMode(Style.Scroll.AUTO);
115 	    svgContainer.setStyleAttribute("background-color", SVGConstants.CSS_WHITE_VALUE);
116 	    SVGProcessor.normalizeIds(svg);
117 	    SVGImage image = new SVGImage(svg) {
118 	    	protected void onAttach() {
119 	    		GWT.log("onAttach");
120 	    		OMSVGRect viewBox = svg.getViewBox().getBaseVal();
121 	    		if (viewBox.getWidth() == 0f || viewBox.getHeight() == 0f) {
122 		    		GWT.log(svg.getBBox().getDescription());
123 	    			OMSVGRect bbox = inset(svg.getBBox(), svg.createSVGRect(), -0.1f * svg.getBBox().getWidth(), -0.1f * svg.getBBox().getHeight());
124 	    			viewBox.setWidth(bbox.getWidth());
125 	    			viewBox.setHeight(bbox.getHeight());
126 	    			setScale(scale);
127 	    		}
128 	    	}
129 	    };
130 	    svgContainer.add(image);
131 	    layersContainer.add(svgContainer, new AbsoluteLayerLayoutData(
132 	    		AbsoluteLayerLayoutData.HORIZONTAL_ATTACH_LEFT | AbsoluteLayerLayoutData.VERTICAL_ATTACH_TOP,
133 	    		0,
134 	    		0,
135 	    		0,
136 	    		0,
137 	    		10));
138 	    
139 	    // Tweak the image to insert a group immediately below
140 	    // the root. This group will be used to control the viewing
141 	    // transform
142 	    xformGroup = reparent(image.getSvgElement());
143 		OMSVGTransformList xformList = xformGroup.getTransform().getBaseVal();
144 		xform = svg.createSVGTransform();
145 		xformList.appendItem(xform);
146 	    setScale(1f);
147 
148 	    /////////////////////////////////////////////////
149 	    // Populate the higher layer
150 	    /////////////////////////////////////////////////
151 		
152 		// Create the compass
153 	    compass = GWT.create(Compass.class);
154 	    final OMSVGSVGElement compassSvg = compass.getSvgElement();
155 	    compassSvg.getStyle().setWidth(100, Unit.PCT);
156 	    compassSvg.getStyle().setHeight(100, Unit.PCT);
157 	    compass.addRotationHandler(new RotationHandler() {
158 	    	@Override
159 	    	public void onRotate(RotationEvent event) {
160 	    		setRotation(event.getAngle());
161 	    	}	
162 	    });
163 	    layersContainer.add(new SVGImage(compassSvg), new AbsoluteLayerLayoutData(
164 	    		AbsoluteLayerLayoutData.HORIZONTAL_ATTACH_RIGHT | AbsoluteLayerLayoutData.VERTICAL_ATTACH_TOP,
165 	    		40,
166 	    		5,
167 	    		100,
168 	    		100,
169 	    		20));
170 	    
171 		// Create the scale slider
172 		scaleSlider = new Slider() {
173 			@Override
174 	    	protected String onFormatValue(int value) {
175 				return Integer.toString((int)(scale * 100)) + "%";
176 	    	}
177 
178 		};
179 		scaleSlider.setHeight(100);
180 		scaleSlider.setMinValue(0);
181 		scaleSlider.setMaxValue(100);
182 		scaleSlider.setIncrement(1);
183 		scaleSlider.setValue(50);
184 		scaleSlider.setVertical(true);
185 		layersContainer.add(scaleSlider, new AbsoluteLayerLayoutData(
186 	    		AbsoluteLayerLayoutData.HORIZONTAL_ATTACH_RIGHT | AbsoluteLayerLayoutData.VERTICAL_ATTACH_TOP,
187 	    		20,
188 	    		5,
189 	    		20,
190 	    		100,
191 	    		20));
192 	    scaleSlider.addListener(Events.Change, new Listener<SliderEvent>() {
193 			@Override
194 			public void handleEvent(SliderEvent be) {
195 				// Convert from slider unit to transform unit
196 				int value = be.getNewValue();
197 				if (value >= 50) {
198 					scale = 1f + (value - 50f) / 10f * 4 / 5;
199 				} else {
200 					scale = 1f / (1f + (49 - value) / 10f * 4 / 5);
201 				}
202 				setScale(scale);
203 			}	    	
204 	    });
205 	    
206 		setLayout(new FitLayout());
207 		add(layersContainer, new FitData(4));
208 	}
209 	
210 	private static OMSVGRect inset(OMSVGRect src, OMSVGRect dest, float x, float y) {
211 		dest.setX(src.getX() + x);
212 		dest.setY(src.getY() + y);
213 		dest.setWidth(src.getWidth() - x * 2);
214 		dest.setHeight(src.getHeight() - y * 2);
215 		return dest;
216 	}
217 	
218 	/**
219 	 * Sets the scaling of the main image through the scale slider.
220 	 * @param scale
221 	 * The scale (50 means scale 1:1)
222 	 */
223 	public void setScaleSlider(int value) {
224 		scaleSlider.setValue(value);
225 	}
226 	
227 	/**
228 	 * Sets the scaling of the main image.
229 	 * @param scale
230 	 * The scale (1 means scale 1:1, 2 means scale 2:1)
231 	 */
232 	protected void setScale(float scale) {
233 		this.scale = scale;
234 		OMSVGRect rect = svg.getViewBox().getBaseVal();
235         svg.getStyle().setWidth(rect.getWidth() * scale, Unit.PX);
236         svg.getStyle().setHeight(rect.getHeight() * scale, Unit.PX);
237 	}
238 
239 	
240 	/**
241 	 * Sets the rotation of the main image through the
242 	 * compass widget.
243 	 * @param angleDeg
244 	 * The angle (in degrees)
245 	 */
246 	public void setRotationCompass(int angleDeg) {
247 		compass.setRotation(angleDeg);
248 	}
249 	
250 	/**
251 	 * Sets the rotation of the main image.
252 	 * @param angle
253 	 * The angle (in degrees)
254 	 */
255 	protected void setRotation(float angle) {
256 		this.angle = angle;
257 		OMSVGRect rect = svg.getViewBox().getBaseVal();
258 		OMSVGPoint center = svg.createSVGPoint(rect.getCenterX(), rect.getCenterY());
259 		OMSVGMatrix m1 = svg.createSVGMatrix().translate(center.getX(), center.getY());
260     	OMSVGMatrix m2 = svg.createSVGMatrix().rotate(this.angle);
261     	OMSVGMatrix m3 = svg.createSVGMatrix().translate(-center.getX(), -center.getY());
262     	OMSVGMatrix m = m1.multiply(m2).multiply(m3);
263 		xform.setMatrix(m);
264 	}
265 	
266 	private OMSVGGElement reparent(OMSVGSVGElement svg) {
267 		OMSVGGElement g = OMSVGParser.currentDocument().createSVGGElement();
268 		Element gElement = g.getElement();
269 		Element svgElement = svg.getElement();
270 		Node node;
271 		while((node = svgElement.getFirstChild()) != null) {
272 			gElement.appendChild(svgElement.removeChild(node));
273 		}
274 		svgElement.appendChild(gElement);
275 		return g;
276 	}
277 	
278 	@Override
279 	protected void moveDrag(DragEvent de) {
280 		int windowBarHeight = VectomaticApp2.getWindowBarHeight();
281 		if (de.getY() < windowBarHeight) {
282 			de.setY(windowBarHeight);
283 		}
284 	}
285 	/* GWT bug ?
286 	 * line 234: The method endDrag(DragEvent) in the type Window is not applicable for the arguments (DragEvent, boolean)*/
287 //	protected void endDrag(DragEvent de, boolean canceled) {
288 //		GWT.log("endDrag" + de.getX() + " " + de.getY());
289 //		int windowBarHeight = VectomaticApp2.getWindowBarHeight();
290 //		if (de.getY() < windowBarHeight) {
291 //			de.setY(windowBarHeight);
292 //		}
293 //		super.endDrag(de, canceled);
294 //	}
295 }
296