View Javadoc

1   /**********************************************
2    * Copyright (C) 2011 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.gxt.form;
19  
20  import org.vectomatic.dnd.DataTransferExt;
21  import org.vectomatic.dnd.DropPanel;
22  import org.vectomatic.file.ErrorCode;
23  import org.vectomatic.file.File;
24  import org.vectomatic.file.FileError;
25  import org.vectomatic.file.FileList;
26  import org.vectomatic.file.FileReader;
27  import org.vectomatic.file.FileUploadExt;
28  import org.vectomatic.file.FileUtils;
29  import org.vectomatic.file.events.LoadEndEvent;
30  import org.vectomatic.file.events.LoadEndHandler;
31  import org.vectomatic.svg.edit.client.AppBundle;
32  import org.vectomatic.svg.edit.client.SvgrealApp;
33  import org.vectomatic.svg.edit.client.model.ModelConstants;
34  import org.vectomatic.svg.edit.client.model.svg.SVGImageElementModel;
35  
36  import com.extjs.gxt.ui.client.Style.Orientation;
37  import com.extjs.gxt.ui.client.event.ButtonEvent;
38  import com.extjs.gxt.ui.client.event.Events;
39  import com.extjs.gxt.ui.client.event.FieldEvent;
40  import com.extjs.gxt.ui.client.event.Listener;
41  import com.extjs.gxt.ui.client.event.SelectionListener;
42  import com.extjs.gxt.ui.client.util.Margins;
43  import com.extjs.gxt.ui.client.util.Size;
44  import com.extjs.gxt.ui.client.util.Util;
45  import com.extjs.gxt.ui.client.widget.Label;
46  import com.extjs.gxt.ui.client.widget.LayoutContainer;
47  import com.extjs.gxt.ui.client.widget.button.Button;
48  import com.extjs.gxt.ui.client.widget.form.AdapterField;
49  import com.extjs.gxt.ui.client.widget.form.Radio;
50  import com.extjs.gxt.ui.client.widget.form.RadioGroup;
51  import com.extjs.gxt.ui.client.widget.form.TextField;
52  import com.extjs.gxt.ui.client.widget.layout.CardLayout;
53  import com.extjs.gxt.ui.client.widget.layout.FitLayout;
54  import com.extjs.gxt.ui.client.widget.layout.RowData;
55  import com.extjs.gxt.ui.client.widget.layout.RowLayout;
56  import com.google.gwt.dom.client.DivElement;
57  import com.google.gwt.dom.client.Document;
58  import com.google.gwt.dom.client.Style.Visibility;
59  import com.google.gwt.dom.client.Text;
60  import com.google.gwt.event.dom.client.ChangeEvent;
61  import com.google.gwt.event.dom.client.ChangeHandler;
62  import com.google.gwt.event.dom.client.DragEnterEvent;
63  import com.google.gwt.event.dom.client.DragEnterHandler;
64  import com.google.gwt.event.dom.client.DragLeaveEvent;
65  import com.google.gwt.event.dom.client.DragLeaveHandler;
66  import com.google.gwt.event.dom.client.DragOverEvent;
67  import com.google.gwt.event.dom.client.DragOverHandler;
68  import com.google.gwt.event.dom.client.DropEvent;
69  import com.google.gwt.event.dom.client.DropHandler;
70  import com.google.gwt.event.dom.client.ErrorEvent;
71  import com.google.gwt.event.dom.client.ErrorHandler;
72  import com.google.gwt.event.dom.client.LoadEvent;
73  import com.google.gwt.event.dom.client.LoadHandler;
74  import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
75  import com.google.gwt.event.logical.shared.ValueChangeEvent;
76  import com.google.gwt.event.logical.shared.ValueChangeHandler;
77  import com.google.gwt.event.shared.GwtEvent;
78  import com.google.gwt.event.shared.HandlerRegistration;
79  import com.google.gwt.user.client.ui.Image;
80  
81  /**
82   * Field subclass to edit SVG image xlink:href values.
83   * This field is used both by the image inspector and the
84   * add new image command.
85   * @author laaglu
86   */
87  public class ImageHrefField extends AdapterField implements HasValueChangeHandlers<Size> {
88  	private static final String ATT_ACCEPT = "accept";
89  	private static final String ATT_ACCEPT_YES = "yes";
90  	private static final String ATT_ACCEPT_NO = "no";
91  	
92  	private class ImageHrefPanel extends LayoutContainer {
93  		/**
94  		 * Card layout to alternate between external
95  		 * and embedded panel (managed by radio buttons)
96  		 */
97  		private CardLayout cardLayout1;
98  		/**
99  		 * Card layout to alternate between status panel
100 		 * and error panel
101 		 */
102 		private CardLayout cardLayout2;
103 		/**
104 		 * Radio button to select an external URL
105 		 */
106 		private Radio externalRadio;
107 		/**
108 		 * Radio button to select an embedded data url
109 		 */
110 		private Radio embeddedRadio;
111 		/**
112 		 * Panel grouping widgets to edit an external URL
113 		 */
114 		private LayoutContainer externalPanel;
115 		/**
116 		 * Panel grouping widgets to edit an embedded URL
117 		 */
118 		private LayoutContainer embeddedPanel;
119 		/**
120 		 * Panel to give info on a loaded image
121 		 */
122 		private LayoutContainer statusPanel;
123 		/**
124 		 * Panel to give error info on a image which could
125 		 * not be loaded
126 		 */
127 		private LayoutContainer errorPanel;
128 		/**
129 		 * A textfield to specify external urls
130 		 */
131 		private TextField<String> urlField;
132 		/**
133 		 * A file upload dialog
134 		 */
135 		private FileUploadExt fileUploadExt;
136 		/**
137 		 * Name of the resource used to initialize this field (url
138 		 * or file name).
139 		 */
140 		private String resourceName;
141 		/**
142 		 * A file reader object
143 		 */
144 		private FileReader reader;
145 		/**
146 		 * The hidden image used to determine the native image size
147 		 */
148 		private Image hiddenImage;
149 		/**
150 		 * Label to display the size of the original image
151 		 */
152 		private Label sizeLabel;
153 		/**
154 		 * Label to display error messages
155 		 */
156 		private Label errorLabel;
157 		/**
158 		 * The bitmap width and height
159 		 */
160 		private int bitmapWidth, bitmapHeight;
161 		
162 		public ImageHrefPanel() {
163 			ModelConstants constants = ModelConstants.INSTANCE;
164 
165 			/*==============================================
166 			 * External URL pane
167 			 *==============================================*/
168 			
169 			Label externalLabel = new Label(constants.url());
170 			
171 			urlField = new TextField<String>();
172 			urlField.setToolTip(constants.urlTooltip());
173 			urlField.setFireChangeEventOnSetValue(true);
174 			urlField.addListener(Events.Change, new Listener<FieldEvent>() {
175 				@Override
176 				public void handleEvent(FieldEvent be) {
177 					String value = urlField.getValue();
178 	 				resourceName = value;
179 	 				if (value != null && value.length() > 0) {
180 	 					hiddenImage.setUrl(value);
181 	 				} else {
182 	 					reportNull();
183 	 				}
184 	 				setValue(value, false);
185 				}
186 			});
187 			
188 			externalPanel = new LayoutContainer(new RowLayout(Orientation.HORIZONTAL));
189 			externalPanel.add(externalLabel, new RowData(.10, 1, new Margins(0, 5, 0, 5)));
190 			externalPanel.add(urlField, new RowData(.90, 1, new Margins(0, 5, 0, 0)));
191 			
192 			/*==============================================
193 			 * Embedded Image pane
194 			 *==============================================*/
195 			final DropPanel dropArea = new DropPanel();
196 			dropArea.addDragEnterHandler(new DragEnterHandler() {	
197 				@Override
198 				public void onDragEnter(DragEnterEvent event) {
199 					dropArea.getElement().setAttribute(ATT_ACCEPT, ATT_ACCEPT_YES);
200 					event.stopPropagation();
201 					event.preventDefault();
202 				}
203 			});
204 			dropArea.addDragLeaveHandler(new DragLeaveHandler() {
205 				@Override
206 				public void onDragLeave(DragLeaveEvent event) {
207 					dropArea.getElement().setAttribute(ATT_ACCEPT, ATT_ACCEPT_NO);
208 					event.stopPropagation();
209 					event.preventDefault();
210 				}
211 			});
212 			dropArea.addDragOverHandler(new DragOverHandler() {
213 				@Override
214 				public void onDragOver(DragOverEvent event) {
215 					event.stopPropagation();
216 					event.preventDefault();
217 				}
218 			});
219 			dropArea.addDropHandler(new DropHandler() {
220 				@Override
221 				public void onDrop(DropEvent event) {
222 					dropArea.getElement().setAttribute(ATT_ACCEPT, ATT_ACCEPT_NO);
223 					processFiles(event.getDataTransfer().<DataTransferExt>cast().getFiles());
224 					event.stopPropagation();
225 					event.preventDefault();
226 				}
227 			});
228 			Document document = Document.get();
229 			DivElement div = document.createDivElement();
230 			Text text = document.createTextNode(constants.dropPanelText());
231 			div.appendChild(text);
232 			dropArea.getElement().appendChild(div);
233 			dropArea.setStyleName(AppBundle.INSTANCE.css().imageHrefDropArea());
234 			dropArea.getElement().setAttribute(ATT_ACCEPT, ATT_ACCEPT_NO);
235 			
236 			fileUploadExt = new FileUploadExt();
237 			fileUploadExt.getElement().getStyle().setVisibility(Visibility.HIDDEN);
238 			fileUploadExt.addChangeHandler(new ChangeHandler() {
239 				@Override
240 				public void onChange(ChangeEvent event) {
241 					processFiles(fileUploadExt.getFiles());
242 				}
243 			});
244 			Button openButton = new Button(constants.openLocalImageButton());
245 			openButton.addSelectionListener(new SelectionListener<ButtonEvent>() {
246 				@Override
247 				public void componentSelected(ButtonEvent ce) {
248 					fileUploadExt.click();	
249 				}
250 			});
251 			
252 			embeddedPanel = new LayoutContainer(new RowLayout(Orientation.HORIZONTAL));
253 			embeddedPanel.add(dropArea, new RowData(.85, 1, new Margins(0, 5, 0, 5)));
254 			embeddedPanel.add(openButton, new RowData(.15, 1, new Margins(0, 5, 0, 0)));
255 
256 			/*==============================================
257 			 * Status panel
258 			 *==============================================*/
259 			statusPanel = new LayoutContainer(new RowLayout(Orientation.HORIZONTAL));
260 			sizeLabel = new Label();
261 			Button resetButton = new Button(constants.resetHrefButton());
262 			resetButton.setToolTip(constants.resetHrefTooltip());
263 			statusPanel.add(sizeLabel, new RowData(.85, 1, new Margins(0, 5, 0, 5)));
264 			statusPanel.add(resetButton, new RowData(.15, 1, new Margins(0, 5, 0, 0)));
265 			resetButton.addSelectionListener(new SelectionListener<ButtonEvent>() {
266 				@Override
267 				public void componentSelected(ButtonEvent ce) {
268 					ValueChangeEvent.fire(ImageHrefField.this, new Size(bitmapWidth, bitmapHeight));
269 				}
270 			});
271 
272 			/*==============================================
273 			 * Error panel
274 			 *==============================================*/
275 			errorPanel = new LayoutContainer(new FitLayout());
276 			errorLabel = new Label();
277 			errorPanel.add(errorLabel);
278 
279 			/*==============================================
280 			 * Main panel
281 			 *==============================================*/
282 			externalRadio = new Radio();
283 			externalRadio.setBoxLabel(constants.externalRadio());
284 			externalRadio.setFireChangeEventOnSetValue(true);
285 			embeddedRadio = new Radio();
286 			embeddedRadio.setBoxLabel(constants.embeddedRadio());
287 			embeddedRadio.setFireChangeEventOnSetValue(true);
288 			
289 			RadioGroup radioGroup = new RadioGroup(constants.dropPanelText());
290 			radioGroup.add(externalRadio);
291 			radioGroup.add(embeddedRadio);
292 			radioGroup.setSelectionRequired(true);
293 			radioGroup.addListener(Events.Change, new Listener<FieldEvent>() {
294 				@Override
295 				public void handleEvent(FieldEvent be) {
296 					cardLayout1.setActiveItem(isExternal() ? externalPanel : embeddedPanel);				
297 				}				
298 			});
299 			
300 			LayoutContainer cardPanel1 = new LayoutContainer();
301 			cardLayout1 = new CardLayout();
302 			cardPanel1.setHeight(25);
303 			cardPanel1.setLayout(cardLayout1);
304 			cardPanel1.add(externalPanel);
305 			cardPanel1.add(embeddedPanel);
306 
307 			LayoutContainer cardPanel2 = new LayoutContainer();
308 			cardLayout2 = new CardLayout();
309 			cardPanel2.setHeight(25);
310 			cardPanel2.setLayout(cardLayout2);
311 			cardPanel2.add(statusPanel);
312 			cardPanel2.add(errorPanel);
313 			
314 			hiddenImage = new Image();
315 			hiddenImage.getElement().getStyle().setVisibility(Visibility.HIDDEN);
316 			hiddenImage.addLoadHandler(new LoadHandler() {
317 				@Override
318 				public void onLoad(LoadEvent event) {
319 					String w = hiddenImage.getElement().getAttribute("width");
320 //					GWT.log("++++++ hiddenImage.load w=" + w);
321 					bitmapWidth = hiddenImage.getWidth();
322 					bitmapHeight = hiddenImage.getHeight();
323 					sizeLabel.setText(ModelConstants.INSTANCE.originalSizeLabel() + ": " + bitmapWidth + "x" + bitmapHeight);
324 					cardLayout2.setActiveItem(statusPanel);
325 				}
326 			});
327 			hiddenImage.addErrorHandler(new ErrorHandler() {		
328 				@Override
329 				public void onError(ErrorEvent event) {
330 //					GWT.log("++++++ hiddenImage.error");
331 					reportError(null);
332 				}
333 			});
334 			
335 			setLayout(new RowLayout(Orientation.VERTICAL));
336 			add(radioGroup, new RowData(1, -1, new Margins(5, 5, 0, 5)));
337 			add(cardPanel1, new RowData(1, -1, new Margins(0, 0, 5, 0)));	
338 			add(cardPanel2, new RowData(1, -1));
339 			add(hiddenImage);
340 			add(fileUploadExt);
341 			setBorders(true);
342 			setHeight(90);
343 		}
344 		
345 		public boolean isExternal() {
346 			return externalRadio.getValue();
347 		}
348 		
349 		public void update(String value) {
350 //			GWT.log("ImageHrefField.update(" + value + ")");
351 			if (SVGImageElementModel.isDataUrl(value)) {
352 				if (!embeddedRadio.getValue()) {
353 					embeddedRadio.setValue(true);
354 				}
355 				hiddenImage.setUrl(value);
356 			} else if (value != null && value.length() > 0) {
357 				if (!externalRadio.getValue()) {
358 					externalRadio.setValue(true);
359 				}
360 				if (!Util.equalWithNull(value, urlField.getValue())) {
361 					urlField.setFireChangeEventOnSetValue(false);
362 					urlField.setValue(value);
363 					urlField.setFireChangeEventOnSetValue(true);
364 				}
365 				hiddenImage.setUrl(value);
366 			} else {
367 				reportNull();
368 			}
369 		}
370 		
371 		public void processFiles(FileList files) {
372 			for (File file : files) {
373 				final String type = file.getType();
374 				if (type.startsWith("image")) {
375 					if (reader == null) {
376 		 				reader = new FileReader();
377 		 				reader.addErrorHandler(new org.vectomatic.file.events.ErrorHandler() {
378 							@Override
379 							public void onError(org.vectomatic.file.events.ErrorEvent event) {
380 								FileError error = reader.getError();
381 								String errorDesc = "";
382 								if (error != null) {
383 									ErrorCode errorCode = error.getCode();
384 									if (errorCode != null) {
385 										errorDesc = errorCode.name();
386 									}
387 								}
388 								reportError(errorDesc);
389 	 							setValue(null, false);
390 							}
391 		 				});
392 		 				reader.addLoadEndHandler(new LoadEndHandler() {
393 		 					
394 		 					@Override
395 		 					public void onLoadEnd(LoadEndEvent event) {
396 		 						if (reader.getError() == null) {
397 			 						try {
398 			 							String result = reader.getStringResult();
399 			 							String url = FileUtils.createDataUrl(type, result);
400 			 							setValue(url, false);
401 			 						} catch(Throwable t) {
402 			 							reportError(t.getMessage());
403 			 							setValue(null, false);
404 			 						}
405 		 						}
406 		 					}
407 		 				});
408 					}
409 	 				try {
410 	 					reader.readAsBinaryString(file);
411 		 				resourceName = file.getName();
412 	 				} catch(Throwable t) {
413 	 					// mozilla bug 701154: exception should not be thrown here
414 	 					// the error handler ought to be invoked instead
415 						reportError(t.getMessage());
416 						setValue(null, false);
417 	 				}
418 	 				break;
419 				}
420 			}
421 		}
422 		
423 		public void reportError(String message) {
424 //			GWT.log("++++++ ImageStatusPanel.reportError");
425 			StringBuilder builder = new StringBuilder(ModelConstants.INSTANCE.imageLoadError());
426 			if (message != null && message.length() > 0) {
427 				builder.append(": ");
428 				builder.append(message);
429 			}
430 			errorLabel.setText(builder.toString());
431 			cardLayout2.setActiveItem(errorPanel);
432 		}
433 		
434 		public void reportNull() {
435 			errorLabel.setText(ModelConstants.INSTANCE.noImage());
436 			cardLayout2.setActiveItem(errorPanel);
437 		}
438 
439 
440 		public String getResourceName() {
441 			return resourceName;
442 		}
443 	}
444 	
445 	public ImageHrefField() {
446 		super(null);
447 		widget = new ImageHrefPanel();
448 		setResizeWidget(true);
449 		setFireChangeEventOnSetValue(true);
450 	}
451 
452 	@Override
453 	public void setValue(Object value) {
454 		setValue(value, true);
455 	}
456 
457 	public void setValue(Object value, boolean update) {
458 		if (update) {
459 			((ImageHrefPanel)widget).update((String)value);
460 		    this.value = value;
461 		} else {
462 			// Fires change event
463 			super.setValue(value);
464 		}
465 	}
466 
467 	@Override
468 	public Object getValue() {
469 		return value;
470 	}
471 	
472 	public String getResourceName() {
473 		return ((ImageHrefPanel)widget).getResourceName();
474 	}
475 
476 	///////////////////////////////////////////////////
477 	// Event management
478 	///////////////////////////////////////////////////
479 	
480 	@Override
481 	public void fireEvent(GwtEvent<?> event) {
482 		SvgrealApp.getApp().getEventBus().fireEventFromSource(event, this);
483 	}
484 	
485 	@Override
486 	public HandlerRegistration addValueChangeHandler(ValueChangeHandler<Size> handler) {
487 		return SvgrealApp.getApp().getEventBus().addHandlerToSource(ValueChangeEvent.getType(), this, handler);
488 	}
489 }