View Javadoc

1   /**********************************************
2    * Copyright (C) 2011 Lukas laag
3    * This file is part of lib-gwt-file.
4    * 
5    * lib-gwt-file 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   * lib-gwt-file 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 lib-gwt-file.  If not, see http://www.gnu.org/licenses/
17   **********************************************/
18  package org.vectomatic.file.client;
19  
20  import java.util.ArrayList;
21  import java.util.List;
22  
23  import org.vectomatic.dnd.DataTransferExt;
24  import org.vectomatic.dnd.DropPanel;
25  import org.vectomatic.dom.svg.OMSVGRect;
26  import org.vectomatic.dom.svg.OMSVGSVGElement;
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.file.Blob;
31  import org.vectomatic.file.ErrorCode;
32  import org.vectomatic.file.File;
33  import org.vectomatic.file.FileError;
34  import org.vectomatic.file.FileList;
35  import org.vectomatic.file.FileReader;
36  import org.vectomatic.file.FileUploadExt;
37  import org.vectomatic.file.FileUtils;
38  import org.vectomatic.file.events.ErrorEvent;
39  import org.vectomatic.file.events.ErrorHandler;
40  import org.vectomatic.file.events.LoadEndEvent;
41  import org.vectomatic.file.events.LoadEndHandler;
42  
43  import com.google.gwt.core.client.EntryPoint;
44  import com.google.gwt.core.client.GWT;
45  import com.google.gwt.core.client.JsDate;
46  import com.google.gwt.dom.client.Document;
47  import com.google.gwt.dom.client.Element;
48  import com.google.gwt.dom.client.Style.Unit;
49  import com.google.gwt.event.dom.client.ChangeEvent;
50  import com.google.gwt.event.dom.client.ClickEvent;
51  import com.google.gwt.event.dom.client.DragEnterEvent;
52  import com.google.gwt.event.dom.client.DragLeaveEvent;
53  import com.google.gwt.event.dom.client.DragOverEvent;
54  import com.google.gwt.event.dom.client.DropEvent;
55  import com.google.gwt.event.dom.client.LoadEvent;
56  import com.google.gwt.event.dom.client.LoadHandler;
57  import com.google.gwt.resources.client.ClientBundle;
58  import com.google.gwt.resources.client.CssResource;
59  import com.google.gwt.typedarrays.client.Int8ArrayNative;
60  import com.google.gwt.typedarrays.shared.ArrayBuffer;
61  import com.google.gwt.typedarrays.shared.Int8Array;
62  import com.google.gwt.uibinder.client.UiBinder;
63  import com.google.gwt.uibinder.client.UiField;
64  import com.google.gwt.uibinder.client.UiHandler;
65  import com.google.gwt.user.client.Window;
66  import com.google.gwt.user.client.ui.Button;
67  import com.google.gwt.user.client.ui.FlowPanel;
68  import com.google.gwt.user.client.ui.Image;
69  import com.google.gwt.user.client.ui.Label;
70  import com.google.gwt.user.client.ui.RootLayoutPanel;
71  import com.google.gwt.user.client.ui.SimplePanel;
72  import com.google.gwt.user.client.ui.Widget;
73  
74  public class TestAppMain implements EntryPoint {
75      private static final char[] BASE64_CHARS = {
76          'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 
77          'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 
78          'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 
79          'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 
80          'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 
81          'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', 
82          '8', '9', '+', '/'
83      };
84      private static final char BASE64_PADDING = '=';
85  	@UiField
86  	Button resetBtn;
87  	@UiField
88  	Button browseBtn;
89  	@UiField
90  	DropPanel dropPanel;
91  	@UiField
92  	FileUploadExt fileUpload;
93  	@UiField
94  	FileUploadExt customUpload;
95  	@UiField
96  	FlowPanel imagePanel;
97  	@UiField(provided=true)
98  	static TestAppMainBundle bundle = GWT.create(TestAppMainBundle.class);
99  
100 	protected boolean useTypedArrays;
101 	protected FileReader reader;
102 	protected List<File> readQueue;
103 	
104     interface TestAppMainBinder extends UiBinder<FlowPanel, TestAppMain> {
105     }
106     private static TestAppMainBinder binder = GWT.create(TestAppMainBinder.class);
107     interface TestAppMainCss extends CssResource {
108     	public String imagePanel();
109     	public String customUpload();
110     	public String dropPanel();
111     	public String thumbnail();
112     	@ClassName("thumbnail-image")
113 		public String thumbnailImage();
114     	@ClassName("thumbnail-text")
115 		public String thumbnailText();
116     	@ClassName("txt")
117 		public String text();
118     }
119     interface TestAppMainBundle extends ClientBundle {
120     	@Source("TestAppMainCss.css")
121     	public TestAppMainCss css();
122     }
123 
124 	@Override
125 	public void onModuleLoad() {
126 		// Use typed arrays by default
127 		useTypedArrays = !"false".equals(Window.Location.getParameter("typedArrays"));
128 		
129 		// Create UI main elements
130 		bundle.css().ensureInjected();
131 		FlowPanel flowPanel = binder.createAndBindUi(this);
132 		Document document = Document.get();
133 		dropPanel.getElement().appendChild(document.createDivElement()).appendChild(document.createTextNode("Drop files here"));		
134 		RootLayoutPanel.get().add(flowPanel);
135 		
136 		// Create a file reader a and queue of files to read.
137 		// UI event handler will populate this queue by calling queueFiles()
138 		reader = new FileReader();
139 		reader.addLoadEndHandler(new LoadEndHandler() {
140 			/**
141 			 * This handler is invoked when FileReader.readAsText(),
142 			 * FileReader.readAsBinaryString() or FileReader.readAsArrayBuffer()
143 			 * successfully completes
144 			 */
145 			@Override
146 			public void onLoadEnd(LoadEndEvent event) {
147 				if (reader.getError() == null) {
148 					if (readQueue.size() > 0) {
149 						File file = readQueue.get(0);
150 						try {
151 							imagePanel.add(createThumbnail(file));
152 						} finally {
153 							readQueue.remove(0);
154 							readNextFile();
155 						}
156 					}
157 				}
158 			}
159 		});
160 		
161 		reader.addErrorHandler(new ErrorHandler() {
162 			/**
163 			 * This handler is invoked when FileReader.readAsText(),
164 			 * FileReader.readAsBinaryString() or FileReader.readAsArrayBuffer()
165 			 * fails
166 			 */
167 			@Override
168 			public void onError(ErrorEvent event) {
169 				if (readQueue.size() > 0) {
170 					File file = readQueue.get(0);
171 					handleError(file);
172 					readQueue.remove(0);
173 					readNextFile();
174 				}
175 			}
176 		});
177 		readQueue = new ArrayList<File>();
178 	}
179 	
180 	private void handleError(File file) {
181 		FileError error = reader.getError();
182 		String errorDesc = "";
183 		if (error != null) {
184 			ErrorCode errorCode = error.getCode();
185 			if (errorCode != null) {
186 				errorDesc = ": " + errorCode.name();
187 			}
188 		}
189 		Window.alert("File loading error for file: " + file.getName() + "\n" + errorDesc);
190 	}
191 	
192 	private void setBorderColor(String color) {
193 		dropPanel.getElement().getStyle().setBorderColor(color);
194 	}
195 	
196 	/**
197 	 * Adds a collection of file the queue and begin processing them
198 	 * @param files
199 	 * The file to process
200 	 */
201 	private void processFiles(FileList files) {
202 		GWT.log("length=" + files.getLength());
203 		for (File file : files) {
204 			readQueue.add(file);
205 		}
206 		// Start processing the queue
207 		readNextFile();
208 	}
209 	
210 	/**
211 	 * Processes the next file in the queue. Depending on the MIME type of the
212 	 * file, a different way of loading the image is used to demonstrate all
213 	 * parts of the API
214 	 */
215 	private void readNextFile() {
216 		if (readQueue.size() > 0) {
217 			File file = readQueue.get(0);
218 			String type = file.getType();
219 			try {
220 				if ("image/svg+xml".equals(type)) {
221 					reader.readAsText(file);	
222 				} else if (type.startsWith("image/png")) {
223 					// Do not use the FileReader for PNG.
224 					// Take advantage of the fact the browser can
225 					// provide a directly usable blob:// URL
226 					imagePanel.add(createThumbnail(file));
227 					readQueue.remove(0);
228 					readNextFile();
229 				} else if (type.startsWith("image/")) {
230 					// For other image types (GIF, JPEG), load them
231 					// as typed arrays
232 					if (useTypedArrays) {
233 						reader.readAsArrayBuffer(file);
234 					} else {
235 						reader.readAsBinaryString(file);
236 					}
237 				} else if (type.startsWith("text/")) {
238 					// If the file is larger than 1kb, read only the first 1000 characters
239 					// to demonstrate file slicing
240 					Blob blob = file;
241 					if (file.getSize() > 0) {
242 						blob = file.slice(0, 1000, "text/plain; charset=utf-8");
243 					}
244 					reader.readAsText(blob);
245 				}
246 			} catch(Throwable t) {
247 				// Necessary for FF (see bug https://bugzilla.mozilla.org/show_bug.cgi?id=701154)
248 				// Standard-complying browsers will not go in this branch
249 				handleError(file);
250 				readQueue.remove(0);
251 				readNextFile();
252 			}
253 		}
254 	}
255 	
256 	private FlowPanel createThumbnail(File file) {
257 		FlowPanel thumbnail = new FlowPanel();
258 		thumbnail.setStyleName(bundle.css().thumbnail());
259 		String type = file.getType();
260 		final String name = file.getName();
261 		final JsDate date = file.getLastModifiedDate();
262 		
263 		Widget image = null;
264 		if ("image/svg+xml".equals(type)) {
265 			image = createSvgImage();
266 		} else if (type.startsWith("image/png")) {
267 			image = createPngImage(file);
268 		} else if (type.startsWith("image/")) {
269 			image = createBitmapImage(file);
270 		} else if (type.startsWith("text/")) {
271 			image = createText(file);
272 		}
273 		SimplePanel thumbnailImage = new SimplePanel(image);
274 		thumbnailImage.setStyleName(bundle.css().thumbnailImage());
275 		thumbnail.add(thumbnailImage);
276 		
277 		StringBuilder description = new StringBuilder(name);
278 		if (date != null) {
279 			description.append(" (");
280 			description.append(date.toLocaleDateString());
281 			description.append(")");
282 		}
283 		Label thumbnailText = new Label(description.toString());
284 		thumbnailText.setStyleName(bundle.css().thumbnailText());
285 		thumbnail.add(thumbnailText);
286 		return thumbnail;
287 	}
288 	
289 	private SVGImage createSvgImage() {
290 		GWT.log(reader.getStringResult());
291 		final OMSVGSVGElement svg = OMSVGParser.parse(reader.getStringResult());
292 		return new SVGImage(svg) {
293 	    	protected void onAttach() {
294 	    		OMSVGRect viewBox = svg.getViewBox().getBaseVal();
295 				if (viewBox.getWidth() == 0 || viewBox.getHeight() == 0) {
296 					OMSVGRect bbox = svg.getBBox();
297 					bbox.assignTo(viewBox);
298 				}
299 				svg.getStyle().setWidth(150, Unit.PX);
300 				svg.getStyle().setHeight(150, Unit.PX);
301 				super.onAttach();
302 	    	}
303 		};
304 	}
305 
306 	private Image createPngImage(final File file) {
307 		final Image image = new Image();
308 		final String url = FileUtils.createObjectURL(file);
309 		image.addLoadHandler(new LoadHandler() {
310 			@Override
311 			public void onLoad(LoadEvent event) {
312 				sizeBitmap(image);
313 				FileUtils.revokeObjectURL(url);
314 			}			
315 		});
316 		image.setUrl(url);
317 		return image;			
318 	}
319 	
320 	private Image createBitmapImage(final File file) {
321 		String url;
322 		if (useTypedArrays) {
323 			ArrayBuffer buffer = reader.getArrayBufferResult();
324 			Int8Array array = Int8ArrayNative.create(buffer);
325 			url = "data:" + file.getType() + ";base64," + toBase64(array);
326 		} else {
327 			String result = reader.getStringResult();
328 			url = FileUtils.createDataUrl(file.getType(), result);
329 		}
330 		final Image image = new Image();
331 		image.setVisible(false);
332 		image.addLoadHandler(new LoadHandler() {
333 			@Override
334 			public void onLoad(LoadEvent event) {
335 				sizeBitmap(image);
336 			}			
337 		});
338 		image.setUrl(url);
339 		return image;			
340 	}
341 	
342 	private void sizeBitmap(Image image) {
343 		int width = image.getWidth();
344 		if (width == 0) {
345 			width = ieWidth(image.getElement());
346 		}
347 		int height = image.getHeight();
348 		if (height == 0) {
349 			height = ieHeight(image.getElement());
350 		}
351 		GWT.log("size=" + width + "x" + height);
352 		float f = 150.0f / Math.max(width, height);
353 		int w = (int)(f * width);
354 		int h = (int)(f * height);
355 		image.setPixelSize(w, h);
356 		image.getElement().getStyle().setWidth(w, Unit.PX);
357 		image.getElement().getStyle().setHeight(h, Unit.PX);
358 		image.setVisible(true);		
359 	}
360 
361 	private FlowPanel createText(final File file) {
362 		String result = reader.getStringResult();
363 		FlowPanel panel = new FlowPanel();
364 		panel.getElement().appendChild(Document.get().createTextNode(result));
365 		panel.addStyleName(bundle.css().text());
366 		return panel;
367 	}
368 	
369 	@UiHandler("browseBtn")
370 	public void browse(ClickEvent event) {
371 		customUpload.click();
372 	}
373 
374 	@UiHandler("resetBtn")
375 	public void reset(ClickEvent event) {
376 		for (int i = imagePanel.getWidgetCount() - 1; i >= 0; i--) {
377 			imagePanel.remove(i);
378 		}
379 	}
380 	
381 	@UiHandler("fileUpload")
382 	public void uploadFile1(ChangeEvent event) {
383 		processFiles(fileUpload.getFiles());
384 	}
385 
386 	@UiHandler("customUpload")
387 	public void uploadFile2(ChangeEvent event) {
388 		processFiles(customUpload.getFiles());
389 	}
390 	
391 	@UiHandler("dropPanel")
392 	public void onDragOver(DragOverEvent event) {
393 		// Mandatory handler, otherwise the default
394 		// behavior will kick in and onDrop will never
395 		// be called
396 		event.stopPropagation();
397 		event.preventDefault();
398 	}
399 	
400 	@UiHandler("dropPanel")
401 	public void onDragEnter(DragEnterEvent event) {
402 		setBorderColor(SVGConstants.CSS_RED_VALUE);
403 		event.stopPropagation();
404 		event.preventDefault();
405 	}
406 	
407 	@UiHandler("dropPanel")
408 	public void onDragLeave(DragLeaveEvent event) {
409 		setBorderColor(SVGConstants.CSS_BLACK_VALUE);
410 		event.stopPropagation();
411 		event.preventDefault();
412 	}
413 	
414 	@UiHandler("dropPanel")
415 	public void onDrop(DropEvent event) {
416 		processFiles(event.getDataTransfer().<DataTransferExt>cast().getFiles());
417 		setBorderColor(SVGConstants.CSS_BLACK_VALUE);
418 		event.stopPropagation();
419 		event.preventDefault();
420 	}
421 
422     public static String toBase64(Int8Array array) {
423     	// Manual conversion to base64. There are probably smarter ways
424     	// to do this but the goal is to demonstrate typed arrays.
425     	StringBuilder builder = new StringBuilder();
426     	int length = array.length();
427         if (length > 0) {
428 	        char[] charArray = new char[4];
429 	        int ix = 0;
430 	         while (length >= 3) {
431 	            int i = ((array.get(ix) & 0xff)<<16)
432 	                + ((array.get(ix+1) & 0xff)<<8)
433 	                + (array.get(ix+2) & 0xff);
434 	            charArray[0] = BASE64_CHARS[i>>18];
435 	            charArray[1] = BASE64_CHARS[(i>>12) & 0x3f];
436 	            charArray[2] = BASE64_CHARS[(i>>6) & 0x3f];
437 	            charArray[3] = BASE64_CHARS[i & 0x3f];
438 	            builder.append(charArray);
439 	            ix += 3;
440 	            length -= 3;
441 	        }
442 	        if (length == 1) {
443 	            int i = array.get(ix)&0xff;
444 	            charArray[0] = BASE64_CHARS[i>>2];
445 	            charArray[1] = BASE64_CHARS[(i<<4)&0x3f];
446 	            charArray[2] = BASE64_PADDING;
447 	            charArray[3] = BASE64_PADDING;
448 	            builder.append(charArray);
449 	        } else if (length == 2) {
450 	            int i = ((array.get(ix) & 0xff)<<8)
451 	            	+ (array.get(ix+1) & 0xff);
452 	            charArray[0] = BASE64_CHARS[i>>10];
453 	            charArray[1] = BASE64_CHARS[(i>>4) & 0x3f];
454 	            charArray[2] = BASE64_CHARS[(i<<2) & 0x3f];
455 	            charArray[3] = BASE64_PADDING;
456 	            builder.append(charArray);
457 	        }
458         }
459         return builder.toString();
460     }
461 
462     // For that piece of crap called IE
463     private static native int ieWidth(Element elt) /*-{
464 	  return elt.naturalWidth;
465 	}-*/;
466     private static native int ieHeight(Element elt) /*-{
467 	  return elt.naturalHeight;
468 	}-*/;
469 }