| // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| cr.define('cr.ui', function() { |
| /** |
| * Creates a selection controller that is to be used with lists. This is |
| * implemented for vertical lists but changing the behavior for horizontal |
| * lists or icon views is a matter of overriding {@code getIndexBefore}, |
| * {@code getIndexAfter}, {@code getIndexAbove} as well as |
| * {@code getIndexBelow}. |
| * |
| * @param {cr.ui.ListSelectionModel} selectionModel The selection model to |
| * interact with. |
| * |
| * @constructor |
| * @extends {!cr.EventTarget} |
| */ |
| function ListSelectionController(selectionModel) { |
| this.selectionModel_ = selectionModel; |
| } |
| |
| ListSelectionController.prototype = { |
| |
| /** |
| * The selection model we are interacting with. |
| * @type {cr.ui.ListSelectionModel} |
| */ |
| get selectionModel() { |
| return this.selectionModel_; |
| }, |
| |
| /** |
| * Returns the index below (y axis) the given element. |
| * @param {number} index The index to get the index below. |
| * @return {number} The index below or -1 if not found. |
| */ |
| getIndexBelow: function(index) { |
| if (index == this.getLastIndex()) |
| return -1; |
| return index + 1; |
| }, |
| |
| /** |
| * Returns the index above (y axis) the given element. |
| * @param {number} index The index to get the index above. |
| * @return {number} The index below or -1 if not found. |
| */ |
| getIndexAbove: function(index) { |
| return index - 1; |
| }, |
| |
| /** |
| * Returns the index before (x axis) the given element. This returns -1 |
| * by default but override this for icon view and horizontal selection |
| * models. |
| * |
| * @param {number} index The index to get the index before. |
| * @return {number} The index before or -1 if not found. |
| */ |
| getIndexBefore: function(index) { |
| return -1; |
| }, |
| |
| /** |
| * Returns the index after (x axis) the given element. This returns -1 |
| * by default but override this for icon view and horizontal selection |
| * models. |
| * |
| * @param {number} index The index to get the index after. |
| * @return {number} The index after or -1 if not found. |
| */ |
| getIndexAfter: function(index) { |
| return -1; |
| }, |
| |
| /** |
| * Returns the next list index. This is the next logical and should not |
| * depend on any kind of layout of the list. |
| * @param {number} index The index to get the next index for. |
| * @return {number} The next index or -1 if not found. |
| */ |
| getNextIndex: function(index) { |
| if (index == this.getLastIndex()) |
| return -1; |
| return index + 1; |
| }, |
| |
| /** |
| * Returns the prevous list index. This is the previous logical and should |
| * not depend on any kind of layout of the list. |
| * @param {number} index The index to get the previous index for. |
| * @return {number} The previous index or -1 if not found. |
| */ |
| getPreviousIndex: function(index) { |
| return index - 1; |
| }, |
| |
| /** |
| * @return {number} The first index. |
| */ |
| getFirstIndex: function() { |
| return 0; |
| }, |
| |
| /** |
| * @return {number} The last index. |
| */ |
| getLastIndex: function() { |
| return this.selectionModel.length - 1; |
| }, |
| |
| /** |
| * Called by the view when the user does a mousedown or mouseup on the list. |
| * @param {!Event} e The browser mousedown event. |
| * @param {number} index The index that was under the mouse pointer, -1 if |
| * none. |
| */ |
| handleMouseDownUp: function(e, index) { |
| var sm = this.selectionModel; |
| var anchorIndex = sm.anchorIndex; |
| var isDown = e.type == 'mousedown'; |
| |
| sm.beginChange(); |
| |
| if (index == -1) { |
| // On Mac we always clear the selection if the user clicks a blank area. |
| // On Windows, we only clear the selection if neither Shift nor Ctrl are |
| // pressed. |
| if (cr.isMac) { |
| sm.leadIndex = sm.anchorIndex = -1; |
| if (sm.multiple) |
| sm.unselectAll(); |
| } else if (!isDown && !e.shiftKey && !e.ctrlKey) |
| // Keep anchor and lead indexes. Note that this is intentionally |
| // different than on the Mac. |
| if (sm.multiple) |
| sm.unselectAll(); |
| } else { |
| if (sm.multiple && (cr.isMac ? e.metaKey : |
| (e.ctrlKey && !e.shiftKey))) { |
| // Selection is handled at mouseUp on windows/linux, mouseDown on mac. |
| if (cr.isMac? isDown : !isDown) { |
| // Toggle the current one and make it anchor index. |
| sm.setIndexSelected(index, !sm.getIndexSelected(index)); |
| sm.leadIndex = index; |
| sm.anchorIndex = index; |
| } |
| } else if (e.shiftKey && anchorIndex != -1 && anchorIndex != index) { |
| // Shift is done in mousedown. |
| if (isDown) { |
| sm.unselectAll(); |
| sm.leadIndex = index; |
| if (sm.multiple) |
| sm.selectRange(anchorIndex, index); |
| else |
| sm.setIndexSelected(index, true); |
| } |
| } else { |
| // Right click for a context menu needs to not clear the selection. |
| var isRightClick = e.button == 2; |
| |
| // If the index is selected this is handled in mouseup. |
| var indexSelected = sm.getIndexSelected(index); |
| if ((indexSelected && !isDown || !indexSelected && isDown) && |
| !(indexSelected && isRightClick)) { |
| sm.unselectAll(); |
| sm.setIndexSelected(index, true); |
| sm.leadIndex = index; |
| sm.anchorIndex = index; |
| } |
| } |
| } |
| |
| sm.endChange(); |
| }, |
| |
| /** |
| * Called by the view when it receives a keydown event. |
| * @param {Event} e The keydown event. |
| */ |
| handleKeyDown: function(e) { |
| const SPACE_KEY_CODE = 32; |
| var tagName = e.target.tagName; |
| // If focus is in an input field of some kind, only handle navigation keys |
| // that aren't likely to conflict with input interaction (e.g., text |
| // editing, or changing the value of a checkbox or select). |
| if (tagName == 'INPUT') { |
| var inputType = e.target.type; |
| // Just protect space (for toggling) for checkbox and radio. |
| if (inputType == 'checkbox' || inputType == 'radio') { |
| if (e.keyCode == SPACE_KEY_CODE) |
| return; |
| // Protect all but the most basic navigation commands in anything else. |
| } else if (e.key != 'ArrowUp' && e.key != 'ArrowDown') { |
| return; |
| } |
| } |
| // Similarly, don't interfere with select element handling. |
| if (tagName == 'SELECT') |
| return; |
| |
| var sm = this.selectionModel; |
| var newIndex = -1; |
| var leadIndex = sm.leadIndex; |
| var prevent = true; |
| |
| // Ctrl/Meta+A |
| if (sm.multiple && e.keyCode == 65 && |
| (cr.isMac && e.metaKey || !cr.isMac && e.ctrlKey)) { |
| sm.selectAll(); |
| e.preventDefault(); |
| return; |
| } |
| |
| // Space |
| if (e.keyCode == SPACE_KEY_CODE) { |
| if (leadIndex != -1) { |
| var selected = sm.getIndexSelected(leadIndex); |
| if (e.ctrlKey || !selected) { |
| sm.setIndexSelected(leadIndex, !selected || !sm.multiple); |
| return; |
| } |
| } |
| } |
| |
| switch (e.key) { |
| case 'Home': |
| newIndex = this.getFirstIndex(); |
| break; |
| case 'End': |
| newIndex = this.getLastIndex(); |
| break; |
| case 'ArrowUp': |
| newIndex = leadIndex == -1 ? |
| this.getLastIndex() : this.getIndexAbove(leadIndex); |
| break; |
| case 'ArrowDown': |
| newIndex = leadIndex == -1 ? |
| this.getFirstIndex() : this.getIndexBelow(leadIndex); |
| break; |
| case 'ArrrowLeft': |
| newIndex = leadIndex == -1 ? |
| this.getLastIndex() : this.getIndexBefore(leadIndex); |
| break; |
| case 'ArrowRight': |
| newIndex = leadIndex == -1 ? |
| this.getFirstIndex() : this.getIndexAfter(leadIndex); |
| break; |
| default: |
| prevent = false; |
| } |
| |
| if (newIndex != -1) { |
| sm.beginChange(); |
| |
| sm.leadIndex = newIndex; |
| if (e.shiftKey) { |
| var anchorIndex = sm.anchorIndex; |
| if (sm.multiple) |
| sm.unselectAll(); |
| if (anchorIndex == -1) { |
| sm.setIndexSelected(newIndex, true); |
| sm.anchorIndex = newIndex; |
| } else { |
| sm.selectRange(anchorIndex, newIndex); |
| } |
| } else if (e.ctrlKey && !cr.isMac) { |
| // Setting the lead index is done above. |
| // Mac does not allow you to change the lead. |
| } else { |
| if (sm.multiple) |
| sm.unselectAll(); |
| sm.setIndexSelected(newIndex, true); |
| sm.anchorIndex = newIndex; |
| } |
| |
| sm.endChange(); |
| |
| if (prevent) |
| e.preventDefault(); |
| } |
| } |
| }; |
| |
| return { |
| ListSelectionController: ListSelectionController |
| }; |
| }); |