list_selection_controller.js revision bda42a81ee5f9b20d2bebedcf0bbef1e30e5b293
1// Copyright (c) 2010 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 5cr.define('cr.ui', function() { 6 /** 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}. 12 * 13 * @param {cr.ui.ListSelectionModel} selectionModel The selection model to 14 * interact with. 15 * 16 * @constructor 17 * @extends {!cr.EventTarget} 18 */ 19 function ListSelectionController(selectionModel) { 20 this.selectionModel_ = selectionModel; 21 } 22 23 ListSelectionController.prototype = { 24 25 /** 26 * The selection model we are interacting with. 27 * @type {cr.ui.ListSelectionModel} 28 */ 29 get selectionModel() { 30 return this.selectionModel_; 31 }, 32 33 /** 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. 37 */ 38 getIndexBelow: function(index) { 39 if (index == this.getLastIndex()) 40 return -1; 41 return index + 1; 42 }, 43 44 /** 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. 48 */ 49 getIndexAbove: function(index) { 50 return index - 1; 51 }, 52 53 /** 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 56 * models. 57 * 58 * @param {number} index The index to get the index before. 59 * @return {number} The index before or -1 if not found. 60 */ 61 getIndexBefore: function(index) { 62 return -1; 63 }, 64 65 /** 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 68 * models. 69 * 70 * @param {number} index The index to get the index after. 71 * @return {number} The index after or -1 if not found. 72 */ 73 getIndexAfter: function(index) { 74 return -1; 75 }, 76 77 /** 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. 82 */ 83 getNextIndex: function(index) { 84 if (index == this.getLastIndex()) 85 return -1; 86 return index + 1; 87 }, 88 89 /** 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. 94 */ 95 getPreviousIndex: function(index) { 96 return index - 1; 97 }, 98 99 /** 100 * @return {number} The first index. 101 */ 102 getFirstIndex: function() { 103 return 0; 104 }, 105 106 /** 107 * @return {number} The last index. 108 */ 109 getLastIndex: function() { 110 return this.selectionModel.length - 1; 111 }, 112 113 /** 114 * Called by the view when the user does a mousedown or mouseup on the list. 115 * @param {!Event} e The browser mousedown event. 116 * @param {number} index The index that was under the mouse pointer, -1 if 117 * none. 118 */ 119 handleMouseDownUp: function(e, index) { 120 var sm = this.selectionModel; 121 var anchorIndex = sm.anchorIndex; 122 var isDown = e.type == 'mousedown'; 123 124 sm.beginChange(); 125 126 if (index == -1) { 127 // On Mac we always clear the selection if the user clicks a blank area. 128 // On Windows, we only clear the selection if neither Shift nor Ctrl are 129 // pressed. 130 if (cr.isMac) { 131 sm.leadIndex = sm.anchorIndex = -1; 132 if (sm.multiple) 133 sm.unselectAll(); 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. 137 if (sm.multiple) 138 sm.unselectAll(); 139 } else { 140 if (sm.multiple && (cr.isMac ? e.metaKey : e.ctrlKey)) { 141 // Selection is handled at mouseUp on windows/linux, mouseDown on mac. 142 if (cr.isMac? isDown : !isDown) { 143 // toggle the current one and make it anchor index 144 sm.setIndexSelected(index, !sm.getIndexSelected(index)); 145 sm.leadIndex = index; 146 sm.anchorIndex = index; 147 } 148 } else if (e.shiftKey && anchorIndex != -1 && anchorIndex != index) { 149 // Shift is done in mousedown 150 if (isDown) { 151 sm.unselectAll(); 152 sm.leadIndex = index; 153 if (sm.multiple) 154 sm.selectRange(anchorIndex, index); 155 else 156 sm.setIndexSelected(index, true); 157 } 158 } else { 159 // Right click for a context menu need to not clear the selection. 160 var isRightClick = e.button == 2; 161 162 // If the index is selected this is handled in mouseup. 163 var indexSelected = sm.getIndexSelected(index); 164 if ((indexSelected && !isDown || !indexSelected && isDown) && 165 !(indexSelected && isRightClick)) { 166 sm.unselectAll(); 167 sm.setIndexSelected(index, true); 168 sm.leadIndex = index; 169 sm.anchorIndex = index; 170 } 171 } 172 } 173 174 sm.endChange(); 175 }, 176 177 /** 178 * Called by the view when it recieves a keydown event. 179 * @param {Event} e The keydown event. 180 */ 181 handleKeyDown: function(e) { 182 var sm = this.selectionModel; 183 var newIndex = -1; 184 var leadIndex = sm.leadIndex; 185 var prevent = true; 186 187 // Ctrl/Meta+A 188 if (sm.multiple && e.keyCode == 65 && 189 (cr.isMac && e.metaKey || !cr.isMac && e.ctrlKey)) { 190 sm.selectAll(); 191 e.preventDefault(); 192 return; 193 } 194 195 // Space 196 if (e.keyCode == 32) { 197 if (leadIndex != -1) { 198 var selected = sm.getIndexSelected(leadIndex); 199 if (e.ctrlKey || !selected) { 200 sm.setIndexSelected(leadIndex, !selected || !sm.multiple); 201 return; 202 } 203 } 204 } 205 206 switch (e.keyIdentifier) { 207 case 'Home': 208 newIndex = this.getFirstIndex(); 209 break; 210 case 'End': 211 newIndex = this.getLastIndex(); 212 break; 213 case 'Up': 214 newIndex = leadIndex == -1 ? 215 this.getLastIndex() : this.getIndexAbove(leadIndex); 216 break; 217 case 'Down': 218 newIndex = leadIndex == -1 ? 219 this.getFirstIndex() : this.getIndexBelow(leadIndex); 220 break; 221 case 'Left': 222 newIndex = leadIndex == -1 ? 223 this.getLastIndex() : this.getIndexBefore(leadIndex); 224 break; 225 case 'Right': 226 newIndex = leadIndex == -1 ? 227 this.getFirstIndex() : this.getIndexAfter(leadIndex); 228 break; 229 default: 230 prevent = false; 231 } 232 233 if (newIndex != -1) { 234 sm.beginChange(); 235 236 sm.leadIndex = newIndex; 237 if (e.shiftKey) { 238 var anchorIndex = sm.anchorIndex; 239 if (sm.multiple) 240 sm.unselectAll(); 241 if (anchorIndex == -1) { 242 sm.setIndexSelected(newIndex, true); 243 sm.anchorIndex = newIndex; 244 } else { 245 sm.selectRange(anchorIndex, newIndex); 246 } 247 } else if (e.ctrlKey && !cr.isMac) { 248 // Setting the lead index is done above 249 // Mac does not allow you to change the lead. 250 } else { 251 if (sm.multiple) 252 sm.unselectAll(); 253 sm.setIndexSelected(newIndex, true); 254 sm.anchorIndex = newIndex; 255 } 256 257 sm.endChange(); 258 259 if (prevent) 260 e.preventDefault(); 261 } 262 } 263 }; 264 265 return { 266 ListSelectionController: ListSelectionController 267 }; 268}); 269