1// Copyright (c) 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// Redefine '$' here rather than including 'cr.js', since this is
6// the only function needed.  This allows this file to be loaded
7// in a browser directly for layout and some testing purposes.
8var $ = function(id) { return document.getElementById(id); };
9
10/**
11 * A generic WebUI for configuring preference values used by Chrome's gesture
12 * recognition systems.
13 * @param {string} title The user-visible title to display for the configuration
14 *    section.
15 * @param {string} prefix The prefix for the configuration fields.
16 * @param {!Object} fields An array of fields that contain the name of the pref
17 *    and user-visible labels.
18 */
19function GeneralConfig(title, prefix, fields) {
20  this.title = title;
21  this.prefix = prefix;
22  this.fields = fields;
23}
24
25GeneralConfig.prototype = {
26  /**
27   * Sets up the form for configuring all the preference values.
28   */
29  buildAll: function() {
30    this.buildForm();
31    this.loadForm();
32    this.initForm();
33  },
34
35  /**
36   * Dynamically builds web-form based on the list of preferences.
37   */
38  buildForm: function() {
39    var buf = [];
40
41    var section = $('section-template').cloneNode(true);
42    section.removeAttribute('id');
43    var title = section.querySelector('.section-title');
44    title.textContent = this.title;
45
46    for (var i = 0; i < this.fields.length; i++) {
47      var field = this.fields[i];
48
49      var row = $('section-row-template').cloneNode(true);
50      row.removeAttribute('id');
51
52      var label = row.querySelector('.row-label');
53      var input = row.querySelector('.input');
54      var units = row.querySelector('.row-units');
55      var reset = row.querySelector('.row-reset');
56
57      label.setAttribute('for', field.key);
58      label.innerHTML = field.label;
59      input.id = field.key;
60      input.min = field.min || 0;
61
62      if (field.max)
63        input.max = field.max;
64
65      input.step = field.step || 'any';
66
67      if (field.units)
68        units.innerHTML = field.units;
69
70      reset.id = field.key + '-reset';
71      gesture_config.updateResetButton(reset, true);
72
73      section.querySelector('.section-properties').appendChild(row);
74    }
75    $('gesture-form').appendChild(section);
76  },
77
78  /**
79   * Initializes the form by adding appropriate event listeners to elements.
80   */
81  initForm: function() {
82    for (var i = 0; i < this.fields.length; i++) {
83      var field = this.fields[i];
84      var config = this;
85      $(field.key).onchange = (function(key) {
86        config.setPreferenceValue(key, $(key).value);
87        gesture_config.updateResetButton($(key + '-reset'), false);
88        gesture_config.updateResetAllButton(false);
89      }).bind(null, field.key);
90      $(field.key + '-reset').onclick = (function(key) {
91        config.resetPreferenceValue(key);
92      }).bind(null, field.key);
93    }
94  },
95
96  /**
97   * Requests preference values for all the relevant fields.
98   */
99  loadForm: function() {
100    for (var i = 0; i < this.fields.length; i++)
101      this.updatePreferenceValue(this.fields[i].key);
102  },
103
104  /**
105   * Handles processing of "Reset All" button.
106   * Causes all form values to be updated based on current preference values.
107   * @return {boolean} Returns false.
108   */
109  onReset: function() {
110    for (var i = 0; i < this.fields.length; i++) {
111      var field = this.fields[i];
112      this.resetPreferenceValue(field.key);
113    }
114    return false;
115  },
116
117  /**
118   * Requests a preference setting's value.
119   * This method is asynchronous; the result is provided by a call to
120   * updatePreferenceValueResult.
121   * @param {string} prefName The name of the preference value being requested.
122   */
123  updatePreferenceValue: function(prefName) {
124    chrome.send('updatePreferenceValue', [this.prefix + prefName]);
125  },
126
127  /**
128   * Sets a preference setting's value.
129   * @param {string} prefName The name of the preference value being set.
130   * @param {value} value The value to be associated with prefName.
131   */
132  setPreferenceValue: function(prefName, value) {
133    chrome.send('setPreferenceValue',
134        [this.prefix + prefName, parseFloat(value)]);
135  },
136
137  /**
138   * Resets a preference to its default value and get that callback
139   * to updatePreferenceValueResult with the new value of the preference.
140   * @param {string} prefName The name of the requested preference.
141   */
142  resetPreferenceValue: function(prefName) {
143    chrome.send('resetPreferenceValue', [this.prefix + prefName]);
144  }
145};
146
147/**
148 * Returns a GeneralConfig for configuring gestures.* preferences.
149 * @return {object} A GeneralConfig object.
150 */
151function GestureConfig() {
152  /** The title of the section for the gesture preferences. **/
153  /** @const */ var GESTURE_TITLE = 'Gesture Configuration';
154
155  /** Common prefix of gesture preferences. **/
156  /** @const */ var GESTURE_PREFIX = 'gesture.';
157
158  /** List of fields used to dynamically build form. **/
159  var GESTURE_FIELDS = [
160    {
161      key: 'fling_max_cancel_to_down_time_in_ms',
162      label: 'Maximum Cancel to Down Time for Tap Suppression',
163      units: 'milliseconds',
164    },
165    {
166      key: 'fling_max_tap_gap_time_in_ms',
167      label: 'Maximum Tap Gap Time for Tap Suppression',
168      units: 'milliseconds',
169    },
170    {
171      key: 'long_press_time_in_seconds',
172      label: 'Long Press Time',
173      units: 'seconds'
174    },
175    {
176      key: 'semi_long_press_time_in_seconds',
177      label: 'Semi Long Press Time',
178      units: 'seconds',
179      step: 0.1
180    },
181    {
182      key: 'max_seconds_between_double_click',
183      label: 'Maximum Double Click Interval',
184      units: 'seconds',
185      step: 0.1
186    },
187    {
188      key: 'max_separation_for_gesture_touches_in_pixels',
189      label: 'Maximum Separation for Gesture Touches',
190      units: 'pixels'
191    },
192    {
193      key: 'max_swipe_deviation_ratio',
194      label: 'Maximum Swipe Deviation',
195      units: ''
196    },
197    {
198      key: 'max_touch_down_duration_in_seconds_for_click',
199      label: 'Maximum Touch-Down Duration for Click',
200      units: 'seconds',
201      step: 0.1
202    },
203    {
204      key: 'max_touch_move_in_pixels_for_click',
205      label: 'Maximum Touch-Move for Click',
206      units: 'pixels'
207    },
208    {
209      key: 'max_distance_between_taps_for_double_tap',
210      label: 'Maximum Distance between two taps for Double Tap',
211      units: 'pixels'
212    },
213    {
214      key: 'min_distance_for_pinch_scroll_in_pixels',
215      label: 'Minimum Distance for Pinch Scroll',
216      units: 'pixels'
217    },
218    {
219      key: 'min_flick_speed_squared',
220      label: 'Minimum Flick Speed Squared',
221      units: '(pixels/sec.)<sup>2</sup>'
222    },
223    {
224      key: 'min_pinch_update_distance_in_pixels',
225      label: 'Minimum Pinch Update Distance',
226      units: 'pixels'
227    },
228    {
229      key: 'min_rail_break_velocity',
230      label: 'Minimum Rail-Break Velocity',
231      units: 'pixels/sec.'
232    },
233    {
234      key: 'min_scroll_delta_squared',
235      label: 'Minimum Scroll Delta Squared',
236      units: ''
237    },
238    {
239      key: 'scroll_prediction_seconds',
240      label: 'Scroll prediction interval<br>' +
241          '(Enable scroll prediction in ' +
242              '<a href="chrome://flags">chrome://flags</a>)',
243      units: 'seconds',
244      step: 0.01
245    },
246    {
247      key: 'min_swipe_speed',
248      label: 'Minimum Swipe Speed',
249      units: 'pixels/sec.'
250    },
251    {
252      key: 'min_touch_down_duration_in_seconds_for_click',
253      label: 'Minimum Touch-Down Duration for Click',
254      units: 'seconds',
255      step: 0.01
256    },
257    {
258      key: 'points_buffered_for_velocity',
259      label: 'Points Buffered for Velocity',
260      units: '',
261      step: 1
262    },
263    {
264      key: 'rail_break_proportion',
265      label: 'Rail-Break Proportion',
266      units: '%'
267    },
268    {
269      key: 'rail_start_proportion',
270      label: 'Rail-Start Proportion',
271      units: '%'
272    },
273    {
274      key: 'fling_acceleration_curve_coefficient_0',
275      label: 'Touchscreen Fling Acceleration',
276      units: 'x<sup>3</sup>',
277      min: '-1'
278    },
279    {
280      key: 'fling_acceleration_curve_coefficient_1',
281      label: '+',
282      units: 'x<sup>2</sup>',
283      min: '-1'
284    },
285    {
286      key: 'fling_acceleration_curve_coefficient_2',
287      label: '+',
288      units: 'x<sup>1</sup>',
289      min: '-1'
290    },
291    {
292      key: 'fling_acceleration_curve_coefficient_3',
293      label: '+',
294      units: 'x<sup>0</sup>',
295      min: '-1'
296    },
297    {
298      key: 'fling_velocity_cap',
299      label: 'Touchscreen Fling Velocity Cap',
300      units: 'pixels / second'
301    },
302    {
303      key: 'tab_scrub_activation_delay_in_ms',
304      label: 'Tab scrub auto activation delay, (-1 for never)',
305      units: 'milliseconds'
306    }
307  ];
308
309  return new GeneralConfig(GESTURE_TITLE, GESTURE_PREFIX, GESTURE_FIELDS);
310}
311
312/**
313 * Returns a GeneralConfig for configuring overscroll.* preferences.
314 * @return {object} A GeneralConfig object.
315 */
316function OverscrollConfig() {
317  /** @const */ var OVERSCROLL_TITLE = 'Overscroll Configuration';
318
319  /** @const */ var OVERSCROLL_PREFIX = 'overscroll.';
320
321  var OVERSCROLL_FIELDS = [
322    {
323      key: 'horizontal_threshold_complete',
324      label: 'Complete when overscrolled (horizontal)',
325      units: '%'
326    },
327    {
328      key: 'vertical_threshold_complete',
329      label: 'Complete when overscrolled (vertical)',
330      units: '%'
331    },
332    {
333      key: 'minimum_threshold_start',
334      label: 'Start overscroll gesture (horizontal)',
335      units: 'pixels'
336    },
337    {
338      key: 'vertical_threshold_start',
339      label: 'Start overscroll gesture (vertical)',
340      units: 'pixels'
341    },
342    {
343      key: 'horizontal_resist_threshold',
344      label: 'Start resisting overscroll after (horizontal)',
345      units: 'pixels'
346    },
347    {
348      key: 'vertical_resist_threshold',
349      label: 'Start resisting overscroll after (vertical)',
350      units: 'pixels'
351    },
352  ];
353
354  return new GeneralConfig(OVERSCROLL_TITLE,
355                           OVERSCROLL_PREFIX,
356                           OVERSCROLL_FIELDS);
357}
358
359/**
360 * Returns a GeneralConfig for configuring immersive.* preferences for
361 * immersive fullscreen in Ash.
362 * @return {object} A GeneralConfig object.
363 */
364function ImmersiveConfig() {
365  /** @const */ var IMMERSIVE_TITLE = 'Immersive Fullscreen Configuration';
366
367  /** @const */ var IMMERSIVE_PREFIX = 'immersive_mode.';
368
369  var IMMERSIVE_FIELDS = [
370    {
371      key: 'reveal_delay_ms',
372      label: 'Top-of-screen reveal delay',
373      units: 'milliseconds'
374    },
375    {
376      key: 'reveal_x_threshold_pixels',
377      label: 'Top-of-screen mouse x threshold',
378      units: 'pixels'
379    },
380  ];
381
382  return new GeneralConfig(IMMERSIVE_TITLE,
383                           IMMERSIVE_PREFIX,
384                           IMMERSIVE_FIELDS);
385}
386
387/**
388 * Returns a GeneralConfig for configuring flingcurve.* preferences.
389 * @return {object} A GeneralConfig object.
390 */
391function FlingConfig() {
392  /** @const */ var FLING_TITLE = 'Fling Configuration';
393
394  /** @const */ var FLING_PREFIX = 'flingcurve.';
395
396  var FLING_FIELDS = [
397    {
398      key: 'touchscreen_alpha',
399      label: 'Touchscreen fling deacceleration coefficients',
400      units: 'alpha',
401      min: '-inf'
402    },
403    {
404      key: 'touchscreen_beta',
405      label: '',
406      units: 'beta',
407      min: '-inf'
408    },
409    {
410      key: 'touchscreen_gamma',
411      label: '',
412      units: 'gamma',
413      min: '-inf'
414    },
415    {
416      key: 'touchpad_alpha',
417      label: 'Touchpad fling deacceleration coefficients',
418      units: 'alpha',
419      min: '-inf'
420    },
421    {
422      key: 'touchpad_beta',
423      label: '',
424      units: 'beta',
425      min: '-inf'
426    },
427    {
428      key: 'touchpad_gamma',
429      label: '',
430      units: 'gamma',
431      min: '-inf'
432    },
433  ];
434
435  return new GeneralConfig(FLING_TITLE, FLING_PREFIX, FLING_FIELDS);
436}
437
438/**
439 * WebUI instance for configuring preference values related to gesture input.
440 */
441window.gesture_config = {
442  /**
443   * Build and initialize the gesture configuration form.
444   */
445  initialize: function() {
446    var g = GestureConfig();
447    g.buildAll();
448
449    var o = OverscrollConfig();
450    o.buildAll();
451
452    var f = FlingConfig();
453    f.buildAll();
454
455    var i = ImmersiveConfig();
456    i.buildAll();
457
458    $('reset-all-button').onclick = function() {
459      g.onReset();
460      o.onReset();
461      f.onReset();
462      i.onReset();
463    };
464  },
465
466  /**
467   * Checks if all gesture preferences are set to default by checking the status
468   * of the reset button associated with each preference.
469   * @return {boolean} True if all gesture preferences are set to default.
470   */
471  areAllPrefsSetToDefault: function() {
472    var resets = $('gesture-form').querySelectorAll('.row-reset');
473    for (var i = 0; i < resets.length; i++) {
474      if (!resets[i].disabled)
475        return false;
476    }
477    return true;
478  },
479
480  /**
481   * Updates the status and label of a preference reset button.
482   * @param {HTMLInputElement} resetButton Reset button for the preference.
483   * @param {boolean} isDefault Whether the preference is set to the default
484   *     value.
485   */
486  updateResetButton: function(resetButton, isDefault) {
487    /** @const */ var TITLE_DEFAULT = 'Default';
488
489    /** @const */ var TITLE_NOT_DEFAULT = 'Reset';
490
491    resetButton.innerHTML = isDefault ? TITLE_DEFAULT : TITLE_NOT_DEFAULT;
492    resetButton.disabled = isDefault;
493  },
494
495  /**
496   * Updates the status and label of "Reset All" button.
497   * @param {boolean} isDefault Whether all preference are set to their default
498   *     values.
499   */
500  updateResetAllButton: function(isDefault) {
501    /** @const */ var TITLE_DEFAULT = 'Everything is set to default';
502
503    /** @const */ var TITLE_NOT_DEFAULT = 'Reset All To Default';
504
505    var button = $('reset-all-button');
506    button.innerHTML = isDefault ? TITLE_DEFAULT : TITLE_NOT_DEFAULT;
507    button.disabled = isDefault;
508  },
509
510  /**
511   * Handle callback from call to updatePreferenceValue.
512   * @param {string} prefName The name of the requested preference value.
513   * @param {value} value The current value associated with prefName.
514   * @param {boolean} isDefault Whether the value is the default value.
515   */
516  updatePreferenceValueResult: function(prefName, value, isDefault) {
517    prefName = prefName.substring(prefName.indexOf('.') + 1);
518    $(prefName).value = value;
519    this.updateResetButton($(prefName + '-reset'), isDefault);
520    this.updateResetAllButton(this.areAllPrefsSetToDefault());
521  },
522};
523
524document.addEventListener('DOMContentLoaded', gesture_config.initialize);
525