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

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

Powered by Google App Engine
This is Rietveld 408576698