12da489cd246702bee5938545b18a6f710ed214bcJamie Gennis// Copyright (c) 2012 The Chromium Authors. All rights reserved.
22da489cd246702bee5938545b18a6f710ed214bcJamie Gennis// Use of this source code is governed by a BSD-style license that can be
32da489cd246702bee5938545b18a6f710ed214bcJamie Gennis// found in the LICENSE file.
42da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
52da489cd246702bee5938545b18a6f710ed214bcJamie Gennis'use strict';
62da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
72da489cd246702bee5938545b18a6f710ed214bcJamie Gennis/**
82da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * @fileoverview Interactive visualizaiton of TimelineModel objects
92da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * based loosely on gantt charts. Each thread in the TimelineModel is given a
102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * set of TimelineTracks, one per subrow in the thread. The Timeline class
112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * acts as a controller, creating the individual tracks, while TimelineTracks
122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * do actual drawing.
132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis *
142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * Visually, the Timeline produces (prettier) visualizations like the following:
152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis *    Thread1:  AAAAAAAAAA         AAAAA
162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis *                  BBBB              BB
172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis *    Thread2:     CCCCCC                 CCCCC
182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis *
192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis */
202da489cd246702bee5938545b18a6f710ed214bcJamie Gennisbase.requireStylesheet('timeline');
212da489cd246702bee5938545b18a6f710ed214bcJamie Gennisbase.require('event_target');
222da489cd246702bee5938545b18a6f710ed214bcJamie Gennisbase.require('measuring_stick');
232da489cd246702bee5938545b18a6f710ed214bcJamie Gennisbase.require('timeline_filter');
242da489cd246702bee5938545b18a6f710ed214bcJamie Gennisbase.require('timeline_selection');
252da489cd246702bee5938545b18a6f710ed214bcJamie Gennisbase.require('timeline_viewport');
262da489cd246702bee5938545b18a6f710ed214bcJamie Gennisbase.require('tracks.timeline_model_track');
272da489cd246702bee5938545b18a6f710ed214bcJamie Gennisbase.require('tracks.timeline_viewport_track');
282da489cd246702bee5938545b18a6f710ed214bcJamie Gennisbase.require('ui');
292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
302da489cd246702bee5938545b18a6f710ed214bcJamie Gennisbase.exportTo('tracing', function() {
312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  var TimelineSelection = tracing.TimelineSelection;
332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  var TimelineViewport = tracing.TimelineViewport;
342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  function intersectRect_(r1, r2) {
362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    var results = new Object;
372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if (r2.left > r1.right || r2.right < r1.left ||
382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis         r2.top > r1.bottom || r2.bottom < r1.top) {
392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      return false;
402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    }
412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    results.left = Math.max(r1.left, r2.left);
422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    results.top = Math.max(r1.top, r2.top);
432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    results.right = Math.min(r1.right, r2.right);
442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    results.bottom = Math.min(r1.bottom, r2.bottom);
452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    results.width = (results.right - results.left);
462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    results.height = (results.bottom - results.top);
472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    return results;
482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  }
492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  /**
512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis   * Renders a TimelineModel into a div element, making one
522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis   * TimelineTrack for each subrow in each thread of the model, managing
532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis   * overall track layout, and handling user interaction with the
542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis   * viewport.
552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis   *
562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis   * @constructor
572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis   * @extends {HTMLDivElement}
582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis   */
592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  var Timeline = base.ui.define('div');
602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  Timeline.prototype = {
622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    __proto__: HTMLDivElement.prototype,
632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    model_: null,
652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    decorate: function() {
672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.classList.add('timeline');
682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.categoryFilter_ = new tracing.TimelineCategoryFilter();
702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.viewport_ = new TimelineViewport(this);
722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      // Add the viewport track.
742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.viewportTrack_ = new tracks.TimelineViewportTrack();
752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.viewportTrack_.viewport = this.viewport_;
762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.appendChild(this.viewportTrack_);
772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.modelTrackContainer_ = document.createElement('div');
792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.modelTrackContainer_.className = 'timeline-model-track-container';
802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.appendChild(this.modelTrackContainer_);
812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.modelTrack_ = new tracks.TimelineModelTrack();
832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.modelTrackContainer_.appendChild(this.modelTrack_);
842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.dragBox_ = this.ownerDocument.createElement('div');
862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.dragBox_.className = 'timeline-drag-box';
872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.appendChild(this.dragBox_);
882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.hideDragBox_();
892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.bindEventListener_(document, 'keypress', this.onKeypress_, this);
912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.bindEventListener_(document, 'keydown', this.onKeydown_, this);
922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.bindEventListener_(document, 'keyup', this.onKeyup_, this);
932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.bindEventListener_(document, 'mousemove', this.onMouseMove_, this);
942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.bindEventListener_(document, 'mouseup', this.onMouseUp_, this);
952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.addEventListener('mousewheel', this.onMouseWheel_);
972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.addEventListener('mousedown', this.onMouseDown_);
982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.addEventListener('dblclick', this.onDblClick_);
992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.lastMouseViewPos_ = {x: 0, y: 0};
1012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.maxHeadingWidth_ = 0;
1022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.selection_ = new TimelineSelection();
1042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    },
1052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    /**
1072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis     * Wraps the standard addEventListener but automatically binds the provided
1082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis     * func to the provided target, tracking the resulting closure. When detach
1092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis     * is called, these listeners will be automatically removed.
1102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis     */
1112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    bindEventListener_: function(object, event, func, target) {
1122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (!this.boundListeners_)
1132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        this.boundListeners_ = [];
1142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var boundFunc = func.bind(target);
1152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.boundListeners_.push({object: object,
1162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        event: event,
1172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        boundFunc: boundFunc});
1182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      object.addEventListener(event, boundFunc);
1192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    },
1202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    detach: function() {
1222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.modelTrack_.detach();
1232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      for (var i = 0; i < this.boundListeners_.length; i++) {
1252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        var binding = this.boundListeners_[i];
1262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        binding.object.removeEventListener(binding.event, binding.boundFunc);
1272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      }
1282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.boundListeners_ = undefined;
1292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.viewport_.detach();
1302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    },
1312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    get viewport() {
1332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      return this.viewport_;
1342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    },
1352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    get categoryFilter() {
1372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      return this.categoryFilter_;
1382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    },
1392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    set categoryFilter(filter) {
1412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.categoryFilter_ = filter;
1422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.modelTrack_.categoryFilter = filter;
1432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    },
1442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    get model() {
1462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      return this.model_;
1472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    },
1482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    set model(model) {
1502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (!model)
1512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        throw new Error('Model cannot be null');
1522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var modelInstanceChanged = this.model_ != model;
1542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.model_ = model;
1552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.modelTrack_.model = model;
1562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.modelTrack_.viewport = this.viewport_;
1572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.modelTrack_.categoryFilter = this.categoryFilter;
1582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.viewportTrack_.headingWidth = this.modelTrack_.headingWidth;
1592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      // Set up a reasonable viewport.
1612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (modelInstanceChanged)
1622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        this.viewport_.setWhenPossible(this.setInitialViewport_.bind(this));
1632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    },
1642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    get numVisibleTracks() {
1662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      return this.modelTrack_.numVisibleTracks;
1672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    },
1682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    setInitialViewport_: function() {
1702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var w = this.firstCanvas.width;
1712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var boost =
1722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          (this.model_.maxTimestamp - this.model_.minTimestamp) * 0.15;
1732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.viewport_.xSetWorldRange(this.model_.minTimestamp - boost,
1742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                                    this.model_.maxTimestamp + boost,
1752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                                    w);
1762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    },
1772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    /**
1792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis     * @param {TimelineFilter} filter The filter to use for finding matches.
1802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis     * @param {TimelineSelection} selection The selection to add matches to.
1812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis     * @return {Array} An array of objects that match the provided
1822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis     * TimelineTitleFilter.
1832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis     */
1842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    addAllObjectsMatchingFilterToSelection: function(filter, selection) {
1852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.modelTrack_.addAllObjectsMatchingFilterToSelection(filter,
1862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                                                              selection);
1872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    },
1882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    /**
1902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis     * @return {Element} The element whose focused state determines
1912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis     * whether to respond to keyboard inputs.
1922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis     * Defaults to the parent element.
1932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis     */
1942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    get focusElement() {
1952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (this.focusElement_)
1962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return this.focusElement_;
1972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      return this.parentElement;
1982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    },
1992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    /**
2012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis     * Sets the element whose focus state will determine whether
2022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis     * to respond to keybaord input.
2032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis     */
2042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    set focusElement(value) {
2052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.focusElement_ = value;
2062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    },
2072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    get listenToKeys_() {
2092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (!this.viewport_.isAttachedToDocument_)
2102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return false;
2112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (this.activeElement instanceof tracing.TimelineFindControl)
2122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return false;
2132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (!this.focusElement_)
2142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return true;
2152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (this.focusElement.tabIndex >= 0)
2162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return document.activeElement == this.focusElement;
2172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      return true;
2182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    },
2192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    onKeypress_: function(e) {
2212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var vp = this.viewport_;
2222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (!this.firstCanvas)
2232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return;
2242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (!this.listenToKeys_)
2252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return;
2262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (document.activeElement.nodeName == 'INPUT')
2272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return;
2282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var viewWidth = this.firstCanvas.clientWidth;
2292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var curMouseV, curCenterW;
2302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      switch (e.keyCode) {
2312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        case 119:  // w
2322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        case 44:   // ,
2332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          this.zoomBy_(1.5);
2342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          break;
2352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        case 115:  // s
2362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        case 111:  // o
2372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          this.zoomBy_(1 / 1.5);
2382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          break;
2392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        case 103:  // g
2402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          this.onGridToggle_(true);
2412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          break;
2422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        case 71:  // G
2432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          this.onGridToggle_(false);
2442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          break;
2452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        case 87:  // W
2462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        case 60:  // <
2472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          this.zoomBy_(10);
2482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          break;
2492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        case 83:  // S
2502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        case 79:  // O
2512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          this.zoomBy_(1 / 10);
2522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          break;
2532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        case 97:  // a
2542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          vp.panX += vp.xViewVectorToWorld(viewWidth * 0.1);
2552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          break;
2562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        case 100:  // d
2572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        case 101:  // e
2582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          vp.panX -= vp.xViewVectorToWorld(viewWidth * 0.1);
2592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          break;
2602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        case 65:  // A
2612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          vp.panX += vp.xViewVectorToWorld(viewWidth * 0.5);
2622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          break;
2632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        case 68:  // D
2642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          vp.panX -= vp.xViewVectorToWorld(viewWidth * 0.5);
2652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          break;
2662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        case 48:  // 0
2672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        case 122: // z
2682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          this.setInitialViewport_();
2692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          break;
2702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        case 102:  // f
2712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          this.zoomToSelection_();
2722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          break;
2732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      }
2742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    },
2752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    onMouseWheel_: function(e) {
2772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (e.altKey) {
2782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        var delta = e.wheelDeltaY / 120;
2792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        var zoomScale = Math.pow(1.5, delta);
2802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        this.zoomBy_(zoomScale);
2812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        e.preventDefault();
2822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      }
2832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    },
2842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    // Not all keys send a keypress.
2862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    onKeydown_: function(e) {
2872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (!this.listenToKeys_)
2882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return;
2892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var sel;
2902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var vp = this.viewport_;
2912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var viewWidth = this.firstCanvas.clientWidth;
2922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      switch (e.keyCode) {
2932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        case 37:   // left arrow
2942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          sel = this.selection.getShiftedSelection(-1);
2952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          if (sel) {
2962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            this.setSelectionAndMakeVisible(sel);
2972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            e.preventDefault();
2982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          } else {
2992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            if (!this.firstCanvas)
3002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              return;
3012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            vp.panX += vp.xViewVectorToWorld(viewWidth * 0.1);
3022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          }
3032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          break;
3042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        case 39:   // right arrow
3052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          sel = this.selection.getShiftedSelection(1);
3062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          if (sel) {
3072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            this.setSelectionAndMakeVisible(sel);
3082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            e.preventDefault();
3092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          } else {
3102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            if (!this.firstCanvas)
3112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              return;
3122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            vp.panX -= vp.xViewVectorToWorld(viewWidth * 0.1);
3132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          }
3142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          break;
3152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        case 9:    // TAB
3162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          if (this.focusElement.tabIndex == -1) {
3172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            if (e.shiftKey)
3182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              this.selectPrevious_(e);
3192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            else
3202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              this.selectNext_(e);
3212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            e.preventDefault();
3222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          }
3232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          break;
3242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      }
3252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (e.shiftKey && this.dragBeginEvent_) {
3262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          var vertical = e.shiftKey;
3272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          if (this.dragBeginEvent_) {
3282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            this.setDragBoxPosition_(this.dragBoxXStart_, this.dragBoxYStart_,
3292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                               this.dragBoxXEnd_, this.dragBoxYEnd_, vertical);
3302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          }
3312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      }
3322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    },
3332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    onKeyup_: function(e) {
3352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (!this.listenToKeys_)
3362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return;
3372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (!e.shiftKey) {
3382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if (this.dragBeginEvent_) {
3392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          var vertical = e.shiftKey;
3402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          this.setDragBoxPosition_(this.dragBoxXStart_, this.dragBoxYStart_,
3412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                                this.dragBoxXEnd_, this.dragBoxYEnd_, vertical);
3422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          }
3432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      }
3442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    },
3452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    /**
3472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis     * Zoom in or out on the timeline by the given scale factor.
3482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis     * @param {integer} scale The scale factor to apply.  If <1, zooms out.
3492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis     */
3502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    zoomBy_: function(scale) {
3512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (!this.firstCanvas)
3522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return;
3532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var vp = this.viewport_;
3542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var viewWidth = this.firstCanvas.clientWidth;
3552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var curMouseV = this.lastMouseViewPos_.x;
3562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var curCenterW = vp.xViewToWorld(curMouseV);
3572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      vp.scaleX = vp.scaleX * scale;
3582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      vp.xPanWorldPosToViewPos(curCenterW, curMouseV, viewWidth);
3592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    },
3602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    /**
3622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis     * Zoom into the current selection.
3632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis     */
3642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    zoomToSelection_: function() {
3652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (!this.selection)
3662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return;
3672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var range = this.selection.range;
3682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var worldCenter = range.min + (range.max - range.min) * 0.5;
3692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var worldRange = (range.max - range.min) * 0.5;
3702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var boost = worldRange * 0.15;
3712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.viewport_.xSetWorldRange(worldCenter - worldRange - boost,
3722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                                    worldCenter + worldRange + boost,
3732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                                    this.firstCanvas.width);
3742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    },
3752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    get keyHelp() {
3772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var mod = navigator.platform.indexOf('Mac') == 0 ? 'cmd' : 'ctrl';
3782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var help = 'Qwerty Controls\n' +
3792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          ' w/s           : Zoom in/out    (with shift: go faster)\n' +
3802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          ' a/d           : Pan left/right\n\n' +
3812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          'Dvorak Controls\n' +
3822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          ' ,/o           : Zoom in/out     (with shift: go faster)\n' +
3832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          ' a/e           : Pan left/right\n\n' +
3842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          'Mouse Controls\n' +
3852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          ' drag          : Select slices   (with ' + mod +
3862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                                                        ': zoom to slices)\n' +
3872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          ' drag + shift  : Select all slices vertically\n\n';
3882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (this.focusElement.tabIndex) {
3902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        help +=
3912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          ' <-            : Select previous event on current timeline\n' +
3922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          ' ->            : Select next event on current timeline\n';
3932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      } else {
3942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        help += 'General Navigation\n' +
3952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          ' g/General     : Shows grid at the start/end of the selected' +
3962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                                                                  ' task\n' +
3972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          ' <-,^TAB       : Select previous event on current timeline\n' +
3982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          ' ->, TAB       : Select next event on current timeline\n';
3992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      }
4002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      help +=
4012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          '\n' +
4022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          'Alt + Scroll to zoom in/out\n' +
4032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          'Dbl-click to zoom in; Shift dbl-click to zoom out\n' +
4042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          'f to zoom into selection\n' +
4052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          'z to reset zoom and pan to initial view\n';
4062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      return help;
4072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    },
4082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    get selection() {
4102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      return this.selection_;
4112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    },
4122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    set selection(selection) {
4142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (!(selection instanceof TimelineSelection))
4152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        throw new Error('Expected TimelineSelection');
4162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      // Clear old selection.
4182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var i;
4192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      for (i = 0; i < this.selection_.length; i++)
4202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        this.selection_[i].selected = false;
4212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.selection_ = selection;
4232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      base.dispatchSimpleEvent(this, 'selectionChange');
4252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      for (i = 0; i < this.selection_.length; i++)
4262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        this.selection_[i].selected = true;
4272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.viewport_.dispatchChangeEvent(); // Triggers a redraw.
4282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    },
4292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    setSelectionAndMakeVisible: function(selection, zoomAllowed) {
4312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (!(selection instanceof TimelineSelection))
4322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        throw new Error('Expected TimelineSelection');
4332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.selection = selection;
4342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var range = this.selection.range;
4352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var size = this.viewport_.xWorldVectorToView(range.max - range.min);
4362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (zoomAllowed && size < 50) {
4372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        var worldCenter = range.min + (range.max - range.min) * 0.5;
4382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        var worldRange = (range.max - range.min) * 5;
4392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        this.viewport_.xSetWorldRange(worldCenter - worldRange * 0.5,
4402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                                      worldCenter + worldRange * 0.5,
4412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                                      this.firstCanvas.width);
4422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return;
4432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      }
4442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.viewport_.xPanWorldRangeIntoView(range.min, range.max,
4462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                                            this.firstCanvas.width);
4472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    },
4482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    get firstCanvas() {
4502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (this.viewportTrack_)
4512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return this.viewportTrack_.firstCanvas;
4522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (this.modelTrack_)
4532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return this.modelTrack_.firstCanvas;
4542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      return undefined;
4552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    },
4562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    hideDragBox_: function() {
4582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.dragBox_.style.left = '-1000px';
4592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.dragBox_.style.top = '-1000px';
4602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.dragBox_.style.width = 0;
4612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.dragBox_.style.height = 0;
4622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    },
4632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    setDragBoxPosition_: function(xStart, yStart, xEnd, yEnd, vertical) {
4652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var loY;
4662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var hiY;
4672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var loX = Math.min(xStart, xEnd);
4682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var hiX = Math.max(xStart, xEnd);
4692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var modelTrackRect = this.modelTrack_.getBoundingClientRect();
4702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (vertical) {
4722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        loY = modelTrackRect.top;
4732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        hiY = modelTrackRect.bottom;
4742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      } else {
4752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        loY = Math.min(yStart, yEnd);
4762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        hiY = Math.max(yStart, yEnd);
4772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      }
4782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var dragRect = {left: loX, top: loY, width: hiX - loX, height: hiY - loY};
4802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      dragRect.right = dragRect.left + dragRect.width;
4812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      dragRect.bottom = dragRect.top + dragRect.height;
4822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var modelTrackContainerRect =
4832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                              this.modelTrackContainer_.getBoundingClientRect();
4842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var clipRect = {
4852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        left: modelTrackContainerRect.left,
4862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        top: modelTrackContainerRect.top,
4872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        right: modelTrackContainerRect.right,
4882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        bottom: modelTrackContainerRect.bottom,
4892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      };
4902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var trackTitleWidth = parseInt(this.modelTrack_.headingWidth);
4912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      clipRect.left = clipRect.left + trackTitleWidth;
4922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var finalDragBox = intersectRect_(clipRect, dragRect);
4942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.dragBox_.style.left = finalDragBox.left + 'px';
4962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.dragBox_.style.width = finalDragBox.width + 'px';
4972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.dragBox_.style.top = finalDragBox.top + 'px';
4982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.dragBox_.style.height = finalDragBox.height + 'px';
4992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
5002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var canv = this.firstCanvas;
5012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var loWX = this.viewport_.xViewToWorld(loX - canv.offsetLeft);
5022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var hiWX = this.viewport_.xViewToWorld(hiX - canv.offsetLeft);
5032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
5042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var roundedDuration = Math.round((hiWX - loWX) * 100) / 100;
5052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.dragBox_.textContent = roundedDuration + 'ms';
5062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
5072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var e = new base.Event('selectionChanging');
5082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      e.loWX = loWX;
5092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      e.hiWX = hiWX;
5102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.dispatchEvent(e);
5112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    },
5122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
5132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    onGridToggle_: function(left) {
5142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var tb;
5152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (left)
5162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        tb = this.selection_.range.min;
5172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      else
5182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        tb = this.selection_.range.max;
5192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
5202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      // Shift the timebase left until its just left of minTimestamp.
5212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var numInterfvalsSinceStart = Math.ceil((tb - this.model_.minTimestamp) /
5222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          this.viewport_.gridStep_);
5232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.viewport_.gridTimebase = tb -
5242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          (numInterfvalsSinceStart + 1) * this.viewport_.gridStep_;
5252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.viewport_.gridEnabled = true;
5262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    },
5272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
5282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    isChildOfThis_: function(el) {
5292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (el == this)
5302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return;
5312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
5322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var isChildOfThis = false;
5332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var cur = el;
5342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      while (cur.parentNode) {
5352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if (cur == this)
5362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          return true;
5372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        cur = cur.parentNode;
5382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      }
5392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      return false;
5402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    },
5412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
5422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    onMouseDown_: function(e) {
5432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (e.button !== 0)
5442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return;
5452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
5462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (e.shiftKey) {
5472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        this.viewportTrack_.placeAndBeginDraggingMarker(e.clientX);
5482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return;
5492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      }
5502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
5512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var canv = this.firstCanvas;
5522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var rect = this.modelTrack_.getBoundingClientRect();
5532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var canvRect = this.firstCanvas.getBoundingClientRect();
5542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
5552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var inside = rect &&
5562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          e.clientX >= rect.left &&
5572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          e.clientX < rect.right &&
5582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          e.clientY >= rect.top &&
5592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          e.clientY < rect.bottom &&
5602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          e.clientX >= canvRect.left &&
5612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          e.clientX < canvRect.right;
5622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
5632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (!inside)
5642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return;
5652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
5662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var pos = {
5672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        x: e.clientX - canv.offsetLeft,
5682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        y: e.clientY - canv.offsetTop
5692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      };
5702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
5712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var wX = this.viewport_.xViewToWorld(pos.x);
5722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
5732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.dragBeginEvent_ = e;
5742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      e.preventDefault();
5752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (document.activeElement)
5762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        document.activeElement.blur();
5772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (this.focusElement.tabIndex >= 0)
5782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        this.focusElement.focus();
5792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    },
5802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
5812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    onMouseMove_: function(e) {
5822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (!this.firstCanvas)
5832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return;
5842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var canv = this.firstCanvas;
5852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var pos = {
5862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        x: e.clientX - canv.offsetLeft,
5872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        y: e.clientY - canv.offsetTop
5882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      };
5892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
5902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      // Remember position. Used during keyboard zooming.
5912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.lastMouseViewPos_ = pos;
5922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
5932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      // Update the drag box
5942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (this.dragBeginEvent_) {
5952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        this.dragBoxXStart_ = this.dragBeginEvent_.clientX;
5962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        this.dragBoxXEnd_ = e.clientX;
5972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        this.dragBoxYStart_ = this.dragBeginEvent_.clientY;
5982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        this.dragBoxYEnd_ = e.clientY;
5992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        var vertical = e.shiftKey;
6002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        this.setDragBoxPosition_(this.dragBoxXStart_, this.dragBoxYStart_,
6012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                                this.dragBoxXEnd_, this.dragBoxYEnd_, vertical);
6022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      }
6032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    },
6042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
6052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    onMouseUp_: function(e) {
6062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var i;
6072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (this.dragBeginEvent_) {
6082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        // Stop the dragging.
6092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        this.hideDragBox_();
6102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        var eDown = this.dragBeginEvent_;
6112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        this.dragBeginEvent_ = null;
6122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
6132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        // Figure out extents of the drag.
6142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        var loY;
6152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        var hiY;
6162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        var loX = Math.min(eDown.clientX, e.clientX);
6172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        var hiX = Math.max(eDown.clientX, e.clientX);
6182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        var tracksContainer = this.modelTrackContainer_.getBoundingClientRect();
6192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        var topBoundary = tracksContainer.height;
6202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        var vertical = e.shiftKey;
6212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if (vertical) {
6222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          var modelTrackRect = this.modelTrack_.getBoundingClientRect();
6232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          loY = modelTrackRect.top;
6242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          hiY = modelTrackRect.bottom;
6252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        } else {
6262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          loY = Math.min(eDown.clientY, e.clientY);
6272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          hiY = Math.max(eDown.clientY, e.clientY);
6282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        }
6292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
6302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        // Convert to worldspace.
6312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        var canv = this.firstCanvas;
6322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        var loVX = loX - canv.offsetLeft;
6332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        var hiVX = hiX - canv.offsetLeft;
6342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
6352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        // Figure out what has been hit.
6362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        var selection = new TimelineSelection();
6372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        this.modelTrack_.addIntersectingItemsInRangeToSelection(
6382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            loVX, hiVX, loY, hiY, selection);
6392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
6402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        // Activate the new selection, and zoom if ctrl key held down.
6412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        this.selection = selection;
6422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        var isMac = navigator.platform.indexOf('Mac') == 0;
6432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if ((isMac && e.metaKey) || (!isMac && e.ctrlKey)) {
6442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          this.zoomToSelection_();
6452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        }
6462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      }
6472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    },
6482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
6492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    onDblClick_: function(e) {
6502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var canv = this.firstCanvas;
6512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
6522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      var scale = 4;
6532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (e.shiftKey)
6542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        scale = 1 / scale;
6552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      this.zoomBy_(scale);
6562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      e.preventDefault();
6572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    }
6582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  };
6592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
6602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  /**
6612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis   * The TimelineModel being viewed by the timeline
6622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis   * @type {TimelineModel}
6632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis   */
6642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  base.defineProperty(Timeline, 'model', base.PropertyKind.JS);
6652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
6662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  return {
6672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Timeline: Timeline
6682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  };
6692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis});
670