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