OLD | NEW |
(Empty) | |
| 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 |
| 5 'use strict'; |
| 6 |
| 7 /** |
| 8 * Drag selector used on the file list or the grid table. |
| 9 * TODO(hirono): Support drag selection for grid view. crbug.com/224832 |
| 10 * @constructor |
| 11 */ |
| 12 function DragSelector() { |
| 13 /** |
| 14 * Target list of drag selection. |
| 15 * @type {cr.ui.List} |
| 16 * @private |
| 17 */ |
| 18 this.target_ = null; |
| 19 |
| 20 /** |
| 21 * Border element of drag handle. |
| 22 * @type {HtmlElement} |
| 23 * @private |
| 24 */ |
| 25 this.border_ = null; |
| 26 |
| 27 /** |
| 28 * Start point of dragging. |
| 29 * @type {number?} |
| 30 * @private |
| 31 */ |
| 32 this.startX_ = null; |
| 33 |
| 34 /** |
| 35 * Start point of dragging. |
| 36 * @type {number?} |
| 37 * @private |
| 38 */ |
| 39 this.startY_ = null; |
| 40 |
| 41 /** |
| 42 * Indexes of selected items by dragging at the last update. |
| 43 * @type {Array.<number>!} |
| 44 * @private |
| 45 */ |
| 46 this.lastSelection_ = []; |
| 47 |
| 48 /** |
| 49 * Indexes of selected items at the start of dragging. |
| 50 * @type {Array.<number>!} |
| 51 * @private |
| 52 */ |
| 53 this.originalSelection_ = []; |
| 54 |
| 55 // Bind handlers to make them removable. |
| 56 this.onMouseMoveBound_ = this.onMouseMove_.bind(this); |
| 57 this.onMouseUpBound_ = this.onMouseUp_.bind(this); |
| 58 } |
| 59 |
| 60 /** |
| 61 * Flag that shows whether the item is included in the selection or not. |
| 62 * @enum {number} |
| 63 * @private |
| 64 */ |
| 65 DragSelector.SelectionFlag_ = { |
| 66 IN_LAST_SELECTION: 1 << 0, |
| 67 IN_CURRENT_SELECTION: 1 << 1 |
| 68 }; |
| 69 |
| 70 /** |
| 71 * Starts drag selection by reacting dragstart event. |
| 72 * This function must be called from handlers of dragstart event. |
| 73 * |
| 74 * @this {DragSelector} |
| 75 * @param {cr.ui.List} list List where the drag selection starts. |
| 76 * @param {Event} event The dragstart event. |
| 77 */ |
| 78 DragSelector.prototype.startDragSelection = function(list, event) { |
| 79 // Precondition check |
| 80 if (!list.selectionModel_.multiple || this.target_) |
| 81 return; |
| 82 |
| 83 // Set the target of the drag selection |
| 84 this.target_ = list; |
| 85 |
| 86 // Create and add the border element |
| 87 if (!this.border_) { |
| 88 this.border_ = this.target_.ownerDocument.createElement('div'); |
| 89 this.border_.className = 'drag-selection-border'; |
| 90 } |
| 91 list.appendChild(this.border_); |
| 92 |
| 93 // Prevent default action. |
| 94 event.preventDefault(); |
| 95 |
| 96 // If no modifier key is pressed, clear the original selection. |
| 97 if (!event.shiftKey && !event.ctrlKey) { |
| 98 this.target_.selectionModel_.unselectAll(); |
| 99 } |
| 100 |
| 101 // Save the start state. |
| 102 var rect = list.getBoundingClientRect(); |
| 103 this.startX_ = event.clientX - rect.left + list.scrollLeft; |
| 104 this.startY_ = event.clientY - rect.top + list.scrollTop; |
| 105 this.border_.style.left = this.startX_ + 'px'; |
| 106 this.border_.style.top = this.startY_ + 'px'; |
| 107 this.lastSelection_ = []; |
| 108 this.originalSelection_ = this.target_.selectionModel_.selectedIndexes; |
| 109 |
| 110 // Register event handlers. |
| 111 // The handlers are bounded at the constructor. |
| 112 this.target_.ownerDocument.addEventListener( |
| 113 'mousemove', this.onMouseMoveBound_, true); |
| 114 this.target_.ownerDocument.addEventListener( |
| 115 'mouseup', this.onMouseUpBound_, true); |
| 116 }; |
| 117 |
| 118 /** |
| 119 * Handle the mousemove event. |
| 120 * @private |
| 121 * @param {MouseEvent} event The mousemove event. |
| 122 */ |
| 123 DragSelector.prototype.onMouseMove_ = function(event) { |
| 124 // Get the selection bounds. |
| 125 var inRect = this.target_.getBoundingClientRect(); |
| 126 var x = event.clientX - inRect.left + this.target_.scrollLeft; |
| 127 var y = event.clientY - inRect.top + this.target_.scrollTop; |
| 128 var borderBounds = { |
| 129 left: Math.max(Math.min(this.startX_, x), 0), |
| 130 top: Math.max(Math.min(this.startY_, y), 0), |
| 131 right: Math.min(Math.max(this.startX_, x), this.target_.scrollWidth), |
| 132 bottom: Math.min(Math.max(this.startY_, y), this.target_.scrollHeight) |
| 133 }; |
| 134 borderBounds.width = borderBounds.right - borderBounds.left; |
| 135 borderBounds.height = borderBounds.bottom - borderBounds.top; |
| 136 |
| 137 // Collect items within the selection rect. |
| 138 var currentSelection = []; |
| 139 var leadIndex = -1; |
| 140 for (var i = 0; i < this.target_.selectionModel_.length; i++) { |
| 141 // TODO(hirono): Stop to use the private method. crbug.com/246459 |
| 142 var itemMetrics = this.target_.getHeightsForIndex_(i); |
| 143 itemMetrics.bottom = itemMetrics.top + itemMetrics.height; |
| 144 if (itemMetrics.top < borderBounds.bottom && |
| 145 itemMetrics.bottom >= borderBounds.top) { |
| 146 currentSelection.push(i); |
| 147 } |
| 148 var pointed = itemMetrics.top <= y && y < itemMetrics.bottom; |
| 149 if (pointed) |
| 150 leadIndex = i; |
| 151 } |
| 152 |
| 153 // Diff the selection between currentSelection and this.lastSelection_. |
| 154 var selectionFlag = []; |
| 155 for (var i = 0; i < this.lastSelection_.length; i++) { |
| 156 var index = this.lastSelection_[i]; |
| 157 // Bit operator can be used for undefined value. |
| 158 selectionFlag[index] = |
| 159 selectionFlag[index] | DragSelector.SelectionFlag_.IN_LAST_SELECTION; |
| 160 } |
| 161 for (var i = 0; i < currentSelection.length; i++) { |
| 162 var index = currentSelection[i]; |
| 163 // Bit operator can be used for undefined value. |
| 164 selectionFlag[index] = |
| 165 selectionFlag[index] | DragSelector.SelectionFlag_.IN_CURRENT_SELECTION; |
| 166 } |
| 167 |
| 168 // Update the selection |
| 169 this.target_.selectionModel_.beginChange(); |
| 170 for (var name in selectionFlag) { |
| 171 var index = parseInt(name); |
| 172 var flag = selectionFlag[name]; |
| 173 // The flag may be one of followings: |
| 174 // - IN_LAST_SELECTION | IN_CURRENT_SELECTION |
| 175 // - IN_LAST_SELECTION |
| 176 // - IN_CURRENT_SELECTION |
| 177 // - undefined |
| 178 |
| 179 // If the flag equals to (IN_LAST_SELECTION | IN_CURRENT_SELECTION), |
| 180 // this is included in both the last selection and the current selection. |
| 181 // We have nothing to do for this item. |
| 182 |
| 183 if (flag == DragSelector.SelectionFlag_.IN_LAST_SELECTION) { |
| 184 // If the flag equals to IN_LAST_SELECTION, |
| 185 // then the item is included in lastSelection but not in currentSelection. |
| 186 // Revert the selection state to this.originalSelection_. |
| 187 this.target_.selectionModel_.setIndexSelected( |
| 188 index, this.originalSelection_.indexOf(index) != -1); |
| 189 } else if (flag == DragSelector.SelectionFlag_.IN_CURRENT_SELECTION) { |
| 190 // If the flag equals to IN_CURRENT_SELECTION, |
| 191 // this is included in currentSelection but not in lastSelection. |
| 192 this.target_.selectionModel_.setIndexSelected(index, true); |
| 193 } |
| 194 } |
| 195 if (leadIndex != -1) { |
| 196 this.target_.selectionModel_.leadIndex = leadIndex; |
| 197 this.target_.selectionModel_.anchorIndex = leadIndex; |
| 198 } |
| 199 this.target_.selectionModel_.endChange(); |
| 200 this.lastSelection_ = currentSelection; |
| 201 |
| 202 // Update the size of border |
| 203 this.border_.style.left = borderBounds.left + 'px'; |
| 204 this.border_.style.top = borderBounds.top + 'px'; |
| 205 this.border_.style.width = borderBounds.width + 'px'; |
| 206 this.border_.style.height = borderBounds.height + 'px'; |
| 207 }; |
| 208 |
| 209 /** |
| 210 * Handle the mouseup event. |
| 211 * @private |
| 212 * @param {MouseEvent} event The mouseup event. |
| 213 */ |
| 214 DragSelector.prototype.onMouseUp_ = function(event) { |
| 215 this.onMouseMove_(event); |
| 216 this.target_.removeChild(this.border_); |
| 217 this.target_.ownerDocument.removeEventListener( |
| 218 'mousemove', this.onMouseMoveBound_, true); |
| 219 this.target_.ownerDocument.removeEventListener( |
| 220 'mouseup', this.onMouseUpBound_, true); |
| 221 event.stopPropagation(); |
| 222 this.target_ = null; |
| 223 }; |
OLD | NEW |