| OLD | NEW |
| (Empty) |
| 1 // Copyright 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 Object.seal(this); | |
| 60 } | |
| 61 | |
| 62 /** | |
| 63 * Flag that shows whether the item is included in the selection or not. | |
| 64 * @enum {number} | |
| 65 * @private | |
| 66 */ | |
| 67 DragSelector.SelectionFlag_ = { | |
| 68 IN_LAST_SELECTION: 1 << 0, | |
| 69 IN_CURRENT_SELECTION: 1 << 1 | |
| 70 }; | |
| 71 | |
| 72 /** | |
| 73 * Obtains the scrolled position in the element of mouse pointer from the mouse | |
| 74 * event. | |
| 75 * | |
| 76 * @param {HTMLElement} element Element that has the scroll bars. | |
| 77 * @param {Event} event The mouse event. | |
| 78 * @return {object} Scrolled position. | |
| 79 */ | |
| 80 DragSelector.getScrolledPosition = function(element, event) { | |
| 81 if (!element.cachedBounds) { | |
| 82 element.cachedBounds = element.getBoundingClientRect(); | |
| 83 if (!element.cachedBounds) | |
| 84 return null; | |
| 85 } | |
| 86 var rect = element.cachedBounds; | |
| 87 return { | |
| 88 x: event.clientX - rect.left + element.scrollLeft, | |
| 89 y: event.clientY - rect.top + element.scrollTop | |
| 90 }; | |
| 91 }; | |
| 92 | |
| 93 /** | |
| 94 * Starts drag selection by reacting dragstart event. | |
| 95 * This function must be called from handlers of dragstart event. | |
| 96 * | |
| 97 * @this {DragSelector} | |
| 98 * @param {cr.ui.List} list List where the drag selection starts. | |
| 99 * @param {Event} event The dragstart event. | |
| 100 */ | |
| 101 DragSelector.prototype.startDragSelection = function(list, event) { | |
| 102 // Precondition check | |
| 103 if (!list.selectionModel_.multiple || this.target_) | |
| 104 return; | |
| 105 | |
| 106 // Set the target of the drag selection | |
| 107 this.target_ = list; | |
| 108 | |
| 109 // Prevent the default action. | |
| 110 event.preventDefault(); | |
| 111 | |
| 112 // Save the start state. | |
| 113 var startPos = DragSelector.getScrolledPosition(list, event); | |
| 114 if (!startPos) | |
| 115 return; | |
| 116 this.startX_ = startPos.x; | |
| 117 this.startY_ = startPos.y; | |
| 118 this.lastSelection_ = []; | |
| 119 this.originalSelection_ = this.target_.selectionModel_.selectedIndexes; | |
| 120 | |
| 121 // Create and add the border element | |
| 122 if (!this.border_) { | |
| 123 this.border_ = this.target_.ownerDocument.createElement('div'); | |
| 124 this.border_.className = 'drag-selection-border'; | |
| 125 } | |
| 126 this.border_.style.left = this.startX_ + 'px'; | |
| 127 this.border_.style.top = this.startY_ + 'px'; | |
| 128 this.border_.style.width = '0'; | |
| 129 this.border_.style.height = '0'; | |
| 130 list.appendChild(this.border_); | |
| 131 | |
| 132 // If no modifier key is pressed, clear the original selection. | |
| 133 if (!event.shiftKey && !event.ctrlKey) | |
| 134 this.target_.selectionModel_.unselectAll(); | |
| 135 | |
| 136 // Register event handlers. | |
| 137 // The handlers are bounded at the constructor. | |
| 138 this.target_.ownerDocument.addEventListener( | |
| 139 'mousemove', this.onMouseMoveBound_, true); | |
| 140 this.target_.ownerDocument.addEventListener( | |
| 141 'mouseup', this.onMouseUpBound_, true); | |
| 142 }; | |
| 143 | |
| 144 /** | |
| 145 * Handles the mousemove event. | |
| 146 * @private | |
| 147 * @param {MouseEvent} event The mousemove event. | |
| 148 */ | |
| 149 DragSelector.prototype.onMouseMove_ = function(event) { | |
| 150 // Get the selection bounds. | |
| 151 var pos = DragSelector.getScrolledPosition(this.target_, event); | |
| 152 var borderBounds = { | |
| 153 left: Math.max(Math.min(this.startX_, pos.x), 0), | |
| 154 top: Math.max(Math.min(this.startY_, pos.y), 0), | |
| 155 right: Math.min(Math.max(this.startX_, pos.x), this.target_.scrollWidth), | |
| 156 bottom: Math.min(Math.max(this.startY_, pos.y), this.target_.scrollHeight) | |
| 157 }; | |
| 158 borderBounds.width = borderBounds.right - borderBounds.left; | |
| 159 borderBounds.height = borderBounds.bottom - borderBounds.top; | |
| 160 | |
| 161 // Collect items within the selection rect. | |
| 162 var currentSelection = this.target_.getHitElements( | |
| 163 borderBounds.left, | |
| 164 borderBounds.top, | |
| 165 borderBounds.width, | |
| 166 borderBounds.height); | |
| 167 var pointedElements = this.target_.getHitElements(pos.x, pos.y); | |
| 168 var leadIndex = pointedElements.length ? pointedElements[0] : -1; | |
| 169 | |
| 170 // Diff the selection between currentSelection and this.lastSelection_. | |
| 171 var selectionFlag = []; | |
| 172 for (var i = 0; i < this.lastSelection_.length; i++) { | |
| 173 var index = this.lastSelection_[i]; | |
| 174 // Bit operator can be used for undefined value. | |
| 175 selectionFlag[index] = | |
| 176 selectionFlag[index] | DragSelector.SelectionFlag_.IN_LAST_SELECTION; | |
| 177 } | |
| 178 for (var i = 0; i < currentSelection.length; i++) { | |
| 179 var index = currentSelection[i]; | |
| 180 // Bit operator can be used for undefined value. | |
| 181 selectionFlag[index] = | |
| 182 selectionFlag[index] | DragSelector.SelectionFlag_.IN_CURRENT_SELECTION; | |
| 183 } | |
| 184 | |
| 185 // Update the selection | |
| 186 this.target_.selectionModel_.beginChange(); | |
| 187 for (var name in selectionFlag) { | |
| 188 var index = parseInt(name); | |
| 189 var flag = selectionFlag[name]; | |
| 190 // The flag may be one of followings: | |
| 191 // - IN_LAST_SELECTION | IN_CURRENT_SELECTION | |
| 192 // - IN_LAST_SELECTION | |
| 193 // - IN_CURRENT_SELECTION | |
| 194 // - undefined | |
| 195 | |
| 196 // If the flag equals to (IN_LAST_SELECTION | IN_CURRENT_SELECTION), | |
| 197 // this is included in both the last selection and the current selection. | |
| 198 // We have nothing to do for this item. | |
| 199 | |
| 200 if (flag == DragSelector.SelectionFlag_.IN_LAST_SELECTION) { | |
| 201 // If the flag equals to IN_LAST_SELECTION, | |
| 202 // then the item is included in lastSelection but not in currentSelection. | |
| 203 // Revert the selection state to this.originalSelection_. | |
| 204 this.target_.selectionModel_.setIndexSelected( | |
| 205 index, this.originalSelection_.indexOf(index) != -1); | |
| 206 } else if (flag == DragSelector.SelectionFlag_.IN_CURRENT_SELECTION) { | |
| 207 // If the flag equals to IN_CURRENT_SELECTION, | |
| 208 // this is included in currentSelection but not in lastSelection. | |
| 209 this.target_.selectionModel_.setIndexSelected(index, true); | |
| 210 } | |
| 211 } | |
| 212 if (leadIndex != -1) { | |
| 213 this.target_.selectionModel_.leadIndex = leadIndex; | |
| 214 this.target_.selectionModel_.anchorIndex = leadIndex; | |
| 215 } | |
| 216 this.target_.selectionModel_.endChange(); | |
| 217 this.lastSelection_ = currentSelection; | |
| 218 | |
| 219 // Update the size of border | |
| 220 this.border_.style.left = borderBounds.left + 'px'; | |
| 221 this.border_.style.top = borderBounds.top + 'px'; | |
| 222 this.border_.style.width = borderBounds.width + 'px'; | |
| 223 this.border_.style.height = borderBounds.height + 'px'; | |
| 224 }; | |
| 225 | |
| 226 /** | |
| 227 * Handle the mouseup event. | |
| 228 * @private | |
| 229 * @param {MouseEvent} event The mouseup event. | |
| 230 */ | |
| 231 DragSelector.prototype.onMouseUp_ = function(event) { | |
| 232 this.onMouseMove_(event); | |
| 233 this.target_.removeChild(this.border_); | |
| 234 this.target_.ownerDocument.removeEventListener( | |
| 235 'mousemove', this.onMouseMoveBound_, true); | |
| 236 this.target_.ownerDocument.removeEventListener( | |
| 237 'mouseup', this.onMouseUpBound_, true); | |
| 238 cr.dispatchSimpleEvent(this.target_, 'dragselectionend'); | |
| 239 this.target_.cachedBounds = null; | |
| 240 this.target_ = null; | |
| 241 // The target may select an item by reacting to the mouseup event. | |
| 242 // This suppress to the selecting behavior. | |
| 243 event.stopPropagation(); | |
| 244 }; | |
| OLD | NEW |