- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / file_manager / foreground / js / file_grid.js
1 // Copyright (c) 2012 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 'use strict';
6
7 /**
8  * FileGrid constructor.
9  *
10  * Represents grid for the Grid Vew in the File Manager.
11  * @constructor
12  * @extends {cr.ui.Grid}
13  */
14
15 function FileGrid() {
16   throw new Error('Use FileGrid.decorate');
17 }
18
19 /**
20  * Thumbnail quality.
21  * @enum {number}
22  */
23 FileGrid.ThumbnailQuality = {
24   LOW: 0,
25   HIGH: 1
26 };
27
28 /**
29  * Inherits from cr.ui.Grid.
30  */
31 FileGrid.prototype.__proto__ = cr.ui.Grid.prototype;
32
33 /**
34  * Decorates an HTML element to be a FileGrid.
35  * @param {HTMLElement} self The grid to decorate.
36  * @param {MetadataCache} metadataCache Metadata cache to find entries
37  *                                      metadata.
38  */
39 FileGrid.decorate = function(self, metadataCache) {
40   cr.ui.Grid.decorate(self);
41   self.__proto__ = FileGrid.prototype;
42   self.metadataCache_ = metadataCache;
43
44   self.scrollBar_ = new MainPanelScrollBar();
45   self.scrollBar_.initialize(self.parentNode, self);
46   self.setBottomMarginForPanel(0);
47
48   self.itemConstructor = function(entry) {
49     var item = self.ownerDocument.createElement('LI');
50     FileGrid.Item.decorate(item, entry, self);
51     return item;
52   };
53
54   self.relayoutAggregation_ =
55       new AsyncUtil.Aggregation(self.relayoutImmediately_.bind(self));
56 };
57
58 /**
59  * Updates items to reflect metadata changes.
60  * @param {string} type Type of metadata changed.
61  * @param {Object.<string, Object>} props Map from entry URLs to metadata props.
62  */
63 FileGrid.prototype.updateListItemsMetadata = function(type, props) {
64   var boxes = this.querySelectorAll('.img-container');
65   for (var i = 0; i < boxes.length; i++) {
66     var box = boxes[i];
67     var entry = this.dataModel.item(this.getListItemAncestor(box));
68     if (!entry || !(entry.toURL() in props))
69       continue;
70
71     FileGrid.decorateThumbnailBox(box,
72                                   entry,
73                                   this.metadataCache_,
74                                   ThumbnailLoader.FillMode.FIT,
75                                   FileGrid.ThumbnailQuality.HIGH);
76   }
77 };
78
79 /**
80  * Redraws the UI. Skips multiple consecutive calls.
81  */
82 FileGrid.prototype.relayout = function() {
83   this.relayoutAggregation_.run();
84 };
85
86 /**
87  * Redraws the UI immediately.
88  * @private
89  */
90 FileGrid.prototype.relayoutImmediately_ = function() {
91   this.startBatchUpdates();
92   this.columns = 0;
93   this.redraw();
94   this.endBatchUpdates();
95   cr.dispatchSimpleEvent(this, 'relayout');
96 };
97
98 /**
99  * Decorates thumbnail.
100  * @param {HTMLElement} li List item.
101  * @param {Entry} entry Entry to render a thumbnail for.
102  * @param {MetadataCache} metadataCache To retrieve metadata.
103  */
104 FileGrid.decorateThumbnail = function(li, entry, metadataCache) {
105   li.className = 'thumbnail-item';
106   if (entry)
107     filelist.decorateListItem(li, entry, metadataCache);
108
109   var frame = li.ownerDocument.createElement('div');
110   frame.className = 'thumbnail-frame';
111   li.appendChild(frame);
112
113   var box = li.ownerDocument.createElement('div');
114   if (entry) {
115     FileGrid.decorateThumbnailBox(box,
116                                   entry,
117                                   metadataCache,
118                                   ThumbnailLoader.FillMode.AUTO,
119                                   FileGrid.ThumbnailQuality.HIGH);
120   }
121   frame.appendChild(box);
122
123   var bottom = li.ownerDocument.createElement('div');
124   bottom.className = 'thumbnail-bottom';
125   bottom.appendChild(filelist.renderFileNameLabel(li.ownerDocument, entry));
126   frame.appendChild(bottom);
127 };
128
129 /**
130  * Decorates the box containing a centered thumbnail image.
131  *
132  * @param {HTMLDivElement} box Box to decorate.
133  * @param {Entry} entry Entry which thumbnail is generating for.
134  * @param {MetadataCache} metadataCache To retrieve metadata.
135  * @param {ThumbnailLoader.FillMode} fillMode Fill mode.
136  * @param {FileGrid.ThumbnailQuality} quality Thumbnail quality.
137  * @param {function(HTMLElement)=} opt_imageLoadCallback Callback called when
138  *     the image has been loaded before inserting it into the DOM.
139  */
140 FileGrid.decorateThumbnailBox = function(
141     box, entry, metadataCache, fillMode, quality, opt_imageLoadCallback) {
142   box.className = 'img-container';
143   if (entry.isDirectory) {
144     box.setAttribute('generic-thumbnail', 'folder');
145     if (opt_imageLoadCallback)
146       setTimeout(opt_imageLoadCallback, 0, null /* callback parameter */);
147     return;
148   }
149
150   var imageUrl = entry.toURL();
151   var metadataTypes = 'thumbnail|filesystem';
152
153   if (FileType.isOnDrive(imageUrl)) {
154     metadataTypes += '|drive';
155   } else {
156     // TODO(dgozman): If we ask for 'media' for a Drive file we fall into an
157     // infinite loop.
158     metadataTypes += '|media';
159   }
160
161   // Drive provides high quality thumbnails via USE_EMBEDDED, however local
162   // images usually provide very tiny thumbnails, therefore USE_EMBEDDE can't
163   // be used to obtain high quality output.
164   var useEmbedded;
165   switch (quality) {
166     case FileGrid.ThumbnailQuality.LOW:
167       useEmbedded = ThumbnailLoader.UseEmbedded.USE_EMBEDDED;
168       break;
169     case FileGrid.ThumbnailQuality.HIGH:
170       useEmbedded = FileType.isOnDrive(imageUrl) ?
171           ThumbnailLoader.UseEmbedded.USE_EMBEDDED :
172           ThumbnailLoader.UseEmbedded.NO_EMBEDDED;
173       break;
174   }
175
176   metadataCache.get(imageUrl, metadataTypes,
177       function(metadata) {
178         new ThumbnailLoader(imageUrl,
179                             ThumbnailLoader.LoaderType.IMAGE,
180                             metadata,
181                             undefined,  // opt_mediaType
182                             useEmbedded).
183             load(box,
184                 fillMode,
185                 ThumbnailLoader.OptimizationMode.DISCARD_DETACHED,
186                 opt_imageLoadCallback);
187       });
188 };
189
190 /**
191  * Item for the Grid View.
192  * @constructor
193  */
194 FileGrid.Item = function() {
195   throw new Error();
196 };
197
198 /**
199  * Inherits from cr.ui.ListItem.
200  */
201 FileGrid.Item.prototype.__proto__ = cr.ui.ListItem.prototype;
202
203 Object.defineProperty(FileGrid.Item.prototype, 'label', {
204   /**
205    * @this {FileGrid.Item}
206    * @return {string} Label of the item.
207    */
208   get: function() {
209     return this.querySelector('filename-label').textContent;
210   }
211 });
212
213 /**
214  * @param {Element} li List item element.
215  * @param {Entry} entry File entry.
216  * @param {FileGrid} grid Owner.
217  */
218 FileGrid.Item.decorate = function(li, entry, grid) {
219   li.__proto__ = FileGrid.Item.prototype;
220   FileGrid.decorateThumbnail(li, entry, grid.metadataCache_, true);
221
222   // Override the default role 'listitem' to 'option' to match the parent's
223   // role (listbox).
224   li.setAttribute('role', 'option');
225 };
226
227 /**
228  * Sets the margin height for the transparent preview panel at the bottom.
229  * @param {number} margin Margin to be set in px.
230  */
231 FileGrid.prototype.setBottomMarginForPanel = function(margin) {
232   // +20 bottom margin is needed to match the bottom margin size with the
233   // margin between its items.
234   this.style.paddingBottom = (margin + 20) + 'px';
235   this.scrollBar_.setBottomMarginForPanel(margin);
236 };
237
238 /**
239  * Obtains if the drag selection should be start or not by referring the mouse
240  * event.
241  * @param {MouseEvent} event Drag start event.
242  * @return {boolean} True if the mouse is hit to the background of the list.
243  */
244 FileGrid.prototype.shouldStartDragSelection = function(event) {
245   var pos = DragSelector.getScrolledPosition(this, event);
246   return this.getHitElements(pos.x, pos.y).length == 0;
247 };
248
249 /**
250  * Obtains the column/row index that the coordinate points.
251  * @param {number} coordinate Vertical/horizontal coodinate value that points
252  *     column/row.
253  * @param {number} step Length from a column/row to the next one.
254  * @param {number} threshold Threshold that determinds whether 1 offset is added
255  *     to the return value or not. This is used in order to handle the margin of
256  *     column/row.
257  * @return {number} Index of hit column/row.
258  * @private
259  */
260 FileGrid.prototype.getHitIndex_ = function(coordinate, step, threshold) {
261   var index = ~~(coordinate / step);
262   return (coordinate % step >= threshold) ? index + 1 : index;
263 };
264
265 /**
266  * Obtains the index list of elements that are hit by the point or the
267  * rectangle.
268  *
269  * We should match its argument interface with FileList.getHitElements.
270  *
271  * @param {number} x X coordinate value.
272  * @param {number} y Y coordinate value.
273  * @param {=number} opt_width Width of the coordinate.
274  * @param {=number} opt_height Height of the coordinate.
275  * @return {Array.<number>} Index list of hit elements.
276  */
277 FileGrid.prototype.getHitElements = function(x, y, opt_width, opt_height) {
278   var currentSelection = [];
279   var right = x + (opt_width || 0);
280   var bottom = y + (opt_height || 0);
281   var itemMetrics = this.measureItem();
282   var horizontalStartIndex = this.getHitIndex_(
283       x, itemMetrics.width, itemMetrics.width - itemMetrics.marginRight);
284   var horizontalEndIndex = Math.min(this.columns, this.getHitIndex_(
285       right, itemMetrics.width, itemMetrics.marginLeft));
286   var verticalStartIndex = this.getHitIndex_(
287       y, itemMetrics.height, itemMetrics.height - itemMetrics.bottom);
288   var verticalEndIndex = this.getHitIndex_(
289       bottom, itemMetrics.height, itemMetrics.marginTop);
290   for (var verticalIndex = verticalStartIndex;
291        verticalIndex < verticalEndIndex;
292        verticalIndex++) {
293     var indexBase = this.getFirstItemInRow(verticalIndex);
294     for (var horizontalIndex = horizontalStartIndex;
295          horizontalIndex < horizontalEndIndex;
296          horizontalIndex++) {
297       var index = indexBase + horizontalIndex;
298       if (0 <= index && index < this.dataModel.length)
299         currentSelection.push(index);
300     }
301   }
302   return currentSelection;
303 };