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.
5 cr.define('cr.ui', function() {
7 * Creates a selection controller that is to be used with lists. This is
8 * implemented for vertical lists but changing the behavior for horizontal
9 * lists or icon views is a matter of overriding {@code getIndexBefore},
10 * {@code getIndexAfter}, {@code getIndexAbove} as well as
11 * {@code getIndexBelow}.
13 * @param {cr.ui.ListSelectionModel} selectionModel The selection model to
17 * @extends {!cr.EventTarget}
19 function ListSelectionController(selectionModel) {
20 this.selectionModel_ = selectionModel;
23 ListSelectionController.prototype = {
26 * The selection model we are interacting with.
27 * @type {cr.ui.ListSelectionModel}
29 get selectionModel() {
30 return this.selectionModel_;
34 * Returns the index below (y axis) the given element.
35 * @param {number} index The index to get the index below.
36 * @return {number} The index below or -1 if not found.
38 getIndexBelow: function(index) {
39 if (index == this.getLastIndex())
45 * Returns the index above (y axis) the given element.
46 * @param {number} index The index to get the index above.
47 * @return {number} The index below or -1 if not found.
49 getIndexAbove: function(index) {
54 * Returns the index before (x axis) the given element. This returns -1
55 * by default but override this for icon view and horizontal selection
58 * @param {number} index The index to get the index before.
59 * @return {number} The index before or -1 if not found.
61 getIndexBefore: function(index) {
66 * Returns the index after (x axis) the given element. This returns -1
67 * by default but override this for icon view and horizontal selection
70 * @param {number} index The index to get the index after.
71 * @return {number} The index after or -1 if not found.
73 getIndexAfter: function(index) {
78 * Returns the next list index. This is the next logical and should not
79 * depend on any kind of layout of the list.
80 * @param {number} index The index to get the next index for.
81 * @return {number} The next index or -1 if not found.
83 getNextIndex: function(index) {
84 if (index == this.getLastIndex())
90 * Returns the prevous list index. This is the previous logical and should
91 * not depend on any kind of layout of the list.
92 * @param {number} index The index to get the previous index for.
93 * @return {number} The previous index or -1 if not found.
95 getPreviousIndex: function(index) {
100 * @return {number} The first index.
102 getFirstIndex: function() {
107 * @return {number} The last index.
109 getLastIndex: function() {
110 return this.selectionModel.length - 1;
114 * Called by the view when the user does a mousedown or mouseup on the
116 * @param {!Event} e The browser mouse event.
117 * @param {number} index The index that was under the mouse pointer, -1 if
120 handlePointerDownUp: function(e, index) {
121 var sm = this.selectionModel;
122 var anchorIndex = sm.anchorIndex;
123 var isDown = (e.type == 'mousedown');
128 // On Mac we always clear the selection if the user clicks a blank area.
129 // On Windows, we only clear the selection if neither Shift nor Ctrl are
131 if (cr.isMac || cr.isChromeOS) {
132 sm.leadIndex = sm.anchorIndex = -1;
134 } else if (!isDown && !e.shiftKey && !e.ctrlKey)
135 // Keep anchor and lead indexes. Note that this is intentionally
136 // different than on the Mac.
140 if (sm.multiple && (cr.isMac ? e.metaKey :
141 (e.ctrlKey && !e.shiftKey))) {
142 // Selection is handled at mouseUp on windows/linux, mouseDown on mac.
143 if (cr.isMac ? isDown : !isDown) {
144 // Toggle the current one and make it anchor index.
145 sm.setIndexSelected(index, !sm.getIndexSelected(index));
146 sm.leadIndex = index;
147 sm.anchorIndex = index;
149 } else if (e.shiftKey && anchorIndex != -1 && anchorIndex != index) {
150 // Shift is done in mousedown.
153 sm.leadIndex = index;
155 sm.selectRange(anchorIndex, index);
157 sm.setIndexSelected(index, true);
160 // Right click for a context menu needs to not clear the selection.
161 var isRightClick = e.button == 2;
163 // If the index is selected this is handled in mouseup.
164 var indexSelected = sm.getIndexSelected(index);
165 if ((indexSelected && !isDown || !indexSelected && isDown) &&
166 !(indexSelected && isRightClick)) {
167 sm.selectedIndex = index;
176 * Called by the view when it receives a keydown event.
177 * @param {Event} e The keydown event.
179 handleKeyDown: function(e) {
180 var SPACE_KEY_CODE = 32;
181 var tagName = e.target.tagName;
182 // If focus is in an input field of some kind, only handle navigation keys
183 // that aren't likely to conflict with input interaction (e.g., text
184 // editing, or changing the value of a checkbox or select).
185 if (tagName == 'INPUT') {
186 var inputType = e.target.type;
187 // Just protect space (for toggling) for checkbox and radio.
188 if (inputType == 'checkbox' || inputType == 'radio') {
189 if (e.keyCode == SPACE_KEY_CODE)
191 // Protect all but the most basic navigation commands in anything else.
192 } else if (e.keyIdentifier != 'Up' && e.keyIdentifier != 'Down') {
196 // Similarly, don't interfere with select element handling.
197 if (tagName == 'SELECT')
200 var sm = this.selectionModel;
202 var leadIndex = sm.leadIndex;
206 if (sm.multiple && e.keyCode == 65 &&
207 (cr.isMac && e.metaKey || !cr.isMac && e.ctrlKey)) {
214 if (e.keyCode == SPACE_KEY_CODE) {
215 if (leadIndex != -1) {
216 var selected = sm.getIndexSelected(leadIndex);
217 if (e.ctrlKey || !selected) {
218 sm.setIndexSelected(leadIndex, !selected || !sm.multiple);
224 switch (e.keyIdentifier) {
226 newIndex = this.getFirstIndex();
229 newIndex = this.getLastIndex();
232 newIndex = leadIndex == -1 ?
233 this.getLastIndex() : this.getIndexAbove(leadIndex);
236 newIndex = leadIndex == -1 ?
237 this.getFirstIndex() : this.getIndexBelow(leadIndex);
240 newIndex = leadIndex == -1 ?
241 this.getLastIndex() : this.getIndexBefore(leadIndex);
244 newIndex = leadIndex == -1 ?
245 this.getFirstIndex() : this.getIndexAfter(leadIndex);
251 if (newIndex != -1) {
254 sm.leadIndex = newIndex;
256 var anchorIndex = sm.anchorIndex;
259 if (anchorIndex == -1) {
260 sm.setIndexSelected(newIndex, true);
261 sm.anchorIndex = newIndex;
263 sm.selectRange(anchorIndex, newIndex);
265 } else if (e.ctrlKey && !cr.isMac && !cr.isChromeOS) {
266 // Setting the lead index is done above.
267 // Mac does not allow you to change the lead.
271 sm.setIndexSelected(newIndex, true);
272 sm.anchorIndex = newIndex;
284 ListSelectionController: ListSelectionController