OLD | NEW |
---|---|
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 }); |
OLD | NEW |