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;
19  
20  import java.util.ArrayList;
21  import java.util.HashMap;
22  import java.util.List;
23  import java.util.Map;
24  
25  import org.vectomatic.dom.svg.OMSVGDocument;
26  import org.vectomatic.dom.svg.OMSVGSVGElement;
27  import org.vectomatic.dom.svg.impl.SVGElement;
28  import org.vectomatic.dom.svg.impl.SVGSVGElement;
29  import org.vectomatic.dom.svg.ui.SVGResource;
30  import org.vectomatic.dom.svg.utils.OMSVGParser;
31  import org.vectomatic.dom.svg.utils.SVGConstants;
32  import org.vectomatic.file.File;
33  import org.vectomatic.file.FileList;
34  import org.vectomatic.file.FileUploadExt;
35  import org.vectomatic.svg.edit.client.command.CommandFactories;
36  import org.vectomatic.svg.edit.client.command.CommandFactorySelector;
37  import org.vectomatic.svg.edit.client.engine.SVGModel;
38  import org.vectomatic.svg.edit.client.engine.SVGProcessor;
39  import org.vectomatic.svg.edit.client.event.ActivateWindowEvent;
40  import org.vectomatic.svg.edit.client.event.ActivateWindowHandler;
41  import org.vectomatic.svg.edit.client.event.DeactivateWindowEvent;
42  import org.vectomatic.svg.edit.client.event.DeactivateWindowHandler;
43  import org.vectomatic.svg.edit.client.event.HasActivateWindowHandlers;
44  import org.vectomatic.svg.edit.client.event.HasDeactivateWindowHandlers;
45  import org.vectomatic.svg.edit.client.gxt.panels.CommandFactoryToolBar;
46  import org.vectomatic.svg.edit.client.gxt.widget.RecentDocMenuItem;
47  import org.vectomatic.svg.edit.client.gxt.widget.ViewportExt;
48  import org.vectomatic.svg.edit.client.inspector.InspectorWindow;
49  import org.vectomatic.svg.edit.client.load.FileLoadRequest;
50  import org.vectomatic.svg.edit.client.load.ILoadRequest;
51  import org.vectomatic.svg.edit.client.load.InternalLoadRequest;
52  import org.vectomatic.svg.edit.client.load.NewDocRequest;
53  import org.vectomatic.svg.edit.client.load.RSSReader;
54  import org.vectomatic.svg.edit.client.load.UrlLoadRequest;
55  import org.vectomatic.svg.edit.client.model.MetaModel;
56  import org.vectomatic.svg.edit.client.model.svg.CssContextModel;
57  import org.vectomatic.svg.edit.client.model.svg.SVGCircleElementModel;
58  import org.vectomatic.svg.edit.client.model.svg.SVGEllipseElementModel;
59  import org.vectomatic.svg.edit.client.model.svg.SVGImageElementModel;
60  import org.vectomatic.svg.edit.client.model.svg.SVGLineElementModel;
61  import org.vectomatic.svg.edit.client.model.svg.SVGNamedElementModel;
62  import org.vectomatic.svg.edit.client.model.svg.SVGPolygonElementModel;
63  import org.vectomatic.svg.edit.client.model.svg.SVGPolylineElementModel;
64  import org.vectomatic.svg.edit.client.model.svg.SVGRectElementModel;
65  import org.vectomatic.svg.edit.client.utils.DecoratedImageCache;
66  import org.vectomatic.svg.edit.client.utils.ImageLoader;
67  
68  import com.extjs.gxt.ui.client.Style.LayoutRegion;
69  import com.extjs.gxt.ui.client.event.Events;
70  import com.extjs.gxt.ui.client.event.Listener;
71  import com.extjs.gxt.ui.client.event.MenuEvent;
72  import com.extjs.gxt.ui.client.event.MessageBoxEvent;
73  import com.extjs.gxt.ui.client.event.SelectionListener;
74  import com.extjs.gxt.ui.client.event.WindowEvent;
75  import com.extjs.gxt.ui.client.util.Rectangle;
76  import com.extjs.gxt.ui.client.util.Size;
77  import com.extjs.gxt.ui.client.widget.Component;
78  import com.extjs.gxt.ui.client.widget.ContentPanel;
79  import com.extjs.gxt.ui.client.widget.Info;
80  import com.extjs.gxt.ui.client.widget.LayoutContainer;
81  import com.extjs.gxt.ui.client.widget.MessageBox;
82  import com.extjs.gxt.ui.client.widget.Window;
83  import com.extjs.gxt.ui.client.widget.button.Button;
84  import com.extjs.gxt.ui.client.widget.layout.BorderLayout;
85  import com.extjs.gxt.ui.client.widget.layout.BorderLayoutData;
86  import com.extjs.gxt.ui.client.widget.layout.HBoxLayout;
87  import com.extjs.gxt.ui.client.widget.layout.HBoxLayout.HBoxLayoutAlign;
88  import com.extjs.gxt.ui.client.widget.layout.HBoxLayoutData;
89  import com.extjs.gxt.ui.client.widget.menu.Menu;
90  import com.extjs.gxt.ui.client.widget.menu.MenuBar;
91  import com.extjs.gxt.ui.client.widget.menu.MenuBarItem;
92  import com.extjs.gxt.ui.client.widget.menu.MenuItem;
93  import com.extjs.gxt.ui.client.widget.menu.SeparatorMenuItem;
94  import com.extjs.gxt.ui.client.widget.toolbar.ToolBar;
95  import com.google.gwt.core.client.EntryPoint;
96  import com.google.gwt.core.client.GWT;
97  import com.google.gwt.dom.client.Style;
98  import com.google.gwt.dom.client.Style.Unit;
99  import com.google.gwt.dom.client.Style.Visibility;
100 import com.google.gwt.event.dom.client.ChangeEvent;
101 import com.google.gwt.event.dom.client.ChangeHandler;
102 import com.google.gwt.event.logical.shared.InitializeEvent;
103 import com.google.gwt.event.logical.shared.InitializeHandler;
104 import com.google.gwt.event.shared.EventBus;
105 import com.google.gwt.event.shared.GwtEvent;
106 import com.google.gwt.event.shared.HandlerRegistration;
107 import com.google.gwt.event.shared.SimpleEventBus;
108 import com.google.gwt.resources.client.ImageResource;
109 import com.google.gwt.user.client.DOM;
110 import com.google.gwt.user.client.Window.Location;
111 import com.google.gwt.user.client.ui.AbstractImagePrototype;
112 import com.google.gwt.user.client.ui.DialogBox;
113 import com.google.gwt.user.client.ui.RootPanel;
114 
115 /**
116  * Application main class.
117  * @author laaglu
118  */
119 public class SvgrealApp implements EntryPoint, HasActivateWindowHandlers, HasDeactivateWindowHandlers {
120 	/**
121 	 * The locale URL parameter
122 	 */
123 	private static final String PARAM_LOCALE = "locale";
124 	/**
125 	 * The gwt.codesvr URL parameter
126 	 */
127 	private static final String PARAM_CODE_SERVER = "gwt.codesvr";
128 	/**
129 	 * The demo URL parameter
130 	 */
131 	private static final String PARAM_DEMO = "demo";
132 	/**
133 	 * Application singleton
134 	 */
135 	private static SvgrealApp instance;
136 	/**
137 	 * Application-wide event bus
138 	 */
139 	private EventBus eventBus;
140 	/**
141 	 * List of open documents
142 	 */
143 	private List<SVGWindow> svgWindows;
144 	/**
145 	 * Currently active document
146 	 */
147 	private SVGWindow activeWindow;
148 	/**
149 	 * Open URL panel
150 	 */
151 	private MessageBox openUrlBox;
152 	/**
153 	 * OpenClipArt RSS feed reader
154 	 */
155 	private RSSReader rssReader;
156 	/**
157 	 * About panel
158 	 */
159 	private AboutDialog aboutDialog;
160 	/**
161 	 * Inspector
162 	 */
163 	private InspectorWindow inspectorWindow;
164 	/**
165 	 * The command factory selector
166 	 */
167 	private CommandFactorySelector commandFactorySelector;
168 	/**
169 	 * The command toolbar at the bottom of the screen
170 	 */
171 	private CommandFactoryToolBar commandToolBar;
172 	/**
173 	 * To process local file open requests
174 	 */
175 	FileUploadExt fileUpload;
176 	/**
177 	 * CSS context model to provide default values
178 	 * when creating new SVG elements
179 	 */
180 	private CssContextModel cssContext;
181 	/**
182 	 * A cache for decorated treeview icons
183 	 */
184 	private DecoratedImageCache imageCache;
185 	
186 	private List<MetaModel<?>> metaModels;
187 	/**
188 	 * The "slot" into which the next new window
189 	 * will be positionned
190 	 */
191 	private int windowSlot;
192 	private ViewportExt viewport;
193 	private Menu recentDocsMenu;
194 	private MenuItem resetViewItem;
195 	private MenuItem tileWindowsItem;
196 	private MenuItem stackWindowsItem;
197 	private MenuItem closeWindowItem;
198 	private MenuItem exportAsSvgMenuItem;
199 	private Menu displayWindowMenu;
200 	private MenuItem displayWindowMenuItem;
201 	private Map<String, ImageResource> localeToIcon;
202 	/**
203 	 * To process menu item selection events
204 	 */
205 	private SelectionListener<MenuEvent> dispatcher;
206 	private Menu languageMenu;
207 	
208 	public SvgrealApp() {
209 		
210 	}
211 
212 	public void onModuleLoad() {
213 		instance = this;
214 		GWT.setUncaughtExceptionHandler(new GWT.UncaughtExceptionHandler() {
215 			public void onUncaughtException(Throwable throwable) {
216 				GWT.log("Uncaught exception", throwable);
217 				if (!GWT.isScript()) {
218 					String text = "Uncaught exception: ";
219 					while (throwable != null) {
220 						StackTraceElement[] stackTraceElements = throwable
221 								.getStackTrace();
222 						text += throwable.toString() + "\n";
223 						for (int i = 0; i < stackTraceElements.length; i++) {
224 							text += "    at " + stackTraceElements[i] + "\n";
225 						}
226 						throwable = throwable.getCause();
227 						if (throwable != null) {
228 							text += "Caused by: ";
229 						}
230 					}
231 					DialogBox dialogBox = new DialogBox(true);
232 					DOM.setStyleAttribute(dialogBox.getElement(),
233 							"backgroundColor", "#ABCDEF");
234 					System.err.print(text);
235 					text = text.replaceAll(" ", "&nbsp;");
236 					dialogBox.setHTML("<pre>" + text + "</pre>");
237 					dialogBox.center();
238 				}
239 			}
240 		});
241 		AppBundle.INSTANCE.css().ensureInjected();
242 
243 		// Create graphical context
244 		OMSVGDocument doc = OMSVGParser.currentDocument();
245 		SVGElement element = doc.createSVGPathElement().getElement().cast();
246 		element.getStyle().setProperty(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_LIGHTBLUE_VALUE);
247 		element.getStyle().setProperty(SVGConstants.CSS_STROKE_PROPERTY, SVGConstants.CSS_BLACK_VALUE);
248 		SVGNamedElementModel.createTitleDesc(element, AppConstants.INSTANCE.graphicalContext());
249 		cssContext = new CssContextModel(element);
250 
251 		svgWindows = new ArrayList<SVGWindow>();
252 		viewport = new ViewportExt();
253 
254 		viewport.setLayout(new BorderLayout());
255 		HBoxLayout hboxLayout = new HBoxLayout();
256 		hboxLayout.setHBoxLayoutAlign(HBoxLayoutAlign.TOP);  
257         LayoutContainer menuBarContainer = new LayoutContainer(hboxLayout);
258 		HBoxLayoutData layoutData = new HBoxLayoutData();
259 		layoutData.setFlex(1);
260 		menuBarContainer.add(createMenuBar(), layoutData);
261 		menuBarContainer.add(createLanguageBar(), new HBoxLayoutData());
262 		viewport.add(menuBarContainer, new BorderLayoutData(LayoutRegion.NORTH, getWindowBarHeight()));
263 		viewport.setStyleAttribute("background-color", SVGConstants.CSS_BEIGE_VALUE);
264 		
265 		commandToolBar = new CommandFactoryToolBar(CommandFactories.getAllFactoriesStore(), getCommandFactorySelector());
266 		ContentPanel commandPanel = new ContentPanel();
267 		commandPanel.setHeaderVisible(false);
268 		commandPanel.setBottomComponent(commandToolBar);
269 		viewport.add(commandPanel, new BorderLayoutData(LayoutRegion.SOUTH, getWindowBarHeight()));
270 
271 
272 		update();
273 		
274 		fileUpload = new FileUploadExt();
275 		Style style = fileUpload.getElement().getStyle();
276 		style.setVisibility(Visibility.HIDDEN);
277 		style.setWidth(0, Unit.PX);
278 		style.setHeight(0, Unit.PX);
279 		fileUpload.addChangeHandler(new ChangeHandler() {
280 			
281 			@Override
282 			public void onChange(ChangeEvent event) {
283 				processFiles(fileUpload.getFiles());				
284 			}
285 		});
286 		
287 		RootPanel.get().add(viewport);
288 		RootPanel.get().add(fileUpload);
289 		
290 		
291 		ImageLoader loader = new ImageLoader();
292 		loader.addInitializeHandler(new InitializeHandler() {
293 			
294 			@Override
295 			public void onInitialize(InitializeEvent event) {
296 				ImageLoader loader = (ImageLoader) event.getSource();
297 				imageCache = new DecoratedImageCache(loader.getImages());
298 				init();
299 			}
300 		});
301 		loader.loadImages(new ImageResource[]{
302 			AppBundle.INSTANCE.altGlyphDef(), 
303 			AppBundle.INSTANCE.altGlyphItem(), 
304 			AppBundle.INSTANCE.altGlyph(), 
305 			AppBundle.INSTANCE.animateColor(), 
306 			AppBundle.INSTANCE.animateMotion(), 
307 			AppBundle.INSTANCE.animate(), 
308 			AppBundle.INSTANCE.animateTransform(), 
309 			AppBundle.INSTANCE.circle(), 
310 			AppBundle.INSTANCE.clipPath(), 
311 			AppBundle.INSTANCE.colorProfile(), 
312 			AppBundle.INSTANCE.cursor(), 
313 			AppBundle.INSTANCE.defs(), 
314 			AppBundle.INSTANCE.desc(), 
315 			AppBundle.INSTANCE.ellipse(), 
316 			AppBundle.INSTANCE.feBlend(), 
317 			AppBundle.INSTANCE.feColorMatrix(), 
318 			AppBundle.INSTANCE.feComponentTransfer(), 
319 			AppBundle.INSTANCE.feComposite(), 
320 			AppBundle.INSTANCE.feConvolveMatrix(), 
321 			AppBundle.INSTANCE.feDiffuseLighting(), 
322 			AppBundle.INSTANCE.feDisplacementMap(), 
323 			AppBundle.INSTANCE.feDistantLight(), 
324 			AppBundle.INSTANCE.feFlood(), 
325 			AppBundle.INSTANCE.feFuncA(), 
326 			AppBundle.INSTANCE.feFuncB(), 
327 			AppBundle.INSTANCE.feFuncG(), 
328 			AppBundle.INSTANCE.feFuncR(), 
329 			AppBundle.INSTANCE.feGaussianBlur(), 
330 			AppBundle.INSTANCE.feMergeNode(), 
331 			AppBundle.INSTANCE.feMerge(), 
332 			AppBundle.INSTANCE.feMorphology(), 
333 			AppBundle.INSTANCE.feOffset(), 
334 			AppBundle.INSTANCE.fePointLight(), 
335 			AppBundle.INSTANCE.feSpecularLight(), 
336 			AppBundle.INSTANCE.feSpotLight(), 
337 			AppBundle.INSTANCE.feTile(), 
338 			AppBundle.INSTANCE.feTurbulence(), 
339 			AppBundle.INSTANCE.filter(), 
340 			AppBundle.INSTANCE.fontFaceFormat(), 
341 			AppBundle.INSTANCE.fontFaceName(), 
342 			AppBundle.INSTANCE.fontFace(), 
343 			AppBundle.INSTANCE.fontFaceSrc(), 
344 			AppBundle.INSTANCE.fontFaceUri(), 
345 			AppBundle.INSTANCE.font(), 
346 			AppBundle.INSTANCE.foreignObject(), 
347 			AppBundle.INSTANCE.glyph(), 
348 			AppBundle.INSTANCE.glyphRef(), 
349 			AppBundle.INSTANCE.g(), 
350 			AppBundle.INSTANCE.hkern(), 
351 			AppBundle.INSTANCE.image(), 
352 			AppBundle.INSTANCE.linearGradient(), 
353 			AppBundle.INSTANCE.line(), 
354 			AppBundle.INSTANCE.marker(), 
355 			AppBundle.INSTANCE.mask(), 
356 			AppBundle.INSTANCE.metadata(), 
357 			AppBundle.INSTANCE.missingGlyph(), 
358 			AppBundle.INSTANCE.mpath(), 
359 			AppBundle.INSTANCE.path(), 
360 			AppBundle.INSTANCE.pattern(), 
361 			AppBundle.INSTANCE.polygon(), 
362 			AppBundle.INSTANCE.polyline(), 
363 			AppBundle.INSTANCE.radialGradient(), 
364 			AppBundle.INSTANCE.rect(), 
365 			AppBundle.INSTANCE.script(), 
366 			AppBundle.INSTANCE.set(), 
367 			AppBundle.INSTANCE.stop(), 
368 			AppBundle.INSTANCE.style(), 
369 			AppBundle.INSTANCE.svg(), 
370 			AppBundle.INSTANCE.switch_(), 
371 			AppBundle.INSTANCE.symbol(), 
372 			AppBundle.INSTANCE.textPath(), 
373 			AppBundle.INSTANCE.text(), 
374 			AppBundle.INSTANCE.title(), 
375 			AppBundle.INSTANCE.tref(), 
376 			AppBundle.INSTANCE.tspan(), 
377 			AppBundle.INSTANCE.use(), 
378 			AppBundle.INSTANCE.view(), 
379 			AppBundle.INSTANCE.vkern(), 			
380 			AppBundle.INSTANCE.error(), 			
381 			AppBundle.INSTANCE.warning(), 			
382 		});
383 	}
384 	
385 	private void init() {
386 		// Open demo documents if requested by the demo URL parameter
387 		String demo = getParameter(PARAM_DEMO);
388 		if (demo != null) {
389 			SVGResource[] demoResources = {
390 					AppBundle.INSTANCE.fish(),
391 					AppBundle.INSTANCE.fries(),
392 					AppBundle.INSTANCE.chess(),
393 					AppBundle.INSTANCE.sample()
394 				};
395 				String[] demoNames = {
396 					"fish.svg",
397 					"fries.svg",
398 					"chess.svg",
399 					"sample.svg"
400 				};
401 			for (int i = 0; i < demoResources.length; i++) {
402 				if (demo.indexOf(Integer.toString(i)) != -1) {
403 					new InternalLoadRequest(demoResources[i], demoNames[i]).load();
404 				}
405 			}
406 		}
407 	}
408 	
409 	private MenuBar createMenuBar() {
410 		Menu fileMenu = new Menu();
411 		AppConstants cst = AppConstants.INSTANCE;
412 		final MenuItem newDocumentMenuItem = new MenuItem(cst.newDocumentMenuItem());
413 		fileMenu.add(newDocumentMenuItem);
414 		/*final MenuItem openUrlItem = new MenuItem(cst.openUrlMenuItem());
415 		fileMenu.add(openUrlItem);*/
416 		final MenuItem openLocalMenuItem = new MenuItem(cst.openLocalMenuItem());
417 		fileMenu.add(openLocalMenuItem);
418 		final MenuItem openRssFeedItem = new MenuItem(cst.openRssFeedMenuItem());
419 		fileMenu.add(openRssFeedItem);
420 		exportAsSvgMenuItem = new MenuItem(cst.exportAsSvgMenuItem());
421 		fileMenu.add(exportAsSvgMenuItem);
422 		MenuItem recentDocumentsItem = new MenuItem(cst.recentDocumentsMenuItem());
423 		recentDocsMenu = new Menu();
424 		recentDocumentsItem.setSubMenu(recentDocsMenu);
425 		fileMenu.add(recentDocumentsItem);
426 		
427 		Menu windowMenu = new Menu();
428 		resetViewItem = new MenuItem(cst.resetViewMenuItem());
429 		windowMenu.add(resetViewItem);
430 		windowMenu.add(new SeparatorMenuItem());
431 		tileWindowsItem = new MenuItem(cst.tileWindowsMenuItem());
432 		windowMenu.add(tileWindowsItem);
433 		stackWindowsItem = new MenuItem(cst.stackWindowsMenuItem());
434 		windowMenu.add(stackWindowsItem);
435 		displayWindowMenuItem = new MenuItem(cst.displayWindowMenuItem());
436 		displayWindowMenu = new Menu();
437 		displayWindowMenuItem.setSubMenu(displayWindowMenu);
438 		windowMenu.add(displayWindowMenuItem);
439 		windowMenu.add(new SeparatorMenuItem());
440 		closeWindowItem = new MenuItem(cst.closeWindowMenuItem());
441 		windowMenu.add(closeWindowItem);
442 		
443 		Menu toolsMenu = new Menu();
444 		final MenuItem inspectorMenuItem = new MenuItem(cst.inspectorMenuItem());
445 		toolsMenu.add(inspectorMenuItem);
446 		
447 		Menu aboutMenu = new Menu();
448 		final MenuItem aboutItem = new MenuItem(cst.aboutMenuItem());
449 		aboutMenu.add(aboutItem);
450 
451 		MenuBar menuBar = new MenuBar();
452 		menuBar.setBorders(true);  
453 		menuBar.setStyleAttribute("borderTop", "none");
454 		menuBar.add(new MenuBarItem(cst.fileMenu(), fileMenu));
455 		menuBar.add(new MenuBarItem(cst.windowMenu(), windowMenu));
456 		menuBar.add(new MenuBarItem(cst.toolsMenu(), toolsMenu));
457 		menuBar.add(new MenuBarItem(cst.aboutMenu(), aboutMenu));
458 		
459 		dispatcher = new SelectionListener<MenuEvent>() {
460 			@Override
461 			public void componentSelected(MenuEvent me) {
462 				MenuItem item = (MenuItem) me.getItem();
463 				if (item == newDocumentMenuItem) {
464 					newDocument();
465 //				} else if (item == openUrlItem) {
466 //					openUrl();
467 				} else if (item == openLocalMenuItem) {
468 					openLocal();
469 				} else if (item == openRssFeedItem) {
470 					openRssFeed();
471 				} else if (item == exportAsSvgMenuItem) {
472 					exportAsSvg();
473 				} else if (item == resetViewItem) {
474 					resetView();
475 				} else if (item == tileWindowsItem) {
476 					tileWindows();
477 				} else if (item == stackWindowsItem) {
478 					stackWindows();
479 				} else if (item == closeWindowItem) {
480 					closeWindow(activeWindow);
481 				} else if (item == inspectorMenuItem) {
482 					inspector();
483 				} else if (item == aboutItem) {
484 					about();
485 				} else if (item.getParentMenu() == displayWindowMenu) {
486 					for (SVGWindow window : svgWindows) {
487 						if (window.getHeading().equals(item.getText())) {
488 							window.setActive(true);
489 							window.toFront();
490 							break;
491 						}
492 					}
493 				} else if (item.getParentMenu() == languageMenu) {
494 					redirect(item.getText());
495 				}
496 			}
497 		};
498 		newDocumentMenuItem.addSelectionListener(dispatcher);
499 //		openUrlItem.addSelectionListener(dispatcher);
500 		openLocalMenuItem.addSelectionListener(dispatcher);
501 		openRssFeedItem.addSelectionListener(dispatcher);
502 		exportAsSvgMenuItem.addSelectionListener(dispatcher);
503 		resetViewItem.addSelectionListener(dispatcher);
504 		tileWindowsItem.addSelectionListener(dispatcher);
505 		stackWindowsItem.addSelectionListener(dispatcher);
506 		closeWindowItem.addSelectionListener(dispatcher);
507 		inspectorMenuItem.addSelectionListener(dispatcher);
508 		aboutItem.addSelectionListener(dispatcher);
509 		return menuBar;
510 	}
511 	
512 	public void redirect(String language) {
513 		StringBuilder path = new StringBuilder(GWT.getHostPageBaseURL());
514 		path.append("svgreal.html");
515 		path.append("?");
516 		path.append(PARAM_LOCALE);
517 		path.append("=");
518 		path.append(language);
519 		path.append(copyParam(PARAM_CODE_SERVER));
520 		path.append(copyParam(PARAM_DEMO));
521 	    Location.replace(path.toString());
522 	}
523 	
524 	private static String copyParam(String name) {
525 		StringBuilder copy = new StringBuilder();
526 		String value = getParameter(name);
527 		if (value != null && value.length() > 0) {
528 			copy.append("&");
529 			copy.append(name);
530 			copy.append("=");
531 			copy.append(value);
532 		}
533 		return copy.toString();
534 	}
535 	
536 	private ToolBar createLanguageBar() {
537 		if (localeToIcon == null) {
538 			localeToIcon = new HashMap<String, ImageResource>();
539 			localeToIcon.put("fr", AppBundle.INSTANCE.flagFr());
540 			localeToIcon.put("en", AppBundle.INSTANCE.flagUs());
541 		}
542 		ToolBar toolBar = new ToolBar();
543 		languageMenu = new Menu();
544 		for (Map.Entry<String, ImageResource> entry : localeToIcon.entrySet()) {
545 			MenuItem item = new MenuItem(entry.getKey(), AbstractImagePrototype.create(entry.getValue()));
546 			item.addSelectionListener(dispatcher);
547 			languageMenu.add(item);
548 		}
549 		String locale = getParameter(PARAM_LOCALE);
550 		if (!localeToIcon.containsKey(locale)) {
551 			locale = "en";
552 		}
553 		Button languageButton = new Button(locale, AbstractImagePrototype.create((localeToIcon.get(locale))));
554 		languageButton.setMenu(languageMenu);
555 		toolBar.add(languageButton);
556 		toolBar.setWidth(60);
557 		toolBar.setHeight(getWindowBarHeight() -1);
558 		return toolBar;
559 	}
560 	
561 
562 	public SVGWindow addWindow(OMSVGSVGElement svg, ILoadRequest request) {
563 		String title = request.getTitle();
564 		SVGModel model = SVGModel.newInstance(svg, title, SVGProcessor.newIdPrefix());
565 		SVGWindow window = new SVGWindow(model);
566 		window.setHeading(title);
567 		svgWindows.add(window);
568 		MenuItem displayItem = new MenuItem(title);
569 		displayItem.addSelectionListener(dispatcher);
570 		displayWindowMenu.add(displayItem);
571 		
572 		// To be notified when a window is activated in order to
573 		// keep track of the active window 
574 		window.addListener(Events.Activate, new Listener<WindowEvent>() {
575 
576 			@Override
577 			public void handleEvent(WindowEvent we) {
578 				SVGWindow svgWindow = (SVGWindow) we.getWindow();
579 				GWT.log("VectomaticApp2.Activate(" + svgWindow.getHeading() + ")");
580 				if (activeWindow != svgWindow) {
581 					if (activeWindow != null) {
582 						activeWindow.deactivate();
583 						fireEvent(new DeactivateWindowEvent(activeWindow));
584 					}
585 					activeWindow = svgWindow;
586 					activeWindow.activate();
587 					fireEvent(new ActivateWindowEvent(activeWindow));
588 				}
589 			}
590 		});
591 		window.addListener(Events.BeforeHide, new Listener<WindowEvent>() {
592 
593 			@Override
594 			public void handleEvent(WindowEvent we) {
595 				SVGWindow svgWindow = (SVGWindow) we.getWindow();
596 				svgWindow.getSvgModel().getSelectionModel().deselectAll();
597 				GWT.log("VectomaticApp2.BeforeHide(" + svgWindow.getHeading() + ")");
598 				svgWindow.removeAllListeners();
599 				svgWindow.deactivate();
600 				fireEvent(new DeactivateWindowEvent(svgWindow));
601 				for (Component c : displayWindowMenu.getItems()) {
602 					MenuItem item = (MenuItem)c;
603 					if (item.getText().equals(svgWindow.getHeading())) {
604 						displayWindowMenu.remove(item);
605 						break;
606 					}
607 				}
608 				svgWindows.remove(svgWindow);
609 				activeWindow = null;
610 				update();
611 			}
612 		});
613 
614 		// Update the recent docs menu
615 		if (!(request instanceof NewDocRequest)) {
616 			List<Component> recentDocs = recentDocsMenu.getItems();
617 			boolean alreadyInRecentDocs = false;
618 			for (Component item : recentDocs) {
619 				RecentDocMenuItem menuItem = (RecentDocMenuItem)item;
620 				if (request.equals(menuItem.getRequest())) {
621 					alreadyInRecentDocs = true;
622 					break;
623 				}
624 			}
625 			if (!alreadyInRecentDocs) {
626 				if (recentDocs.size() >= 8) {
627 					Component oldestItem = recentDocs.get(0);
628 					oldestItem.removeAllListeners();
629 					recentDocsMenu.remove(oldestItem);
630 				}
631 				RecentDocMenuItem recentDocItem = new RecentDocMenuItem(request);
632 				recentDocsMenu.add(recentDocItem);
633 			}
634 		}
635 
636 		slotWindow(window);
637 //		window.show();
638 		viewport.add(window);
639 		window.setVisible(true);
640 		update();
641 		return window;
642 	}
643 
644 	public void newDocument() {
645 		GWT.log("newDocument()");
646 		new NewDocRequest().load();
647 	}
648 	
649 	public void openUrl() {
650 		GWT.log("openUrl()");
651 		openUrlBox = MessageBox.prompt(AppConstants.INSTANCE.openUrlMenuItem(), AppConstants.INSTANCE.openUrlText());
652 		openUrlBox.addCallback(new Listener<MessageBoxEvent>() {  
653 			public void handleEvent(MessageBoxEvent be) {
654 				new UrlLoadRequest(be.getValue()).load();
655 			}  
656 		});  
657 	}
658 	
659 	public void info(String command, String message) {
660 		Info.display(command, message);  
661 	}
662 	
663 	public void openRssFeed() {
664 		GWT.log("openRssFeed()");
665 		if (rssReader == null) {
666 			rssReader = new RSSReader();
667 		}
668 		rssReader.load();
669 		rssReader.show();
670 	}
671 
672 	private void openLocal() {
673 		GWT.log("openLocal()");
674 		fileUpload.click();
675 	}
676 	
677 	private void processFiles(FileList files) {
678 		for (File file : files) {
679 			String type = file.getType();
680 			if ("image/svg+xml".equals(type)) {
681 				new FileLoadRequest(file).load();
682 			}
683 		}		
684 	}
685 
686 	protected void exportAsSvg() {
687 		GWT.log("exportAsSvg()");
688 		SVGModel model = getActiveModel();
689 		String markup = model.getMarkup();
690 		String url = "data:image/svg+xml;base64," + base64encode(markup);
691 		String title = ((SVGNamedElementModel)model.getRoot()).getName();
692 		com.google.gwt.user.client.Window.open(url, title, "");
693 	}
694 	
695 	private static native String base64encode(String str) /*-{
696 		return $wnd.btoa(str);
697 	}-*/;
698 
699 	
700 	public void resetView() {
701 		GWT.log("resetView()");
702 		activeWindow.setRotationCompass(0);
703 		activeWindow.setScaleSlider(50);
704 	}
705 	private List<Window> getAllWindows() {
706 		List<Window> windows = new ArrayList<Window>(svgWindows);
707 		if (inspectorWindow != null && inspectorWindow.isVisible()) {
708 			windows.add(inspectorWindow);
709 		}
710 		return windows;
711 	}
712 	public void tileWindows() {
713 		GWT.log("tileWindows()");
714 		List<Window> windows = getAllWindows();
715 		Rectangle rect = getRectangle();
716 		int count = windows.size();
717 		int cols = (int)Math.ceil(Math.sqrt(count));
718 		int rows = (int)Math.ceil((double)count/cols);
719 		GWT.log("cols=" + cols + "; rows=" + rows);
720 		Size windowSize = new Size(rect.width / cols, rect.height / rows);
721 		int index = 0;
722 		for (Window window : windows) {
723 			window.setSize(windowSize.width, windowSize.height);
724 			int x = index % cols;
725 			int y = index / cols;
726 			window.setPagePosition(rect.x + x * windowSize.width, rect.y + y * windowSize.height);
727 			index++;
728 		}
729 	}
730 	public void stackWindows() {
731 		GWT.log("stackWindows()");
732 		windowSlot = 0;
733 		for (Window window : getAllWindows()) {
734 			slotWindow(window);
735 		}
736 	}
737 	
738 	/**
739 	 * Positions a window on the next available graphical "slot"
740 	 * @param window the window to position
741 	 */
742 	public void slotWindow(Window window) {
743 		Rectangle rect = getRectangle();
744 		Size windowSize = new Size((int)(rect.width * 0.8f), (int)(rect.height * 0.8f));
745 		int windowBarHeight = getWindowBarHeight();
746 		window.setSize(windowSize.width, windowSize.height);
747 		int x = rect.x + windowSlot * windowBarHeight;
748 		int y = rect.y + windowSlot * windowBarHeight;
749 		window.setPagePosition(x, y);
750 		windowSlot++;
751 		if (x + windowSize.width > rect.x + rect.width || y + windowSize.height > rect.y + rect.height) {
752 			x = rect.x;
753 			y = rect.y;
754 			windowSlot = 0;
755 		}
756 	}
757 	
758 	public void closeWindow(SVGWindow window) {
759 		GWT.log("closeWindow()");
760 		if (window != null) {
761 			window.hide();
762 		}
763 	}
764 
765 	public static String getParameter(String param) {
766 		return com.google.gwt.user.client.Window.Location.getParameter(param);
767 	}
768 
769 	public SVGWindow getActiveWindow() {
770 		return activeWindow;
771 	}
772 	
773 	public SVGModel getActiveModel() {
774 		if (activeWindow != null) {
775 			return activeWindow.getSvgModel();
776 		}
777 		return null;
778 	}
779 	
780 	public SVGWindow getWindow(SVGElement element) {
781 		SVGSVGElement svg = element.getOwnerSVGElement();
782 		for (SVGWindow svgWindow : svgWindows) {
783 			if (svg == svgWindow.getSvgModel().getSvgElement().getElement()) {
784 				return svgWindow;
785 			}
786 		}
787 		return null;
788 	}
789 
790 	public void about() {
791 		GWT.log("about()");
792 		if (aboutDialog == null) {
793 			aboutDialog = new AboutDialog();
794 		}
795 		aboutDialog.show();
796 	}
797 	public Rectangle getRectangle() {
798 		int windowBarHeight = getWindowBarHeight();
799 		Size commandBarSize = commandToolBar.getSize();
800 		Size viewPortSize = viewport.getSize();
801 		Rectangle rect = new Rectangle(0, windowBarHeight, viewPortSize.width, viewPortSize.height - windowBarHeight - commandBarSize.height);
802 		return rect;
803 	}
804 	public static final int getWindowBarHeight() {
805 		return 27;
806 	}
807 	
808 	private void update() {
809 		boolean hasActiveWindow = svgWindows.size() > 0;
810 		exportAsSvgMenuItem.setEnabled(hasActiveWindow);
811 		closeWindowItem.setEnabled(hasActiveWindow);
812 		tileWindowsItem.setEnabled(hasActiveWindow);
813 		stackWindowsItem.setEnabled(hasActiveWindow);
814 		resetViewItem.setEnabled(hasActiveWindow);
815 		displayWindowMenuItem.setEnabled(svgWindows.size() > 0);
816 	}
817 	
818     public static final native void log(String msg) /*-{
819 	    console.log(msg);
820 	}-*/;
821     
822     public void inspector() {
823     	GWT.log("inspector()");
824     	if (inspectorWindow == null) {
825     		inspectorWindow = new InspectorWindow();
826     	}
827     	inspectorWindow.show();
828     	if (activeWindow != null) {
829     		activeWindow.updateSelectionListeners();
830     	}
831     }
832     
833     public CssContextModel getCssContext() {
834     	return cssContext;
835     }
836     
837 	public List<MetaModel<?>> getMetaModels() {
838 		if (metaModels == null) {
839 			metaModels = new ArrayList<MetaModel<?>>();
840 			metaModels.add(SVGLineElementModel.getLineElementMetaModel());
841 			metaModels.add(SVGCircleElementModel.getCircleElementMetaModel());
842 			metaModels.add(SVGEllipseElementModel.getEllipseElementMetaModel());
843 			metaModels.add(SVGRectElementModel.getRectElementMetaModel());
844 			metaModels.add(SVGPolygonElementModel.getPolygonElementMetaModel());
845 			metaModels.add(SVGPolylineElementModel.getPolylineElementMetaModel());
846 			metaModels.add(SVGImageElementModel.getImageElementMetaModel());
847 		}
848 		return metaModels;
849 	}
850 	
851 	/**
852 	 * Returns the global command factory selector
853 	 * @return
854 	 */
855 	public CommandFactorySelector getCommandFactorySelector() {
856 		if (commandFactorySelector == null) {
857 		    commandFactorySelector = new CommandFactorySelector();		
858 		}
859 		return commandFactorySelector;
860 	}
861 	/**
862 	 * Returns the application event bus
863 	 * @return the application event bus
864 	 */
865 	public EventBus getEventBus() {
866 		if (eventBus == null) {
867 			eventBus = new SimpleEventBus();
868 		}
869 		return eventBus;
870 	}
871 	
872 	public CommandFactoryToolBar getCommandToolBar() {
873 		return commandToolBar;
874 	}
875 	
876 	public DecoratedImageCache getImageCache() {
877 		return imageCache;
878 	}
879 
880 	@Override
881 	public void fireEvent(GwtEvent<?> event) {
882 		eventBus.fireEventFromSource(event, this);
883 	}
884 
885 	@Override
886 	public HandlerRegistration addDeactivateWindowHandler(
887 			DeactivateWindowHandler handler) {
888 		return eventBus.addHandlerToSource(DeactivateWindowEvent.getType(), this, handler);
889 	}
890 
891 	@Override
892 	public HandlerRegistration addActivateWindowHandler(
893 			ActivateWindowHandler handler) {
894 		return eventBus.addHandlerToSource(ActivateWindowEvent.getType(), this, handler);
895 	}
896 
897 	public static SvgrealApp getApp() {
898 		if (instance == null) {
899 			instance = new SvgrealApp();
900 		}
901 		return instance;
902 	}
903 }