Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / ui / webui / resources / js / cr / ui / list_selection_controller.js
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.
4
5 cr.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
115      * list.
116      * @param {!Event} e The browser mouse event.
117      * @param {number} index The index that was under the mouse pointer, -1 if
118      *     none.
119      */
120     handlePointerDownUp: function(e, index) {
121       var sm = this.selectionModel;
122       var anchorIndex = sm.anchorIndex;
123       var isDown = (e.type == 'mousedown');
124
125       sm.beginChange();
126
127       if (index == -1) {
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
130         // pressed.
131         if (cr.isMac || cr.isChromeOS) {
132           sm.leadIndex = sm.anchorIndex = -1;
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 :
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;
148           }
149         } else if (e.shiftKey && anchorIndex != -1 && anchorIndex != index) {
150           // Shift is done in mousedown.
151           if (isDown) {
152             sm.unselectAll();
153             sm.leadIndex = index;
154             if (sm.multiple)
155               sm.selectRange(anchorIndex, index);
156             else
157               sm.setIndexSelected(index, true);
158           }
159         } else {
160           // Right click for a context menu needs to not clear the selection.
161           var isRightClick = e.button == 2;
162
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;
168           }
169         }
170       }
171
172       sm.endChange();
173     },
174
175     /**
176      * Called by the view when it receives a keydown event.
177      * @param {Event} e The keydown event.
178      */
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)
190             return;
191         // Protect all but the most basic navigation commands in anything else.
192         } else if (e.keyIdentifier != 'Up' && e.keyIdentifier != 'Down') {
193           return;
194         }
195       }
196       // Similarly, don't interfere with select element handling.
197       if (tagName == 'SELECT')
198         return;
199
200       var sm = this.selectionModel;
201       var newIndex = -1;
202       var leadIndex = sm.leadIndex;
203       var prevent = true;
204
205       // Ctrl/Meta+A
206       if (sm.multiple && e.keyCode == 65 &&
207           (cr.isMac && e.metaKey || !cr.isMac && e.ctrlKey)) {
208         sm.selectAll();
209         e.preventDefault();
210         return;
211       }
212
213       // Space
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);
219             return;
220           }
221         }
222       }
223
224       switch (e.keyIdentifier) {
225         case 'Home':
226           newIndex = this.getFirstIndex();
227           break;
228         case 'End':
229           newIndex = this.getLastIndex();
230           break;
231         case 'Up':
232           newIndex = leadIndex == -1 ?
233               this.getLastIndex() : this.getIndexAbove(leadIndex);
234           break;
235         case 'Down':
236           newIndex = leadIndex == -1 ?
237               this.getFirstIndex() : this.getIndexBelow(leadIndex);
238           break;
239         case 'Left':
240         case 'MediaPreviousTrack':
241           newIndex = leadIndex == -1 ?
242               this.getLastIndex() : this.getIndexBefore(leadIndex);
243           break;
244         case 'Right':
245         case 'MediaNextTrack':
246           newIndex = leadIndex == -1 ?
247               this.getFirstIndex() : this.getIndexAfter(leadIndex);
248           break;
249         default:
250           prevent = false;
251       }
252
253       if (newIndex != -1) {
254         sm.beginChange();
255
256         sm.leadIndex = newIndex;
257         if (e.shiftKey) {
258           var anchorIndex = sm.anchorIndex;
259           if (sm.multiple)
260             sm.unselectAll();
261           if (anchorIndex == -1) {
262             sm.setIndexSelected(newIndex, true);
263             sm.anchorIndex = newIndex;
264           } else {
265             sm.selectRange(anchorIndex, newIndex);
266           }
267         } else if (e.ctrlKey && !cr.isMac && !cr.isChromeOS) {
268           // Setting the lead index is done above.
269           // Mac does not allow you to change the lead.
270         } else {
271           if (sm.multiple)
272             sm.unselectAll();
273           sm.setIndexSelected(newIndex, true);
274           sm.anchorIndex = newIndex;
275         }
276
277         sm.endChange();
278
279         if (prevent)
280           e.preventDefault();
281       }
282     }
283   };
284
285   return {
286     ListSelectionController: ListSelectionController
287   };
288 });