Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(48)

Side by Side Diff: chrome/browser/resources/tracing/timeline.js

Issue 8513009: Add TRACE_COUNTER support to about:tracing (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 9 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 /** 5 /**
6 * @fileoverview Interactive visualizaiton of TimelineModel objects 6 * @fileoverview Interactive visualizaiton of TimelineModel objects
7 * based loosely on gantt charts. Each thread in the TimelineModel is given a 7 * based loosely on gantt charts. Each thread in the TimelineModel is given a
8 * set of TimelineTracks, one per subrow in the thread. The Timeline class 8 * set of TimelineTracks, one per subrow in the thread. The Timeline class
9 * acts as a controller, creating the individual tracks, while TimelineTracks 9 * acts as a controller, creating the individual tracks, while TimelineTracks
10 * do actual drawing. 10 * do actual drawing.
(...skipping 12 matching lines...) Expand all
23 * x' = (x+pan) * scale 23 * x' = (x+pan) * scale
24 * 24 *
25 * The timeline code tries to avoid directly accessing this transform, 25 * The timeline code tries to avoid directly accessing this transform,
26 * instead using this class to do conversion between world and view space, 26 * instead using this class to do conversion between world and view space,
27 * as well as the math for centering the viewport in various interesting 27 * as well as the math for centering the viewport in various interesting
28 * ways. 28 * ways.
29 * 29 *
30 * @constructor 30 * @constructor
31 * @extends {cr.EventTarget} 31 * @extends {cr.EventTarget}
32 */ 32 */
33 function TimelineViewport() { 33 function TimelineViewport(parentEl) {
34 if (!parentEl)
35 throw 'parentEl must be provided';
James Hawkins 2011/11/16 17:41:38 In general we don't check parameters like this.
nduca 2011/11/16 19:03:56 Done.
36 this.parentEl_ = parentEl;
34 this.scaleX_ = 1; 37 this.scaleX_ = 1;
35 this.panX_ = 0; 38 this.panX_ = 0;
36 this.gridTimebase_ = 0; 39 this.gridTimebase_ = 0;
37 this.gridStep_ = 1000 / 60; 40 this.gridStep_ = 1000 / 60;
38 this.gridEnabled_ = false; 41 this.gridEnabled_ = false;
42 this.hasCalledSetupFunction_ = false;
43
44 this.onResizeBoundToThis_ = this.onResize_.bind(this);
James Hawkins 2011/11/16 17:41:38 Hmm I'm not a fan of this construct. You lose rea
nduca 2011/11/16 19:03:56 Sorry, what's the alternative? I need to remove th
45
46 // The following code uses an interval to detect when the parent element
47 // is attached to the document. That is a trigger to run the setup function
48 // and install a resize listener.
49 this.checkForAttachInterval_ = setInterval(
50 this.checkForAttach_.bind(this), 250);
39 } 51 }
40 52
41 TimelineViewport.prototype = { 53 TimelineViewport.prototype = {
42 __proto__: cr.EventTarget.prototype, 54 __proto__: cr.EventTarget.prototype,
43 55
56 /**
57 * Allows initialization of the viewport when the viewport's parent element
58 * has been attached to the document and given a size.
59 * @param {Function} fn Function to call when the viewport can be safely
60 * initialized.
61 */
62 setWhenPossible: function(fn) {
63 this.pendingSetFunction_ = fn;
64 },
65
James Hawkins 2011/11/16 17:41:38 Remove extra blank line.
nduca 2011/11/16 19:03:56 Done.
66
67 /**
68 * @return {boolean} Whether the current timeline is attached to the
69 * document.
70 */
71 get isAttachedToDocument_() {
72 var cur = this.parentEl_;
73 while (cur.parentNode)
74 cur = cur.parentNode;
75 return cur == this.parentEl_.ownerDocument;
76 },
77
78 onResize_: function() {
79 cr.dispatchSimpleEvent(this, 'change');
James Hawkins 2011/11/16 17:41:38 You have this as fireChanged() below; probably goo
nduca 2011/11/16 19:03:56 Done.
80 },
81
82 checkForAttach_: function() {
83 if (!this.isAttachedToDocument_ || this.clientWidth == 0)
84 return;
85
86 if (!this.iframe_) {
87 this.iframe_ = document.createElement('iframe');
88 this.iframe_.style.cssText =
89 'position:absolute;width:100%;height:0;border:0;visibility:hidden;';
90 this.parentEl_.appendChild(this.iframe_);
91
92 this.iframe_.contentWindow.addEventListener('resize',
93 this.onResizeBoundToThis_);
94 }
95
96 var curSize = this.clientWidth + 'x' + this.clientHeight;
97 if (this.pendingSetFunction_) {
98 this.lastSize_ = curSize;
99 try {
100 this.pendingSetFunction_();
James Hawkins 2011/11/16 17:41:38 Are you expecting this to fail in some way?
nduca 2011/11/16 19:03:56 Done.
101 } finally {
102 this.pendingSetFunction_ = undefined;
103 }
104 }
105
106 window.clearInterval(this.checkForAttachInterval_);
107 this.checkForAttachInterval_ = undefined;
108 },
109
110 /**
111 * Fires the change event on this viewport. Used to notify listeners
112 * to redraw when the underlying model has been mutated.
113 */
114 fireChanged: function() {
115 cr.dispatchSimpleEvent(this, 'change');
116 },
117
118 detach: function() {
119 if (this.checkForAttachInterval_) {
120 window.clearInterval(this.checkForAttachInterval_);
121 this.checkForAttachInterval_ = undefined;
122 }
123 this.iframe_.removeListener('resize', this.onResizeBoundToThis_);
124 this.parentEl_.removeChild(this.iframe_);
125 },
126
44 get scaleX() { 127 get scaleX() {
45 return this.scaleX_; 128 return this.scaleX_;
46 }, 129 },
47 set scaleX(s) { 130 set scaleX(s) {
48 var changed = this.scaleX_ != s; 131 var changed = this.scaleX_ != s;
49 if (changed) { 132 if (changed) {
50 this.scaleX_ = s; 133 this.scaleX_ = s;
51 cr.dispatchSimpleEvent(this, 'change'); 134 cr.dispatchSimpleEvent(this, 'change');
52 } 135 }
53 }, 136 },
(...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after
145 */ 228 */
146 Timeline = cr.ui.define('div'); 229 Timeline = cr.ui.define('div');
147 230
148 Timeline.prototype = { 231 Timeline.prototype = {
149 __proto__: HTMLDivElement.prototype, 232 __proto__: HTMLDivElement.prototype,
150 233
151 model_: null, 234 model_: null,
152 235
153 decorate: function() { 236 decorate: function() {
154 this.classList.add('timeline'); 237 this.classList.add('timeline');
155 this.needsViewportReset_ = false;
156 238
157 this.viewport_ = new TimelineViewport(); 239 this.viewport_ = new TimelineViewport(this);
158 this.viewport_.addEventListener('change',
159 this.viewportChange_.bind(this));
160
161 this.invalidatePending_ = false;
162 240
163 this.tracks_ = this.ownerDocument.createElement('div'); 241 this.tracks_ = this.ownerDocument.createElement('div');
164 this.tracks_.invalidate = this.invalidate.bind(this);
165 this.appendChild(this.tracks_); 242 this.appendChild(this.tracks_);
166 243
167 this.dragBox_ = this.ownerDocument.createElement('div'); 244 this.dragBox_ = this.ownerDocument.createElement('div');
168 this.dragBox_.className = 'timeline-drag-box'; 245 this.dragBox_.className = 'timeline-drag-box';
169 this.appendChild(this.dragBox_); 246 this.appendChild(this.dragBox_);
170 this.hideDragBox_(); 247 this.hideDragBox_();
171 248
172 // The following code uses a setInterval to monitor the timeline control
173 // for size changes. This is so that we can keep the canvas' bitmap size
174 // correctly synchronized with its presentation size.
175 // TODO(nduca): detect this in a more efficient way, e.g. iframe hack.
176 this.lastSize_ = this.clientWidth + 'x' + this.clientHeight;
177 this.checkForResizeInterval_ =
178 this.ownerDocument.defaultView.setInterval(function() {
179 if (!this.isAttachedToDocument_)
180 return;
181 var curSize = this.clientWidth + 'x' + this.clientHeight;
182 if (this.clientWidth && curSize != this.lastSize_) {
183 this.lastSize_ = curSize;
184 this.onResize();
185 }
186 }.bind(this), 250);
187
188 this.bindEventListener_(document, 'keypress', this.onKeypress_, this); 249 this.bindEventListener_(document, 'keypress', this.onKeypress_, this);
189 this.bindEventListener_(document, 'keydown', this.onKeydown_, this); 250 this.bindEventListener_(document, 'keydown', this.onKeydown_, this);
190 this.bindEventListener_(document, 'mousedown', this.onMouseDown_, this); 251 this.bindEventListener_(document, 'mousedown', this.onMouseDown_, this);
191 this.bindEventListener_(document, 'mousemove', this.onMouseMove_, this); 252 this.bindEventListener_(document, 'mousemove', this.onMouseMove_, this);
192 this.bindEventListener_(document, 'mouseup', this.onMouseUp_, this); 253 this.bindEventListener_(document, 'mouseup', this.onMouseUp_, this);
193 this.bindEventListener_(document, 'dblclick', this.onDblClick_, this); 254 this.bindEventListener_(document, 'dblclick', this.onDblClick_, this);
194 255
195 this.lastMouseViewPos_ = {x: 0, y: 0}; 256 this.lastMouseViewPos_ = {x: 0, y: 0};
196 257
197 this.selection_ = []; 258 this.selection_ = [];
198 }, 259 },
199 260
200 /** 261 /**
201 * Wraps the standard addEventListener but automatically binds the provided 262 * Wraps the standard addEventListener but automatically binds the provided
202 * func to the provided target, tracking the resulting closure. When detach 263 * func to the provided target, tracking the resulting closure. When detach
203 * is called, these listeners will be automatically removed. 264 * is called, these listeners will be automatically removed.
204 */ 265 */
205 bindEventListener_: function(object, event, func, target) { 266 bindEventListener_: function(object, event, func, target) {
206 if (!this.boundListeners_) 267 if (!this.boundListeners_)
207 this.boundListeners_ = []; 268 this.boundListeners_ = [];
208 var boundFunc = func.bind(target); 269 var boundFunc = func.bind(target);
209 this.boundListeners_.push({object: object, 270 this.boundListeners_.push({object: object,
210 event: event, 271 event: event,
211 boundFunc: boundFunc}); 272 boundFunc: boundFunc});
212 object.addEventListener(event, boundFunc); 273 object.addEventListener(event, boundFunc);
213 }, 274 },
214 275
215 detach: function() { 276 detach: function() {
277 for (var i = 0; i < this.tracks_.children.length; ++i)
James Hawkins 2011/11/16 17:41:38 JS style is i++.
nduca 2011/11/16 19:03:56 Done.
278 this.tracks_.children[i].detach();
279
216 for (var i = 0; i < this.boundListeners_.length; i++) { 280 for (var i = 0; i < this.boundListeners_.length; i++) {
217 var binding = this.boundListeners_[i]; 281 var binding = this.boundListeners_[i];
218 binding.object.removeEventListener(binding.event, binding.boundFunc); 282 binding.object.removeEventListener(binding.event, binding.boundFunc);
219 } 283 }
220 this.boundListeners_ = undefined; 284 this.boundListeners_ = undefined;
221 window.clearInterval(this.checkForResizeInterval_); 285 this.viewport_.detach();
222 this.checkForResizeInterval_ = undefined; 286 },
287
288 get viewport() {
289 return this.viewport_;
223 }, 290 },
224 291
225 get model() { 292 get model() {
226 return this.model_; 293 return this.model_;
227 }, 294 },
228 295
229 set model(model) { 296 set model(model) {
230 if (!model) 297 if (!model)
231 throw Error('Model cannot be null'); 298 throw Error('Model cannot be null');
232 if (this.model) { 299 if (this.model) {
233 throw Error('Cannot set model twice.'); 300 throw Error('Cannot set model twice.');
234 } 301 }
235 this.model_ = model; 302 this.model_ = model;
236 303
237 // Create tracks and measure their heading size. 304 // Figure out all the headings.
238 var threads = model.getAllThreads(); 305 var allHeadings = [];
306 model.getAllThreads().forEach(function(t) {
307 allHeadings.push(t.userFriendlyName);
308 });
309 model.getAllCounters().forEach(function(c) {
310 allHeadings.push(c.userFriendlyName);
311 });
312
313 // Figure out the maximum heading size.
239 var maxHeadingWidth = 0; 314 var maxHeadingWidth = 0;
240 var tracks = [];
241 var measuringStick = new tracing.MeasuringStick(); 315 var measuringStick = new tracing.MeasuringStick();
242 var headingEl = document.createElement('div'); 316 var headingEl = document.createElement('div');
243 headingEl.style.position = 'fixed'; 317 headingEl.style.position = 'fixed';
244 headingEl.className = 'timeline-slice-track-title'; 318 headingEl.className = 'timeline-canvas-based-track-title';
245 for (var tI = 0; tI < threads.length; tI++) { 319 allHeadings.forEach(function(text) {
246 var thread = threads[tI]; 320 headingEl.textContent = text + ':__';
247 var track = new TimelineThreadTrack();
248 track.thread = thread;
249 track.viewport = this.viewport_;
250 tracks.push(track);
251 headingEl.textContent = track.heading;
252 var w = measuringStick.measure(headingEl).width; 321 var w = measuringStick.measure(headingEl).width;
253 // Limit heading width to 300px. 322 // Limit heading width to 300px.
254 if (w > 300) 323 if (w > 300)
255 w = 300; 324 w = 300;
256 if (w > maxHeadingWidth) 325 if (w > maxHeadingWidth)
257 maxHeadingWidth = w; 326 maxHeadingWidth = w;
258 } 327 });
259 var extraHeadingPadding = 4; 328 maxHeadingWidth = maxHeadingWidth + 'px';
260 maxHeadingWidth += maxHeadingWidth + extraHeadingPadding;
261 329
262 // Attach tracks and set width. 330 // Reset old tracks.
331 for (var i = 0; i < this.tracks_.children.length; ++i)
332 this.tracks_.children[i].detach();
263 this.tracks_.textContent = ''; 333 this.tracks_.textContent = '';
264 threads.sort(tracing.TimelineThread.compare);
265 for (var tI = 0; tI < tracks.length; tI++) {
266 var track = tracks[tI];
267 track.headingWidth = maxHeadingWidth + 'px';
268 this.tracks_.appendChild(track);
269 }
270 334
271 if (this.isAttachedToDocument_) 335 // Get a sorted list of processes.
272 this.onResize(); 336 var processes = [];
273 else 337 for (var pid in model.processes)
274 this.needsViewportReset_ = true; 338 processes.push(model.processes[pid]);
275 }, 339 processes.sort(tracing.TimelineProcess.compare);
276 340
277 viewportChange_: function() { 341 // Create tracks for each process.
278 this.invalidate(); 342 processes.forEach(function(process) {
279 }, 343 // Add counter tracks for this process.
344 var counters = [];
345 for (var tid in process.counters)
346 counters.push(process.counters[tid]);
347 counters.sort(tracing.TimelineCounter.compare);
280 348
281 invalidate: function() { 349 // Create the counters for this process.
282 if (this.invalidatePending_) 350 counters.forEach(function(counter) {
283 return; 351 var track = new tracing.TimelineCounterTrack();
284 this.invalidatePending_ = true; 352 track.heading = counter.name + ':';
285 if (this.isAttachedToDocument_) 353 track.headingWidth = maxHeadingWidth;
286 window.setTimeout(function() { 354 track.viewport = this.viewport_;
287 this.invalidatePending_ = false; 355 track.counter = counter;
288 this.redrawAllTracks_(); 356 this.tracks_.appendChild(track);
289 }.bind(this), 0); 357 }.bind(this));
290 },
291 358
292 /** 359 // Get a sorted list of threads.
293 * @return {boolean} Whether the current timeline is attached to the 360 var threads = [];
294 * document. 361 for (var tid in process.threads)
295 */ 362 threads.push(process.threads[tid]);
296 get isAttachedToDocument_() { 363 threads.sort(tracing.TimelineThread.compare);
297 var cur = this;
298 while (cur.parentNode)
299 cur = cur.parentNode;
300 return cur == this.ownerDocument;
301 },
302 364
303 onResize: function() { 365 // Create the threads.
304 if (!this.isAttachedToDocument_) 366 threads.forEach(function(thread) {
305 throw 'Not attached to document!'; 367 var track = new tracing.TimelineThreadTrack();
306 for (var i = 0; i < this.tracks_.children.length; i++) { 368 track.heading = thread.userFriendlyName + ':';
307 var track = this.tracks_.children[i]; 369 track.tooltip = thread.userFriendlyDetials;
308 track.onResize(); 370 track.headingWidth = maxHeadingWidth;
309 } 371 track.viewport = this.viewport_;
310 if (this.invalidatePending_) { 372 track.thread = thread;
311 this.invalidatePending_ = false; 373 this.tracks_.appendChild(track);
312 this.redrawAllTracks_(); 374 }.bind(this));
313 } 375 }.bind(this));
314 },
315 376
316 redrawAllTracks_: function() { 377 // Set up a reasonable viewport.
317 if (this.needsViewportReset_ && this.clientWidth != 0) { 378 this.viewport_.setWhenPossible(function() {
318 if (!this.isAttachedToDocument_)
319 throw 'Not attached to document!';
320 this.needsViewportReset_ = false;
321 /* update viewport */
322 var rangeTimestamp = this.model_.maxTimestamp - 379 var rangeTimestamp = this.model_.maxTimestamp -
323 this.model_.minTimestamp; 380 this.model_.minTimestamp;
324 var w = this.firstCanvas.width; 381 var w = this.firstCanvas.width;
325 var scaleX = w / rangeTimestamp; 382 var scaleX = w / rangeTimestamp;
326 var panX = -this.model_.minTimestamp; 383 var panX = -this.model_.minTimestamp;
327 this.viewport_.setPanAndScale(panX, scaleX); 384 this.viewport_.setPanAndScale(panX, scaleX);
328 } 385 }.bind(this));
329 for (var i = 0; i < this.tracks_.children.length; i++) {
330 this.tracks_.children[i].redraw();
331 }
332 }, 386 },
333 387
334 updateChildViewports_: function() { 388 /**
335 for (var cI = 0; cI < this.tracks_.children.length; cI++) { 389 * @return {Element} The element whose focused state determines
336 var child = this.tracks_.children[cI]; 390 * whether whether to respond to keyboard inputs.
337 child.setViewport(this.panX, this.scaleX); 391 * Defaults to the parent element.
338 } 392 */
393 get focusElement() {
394 if (this.focusElement_)
395 return this.focusElement_;
396 return this.parentElement;
397 },
398
399 /**
400 * Sets the element whose focus state will determine whether
401 * to respond to keybaord input.
402 */
403 set focusElement(value) {
404 this.focusElement_ = value;
339 }, 405 },
340 406
341 get listenToKeys_() { 407 get listenToKeys_() {
342 if (this.parentElement.parentElement.tabIndex >= 0) 408 if (!this.focusElement_)
343 return document.activeElement == this.parentElement.parentElement; 409 return true;
410 if (this.focusElement.tabIndex >= 0)
411 return document.activeElement == this.focusElement;
344 return true; 412 return true;
345 }, 413 },
346 414
347 onKeypress_: function(e) { 415 onKeypress_: function(e) {
348 var vp = this.viewport_; 416 var vp = this.viewport_;
349 if (!this.firstCanvas) 417 if (!this.firstCanvas)
350 return; 418 return;
351 if (!this.listenToKeys_) 419 if (!this.listenToKeys_)
352 return; 420 return;
353 var viewWidth = this.firstCanvas.clientWidth; 421 var viewWidth = this.firstCanvas.clientWidth;
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
401 switch (e.keyCode) { 469 switch (e.keyCode) {
402 case 37: // left arrow 470 case 37: // left arrow
403 this.selectPrevious_(e); 471 this.selectPrevious_(e);
404 e.preventDefault(); 472 e.preventDefault();
405 break; 473 break;
406 case 39: // right arrow 474 case 39: // right arrow
407 this.selectNext_(e); 475 this.selectNext_(e);
408 e.preventDefault(); 476 e.preventDefault();
409 break; 477 break;
410 case 9: // TAB 478 case 9: // TAB
411 if (this.parentElement.parentElement.tabIndex == -1) { 479 if (this.focusElement.tabIndex == -1) {
412 if (e.shiftKey) 480 if (e.shiftKey)
413 this.selectPrevious_(e); 481 this.selectPrevious_(e);
414 else 482 else
415 this.selectNext_(e); 483 this.selectNext_(e);
416 e.preventDefault(); 484 e.preventDefault();
417 } 485 }
418 break; 486 break;
419 } 487 }
420 }, 488 },
421 489
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after
461 slice = this.selection_[i].slice; 529 slice = this.selection_[i].slice;
462 if (slice) { 530 if (slice) {
463 if (forwardp) 531 if (forwardp)
464 adjoining = track.pickNext(slice); 532 adjoining = track.pickNext(slice);
465 else 533 else
466 adjoining = track.pickPrevious(slice); 534 adjoining = track.pickPrevious(slice);
467 } 535 }
468 if (adjoining != undefined) 536 if (adjoining != undefined)
469 selection.push({track: track, slice: adjoining}); 537 selection.push({track: track, slice: adjoining});
470 } 538 }
471 // Activate the new selection. 539 this.selection = selection;
472 this.selection_ = selection;
473 for (i = 0; i < this.selection_.length; i++)
474 this.selection_[i].slice.selected = true;
475 cr.dispatchSimpleEvent(this, 'selectionChange');
476 this.invalidate(); // Cause tracks to redraw.
477 e.preventDefault(); 540 e.preventDefault();
478 }, 541 },
479 542
480 get keyHelp() { 543 get keyHelp() {
481 var help = 'Keyboard shortcuts:\n' + 544 var help = 'Keyboard shortcuts:\n' +
482 ' w/s : Zoom in/out (with shift: go faster)\n' + 545 ' w/s : Zoom in/out (with shift: go faster)\n' +
483 ' a/d : Pan left/right\n' + 546 ' a/d : Pan left/right\n' +
484 ' e : Center on mouse\n' + 547 ' e : Center on mouse\n' +
485 ' g/G : Shows grid at the start/end of the selected task\n'; 548 ' g/G : Shows grid at the start/end of the selected task\n';
486 549
487 if (this.parentElement.parentElement.tabIndex) { 550 if (this.focusElement.tabIndex) {
488 help += ' <- : Select previous event on current timeline\n' + 551 help += ' <- : Select previous event on current timeline\n' +
489 ' -> : Select next event on current timeline\n'; 552 ' -> : Select next event on current timeline\n';
490 } else { 553 } else {
491 help += ' <-,^TAB : Select previous event on current timeline\n' + 554 help += ' <-,^TAB : Select previous event on current timeline\n' +
492 ' ->, TAB : Select next event on current timeline\n'; 555 ' ->, TAB : Select next event on current timeline\n';
493 } 556 }
494
495 help += 557 help +=
496 '\n' + 558 '\n' +
497 'Dbl-click to zoom in; Shift dbl-click to zoom out\n'; 559 'Dbl-click to zoom in; Shift dbl-click to zoom out\n';
498 return help; 560 return help;
499 }, 561 },
500 562
501 get selection() { 563 get selection() {
502 return this.selection_; 564 return this.selection_;
503 }, 565 },
504 566
505 set selection(selection) { 567 set selection(selection) {
506 // Clear old selection. 568 // Clear old selection.
507 for (i = 0; i < this.selection_.length; i++) 569 for (i = 0; i < this.selection_.length; i++)
508 this.selection_[i].slice.selected = false; 570 this.selection_[i].slice.selected = false;
509 571
510 this.selection_ = selection; 572 this.selection_ = selection;
511 573
512 cr.dispatchSimpleEvent(this, 'selectionChange'); 574 cr.dispatchSimpleEvent(this, 'selectionChange');
513 for (i = 0; i < this.selection_.length; i++) 575 for (i = 0; i < this.selection_.length; i++)
514 this.selection_[i].slice.selected = true; 576 this.selection_[i].slice.selected = true;
515 this.invalidate(); // Cause tracks to redraw. 577 this.viewport_.fireChanged(); // Triggers a redraw.
516 }, 578 },
517 579
518 get firstCanvas() { 580 get firstCanvas() {
519 return this.tracks_.firstChild ? 581 return this.tracks_.firstChild ?
520 this.tracks_.firstChild.firstCanvas : undefined; 582 this.tracks_.firstChild.firstCanvas : undefined;
521 }, 583 },
522 584
523 hideDragBox_: function() { 585 hideDragBox_: function() {
524 this.dragBox_.style.left = '-1000px'; 586 this.dragBox_.style.left = '-1000px';
525 this.dragBox_.style.top = '-1000px'; 587 this.dragBox_.style.top = '-1000px';
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
562 624
563 // Shift the timebase left until its just left of minTimestamp. 625 // Shift the timebase left until its just left of minTimestamp.
564 var numInterfvalsSinceStart = Math.ceil((tb - this.model_.minTimestamp) / 626 var numInterfvalsSinceStart = Math.ceil((tb - this.model_.minTimestamp) /
565 this.viewport_.gridStep_); 627 this.viewport_.gridStep_);
566 this.viewport_.gridTimebase = tb - 628 this.viewport_.gridTimebase = tb -
567 (numInterfvalsSinceStart + 1) * this.viewport_.gridStep_; 629 (numInterfvalsSinceStart + 1) * this.viewport_.gridStep_;
568 this.viewport_.gridEnabled = true; 630 this.viewport_.gridEnabled = true;
569 }, 631 },
570 632
571 onMouseDown_: function(e) { 633 onMouseDown_: function(e) {
572 if (e.clientX < this.offsetLeft || 634 rect = this.getClientRects()[0];
573 e.clientX >= this.offsetLeft + this.offsetWidth || 635 if (!rect ||
574 e.clientY < this.offsetTop || 636 e.clientX < rect.left ||
575 e.clientY >= this.offsetTop + this.offsetHeight) 637 e.clientX >= rect.right ||
638 e.clientY < rect.top ||
639 e.clientY >= rect.bottom)
576 return; 640 return;
577 641
578 var canv = this.firstCanvas; 642 var canv = this.firstCanvas;
579 var pos = { 643 var pos = {
580 x: e.clientX - canv.offsetLeft, 644 x: e.clientX - canv.offsetLeft,
581 y: e.clientY - canv.offsetTop 645 y: e.clientY - canv.offsetTop
582 }; 646 };
583 647
584 var wX = this.viewport_.xViewToWorld(pos.x); 648 var wX = this.viewport_.xViewToWorld(pos.x);
585 649
586 this.dragBeginEvent_ = e; 650 this.dragBeginEvent_ = e;
587 e.preventDefault(); 651 e.preventDefault();
588 if (this.parentElement.parentElement.tabIndex) 652 if (this.focusElement.tabIndex >= 0)
589 this.parentElement.parentElement.focus(); 653 this.focusElement.focus();
590 }, 654 },
591 655
592 onMouseMove_: function(e) { 656 onMouseMove_: function(e) {
593 if (!this.firstCanvas) 657 if (!this.firstCanvas)
594 return; 658 return;
595 var canv = this.firstCanvas; 659 var canv = this.firstCanvas;
596 var pos = { 660 var pos = {
597 x: e.clientX - canv.offsetLeft, 661 x: e.clientX - canv.offsetLeft,
598 y: e.clientY - canv.offsetTop 662 y: e.clientY - canv.offsetTop
599 }; 663 };
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
646 this.selection = selection; 710 this.selection = selection;
647 } 711 }
648 }, 712 },
649 713
650 onDblClick_: function(e) { 714 onDblClick_: function(e) {
651 var scale = 4; 715 var scale = 4;
652 if (e.shiftKey) 716 if (e.shiftKey)
653 scale = 1 / scale; 717 scale = 1 / scale;
654 this.zoomBy_(scale); 718 this.zoomBy_(scale);
655 e.preventDefault(); 719 e.preventDefault();
656 }, 720 }
657 }; 721 };
658 722
659 /** 723 /**
660 * The TimelineModel being viewed by the timeline 724 * The TimelineModel being viewed by the timeline
661 * @type {TimelineModel} 725 * @type {TimelineModel}
662 */ 726 */
663 cr.defineProperty(Timeline, 'model', cr.PropertyKind.JS); 727 cr.defineProperty(Timeline, 'model', cr.PropertyKind.JS);
664 728
665 return { 729 return {
666 Timeline: Timeline 730 Timeline: Timeline,
731 TimelineViewport: TimelineViewport
667 }; 732 };
668 }); 733 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698