View Javadoc

1   /**********************************************
2    * Copyright (C) 2010 Lukas Laag
3    * This file is part of lib-gwt-svg-edu.
4    * 
5    * libgwtsvg-edu 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   * libgwtsvg-edu 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 libgwtsvg-edu.  If not, see http://www.gnu.org/licenses/
17   **********************************************/
18  package org.vectomatic.svg.edu.client.dots;
19  
20  import java.util.ArrayList;
21  import java.util.List;
22  
23  import org.vectomatic.dom.svg.OMNode;
24  import org.vectomatic.dom.svg.OMNodeList;
25  import org.vectomatic.dom.svg.OMSVGCircleElement;
26  import org.vectomatic.dom.svg.OMSVGDefsElement;
27  import org.vectomatic.dom.svg.OMSVGFEColorMatrixElement;
28  import org.vectomatic.dom.svg.OMSVGFEGaussianBlurElement;
29  import org.vectomatic.dom.svg.OMSVGFilterElement;
30  import org.vectomatic.dom.svg.OMSVGGElement;
31  import org.vectomatic.dom.svg.OMSVGLength;
32  import org.vectomatic.dom.svg.OMSVGMatrix;
33  import org.vectomatic.dom.svg.OMSVGNumber;
34  import org.vectomatic.dom.svg.OMSVGPoint;
35  import org.vectomatic.dom.svg.OMSVGPointList;
36  import org.vectomatic.dom.svg.OMSVGPolylineElement;
37  import org.vectomatic.dom.svg.OMSVGRect;
38  import org.vectomatic.dom.svg.OMSVGSVGElement;
39  import org.vectomatic.dom.svg.OMSVGTextElement;
40  import org.vectomatic.dom.svg.OMSVGTransform;
41  import org.vectomatic.dom.svg.OMText;
42  import org.vectomatic.dom.svg.ui.SVGPushButton;
43  import org.vectomatic.dom.svg.utils.AsyncXmlLoader;
44  import org.vectomatic.dom.svg.utils.AsyncXmlLoaderCallback;
45  import org.vectomatic.dom.svg.utils.DOMHelper;
46  import org.vectomatic.dom.svg.utils.OMSVGParser;
47  import org.vectomatic.dom.svg.utils.SVGConstants;
48  import org.vectomatic.svg.edu.client.commons.CommonConstants;
49  import org.vectomatic.svg.edu.client.commons.LicenseBox;
50  import org.vectomatic.svg.edu.client.commons.Utils;
51  
52  import com.google.gwt.animation.client.Animation;
53  import com.google.gwt.core.client.Duration;
54  import com.google.gwt.core.client.EntryPoint;
55  import com.google.gwt.core.client.GWT;
56  import com.google.gwt.dom.client.Element;
57  import com.google.gwt.dom.client.Node;
58  import com.google.gwt.dom.client.Style.Unit;
59  import com.google.gwt.dom.client.Style.Visibility;
60  import com.google.gwt.event.dom.client.ChangeEvent;
61  import com.google.gwt.event.dom.client.ClickEvent;
62  import com.google.gwt.event.dom.client.MouseDownEvent;
63  import com.google.gwt.event.dom.client.MouseDownHandler;
64  import com.google.gwt.event.dom.client.MouseEvent;
65  import com.google.gwt.event.dom.client.MouseMoveEvent;
66  import com.google.gwt.event.dom.client.MouseMoveHandler;
67  import com.google.gwt.event.dom.client.MouseUpEvent;
68  import com.google.gwt.event.dom.client.MouseUpHandler;
69  import com.google.gwt.event.logical.shared.ResizeEvent;
70  import com.google.gwt.event.logical.shared.ResizeHandler;
71  import com.google.gwt.event.shared.EventHandler;
72  import com.google.gwt.resources.client.ImageResource;
73  import com.google.gwt.uibinder.client.UiBinder;
74  import com.google.gwt.uibinder.client.UiFactory;
75  import com.google.gwt.uibinder.client.UiField;
76  import com.google.gwt.uibinder.client.UiHandler;
77  import com.google.gwt.user.client.Window;
78  import com.google.gwt.user.client.ui.Button;
79  import com.google.gwt.user.client.ui.CheckBox;
80  import com.google.gwt.user.client.ui.DecoratorPanel;
81  import com.google.gwt.user.client.ui.FlowPanel;
82  import com.google.gwt.user.client.ui.HTML;
83  import com.google.gwt.user.client.ui.Image;
84  import com.google.gwt.user.client.ui.Label;
85  import com.google.gwt.user.client.ui.ListBox;
86  import com.google.gwt.user.client.ui.PushButton;
87  import com.google.gwt.user.client.ui.RootPanel;
88  import com.google.gwt.user.client.ui.TextArea;
89  import com.google.gwt.user.client.ui.Widget;
90  import com.google.gwt.widgetideas.client.HSliderBar;
91  import com.google.gwt.widgetideas.client.SliderBar;
92  import com.google.gwt.widgetideas.client.SliderListenerAdapter;
93  
94  /**
95   * Main game class
96   * @author laaglu
97   */
98  public class DotsMain implements MouseDownHandler, MouseMoveHandler, MouseUpHandler, EntryPoint {
99  	interface DotsMainBinder extends UiBinder<FlowPanel, DotsMain> {
100 	}
101 	private static DotsMainBinder mainBinder = GWT.create(DotsMainBinder.class);
102 	
103 	private static String[] pictures;
104 	
105 	enum Mode {
106 		GAME,
107 		DESIGN
108 	};
109 	private static final String DIR = "dots";
110 	private static final String ID_ALPHA1_FILTER = "pictureAlpha";
111 	private static final String ID_TRANSITION_FILTER = "pictureTransition";
112 	private static final String ID_ALPHA2_FILTER = "dotAlpha";
113 
114 	private DotsCss css = DotsResources.INSTANCE.css();
115 	
116 	@UiField(provided=true)
117 	DotsBundle common = DotsBundle.INSTANCE;
118 	@UiField
119 	HTML svgContainer;
120 	@UiField
121 	SVGPushButton prevButton;
122 	@UiField
123 	SVGPushButton nextButton;
124 	@UiField
125 	FlowPanel navigationPanel;
126 	Widget menuWidget;
127 
128 	@UiField
129 	DecoratorPanel designPanel;
130 	@UiField
131 	Button addButton;
132 	@UiField
133 	Button removeButton;
134 	@UiField
135 	Button saveButton;
136 	@UiField
137 	Button testButton;
138 	@UiField
139 	CheckBox showLineCheck;
140 	@UiField
141 	HSliderBar pictureAlphaSlider;
142 	@UiField
143 	Label fileLabel;
144 	@UiField
145 	ListBox dotList;
146 	@UiField
147 	TextArea textArea;
148 	private FlowPanel panel;
149 	
150 	/**
151 	 * Index of the currently displayed image in the pictures array
152 	 */
153 	private int level;
154 	/**
155 	 * The SVG document. The document has the following structure
156 	 * <tt>
157 	 * <svg>          // rootSvg
158 	 *  <defs>
159 	 *  <g/>          // pictureGroup
160 	 *  <g>           // lineGroup
161 	 *   <polyline>   // polyline
162 	 *  </g>
163 	 *  <g>           // dotGroup
164 	 *   <g/>         // first dot
165 	 *   <g/>         // N-th dot
166 	 *  </g>
167 	 * </svg>
168 	 * </tt>
169 	 */
170 	private OMSVGSVGElement rootSvg;
171 	private OMSVGDefsElement defs;
172 	private OMSVGGElement pictureGroup;
173 	private OMSVGGElement lineGroup;
174 	private OMSVGPolylineElement polyline;
175 	private OMSVGGElement dotGroup;
176 	/**
177 	 * The vertices of the polyline
178 	 */
179 	OMSVGPointList points;
180 	/**
181 	 * The dots in dotGroup
182 	 */
183 	private List<OMSVGGElement> dots;
184 	/**
185 	 * The dot currently being moved
186 	 */
187 	private OMSVGGElement currentDot;
188 	/**
189 	 * The index in dotGroup of dot currently being moved
190 	 */
191 	private int currentDotIndex;
192 	/**
193 	 * The alpha channel in the filter applied to pictureSvg in edit mode
194 	 */
195 	private OMSVGNumber pictureAlpha1;
196 	/**
197 	 * The alpha channel in the filter applied to pictureSvg in game mode
198 	 */
199 	private OMSVGNumber pictureAlpha2;
200 	/**
201 	 * The Gaussian blur filter applied to pictureSvg
202 	 */
203 	private OMSVGFEGaussianBlurElement gaussianBlur;
204 	/**
205 	 * The alpha channel in the filter applied to dotSvg
206 	 */
207 	private OMSVGNumber dotAlpha;
208 	/**
209 	 * The mousedown point
210 	 */
211 	private OMSVGPoint mouseDownPoint;
212 	/**
213 	 * The position of the current dot at the time of mousedown
214 	 */
215 	private OMSVGPoint p0;
216 	/**
217 	 * The index of the last dot found by the player
218 	 */
219 	private int maxIndex;
220 	/**
221 	 * To load game levels
222 	 */
223 	AsyncXmlLoader loader;
224 
225 	/**
226 	 * Constructor for standalone game
227 	 */
228 	public DotsMain() {
229 	}
230 	/**
231 	 * Constructor for integration in a menu
232 	 */
233 	public DotsMain(Widget menuWidget) {
234 		this.menuWidget = menuWidget;
235 	}
236 
237 	/**
238 	 * Entry point
239 	 */
240 	@Override
241 	public void onModuleLoad() {
242 		css.ensureInjected();
243 		common.css().ensureInjected();
244 		common.mediaQueries().ensureInjected();
245 		Utils.injectMediaQuery("(orientation:landscape)", common.mediaQueriesLandscape());
246 		Utils.injectMediaQuery("(orientation:portrait)", common.mediaQueriesPortrait());
247 
248 		// Initialize the UI with UiBinder
249 		panel = mainBinder.createAndBindUi(this);
250 		if (menuWidget == null) {
251 			menuWidget = LicenseBox.createAboutButton();
252 		}
253 		navigationPanel.insert(menuWidget, 0);
254 		designPanel.setVisible(false);
255 		RootPanel.get(CommonConstants.ID_UIROOT).add(panel);
256 		Element div = svgContainer.getElement();
257 		
258 		// Handle resizing issues.
259 		ResizeHandler resizeHandler = new ResizeHandler() {
260 			@Override
261 			public void onResize(ResizeEvent event) {
262 				updatePictureSize();
263 			}
264 		};
265 		Window.addResizeHandler(resizeHandler);
266 		
267 		pictureAlphaSlider.addSliderListener(new SliderListenerAdapter() {
268 			@Override
269 			public void onValueChanged(SliderBar slider, double curValue) {
270 				setPictureAlpha((float)curValue);
271 			}			
272 		});
273 
274 		dots = new ArrayList<OMSVGGElement>();
275 		
276 		// Create the root SVG structure elements
277 		rootSvg = new OMSVGSVGElement();
278 		rootSvg.getWidth().getBaseVal().newValueSpecifiedUnits(Unit.PCT, 100);
279 		rootSvg.getHeight().getBaseVal().newValueSpecifiedUnits(Unit.PCT, 100);
280 		rootSvg.addClassNameBaseVal(css.rootSvg());
281 
282 		// Create the SVG filters
283 		defs = new OMSVGDefsElement();
284 		OMSVGFilterElement alpha1Filter = new OMSVGFilterElement();
285 		alpha1Filter.setId(ID_ALPHA1_FILTER);
286 		OMSVGFEColorMatrixElement feColorMatrix1 = new OMSVGFEColorMatrixElement();
287 		feColorMatrix1.getIn1().setBaseVal(OMSVGFilterElement.IN_SOURCE_GRAPHIC);
288 		feColorMatrix1.getType().setBaseVal(OMSVGFEColorMatrixElement.SVG_FECOLORMATRIX_TYPE_MATRIX);
289 		pictureAlpha1 = feColorMatrix1.getValues().getBaseVal().appendItems(rootSvg, new float[]{
290 				1f, 0f, 0f, 0f, 0f,
291 				0f, 1f, 0f, 0f, 0f,
292 				0f, 0f, 1f, 0f, 0f,
293 				0f, 0f, 0f, 1f, 0f,
294 		})[18];
295 		
296 		OMSVGFilterElement transitionFilter = new OMSVGFilterElement();
297 		transitionFilter.setId(ID_TRANSITION_FILTER);
298 		gaussianBlur = new OMSVGFEGaussianBlurElement();
299 		gaussianBlur.setStdDeviation(0, 0);
300 		gaussianBlur.getIn1().setBaseVal(OMSVGFilterElement.IN_SOURCE_GRAPHIC);
301 		// Bypass for firefox bug 935902
302 		//gaussianBlur.getResult().setBaseVal("blur");
303 		gaussianBlur.setAttribute(SVGConstants.SVG_RESULT_ATTRIBUTE, "blur");
304 		
305 		OMSVGFEColorMatrixElement feColorMatrix2 = new OMSVGFEColorMatrixElement();
306 		feColorMatrix2.getIn1().setBaseVal("blur");
307 		feColorMatrix2.getType().setBaseVal(OMSVGFEColorMatrixElement.SVG_FECOLORMATRIX_TYPE_MATRIX);
308 		pictureAlpha2 = feColorMatrix2.getValues().getBaseVal().appendItems(rootSvg, new float[]{
309 				1f, 0f, 0f, 0f, 0f,
310 				0f, 1f, 0f, 0f, 0f,
311 				0f, 0f, 1f, 0f, 0f,
312 				0f, 0f, 0f, 1f, 0f,
313 		})[18];
314 
315 		OMSVGFilterElement alpha3Filter = new OMSVGFilterElement();
316 		alpha3Filter.setId(ID_ALPHA2_FILTER);
317 		OMSVGFEColorMatrixElement feColorMatrix3 = new OMSVGFEColorMatrixElement();
318 		feColorMatrix3.getIn1().setBaseVal(OMSVGFilterElement.IN_SOURCE_GRAPHIC);
319 		feColorMatrix3.getType().setBaseVal(OMSVGFEColorMatrixElement.SVG_FECOLORMATRIX_TYPE_MATRIX);
320 		dotAlpha = feColorMatrix3.getValues().getBaseVal().appendItems(rootSvg, new float[]{
321 				1f, 0f, 0f, 0f, 0f,
322 				0f, 1f, 0f, 0f, 0f,
323 				0f, 0f, 1f, 0f, 0f,
324 				0f, 0f, 0f, 1f, 0f,
325 		})[18];
326 
327 
328 		// Compose the root SVG structure
329 		rootSvg.appendChild(defs);
330 		pictureGroup = new OMSVGGElement();
331 		dotGroup = new OMSVGGElement();
332 		dotGroup.setAttribute("id", "dots");
333 		lineGroup = new OMSVGGElement();
334 		polyline = new OMSVGPolylineElement();
335 		polyline.setClassNameBaseVal(css.lineInvisible());			
336 		polyline.getStyle().setSVGProperty(SVGConstants.SVG_FILTER_ATTRIBUTE, DOMHelper.toUrl(ID_ALPHA2_FILTER));
337 		points = polyline.getPoints();
338 		lineGroup.appendChild(polyline);
339 		rootSvg.appendChild(pictureGroup);
340 		defs.appendChild(alpha1Filter);
341 		alpha1Filter.appendChild(feColorMatrix1);
342 		defs.appendChild(transitionFilter);
343 		transitionFilter.appendChild(gaussianBlur);
344 		transitionFilter.appendChild(feColorMatrix2);
345 		defs.appendChild(alpha3Filter);
346 		alpha3Filter.appendChild(feColorMatrix3);
347 		rootSvg.appendChild(lineGroup);
348 		rootSvg.appendChild(dotGroup);
349 
350 		// Add the SVG to the HTML page
351 		div.appendChild(rootSvg.getElement());					
352 
353 		// Read the picture list
354 		pictures = DotsResources.INSTANCE.pictureList().getText().split("\\s");
355 		String levelParam = Window.Location.getParameter("level");
356 		if (levelParam != null) {
357 			try {
358 				int value = Integer.parseInt(levelParam);
359 				if (value >= 0 && value < pictures.length) {
360 					level = value;
361 				}
362 			} catch(NumberFormatException e) {
363 			}
364 		}
365 		
366 		loader = GWT.create(AsyncXmlLoader.class);
367 
368 		updateLevel();
369 	}
370 	
371 	/**
372 	 * UiBinder factory method to instantiate HSliderBar 
373 	 * @return
374 	 */
375 	@UiFactory
376 	HSliderBar makeHSliderBar() {
377 		HSliderBar sliderBar = new HSliderBar(0, 1);
378 		sliderBar.setStepSize(0.1);
379 		sliderBar.setCurrentValue(0);
380 		return sliderBar;
381 	}
382 
383 	private void updatePictureSize() {
384 		if (rootSvg != null) {
385 			OMSVGMatrix m = dotGroup.getCTM().inverse();
386 			updateScales(m.getA(), m.getD());
387 		}
388 	}
389 	
390 	private void updateLevel() {
391 		fileLabel.setText(pictures[level]);
392 
393 		// The data come in two files: a picture file and a dot file
394 		// In design mode, both must be read
395 		// In game mode, the dot file is read first and the picture file
396 		// is read later if the player succeeds
397 		if (getMode() == Mode.DESIGN) {
398 			readPicture(true);
399 		} else {
400 			readDots();
401 		}
402 	}
403 	
404 	private String getPictureUrl() {
405 		return GWT.getModuleBaseURL() + DIR + "/" + pictures[level];
406 	}
407 	
408 	private String getDotsUrl() {
409 		String url = GWT.getModuleBaseURL() + DIR + "/" + pictures[level] + ".dots";
410 		// Add a bogus query to bypass the browser cache as advised by:
411 		// https://developer.mozilla.org/En/Using_XMLHttpRequest#Bypassing_the_cache
412 		url += (url.indexOf("?") == -1) ? ("?ts=" + System.currentTimeMillis()) : ("&ts=" + + System.currentTimeMillis());
413 		return url;
414 	}
415 	
416 	private Mode getMode() {
417 		return "design".equals(Window.Location.getParameter("mode")) ? Mode.DESIGN : Mode.GAME;
418 	}
419 	
420 	public void readPicture(final boolean readDots) {
421 		loader.loadResource(getPictureUrl(), new AsyncXmlLoaderCallback() {
422 			@Override
423 			public void onError(String resourceName, Throwable error) {
424 				svgContainer.setHTML("Cannot find resource");
425 			}
426 
427 			@Override
428 			public void onSuccess(String resourceName, com.google.gwt.dom.client.Element root) {
429 				OMSVGSVGElement svg = OMNode.convert(root);
430 
431 				// Position the filter on the picture
432 				OMSVGGElement g = reparent(svg);
433 				pictureAlpha2.setValue(0f);
434 				OMSVGRect viewBox = svg.getViewBox().getBaseVal();
435 				rootSvg.setViewBox(viewBox.inset(svg.createSVGRect(), viewBox.getWidth() * -0.025f, viewBox.getHeight() * -0.025f));
436 				
437 				// Insert the picture into the SVG structure
438 				rootSvg.replaceChild(g, pictureGroup);
439 				pictureGroup = g;
440 				pictureGroup.getStyle().setSVGProperty(SVGConstants.SVG_FILTER_ATTRIBUTE, DOMHelper.toUrl(ID_TRANSITION_FILTER));
441 
442 				// Send the dots request
443 				if (readDots) {
444 					readDots();
445 				} else {
446 					transition(null);
447 				}				
448 			}
449 		});
450 	}
451 	
452 	public void readDots() {
453 		maxIndex = -1;
454 		dots.clear();
455 		dotList.clear();
456 		points.clear();
457 		loader.loadResource(getDotsUrl(), new AsyncXmlLoaderCallback() {
458 			private void finish() {
459 				if (getMode() == Mode.DESIGN) {
460 					pictureGroup.getStyle().setSVGProperty(SVGConstants.SVG_FILTER_ATTRIBUTE, DOMHelper.toUrl(ID_ALPHA1_FILTER));
461 					setPictureAlpha(1f);
462 					showLineCheck.setValue(false);
463 					pictureAlphaSlider.setCurrentValue(1);			
464 				} else {
465 					pictureGroup.getStyle().setVisibility(Visibility.HIDDEN);
466 					polyline.setClassNameBaseVal(css.lineVisible());
467 				}
468 				dotAlpha.setValue(1f);
469 				dotGroup.getStyle().setSVGProperty(SVGConstants.SVG_FILTER_ATTRIBUTE, DOMHelper.toUrl(ID_ALPHA2_FILTER));
470 				designPanel.setVisible(getMode() == Mode.DESIGN);
471 
472 				// Resize to the size of the window
473 				updatePictureSize();
474 				updateUI();
475 			}
476 			
477 			@Override
478 			public void onError(String resourceName, Throwable error) {
479 				if (getMode() == Mode.GAME) {
480 					svgContainer.setHTML("Cannot find resource");
481 					return;
482 				}
483 				OMSVGGElement g = new OMSVGGElement();
484 				rootSvg.replaceChild(g, dotGroup);
485 				dotGroup = g;
486 				finish();
487 			}
488 
489 			@Override
490 			public void onSuccess(String resourceName, com.google.gwt.dom.client.Element root) {
491 				OMSVGSVGElement svg = OMNode.convert(root);
492 				OMSVGGElement g = (OMSVGGElement) svg.getFirstChild();
493 				rootSvg.replaceChild(g, dotGroup);
494 				dotGroup = g;
495 				OMSVGRect viewBox = svg.getViewBox().getBaseVal();
496 				rootSvg.setViewBox(viewBox.inset(svg.createSVGRect(), viewBox.getWidth() * -0.025f, viewBox.getHeight() * -0.025f));
497 				
498 				// Parse the dots to recreate the polyline
499 				OMNodeList<OMSVGGElement> childNodes = dotGroup.getChildNodes();
500 				for (int i = 0, size = childNodes.getLength(); i < size; i++) {
501 					OMSVGGElement g1 = childNodes.getItem(i);
502 					g1.addMouseDownHandler(DotsMain.this);
503 					dots.add(g1);
504 					if (getMode() == Mode.DESIGN) {
505 						g1.addMouseMoveHandler(DotsMain.this);
506 						g1.addMouseUpHandler(DotsMain.this);
507 						dotList.addItem(toDotName(i));
508 						OMSVGMatrix m = g1.getTransform().getBaseVal().getItem(0).getMatrix();
509 						points.appendItem(rootSvg.createSVGPoint(m.getE(), m.getF()));
510 					}
511 				}
512 				finish();
513 			}
514 		});
515 	}
516 	
517 	@UiHandler("prevButton")
518 	public void previousPicture(ClickEvent event) {
519 		level--;
520 		if (level < 0) {
521 			level = pictures.length - 1;
522 		}
523 		updateLevel();
524 	}
525 	
526 	@UiHandler("nextButton")
527 	public void nextPicture(ClickEvent event) {
528 		level++;
529 		if (level >= pictures.length) {
530 			level = 0;
531 		}
532 		updateLevel();
533 	}
534 
535 	private OMSVGPoint getLocalCoordinates(MouseEvent<? extends EventHandler> e) {
536 		OMSVGPoint p = rootSvg.createSVGPoint(e.getClientX(), e.getClientY());
537 		OMSVGMatrix m = rootSvg.getScreenCTM().inverse();
538 		return p.matrixTransform(m);
539 	}
540 	
541 	@UiHandler("addButton")
542 	public void addDot(ClickEvent event) {
543 		int pIndex = dots.size();
544 		OMSVGRect viewBox = rootSvg.getViewBox().getBaseVal();
545 		// Position the new points in a circle centered at the view box
546 		// with a radius of 20%
547 		float r = Math.min(viewBox.getWidth(), viewBox.getHeight()) * 0.2f;
548 		float x = ((float)Math.cos(pIndex / 16d * 2d * Math.PI)) * r + viewBox.getCenterX();
549 		float y = ((float)Math.sin(pIndex / 16d * 2d * Math.PI)) * r + viewBox.getCenterY();
550 		OMSVGGElement g1 = createDot(pIndex + 1, x, y);
551 		dots.add(g1);
552 		points.appendItem(rootSvg.createSVGPoint(x, y));
553 		dotGroup.appendChild(g1);
554 		dotList.addItem(toDotName(pIndex));
555 		// Autoselect the new point
556 		dotList.setSelectedIndex(dotList.getItemCount() - 1);
557 		updateUI();	
558 	}
559 
560 	@UiHandler("removeButton")
561 	public void removeDot(ClickEvent event) {
562 		int index = dotList.getSelectedIndex();
563 		OMSVGGElement g1 = dots.remove(index);
564 		dotGroup.removeChild(g1);
565 		dotList.removeItem(index);
566 		points.removeItem(index);
567 		updateUI();
568 		renumber();
569 	}
570 
571 	@UiHandler("saveButton")
572 	public void save(ClickEvent event) {
573 		rootSvg.removeChild(defs);
574 		rootSvg.removeChild(pictureGroup);
575 		rootSvg.removeChild(lineGroup);
576 		dotGroup.removeAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE);
577 		textArea.setText(rootSvg.getMarkup());
578 		rootSvg.insertBefore(lineGroup, dotGroup);
579 		rootSvg.insertBefore(pictureGroup, lineGroup);
580 		rootSvg.insertBefore(defs, pictureGroup);
581 		dotGroup.getStyle().setSVGProperty(SVGConstants.SVG_FILTER_ATTRIBUTE, DOMHelper.toUrl(ID_ALPHA2_FILTER));
582 	}
583 
584 	@UiHandler("showLineCheck")
585 	public void toggleShowLine(ClickEvent event) {
586 		polyline.setClassNameBaseVal(showLineCheck.getValue() ? css.lineVisible() : css.lineInvisible());
587 	}
588 		
589 	@UiHandler("dotList")
590 	void onChange(ChangeEvent event) {
591 		updateUI();
592 	}
593 	
594 	@UiHandler("testButton")
595 	public void transition(ClickEvent event) {
596 		pictureAlpha2.setValue(0f);
597 		gaussianBlur.setStdDeviation(10f, 10f);
598 		polyline.setClassNameBaseVal(css.lineVisible());
599 		pictureGroup.getStyle().setSVGProperty(SVGConstants.SVG_FILTER_ATTRIBUTE, DOMHelper.toUrl(ID_TRANSITION_FILTER));
600 		if (points.getNumberOfItems() > 0) {
601 			points.appendItem(points.getItem(0).assignTo(rootSvg.createSVGPoint()));
602 		}
603 		Animation transition = new Animation() {
604 			@Override
605 			protected void onUpdate(double progress) {
606 				pictureAlpha2.setValue((float)progress);
607 				float stdDev = 10f * (1f - (float)progress);
608 				gaussianBlur.setStdDeviation(stdDev, stdDev);
609 				dotAlpha.setValue(1f - (float)progress);
610 			}
611 			@Override
612 			protected void onComplete() {
613 				if (getMode() == Mode.DESIGN) {
614 					polyline.setClassNameBaseVal(showLineCheck.getValue() ? css.lineVisible() : css.lineInvisible());
615 					pictureGroup.getStyle().setSVGProperty(SVGConstants.SVG_FILTER_ATTRIBUTE, DOMHelper.toUrl(ID_ALPHA1_FILTER));
616 					dotAlpha.setValue(1f);
617 					if (points.getNumberOfItems() > 0) {
618 						points.removeItem(points.getNumberOfItems() - 1);
619 					}
620 				} else {
621 					pictureAlpha2.setValue(1f);
622 					gaussianBlur.setStdDeviation(0.00001f, 0.00001f);
623 					dotAlpha.setValue(0f);		
624 				}
625 			}
626 		};
627 		pictureGroup.getStyle().setVisibility(Visibility.VISIBLE);
628 		transition.run(2000, Duration.currentTimeMillis() + 1000);
629 	}
630 	
631 	private void updateUI() {
632 		textArea.setText("");
633 		removeButton.setEnabled(dotList.getSelectedIndex() != -1);
634 	}
635 
636 	private OMSVGGElement createDot(int pIndex, float x, float y) {
637 		OMSVGGElement g1 = new OMSVGGElement();
638 		OMSVGTransform translation = rootSvg.createSVGTransform();
639 		translation.setTranslate(x, y);
640 		g1.getTransform().getBaseVal().appendItem(translation);
641 
642 		OMSVGGElement g2 = new OMSVGGElement();
643 		OMSVGTransform scaling = rootSvg.createSVGTransform();
644 		OMSVGMatrix m = rootSvg.getScreenCTM().inverse();
645 		scaling.setScale(m.getA(), m.getD());
646 		g2.getTransform().getBaseVal().appendItem(scaling);
647 
648 		OMSVGCircleElement circle1 = new OMSVGCircleElement(0f, 0f, 5f);
649 		OMSVGCircleElement circle2 = new OMSVGCircleElement(0f, 0f, 3f);
650 		OMSVGTextElement text = new OMSVGTextElement(0f, 16f, OMSVGLength.SVG_LENGTHTYPE_PX, Integer.toString(pIndex));
651 		
652 		g1.appendChild(g2);
653 		g2.appendChild(circle1);
654 		g2.appendChild(circle2);
655 		g2.appendChild(text);
656 		
657 		g1.addMouseDownHandler(this);
658 		g1.addMouseMoveHandler(this);
659 		g1.addMouseUpHandler(this);
660 		
661 		return g1;
662 	}
663 	
664 	private void updateScales(float sx, float sy) {
665 		for (OMSVGGElement g1 : dots) {
666 			OMSVGGElement g2 = (OMSVGGElement)g1.getFirstChild();
667 			OMSVGTransform scaling = g2.getTransform().getBaseVal().getItem(0);
668 			scaling.setScale(sx, sy);
669 		}
670 		polyline.getStyle().setSVGProperty(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, Float.toString(sx));
671 	}
672 	
673 	private void setPictureAlpha(float value) {
674 		pictureAlpha1.setValue(value);
675 	}
676 
677 	private void renumber() {
678 		for (int i = 0, size = dots.size(); i < size; i++) {
679 			OMText data = (OMText)dots.get(i).getFirstChild().getLastChild().getFirstChild();
680 			data.setData(Integer.toString(i + 1));
681 			dotList.setItemText(i, toDotName(i));
682 		}
683 	}
684 
685 	@Override
686 	public void onMouseDown(MouseDownEvent event) {
687 		mouseDownPoint = getLocalCoordinates(event);
688 		currentDot = (OMSVGGElement) event.getSource();
689 		currentDotIndex = dots.indexOf(currentDot);
690 		if (getMode() == Mode.DESIGN) {
691 			OMSVGMatrix m = currentDot.getTransform().getBaseVal().getItem(0).getMatrix();
692 			p0 = rootSvg.createSVGPoint(m.getE(), m.getF());
693 			DOMHelper.setCaptureElement(currentDot, null);
694 			event.stopPropagation();
695 			event.preventDefault();
696 		} else {
697 			if (currentDotIndex == maxIndex + 1) {
698 				maxIndex++;
699 				currentDot.setClassNameBaseVal(css.validated());
700 				OMSVGMatrix m = currentDot.getTransform().getBaseVal().getItem(0).getMatrix();
701 				points.appendItem(rootSvg.createSVGPoint(m.getE(), m.getF()));
702 				if (maxIndex + 1 == dots.size()) {
703 					// Level is succcessfully completed
704 					readPicture(false);
705 				}
706 			}
707 			currentDot = null;
708 		}
709 	}
710 	
711 	@Override
712 	public void onMouseMove(MouseMoveEvent event) {
713 		if (currentDot != null) {
714 			OMSVGPoint p1 = getLocalCoordinates(event).substract(mouseDownPoint).add(p0);
715 			// Update the dot position
716 			OMSVGMatrix m = currentDot.getTransform().getBaseVal().getItem(0).getMatrix();
717 			m.setE(p1.getX());
718 			m.setF(p1.getY());
719 			// Update the polyline
720 			p1.assignTo(points.getItem(currentDotIndex));
721 		}
722 		event.stopPropagation();
723 		event.preventDefault();
724 	}
725 
726 	@Override
727 	public void onMouseUp(MouseUpEvent event) {
728 		if (currentDot != null) {
729 			DOMHelper.releaseCaptureElement();
730 			currentDot = null;
731 			currentDotIndex = -1;
732 		}
733 		event.stopPropagation();
734 		event.preventDefault();
735 	}
736 	
737 	private static String toDotName(int pIndex) {
738 		return DotsConstants.INSTANCE.dot() + " #" + (pIndex + 1);
739 	}
740 	
741 	@UiFactory
742 	PushButton createPushButton(ImageResource image) {
743 		return new PushButton(new Image(image));
744 	}
745 	
746 	/**
747 	 * Removes all the child nodes of the svg document and
748 	 * puts them in a group
749 	 * @param svg the svg document root
750 	 * @return the new group
751 	 */
752 	protected OMSVGGElement reparent(OMSVGSVGElement svg) {
753 		OMSVGGElement g = OMSVGParser.currentDocument().createSVGGElement();
754 		Element gElement = g.getElement();
755 		Element svgElement = svg.getElement();
756 		Node node;
757 		while((node = svgElement.getFirstChild()) != null) {
758 			gElement.appendChild(svgElement.removeChild(node));
759 		}
760 		svgElement.appendChild(gElement);
761 		return g;
762 	}
763 
764 
765 }