Upstream version 7.35.144.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / file_manager / foreground / js / file_selection.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  * The current selection object.
9  *
10  * @param {FileManager} fileManager FileManager instance.
11  * @param {Array.<number>} indexes Selected indexes.
12  * @constructor
13  */
14 function FileSelection(fileManager, indexes) {
15   this.fileManager_ = fileManager;
16   this.computeBytesSequence_ = 0;
17   this.indexes = indexes;
18   this.entries = [];
19   this.totalCount = 0;
20   this.fileCount = 0;
21   this.directoryCount = 0;
22   this.bytes = 0;
23   this.showBytes = false;
24   this.allDriveFilesPresent = false,
25   this.iconType = null;
26   this.bytesKnown = false;
27   this.mustBeHidden_ = false;
28   this.mimeTypes = null;
29
30   // Synchronously compute what we can.
31   for (var i = 0; i < this.indexes.length; i++) {
32     var entry = fileManager.getFileList().item(this.indexes[i]);
33     if (!entry)
34       continue;
35
36     this.entries.push(entry);
37
38     if (this.iconType == null) {
39       this.iconType = FileType.getIcon(entry);
40     } else if (this.iconType != 'unknown') {
41       var iconType = FileType.getIcon(entry);
42       if (this.iconType != iconType)
43         this.iconType = 'unknown';
44     }
45
46     if (entry.isFile) {
47       this.fileCount += 1;
48     } else {
49       this.directoryCount += 1;
50     }
51     this.totalCount++;
52   }
53
54   this.tasks = new FileTasks(this.fileManager_);
55
56   Object.seal(this);
57 }
58
59 /**
60  * Computes data required to get file tasks and requests the tasks.
61  *
62  * @param {function} callback The callback.
63  */
64 FileSelection.prototype.createTasks = function(callback) {
65   if (!this.fileManager_.isOnDrive()) {
66     this.tasks.init(this.entries);
67     callback();
68     return;
69   }
70
71   this.fileManager_.metadataCache_.get(this.entries, 'drive', function(props) {
72     var present = props.filter(function(p) { return p && p.availableOffline });
73     this.allDriveFilesPresent = present.length == props.length;
74
75     // Collect all of the mime types and push that info into the selection.
76     this.mimeTypes = props.map(function(value) {
77       return (value && value.contentMimeType) || '';
78     });
79
80     this.tasks.init(this.entries, this.mimeTypes);
81     callback();
82   }.bind(this));
83 };
84
85 /**
86  * Computes the total size of selected files.
87  *
88  * @param {function} callback Completion callback. Not called when cancelled,
89  *     or a new call has been invoked in the meantime.
90  */
91 FileSelection.prototype.computeBytes = function(callback) {
92   if (this.entries.length == 0) {
93     this.bytesKnown = true;
94     this.showBytes = false;
95     this.bytes = 0;
96     return;
97   }
98
99   var computeBytesSequence = ++this.computeBytesSequence_;
100   var pendingMetadataCount = 0;
101
102   var maybeDone = function() {
103     if (pendingMetadataCount == 0) {
104       this.bytesKnown = true;
105       callback();
106     }
107   }.bind(this);
108
109   var onProps = function(properties) {
110     // Ignore if the call got cancelled, or there is another new one fired.
111     if (computeBytesSequence != this.computeBytesSequence_)
112       return;
113
114     // It may happen that the metadata is not available because a file has been
115     // deleted in the meantime.
116     if (properties)
117       this.bytes += properties.size;
118     pendingMetadataCount--;
119     maybeDone();
120   }.bind(this);
121
122   for (var index = 0; index < this.entries.length; index++) {
123     var entry = this.entries[index];
124     if (entry.isFile) {
125       this.showBytes |= !FileType.isHosted(entry);
126       pendingMetadataCount++;
127       this.fileManager_.metadataCache_.get(entry, 'filesystem', onProps);
128     } else if (entry.isDirectory) {
129       // Don't compute the directory size as it's expensive.
130       // crbug.com/179073.
131       this.showBytes = false;
132       break;
133     }
134   }
135   maybeDone();
136 };
137
138 /**
139  * Cancels any async computation by increasing the sequence number. Results
140  * of any previous call to computeBytes() will be discarded.
141  *
142  * @private
143  */
144 FileSelection.prototype.cancelComputing_ = function() {
145   this.computeBytesSequence_++;
146 };
147
148 /**
149  * This object encapsulates everything related to current selection.
150  *
151  * @param {FileManager} fileManager File manager instance.
152  * @extends {cr.EventTarget}
153  * @constructor
154  */
155 function FileSelectionHandler(fileManager) {
156   this.fileManager_ = fileManager;
157   // TODO(dgozman): create a shared object with most of UI elements.
158   this.okButton_ = fileManager.okButton_;
159   this.filenameInput_ = fileManager.filenameInput_;
160   this.previewPanel_ = fileManager.previewPanel_;
161   this.taskItems_ = fileManager.taskItems_;
162 }
163
164 /**
165  * Create the temporary disabled action menu item.
166  * @return {Object} Created disabled item.
167  * @private
168  */
169 FileSelectionHandler.createTemporaryDisabledActionMenuItem_ = function() {
170   if (!FileSelectionHandler.cachedDisabledActionMenuItem_) {
171     FileSelectionHandler.cachedDisabledActionMenuItem_ = {
172       label: str('ACTION_OPEN'),
173       disabled: true
174     };
175   }
176
177   return FileSelectionHandler.cachedDisabledActionMenuItem_;
178 };
179
180 /**
181  * Cached the temporary disabled action menu item. Used inside
182  * FileSelectionHandler.createTemporaryDisabledActionMenuItem_().
183  * @private
184  */
185 FileSelectionHandler.cachedDisabledActionMenuItem_ = null;
186
187 /**
188  * FileSelectionHandler extends cr.EventTarget.
189  */
190 FileSelectionHandler.prototype.__proto__ = cr.EventTarget.prototype;
191
192 /**
193  * Maximum amount of thumbnails in the preview pane.
194  *
195  * @const
196  * @type {number}
197  */
198 FileSelectionHandler.MAX_PREVIEW_THUMBNAIL_COUNT = 4;
199
200 /**
201  * Maximum width or height of an image what pops up when the mouse hovers
202  * thumbnail in the bottom panel (in pixels).
203  *
204  * @const
205  * @type {number}
206  */
207 FileSelectionHandler.IMAGE_HOVER_PREVIEW_SIZE = 200;
208
209 /**
210  * Update the UI when the selection model changes.
211  *
212  * @param {Event} event The change event.
213  */
214 FileSelectionHandler.prototype.onFileSelectionChanged = function(event) {
215   var indexes =
216       this.fileManager_.getCurrentList().selectionModel.selectedIndexes;
217   if (this.selection) this.selection.cancelComputing_();
218   var selection = new FileSelection(this.fileManager_, indexes);
219   this.selection = selection;
220
221   if (this.fileManager_.dialogType == DialogType.SELECT_SAVEAS_FILE) {
222     // If this is a save-as dialog, copy the selected file into the filename
223     // input text box.
224     if (this.selection.totalCount == 1 &&
225         this.selection.entries[0].isFile &&
226         this.filenameInput_.value != this.selection.entries[0].name) {
227       this.filenameInput_.value = this.selection.entries[0].name;
228     }
229   }
230
231   this.updateOkButton();
232
233   if (this.selectionUpdateTimer_) {
234     clearTimeout(this.selectionUpdateTimer_);
235     this.selectionUpdateTimer_ = null;
236   }
237
238   // The rest of the selection properties are computed via (sometimes lengthy)
239   // asynchronous calls. We initiate these calls after a timeout. If the
240   // selection is changing quickly we only do this once when it slows down.
241
242   var updateDelay = 200;
243   var now = Date.now();
244   if (now > (this.lastFileSelectionTime_ || 0) + updateDelay) {
245     // The previous selection change happened a while ago. Update the UI soon.
246     updateDelay = 0;
247   }
248   this.lastFileSelectionTime_ = now;
249
250   if (this.fileManager_.dialogType === DialogType.FULL_PAGE &&
251       selection.directoryCount === 0 && selection.fileCount > 0) {
252     // Show disabled items for position calculation of the menu. They will be
253     // overridden in this.updateFileSelectionAsync().
254     this.fileManager_.updateContextMenuActionItems(
255         FileSelectionHandler.createTemporaryDisabledActionMenuItem_(), true);
256   } else {
257     // Update context menu.
258     this.fileManager_.updateContextMenuActionItems(null, false);
259   }
260
261   this.selectionUpdateTimer_ = setTimeout(function() {
262     this.selectionUpdateTimer_ = null;
263     if (this.selection == selection)
264       this.updateFileSelectionAsync(selection);
265   }.bind(this), updateDelay);
266 };
267
268 /**
269  * Updates the Ok button enabled state.
270  *
271  * @return {boolean} Whether button is enabled.
272  */
273 FileSelectionHandler.prototype.updateOkButton = function() {
274   var selectable;
275   var dialogType = this.fileManager_.dialogType;
276
277   if (DialogType.isFolderDialog(dialogType)) {
278     // In SELECT_FOLDER mode, we allow to select current directory
279     // when nothing is selected.
280     selectable = this.selection.directoryCount <= 1 &&
281         this.selection.fileCount == 0;
282   } else if (dialogType == DialogType.SELECT_OPEN_FILE) {
283     selectable = (this.isFileSelectionAvailable() &&
284                   this.selection.directoryCount == 0 &&
285                   this.selection.fileCount == 1);
286   } else if (dialogType == DialogType.SELECT_OPEN_MULTI_FILE) {
287     selectable = (this.isFileSelectionAvailable() &&
288                   this.selection.directoryCount == 0 &&
289                   this.selection.fileCount >= 1);
290   } else if (dialogType == DialogType.SELECT_SAVEAS_FILE) {
291     if (this.fileManager_.isOnReadonlyDirectory()) {
292       selectable = false;
293     } else {
294       selectable = !!this.filenameInput_.value;
295     }
296   } else if (dialogType == DialogType.FULL_PAGE) {
297     // No "select" buttons on the full page UI.
298     selectable = true;
299   } else {
300     throw new Error('Unknown dialog type');
301   }
302
303   this.okButton_.disabled = !selectable;
304   return selectable;
305 };
306
307 /**
308   * Check if all the files in the current selection are available. The only
309   * case when files might be not available is when the selection contains
310   * uncached Drive files and the browser is offline.
311   *
312   * @return {boolean} True if all files in the current selection are
313   *                   available.
314   */
315 FileSelectionHandler.prototype.isFileSelectionAvailable = function() {
316   var isDriveOffline =
317       this.fileManager_.volumeManager.getDriveConnectionState().type ===
318           util.DriveConnectionType.OFFLINE;
319   return !this.fileManager_.isOnDrive() || !isDriveOffline ||
320       this.selection.allDriveFilesPresent;
321 };
322
323 /**
324  * Calculates async selection stats and updates secondary UI elements.
325  *
326  * @param {FileSelection} selection The selection object.
327  */
328 FileSelectionHandler.prototype.updateFileSelectionAsync = function(selection) {
329   if (this.selection != selection) return;
330
331   // Update the file tasks.
332   if (this.fileManager_.dialogType === DialogType.FULL_PAGE &&
333       selection.directoryCount === 0 && selection.fileCount > 0) {
334     selection.createTasks(function() {
335       if (this.selection != selection)
336         return;
337       selection.tasks.display(this.taskItems_);
338       selection.tasks.updateMenuItem();
339     }.bind(this));
340   } else {
341     this.taskItems_.hidden = true;
342   }
343
344   // Update preview panels.
345   var wasVisible = this.previewPanel_.visible;
346   this.previewPanel_.setSelection(selection);
347
348   // Scroll to item
349   if (!wasVisible && this.selection.totalCount == 1) {
350     var list = this.fileManager_.getCurrentList();
351     list.scrollIndexIntoView(list.selectionModel.selectedIndex);
352   }
353
354   // Sync the commands availability.
355   if (this.fileManager_.commandHandler)
356     this.fileManager_.commandHandler.updateAvailability();
357
358   // Inform tests it's OK to click buttons now.
359   if (selection.totalCount > 0) {
360     util.testSendMessage('selection-change-complete');
361   }
362 };