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
5cr.define('wallpapers', function() {
6  /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
7  /** @const */ var Grid = cr.ui.Grid;
8  /** @const */ var GridItem = cr.ui.GridItem;
9  /** @const */ var GridSelectionController = cr.ui.GridSelectionController;
10  /** @const */ var ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
11  /** @const */ var ThumbnailSuffix = '_thumbnail.png';
12
13  /**
14   * Creates a new wallpaper thumbnails grid item.
15   * @param {{baseURL: string, layout: string, source: string,
16   *          availableOffline: boolean, opt_dynamicURL: string,
17   *          opt_author: string, opt_authorWebsite: string}}
18   *     wallpaperInfo Wallpaper data item in WallpaperThumbnailsGrid's data
19   *     model.
20   * @constructor
21   * @extends {cr.ui.GridItem}
22   */
23  function WallpaperThumbnailsGridItem(wallpaperInfo) {
24    var el = new GridItem(wallpaperInfo);
25    el.__proto__ = WallpaperThumbnailsGridItem.prototype;
26    return el;
27  }
28
29  WallpaperThumbnailsGridItem.prototype = {
30    __proto__: GridItem.prototype,
31
32    /** @override */
33    decorate: function() {
34      GridItem.prototype.decorate.call(this);
35      // Removes garbage created by GridItem.
36      this.innerText = '';
37      var imageEl = cr.doc.createElement('img');
38      imageEl.classList.add('thumbnail');
39      cr.defineProperty(imageEl, 'offline', cr.PropertyKind.BOOL_ATTR);
40      imageEl.offline = this.dataItem.availableOffline;
41      this.appendChild(imageEl);
42      var self = this;
43
44      switch (this.dataItem.source) {
45        case Constants.WallpaperSourceEnum.AddNew:
46          this.id = 'add-new';
47          this.addEventListener('click', function(e) {
48            $('wallpaper-selection-container').hidden = false;
49          });
50          break;
51        case Constants.WallpaperSourceEnum.Custom:
52          var errorHandler = function(e) {
53            console.error('Can not access file system.');
54          };
55          var wallpaperDirectories = WallpaperDirectories.getInstance();
56          var getThumbnail = function(fileName) {
57            var setURL = function(fileEntry) {
58              imageEl.src = fileEntry.toURL();
59            };
60            var fallback = function() {
61              wallpaperDirectories.getDirectory(WallpaperDirNameEnum.ORIGINAL,
62                                          function(dirEntry) {
63                dirEntry.getFile(fileName, {create: false}, setURL,
64                                 errorHandler);
65              }, errorHandler);
66            };
67            var success = function(dirEntry) {
68              dirEntry.getFile(fileName, {create: false}, setURL, fallback);
69            };
70            wallpaperDirectories.getDirectory(WallpaperDirNameEnum.THUMBNAIL,
71                                              success,
72                                              errorHandler);
73          }
74          getThumbnail(self.dataItem.baseURL);
75          break;
76        case Constants.WallpaperSourceEnum.OEM:
77        case Constants.WallpaperSourceEnum.Online:
78          chrome.wallpaperPrivate.getThumbnail(this.dataItem.baseURL,
79                                               this.dataItem.source,
80                                               function(data) {
81            if (data) {
82              var blob = new Blob([new Int8Array(data)],
83                                  {'type': 'image\/png'});
84              imageEl.src = window.URL.createObjectURL(blob);
85              imageEl.addEventListener('load', function(e) {
86                window.URL.revokeObjectURL(this.src);
87              });
88            } else if (self.dataItem.source ==
89                       Constants.WallpaperSourceEnum.Online) {
90              var xhr = new XMLHttpRequest();
91              xhr.open('GET', self.dataItem.baseURL + ThumbnailSuffix, true);
92              xhr.responseType = 'arraybuffer';
93              xhr.send(null);
94              xhr.addEventListener('load', function(e) {
95                if (xhr.status === 200) {
96                  chrome.wallpaperPrivate.saveThumbnail(self.dataItem.baseURL,
97                                                        xhr.response);
98                  var blob = new Blob([new Int8Array(xhr.response)],
99                                      {'type' : 'image\/png'});
100                  imageEl.src = window.URL.createObjectURL(blob);
101                  // TODO(bshe): We currently use empty div to reserve space for
102                  // thumbnail. Use a placeholder like "loading" image may
103                  // better.
104                  imageEl.addEventListener('load', function(e) {
105                    window.URL.revokeObjectURL(this.src);
106                  });
107                }
108              });
109            }
110          });
111          break;
112        default:
113          console.error('Unsupported image source.');
114      }
115    },
116  };
117
118  /**
119   * Creates a selection controller that wraps selection on grid ends
120   * and translates Enter presses into 'activate' events.
121   * @param {cr.ui.ListSelectionModel} selectionModel The selection model to
122   *     interact with.
123   * @param {cr.ui.Grid} grid The grid to interact with.
124   * @constructor
125   * @extends {cr.ui.GridSelectionController}
126   */
127  function WallpaperThumbnailsGridSelectionController(selectionModel, grid) {
128    GridSelectionController.call(this, selectionModel, grid);
129  }
130
131  WallpaperThumbnailsGridSelectionController.prototype = {
132    __proto__: GridSelectionController.prototype,
133
134    /** @override */
135    getIndexBefore: function(index) {
136      var result =
137          GridSelectionController.prototype.getIndexBefore.call(this, index);
138      return result == -1 ? this.getLastIndex() : result;
139    },
140
141    /** @override */
142    getIndexAfter: function(index) {
143      var result =
144          GridSelectionController.prototype.getIndexAfter.call(this, index);
145      return result == -1 ? this.getFirstIndex() : result;
146    },
147
148    /** @override */
149    handleKeyDown: function(e) {
150      if (e.keyIdentifier == 'Enter')
151        cr.dispatchSimpleEvent(this.grid_, 'activate');
152      else
153        GridSelectionController.prototype.handleKeyDown.call(this, e);
154    },
155  };
156
157  /**
158   * Creates a new user images grid element.
159   * @param {Object=} opt_propertyBag Optional properties.
160   * @constructor
161   * @extends {cr.ui.Grid}
162   */
163  var WallpaperThumbnailsGrid = cr.ui.define('grid');
164
165  WallpaperThumbnailsGrid.prototype = {
166    __proto__: Grid.prototype,
167
168    /**
169     * The checkbox element.
170     */
171    checkmark_: undefined,
172
173    /**
174     * The item in data model which should have a checkmark.
175     * @type {{baseURL: string, dynamicURL: string, layout: string,
176     *         author: string, authorWebsite: string,
177     *         availableOffline: boolean}}
178     *     wallpaperInfo The information of the wallpaper to be set active.
179     */
180    activeItem_: undefined,
181    set activeItem(activeItem) {
182      if (this.activeItem_ != activeItem) {
183        this.activeItem_ = activeItem;
184        this.updateActiveThumb_();
185      }
186    },
187
188    /** @override */
189    createSelectionController: function(sm) {
190      return new WallpaperThumbnailsGridSelectionController(sm, this);
191    },
192
193    /** @override */
194    decorate: function() {
195      Grid.prototype.decorate.call(this);
196      // checkmark_ needs to be initialized before set data model. Otherwise, we
197      // may try to access checkmark before initialization in
198      // updateActiveThumb_().
199      this.checkmark_ = cr.doc.createElement('div');
200      this.checkmark_.classList.add('check');
201      this.dataModel = new ArrayDataModel([]);
202      this.itemConstructor = WallpaperThumbnailsGridItem;
203      this.selectionModel = new ListSingleSelectionModel();
204      this.inProgramSelection_ = false;
205    },
206
207    /**
208     * Should only be queried from the 'change' event listener, true if the
209     * change event was triggered by a programmatical selection change.
210     * @type {boolean}
211     */
212    get inProgramSelection() {
213      return this.inProgramSelection_;
214    },
215
216    /**
217     * Set index to the image selected.
218     * @type {number} index The index of selected image.
219     */
220    set selectedItemIndex(index) {
221      this.inProgramSelection_ = true;
222      this.selectionModel.selectedIndex = index;
223      this.inProgramSelection_ = false;
224    },
225
226    /**
227     * The selected item.
228     * @type {!Object} Wallpaper information inserted into the data model.
229     */
230    get selectedItem() {
231      var index = this.selectionModel.selectedIndex;
232      return index != -1 ? this.dataModel.item(index) : null;
233    },
234    set selectedItem(selectedItem) {
235      var index = this.dataModel.indexOf(selectedItem);
236      this.inProgramSelection_ = true;
237      this.selectionModel.leadIndex = index;
238      this.selectionModel.selectedIndex = index;
239      this.inProgramSelection_ = false;
240    },
241
242    /**
243     * Forces re-display, size re-calculation and focuses grid.
244     */
245    updateAndFocus: function() {
246      // Recalculate the measured item size.
247      this.measured_ = null;
248      this.columns = 0;
249      this.redraw();
250      this.focus();
251    },
252
253    /**
254     * Shows a checkmark on the active thumbnail and clears previous active one
255     * if any. Note if wallpaper was not set successfully, checkmark should not
256     * show on that thumbnail.
257     */
258    updateActiveThumb_: function() {
259      var selectedGridItem = this.getListItem(this.activeItem_);
260      if (this.checkmark_.parentNode &&
261          this.checkmark_.parentNode == selectedGridItem) {
262        return;
263      }
264
265      // Clears previous checkmark.
266      if (this.checkmark_.parentNode)
267        this.checkmark_.parentNode.removeChild(this.checkmark_);
268
269      if (!selectedGridItem)
270        return;
271      selectedGridItem.appendChild(this.checkmark_);
272    },
273
274    /**
275     * Redraws the viewport.
276     */
277    redraw: function() {
278      Grid.prototype.redraw.call(this);
279      // The active thumbnail maybe deleted in the above redraw(). Sets it again
280      // to make sure checkmark shows correctly.
281      this.updateActiveThumb_();
282    }
283  };
284
285  return {
286    WallpaperThumbnailsGrid: WallpaperThumbnailsGrid
287  };
288});
289