Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / ui / file_manager / file_manager / foreground / js / ui / preview_panel.js
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 /**
6  * PreviewPanel UI class.
7  * @param {Element} element DOM Element of preview panel.
8  * @param {PreviewPanel.VisibilityType} visibilityType Initial value of the
9  *     visibility type.
10  * @param {MetadataCache} metadataCache Metadata cache.
11  * @param {VolumeManagerWrapper} volumeManager Volume manager.
12  * @constructor
13  * @extends {cr.EventTarget}
14  */
15 var PreviewPanel = function(element,
16                             visibilityType,
17                             metadataCache,
18                             volumeManager) {
19   /**
20    * The cached height of preview panel.
21    * @type {number}
22    * @private
23    */
24   this.height_ = 0;
25
26   /**
27    * Visibility type of the preview panel.
28    * @type {PreviewPanel.VisibilityType}
29    * @private
30    */
31   this.visibilityType_ = visibilityType;
32
33   /**
34    * Current entry to be displayed.
35    * @type {Entry}
36    * @private
37    */
38   this.currentEntry_ = null;
39
40   /**
41    * Dom element of the preview panel.
42    * @type {Element}
43    * @private
44    */
45   this.element_ = element;
46
47   /**
48    * @type {PreviewPanel.Thumbnails}
49    */
50   this.thumbnails = new PreviewPanel.Thumbnails(
51       element.querySelector('.preview-thumbnails'),
52       metadataCache,
53       volumeManager);
54
55   /**
56    * @type {Element}
57    * @private
58    */
59   this.summaryElement_ = element.querySelector('.preview-summary');
60
61   /**
62    * @type {PreviewPanel.CalculatingSizeLabel}
63    * @private
64    */
65   this.calculatingSizeLabel_ = new PreviewPanel.CalculatingSizeLabel(
66       this.summaryElement_.querySelector('.calculating-size'));
67
68   /**
69    * @type {Element}
70    * @private
71    */
72   this.previewText_ = element.querySelector('.preview-text');
73
74   /**
75    * FileSelection to be displayed.
76    * @type {FileSelection}
77    * @private
78    */
79   this.selection_ = /** @type {FileSelection} */
80       ({entries: [], computeBytes: function() {}});
81
82   /**
83    * Sequence value that is incremented by every selection update and is used to
84    * check if the callback is up to date or not.
85    * @type {number}
86    * @private
87    */
88   this.sequence_ = 0;
89
90   /**
91    * @type {VolumeManagerWrapper}
92    * @private
93    */
94   this.volumeManager_ = volumeManager;
95
96   cr.EventTarget.call(this);
97 };
98
99 /**
100  * Name of PreviewPanels's event.
101  * @enum {string}
102  * @const
103  */
104 PreviewPanel.Event = {
105   // Event to be triggered at the end of visibility change.
106   VISIBILITY_CHANGE: 'visibilityChange'
107 };
108 Object.freeze(PreviewPanel.Event);
109
110 /**
111  * Visibility type of the preview panel.
112  * @enum {string}
113  * @const
114  */
115 PreviewPanel.VisibilityType = {
116   // Preview panel always shows.
117   ALWAYS_VISIBLE: 'alwaysVisible',
118   // Preview panel shows when the selection property are set.
119   AUTO: 'auto',
120   // Preview panel does not show.
121   ALWAYS_HIDDEN: 'alwaysHidden'
122 };
123 Object.freeze(PreviewPanel.VisibilityType);
124
125 /**
126  * @enum {string}
127  * @const
128  * @private
129  */
130 PreviewPanel.Visibility_ = {
131   VISIBLE: 'visible',
132   HIDING: 'hiding',
133   HIDDEN: 'hidden'
134 };
135 Object.freeze(PreviewPanel.Visibility_);
136
137 PreviewPanel.prototype = {
138   __proto__: cr.EventTarget.prototype,
139
140   /**
141    * Setter for the current entry.
142    * @param {Entry} entry New entry.
143    */
144   set currentEntry(entry) {
145     if (util.isSameEntry(this.currentEntry_, entry))
146       return;
147     this.currentEntry_ = entry;
148     this.updateVisibility_();
149     this.updatePreviewArea_();
150   },
151
152   /**
153    * Setter for the visibility type.
154    * @param {PreviewPanel.VisibilityType} visibilityType New value of visibility
155    *     type.
156    */
157   set visibilityType(visibilityType) {
158     this.visibilityType_ = visibilityType;
159     this.updateVisibility_();
160     // Also update the preview area contents, because the update is suppressed
161     // while the visibility is hiding or hidden.
162     this.updatePreviewArea_();
163   },
164
165   get visible() {
166     return this.element_.getAttribute('visibility') ==
167         PreviewPanel.Visibility_.VISIBLE;
168   },
169
170   /**
171    * Obtains the height of preview panel.
172    * @return {number} Height of preview panel.
173    */
174   get height() {
175     this.height_ = this.height_ || this.element_.clientHeight;
176     return this.height_;
177   }
178 };
179
180 /**
181  * Initializes the element.
182  */
183 PreviewPanel.prototype.initialize = function() {
184   this.element_.addEventListener('webkitTransitionEnd',
185                                  this.onTransitionEnd_.bind(this));
186   this.updateVisibility_();
187   // Also update the preview area contents, because the update is suppressed
188   // while the visibility is hiding or hidden.
189   this.updatePreviewArea_();
190 };
191
192 /**
193  * Apply the selection and update the view of the preview panel.
194  * @param {FileSelection} selection Selection to be applied.
195  */
196 PreviewPanel.prototype.setSelection = function(selection) {
197   this.sequence_++;
198   this.selection_ = selection;
199   this.updateVisibility_();
200   this.updatePreviewArea_();
201 };
202
203 /**
204  * Update the visibility of the preview panel.
205  * @private
206  */
207 PreviewPanel.prototype.updateVisibility_ = function() {
208   // Get the new visibility value.
209   var visibility = this.element_.getAttribute('visibility');
210   var newVisible = null;
211   switch (this.visibilityType_) {
212     case PreviewPanel.VisibilityType.ALWAYS_VISIBLE:
213       newVisible = true;
214       break;
215     case PreviewPanel.VisibilityType.AUTO:
216       newVisible = this.selection_.entries.length !== 0;
217       break;
218     case PreviewPanel.VisibilityType.ALWAYS_HIDDEN:
219       newVisible = false;
220       break;
221     default:
222       console.error('Invalid visibilityType.');
223       return;
224   }
225
226   // If the visibility has been already the new value, just return.
227   if ((visibility == PreviewPanel.Visibility_.VISIBLE && newVisible) ||
228       (visibility == PreviewPanel.Visibility_.HIDDEN && !newVisible))
229     return;
230
231   // Set the new visibility value.
232   if (newVisible) {
233     this.element_.setAttribute('visibility', PreviewPanel.Visibility_.VISIBLE);
234     cr.dispatchSimpleEvent(this, PreviewPanel.Event.VISIBILITY_CHANGE);
235   } else {
236     this.element_.setAttribute('visibility', PreviewPanel.Visibility_.HIDING);
237   }
238 };
239
240 /**
241  * Update the text in the preview panel.
242  * @private
243  */
244 PreviewPanel.prototype.updatePreviewArea_ = function() {
245   // If the preview panel is hiding, does not update the current view.
246   if (!this.visible)
247     return;
248   var selection = this.selection_;
249
250   // If no item is selected, no information is displayed on the footer.
251   if (selection.totalCount === 0) {
252     this.thumbnails.hidden = true;
253     this.calculatingSizeLabel_.hidden = true;
254     this.previewText_.textContent = '';
255     return;
256   }
257
258   // If one item is selected, show thumbnail and entry name of the item.
259   if (selection.totalCount === 1) {
260     this.thumbnails.hidden = false;
261     this.thumbnails.selection = selection;
262     this.calculatingSizeLabel_.hidden = true;
263     this.previewText_.textContent = util.getEntryLabel(
264         this.volumeManager_, selection.entries[0]);
265     return;
266   }
267
268   // Update thumbnails.
269   this.thumbnails.hidden = false;
270   this.thumbnails.selection = selection;
271
272   // Obtains the preview text.
273   var text;
274   if (selection.directoryCount == 0)
275     text = strf('MANY_FILES_SELECTED', selection.fileCount);
276   else if (selection.fileCount == 0)
277     text = strf('MANY_DIRECTORIES_SELECTED', selection.directoryCount);
278   else
279     text = strf('MANY_ENTRIES_SELECTED', selection.totalCount);
280
281   // Obtains the size of files.
282   this.calculatingSizeLabel_.hidden = selection.bytesKnown;
283   if (selection.bytesKnown && selection.showBytes)
284     text += ', ' + util.bytesToString(selection.bytes);
285
286   // Set the preview text to the element.
287   this.previewText_.textContent = text;
288
289   // Request the byte calculation if needed.
290   if (!selection.bytesKnown) {
291     this.selection_.computeBytes(function(sequence) {
292       // Selection has been already updated.
293       if (this.sequence_ != sequence)
294         return;
295       this.updatePreviewArea_();
296     }.bind(this, this.sequence_));
297   }
298 };
299
300 /**
301  * Event handler to be called at the end of hiding transition.
302  * @param {Event} event The webkitTransitionEnd event.
303  * @private
304  */
305 PreviewPanel.prototype.onTransitionEnd_ = function(event) {
306   if (event.target != this.element_ || event.propertyName != 'opacity')
307     return;
308   var visibility = this.element_.getAttribute('visibility');
309   if (visibility != PreviewPanel.Visibility_.HIDING)
310     return;
311   this.element_.setAttribute('visibility', PreviewPanel.Visibility_.HIDDEN);
312   cr.dispatchSimpleEvent(this, PreviewPanel.Event.VISIBILITY_CHANGE);
313 };
314
315 /**
316  * Animating label that is shown during the bytes of selection entries is being
317  * calculated.
318  *
319  * This label shows dots and varying the number of dots every
320  * CalculatingSizeLabel.PERIOD milliseconds.
321  * @param {Element} element DOM element of the label.
322  * @constructor
323  */
324 PreviewPanel.CalculatingSizeLabel = function(element) {
325   this.element_ = element;
326   this.count_ = 0;
327   this.intervalID_ = 0;
328   Object.seal(this);
329 };
330
331 /**
332  * Time period in milliseconds.
333  * @const {number}
334  */
335 PreviewPanel.CalculatingSizeLabel.PERIOD = 500;
336
337 PreviewPanel.CalculatingSizeLabel.prototype = {
338   /**
339    * Set visibility of the label.
340    * When it is displayed, the text is animated.
341    * @param {boolean} hidden Whether to hide the label or not.
342    */
343   set hidden(hidden) {
344     this.element_.hidden = hidden;
345     if (!hidden) {
346       if (this.intervalID_ !== 0)
347         return;
348       this.count_ = 2;
349       this.intervalID_ =
350           setInterval(this.onStep_.bind(this),
351                       PreviewPanel.CalculatingSizeLabel.PERIOD);
352       this.onStep_();
353     } else {
354       if (this.intervalID_ === 0)
355         return;
356       clearInterval(this.intervalID_);
357       this.intervalID_ = 0;
358     }
359   }
360 };
361
362 /**
363  * Increments the counter and updates the number of dots.
364  * @private
365  */
366 PreviewPanel.CalculatingSizeLabel.prototype.onStep_ = function() {
367   var text = str('CALCULATING_SIZE');
368   for (var i = 0; i < ~~(this.count_ / 2) % 4; i++) {
369     text += '.';
370   }
371   this.element_.textContent = text;
372   this.count_++;
373 };
374
375 /**
376  * Thumbnails on the preview panel.
377  *
378  * @param {Element} element DOM Element of thumbnail container.
379  * @param {MetadataCache} metadataCache MetadataCache.
380  * @param {VolumeManagerWrapper} volumeManager Volume manager instance.
381  * @constructor
382  */
383 PreviewPanel.Thumbnails = function(element, metadataCache, volumeManager) {
384   this.element_ = element;
385   this.metadataCache_ = metadataCache;
386   this.volumeManager_ = volumeManager;
387   this.sequence_ = 0;
388   Object.seal(this);
389 };
390
391 /**
392  * Maximum number of thumbnails.
393  * @const {number}
394  */
395 PreviewPanel.Thumbnails.MAX_THUMBNAIL_COUNT = 4;
396
397 /**
398  * Edge length of the thumbnail square.
399  * @const {number}
400  */
401 PreviewPanel.Thumbnails.THUMBNAIL_SIZE = 35;
402
403 /**
404  * Longer edge length of zoomed thumbnail rectangle.
405  * @const {number}
406  */
407 PreviewPanel.Thumbnails.ZOOMED_THUMBNAIL_SIZE = 200;
408
409 PreviewPanel.Thumbnails.prototype = {
410   /**
411    * Sets entries to be displayed in the view.
412    * @param {FileSelection} value Entries.
413    */
414   set selection(value) {
415     this.sequence_++;
416     this.loadThumbnails_(value);
417   },
418
419   /**
420    * Set visibility of the thumbnails.
421    * @param {boolean} value Whether to hide the thumbnails or not.
422    */
423   set hidden(value) {
424     this.element_.hidden = value;
425   }
426 };
427
428 /**
429  * Loads thumbnail images.
430  * @param {FileSelection} selection Selection containing entries that are
431  *     sources of images.
432  * @private
433  */
434 PreviewPanel.Thumbnails.prototype.loadThumbnails_ = function(selection) {
435   var entries = selection.entries;
436   this.element_.classList.remove('has-zoom');
437   this.element_.innerText = '';
438   var clickHandler = selection.tasks &&
439       selection.tasks.executeDefault.bind(selection.tasks);
440   var length = Math.min(entries.length,
441                         PreviewPanel.Thumbnails.MAX_THUMBNAIL_COUNT);
442   for (var i = 0; i < length; i++) {
443     // Create a box.
444     var box = this.element_.ownerDocument.createElement('div');
445     box.style.zIndex = PreviewPanel.Thumbnails.MAX_THUMBNAIL_COUNT + 1 - i;
446
447     // Load the image.
448     if (entries[i]) {
449       FileGrid.decorateThumbnailBox(
450           box,
451           entries[i],
452           this.metadataCache_,
453           this.volumeManager_,
454           ThumbnailLoader.FillMode.FILL,
455           FileGrid.ThumbnailQuality.LOW,
456           i == 0 && length == 1 ? this.setZoomedImage_.bind(this) : undefined);
457     }
458
459     // Register the click handler.
460     if (clickHandler)
461       box.addEventListener('click', clickHandler);
462
463     // Append
464     this.element_.appendChild(box);
465   }
466 };
467
468 /**
469  * Create the zoomed version of image and set it to the DOM element to show the
470  * zoomed image.
471  *
472  * @param {HTMLImageElement} image Image to be source of the zoomed image.
473  * @param {Object=} opt_transform Transformation to be applied to the image.
474  * @private
475  */
476 PreviewPanel.Thumbnails.prototype.setZoomedImage_ = function(image,
477                                                              opt_transform) {
478   if (!image)
479     return;
480   var width = image.width || 0;
481   var height = image.height || 0;
482   if (width == 0 ||
483       height == 0 ||
484       (width < PreviewPanel.Thumbnails.THUMBNAIL_SIZE * 2 &&
485        height < PreviewPanel.Thumbnails.THUMBNAIL_SIZE * 2))
486     return;
487
488   var scale = Math.min(1,
489                        PreviewPanel.Thumbnails.ZOOMED_THUMBNAIL_SIZE /
490                            Math.max(width, height));
491   var imageWidth = ~~(width * scale);
492   var imageHeight = ~~(height * scale);
493   var zoomedImage = this.element_.ownerDocument.createElement('img');
494
495   if (scale < 0.3) {
496     // Scaling large images kills animation. Downscale it in advance.
497     // Canvas scales images with liner interpolation. Make a larger
498     // image (but small enough to not kill animation) and let IMAGE
499     // scale it smoothly.
500     var INTERMEDIATE_SCALE = 3;
501     var canvas = this.element_.ownerDocument.createElement('canvas');
502     canvas.width = imageWidth * INTERMEDIATE_SCALE;
503     canvas.height = imageHeight * INTERMEDIATE_SCALE;
504     var ctx = canvas.getContext('2d');
505     ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
506     // Using bigger than default compression reduces image size by
507     // several times. Quality degradation compensated by greater resolution.
508     zoomedImage.src = canvas.toDataURL('image/jpeg', 0.6);
509   } else {
510     zoomedImage.src = image.src;
511   }
512
513   var boxWidth = Math.max(PreviewPanel.Thumbnails.THUMBNAIL_SIZE, imageWidth);
514   var boxHeight = Math.max(PreviewPanel.Thumbnails.THUMBNAIL_SIZE, imageHeight);
515   if (opt_transform && opt_transform.rotate90 % 2 == 1) {
516     var t = boxWidth;
517     boxWidth = boxHeight;
518     boxHeight = t;
519   }
520
521   if (opt_transform)
522     util.applyTransform(zoomedImage, opt_transform);
523
524   var zoomedBox = this.element_.ownerDocument.createElement('div');
525   zoomedBox.className = 'popup';
526   zoomedBox.style.width = boxWidth + 'px';
527   zoomedBox.style.height = boxHeight + 'px';
528   zoomedBox.appendChild(zoomedImage);
529
530   this.element_.appendChild(zoomedBox);
531   this.element_.classList.add('has-zoom');
532   return;
533 };