Index: chrome/browser/resources/file_manager/js/drag_selector.js |
diff --git a/chrome/browser/resources/file_manager/js/drag_selector.js b/chrome/browser/resources/file_manager/js/drag_selector.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..3895483ff2e7d7b5c99b82de4758c02dbcd86382 |
--- /dev/null |
+++ b/chrome/browser/resources/file_manager/js/drag_selector.js |
@@ -0,0 +1,223 @@ |
+// Copyright (c) 2013 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+'use strict'; |
+ |
+/** |
+ * Drag selector used on the file list or the grid table. |
+ * TODO(hirono): Support drag selection for grid view. crbug.com/224832 |
+ * @constructor |
+ */ |
+function DragSelector() { |
+ /** |
+ * Target list of drag selection. |
+ * @type {cr.ui.List} |
+ * @private |
+ */ |
+ this.target_ = null; |
+ |
+ /** |
+ * Border element of drag handle. |
+ * @type {HtmlElement} |
+ * @private |
+ */ |
+ this.border_ = null; |
+ |
+ /** |
+ * Start point of dragging. |
+ * @type {number?} |
+ * @private |
+ */ |
+ this.startX_ = null; |
+ |
+ /** |
+ * Start point of dragging. |
+ * @type {number?} |
+ * @private |
+ */ |
+ this.startY_ = null; |
+ |
+ /** |
+ * Indexes of selected items by dragging at the last update. |
+ * @type {Array.<number>!} |
+ * @private |
+ */ |
+ this.lastSelection_ = []; |
+ |
+ /** |
+ * Indexes of selected items at the start of dragging. |
+ * @type {Array.<number>!} |
+ * @private |
+ */ |
+ this.originalSelection_ = []; |
+ |
+ // Bind handlers to make them removable. |
+ this.onMouseMoveBound_ = this.onMouseMove_.bind(this); |
+ this.onMouseUpBound_ = this.onMouseUp_.bind(this); |
+} |
+ |
+/** |
+ * Flag that shows whether the item is included in the selection or not. |
+ * @enum {number} |
+ * @private |
+ */ |
+DragSelector.SelectionFlag_ = { |
+ IN_LAST_SELECTION: 1 << 0, |
+ IN_CURRENT_SELECTION: 1 << 1 |
+}; |
+ |
+/** |
+ * Starts drag selection by reacting dragstart event. |
+ * This function must be called from handlers of dragstart event. |
+ * |
+ * @this {DragSelector} |
+ * @param {cr.ui.List} list List where the drag selection starts. |
+ * @param {Event} event The dragstart event. |
+ */ |
+DragSelector.prototype.startDragSelection = function(list, event) { |
+ // Precondition check |
+ if (!list.selectionModel_.multiple || this.target_) |
+ return; |
+ |
+ // Set the target of the drag selection |
+ this.target_ = list; |
+ |
+ // Create and add the border element |
+ if (!this.border_) { |
+ this.border_ = this.target_.ownerDocument.createElement('div'); |
+ this.border_.className = 'drag-selection-border'; |
+ } |
+ list.appendChild(this.border_); |
+ |
+ // Prevent default action. |
+ event.preventDefault(); |
+ |
+ // If no modifier key is pressed, clear the original selection. |
+ if (!event.shiftKey && !event.ctrlKey) { |
+ this.target_.selectionModel_.unselectAll(); |
+ } |
+ |
+ // Save the start state. |
+ var rect = list.getBoundingClientRect(); |
+ this.startX_ = event.clientX - rect.left + list.scrollLeft; |
+ this.startY_ = event.clientY - rect.top + list.scrollTop; |
+ this.border_.style.left = this.startX_ + 'px'; |
+ this.border_.style.top = this.startY_ + 'px'; |
+ this.lastSelection_ = []; |
+ this.originalSelection_ = this.target_.selectionModel_.selectedIndexes; |
+ |
+ // Register event handlers. |
+ // The handlers are bounded at the constructor. |
+ this.target_.ownerDocument.addEventListener( |
+ 'mousemove', this.onMouseMoveBound_, true); |
+ this.target_.ownerDocument.addEventListener( |
+ 'mouseup', this.onMouseUpBound_, true); |
+}; |
+ |
+/** |
+ * Handle the mousemove event. |
+ * @private |
+ * @param {MouseEvent} event The mousemove event. |
+ */ |
+DragSelector.prototype.onMouseMove_ = function(event) { |
+ // Get the selection bounds. |
+ var inRect = this.target_.getBoundingClientRect(); |
+ var x = event.clientX - inRect.left + this.target_.scrollLeft; |
+ var y = event.clientY - inRect.top + this.target_.scrollTop; |
+ var borderBounds = { |
+ left: Math.max(Math.min(this.startX_, x), 0), |
+ top: Math.max(Math.min(this.startY_, y), 0), |
+ right: Math.min(Math.max(this.startX_, x), this.target_.scrollWidth), |
+ bottom: Math.min(Math.max(this.startY_, y), this.target_.scrollHeight) |
+ }; |
+ borderBounds.width = borderBounds.right - borderBounds.left; |
+ borderBounds.height = borderBounds.bottom - borderBounds.top; |
+ |
+ // Collect items within the selection rect. |
+ var currentSelection = []; |
+ var leadIndex = -1; |
+ for (var i = 0; i < this.target_.selectionModel_.length; i++) { |
+ // TODO(hirono): Stop to use the private method. crbug.com/246459 |
+ var itemMetrics = this.target_.getHeightsForIndex_(i); |
+ itemMetrics.bottom = itemMetrics.top + itemMetrics.height; |
+ if (itemMetrics.top < borderBounds.bottom && |
+ itemMetrics.bottom >= borderBounds.top) { |
+ currentSelection.push(i); |
+ } |
+ var pointed = itemMetrics.top <= y && y < itemMetrics.bottom; |
+ if (pointed) |
+ leadIndex = i; |
+ } |
+ |
+ // Diff the selection between currentSelection and this.lastSelection_. |
+ var selectionFlag = []; |
+ for (var i = 0; i < this.lastSelection_.length; i++) { |
+ var index = this.lastSelection_[i]; |
+ // Bit operator can be used for undefined value. |
+ selectionFlag[index] = |
+ selectionFlag[index] | DragSelector.SelectionFlag_.IN_LAST_SELECTION; |
+ } |
+ for (var i = 0; i < currentSelection.length; i++) { |
+ var index = currentSelection[i]; |
+ // Bit operator can be used for undefined value. |
+ selectionFlag[index] = |
+ selectionFlag[index] | DragSelector.SelectionFlag_.IN_CURRENT_SELECTION; |
+ } |
+ |
+ // Update the selection |
+ this.target_.selectionModel_.beginChange(); |
+ for (var name in selectionFlag) { |
+ var index = parseInt(name); |
+ var flag = selectionFlag[name]; |
+ // The flag may be one of followings: |
+ // - IN_LAST_SELECTION | IN_CURRENT_SELECTION |
+ // - IN_LAST_SELECTION |
+ // - IN_CURRENT_SELECTION |
+ // - undefined |
+ |
+ // If the flag equals to (IN_LAST_SELECTION | IN_CURRENT_SELECTION), |
+ // this is included in both the last selection and the current selection. |
+ // We have nothing to do for this item. |
+ |
+ if (flag == DragSelector.SelectionFlag_.IN_LAST_SELECTION) { |
+ // If the flag equals to IN_LAST_SELECTION, |
+ // then the item is included in lastSelection but not in currentSelection. |
+ // Revert the selection state to this.originalSelection_. |
+ this.target_.selectionModel_.setIndexSelected( |
+ index, this.originalSelection_.indexOf(index) != -1); |
+ } else if (flag == DragSelector.SelectionFlag_.IN_CURRENT_SELECTION) { |
+ // If the flag equals to IN_CURRENT_SELECTION, |
+ // this is included in currentSelection but not in lastSelection. |
+ this.target_.selectionModel_.setIndexSelected(index, true); |
+ } |
+ } |
+ if (leadIndex != -1) { |
+ this.target_.selectionModel_.leadIndex = leadIndex; |
+ this.target_.selectionModel_.anchorIndex = leadIndex; |
+ } |
+ this.target_.selectionModel_.endChange(); |
+ this.lastSelection_ = currentSelection; |
+ |
+ // Update the size of border |
+ this.border_.style.left = borderBounds.left + 'px'; |
+ this.border_.style.top = borderBounds.top + 'px'; |
+ this.border_.style.width = borderBounds.width + 'px'; |
+ this.border_.style.height = borderBounds.height + 'px'; |
+}; |
+ |
+/** |
+ * Handle the mouseup event. |
+ * @private |
+ * @param {MouseEvent} event The mouseup event. |
+ */ |
+DragSelector.prototype.onMouseUp_ = function(event) { |
+ this.onMouseMove_(event); |
+ this.target_.removeChild(this.border_); |
+ this.target_.ownerDocument.removeEventListener( |
+ 'mousemove', this.onMouseMoveBound_, true); |
+ this.target_.ownerDocument.removeEventListener( |
+ 'mouseup', this.onMouseUpBound_, true); |
+ event.stopPropagation(); |
+ this.target_ = null; |
+}; |