Upstream version 7.35.144.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / file_manager / foreground / js / drag_selector.js
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 };