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 149 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
160 | 160 |
161 this.invalidatePending_ = false; | 161 this.invalidatePending_ = false; |
162 | 162 |
163 this.tracks_ = this.ownerDocument.createElement('div'); | 163 this.tracks_ = this.ownerDocument.createElement('div'); |
164 this.tracks_.invalidate = this.invalidate.bind(this); | 164 this.tracks_.invalidate = this.invalidate.bind(this); |
165 this.appendChild(this.tracks_); | 165 this.appendChild(this.tracks_); |
166 | 166 |
167 this.dragBox_ = this.ownerDocument.createElement('div'); | 167 this.dragBox_ = this.ownerDocument.createElement('div'); |
168 this.dragBox_.className = 'timeline-drag-box'; | 168 this.dragBox_.className = 'timeline-drag-box'; |
169 this.appendChild(this.dragBox_); | 169 this.appendChild(this.dragBox_); |
170 this.hideDragBox_(); | |
170 | 171 |
171 // The following code uses a setInterval to monitor the timeline control | 172 // The following code uses a setInterval to monitor the timeline control |
172 // for size changes. This is so that we can keep the canvas' bitmap size | 173 // for size changes. This is so that we can keep the canvas' bitmap size |
173 // correctly synchronized with its presentation size. | 174 // correctly synchronized with its presentation size. |
174 // TODO(nduca): detect this in a more efficient way, e.g. iframe hack. | 175 // TODO(nduca): detect this in a more efficient way, e.g. iframe hack. |
175 this.lastSize_ = this.clientWidth + 'x' + this.clientHeight; | 176 this.lastSize_ = this.clientWidth + 'x' + this.clientHeight; |
176 this.ownerDocument.defaultView.setInterval(function() { | 177 this.checkForResizeInterval_ = |
178 this.ownerDocument.defaultView.setInterval(function() { | |
179 if (!this.isAttachedToDocument_) | |
180 return; | |
177 var curSize = this.clientWidth + 'x' + this.clientHeight; | 181 var curSize = this.clientWidth + 'x' + this.clientHeight; |
178 if (this.clientWidth && curSize != this.lastSize_) { | 182 if (this.clientWidth && curSize != this.lastSize_) { |
179 this.lastSize_ = curSize; | 183 this.lastSize_ = curSize; |
180 this.onResize(); | 184 this.onResize(); |
181 } | 185 } |
182 }.bind(this), 250); | 186 }.bind(this), 250); |
183 | 187 |
184 document.addEventListener('keypress', this.onKeypress_.bind(this)); | 188 this.bindEventListener_(document, 'keypress', this.onKeypress_, this); |
185 document.addEventListener('keydown', this.onKeydown_.bind(this)); | 189 this.bindEventListener_(document, 'keydown', this.onKeydown_, this); |
186 document.addEventListener('mousedown', this.onMouseDown_.bind(this)); | 190 this.bindEventListener_(document, 'mousedown', this.onMouseDown_, this); |
187 document.addEventListener('mousemove', this.onMouseMove_.bind(this)); | 191 this.bindEventListener_(document, 'mousemove', this.onMouseMove_, this); |
188 document.addEventListener('mouseup', this.onMouseUp_.bind(this)); | 192 this.bindEventListener_(document, 'mouseup', this.onMouseUp_, this); |
189 document.addEventListener('dblclick', this.onDblClick_.bind(this)); | 193 this.bindEventListener_(document, 'dblclick', this.onDblClick_, this); |
190 | 194 |
191 this.lastMouseViewPos_ = {x: 0, y: 0}; | 195 this.lastMouseViewPos_ = {x: 0, y: 0}; |
192 | 196 |
193 this.selection_ = []; | 197 this.selection_ = []; |
194 }, | 198 }, |
195 | 199 |
200 /** | |
201 * Wraps the standard addEventListener but automatically binds the provided | |
202 * func to the provided target, tracking the resulting closure. When detach | |
203 * is called, these listeners will be automatically removed. | |
204 */ | |
205 bindEventListener_: function(object, event, func, target) { | |
206 if (!this.boundListeners_) | |
207 this.boundListeners_ = []; | |
208 var boundFunc = func.bind(target); | |
209 this.boundListeners_.push({object: object, | |
210 event: event, | |
211 boundFunc: boundFunc}); | |
212 object.addEventListener(event, boundFunc); | |
213 }, | |
214 | |
215 detach: function() { | |
216 for (var i = 0; i < this.boundListeners_.length; ++i) { | |
James Hawkins
2011/11/11 23:37:40
nit: i++
nduca
2011/11/12 01:28:27
Done.
| |
217 var binding = this.boundListeners_[i]; | |
218 binding.object.removeEventListener(binding.event, binding.boundFunc); | |
219 } | |
220 this.boundListeners_ = undefined; | |
221 window.clearInterval(this.checkForResizeInterval_); | |
222 this.checkForResizeInterval_ = undefined; | |
223 }, | |
224 | |
196 get model() { | 225 get model() { |
197 return this.model_; | 226 return this.model_; |
198 }, | 227 }, |
199 | 228 |
200 set model(model) { | 229 set model(model) { |
201 if (!model) | 230 if (!model) |
202 throw Error('Model cannot be null'); | 231 throw Error('Model cannot be null'); |
203 if (this.model) { | 232 if (this.model) { |
204 throw Error('Cannot set model twice.'); | 233 throw Error('Cannot set model twice.'); |
205 } | 234 } |
206 this.model_ = model; | 235 this.model_ = model; |
207 | 236 |
208 // Create tracks. | 237 // Create tracks and measure their heading size. |
209 this.tracks_.textContent = ''; | |
210 var threads = model.getAllThreads(); | 238 var threads = model.getAllThreads(); |
211 threads.sort(tracing.TimelineThread.compare); | 239 var maxHeadingWidth = 0; |
240 var tracks = []; | |
241 var measuringStick = new tracing.MeasuringStick(); | |
242 var headingEl = document.createElement('div'); | |
243 headingEl.style.position = 'fixed'; | |
244 headingEl.className = 'timeline-slice-track-title'; | |
212 for (var tI = 0; tI < threads.length; tI++) { | 245 for (var tI = 0; tI < threads.length; tI++) { |
213 var thread = threads[tI]; | 246 var thread = threads[tI]; |
214 var track = new TimelineThreadTrack(); | 247 var track = new TimelineThreadTrack(); |
215 track.thread = thread; | 248 track.thread = thread; |
216 track.viewport = this.viewport_; | 249 track.viewport = this.viewport_; |
250 tracks.push(track); | |
251 headingEl.textContent = track.heading; | |
252 var w = measuringStick.measure(headingEl).width; | |
253 // Limit heading width to 300px. | |
254 if (w > 300) | |
255 w = 300; | |
256 if (w > maxHeadingWidth) | |
257 maxHeadingWidth = w; | |
258 } | |
259 var extraHeadingPadding = 4; | |
260 maxHeadingWidth += maxHeadingWidth + extraHeadingPadding; | |
261 | |
262 // Attach tracks and set width. | |
263 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'; | |
217 this.tracks_.appendChild(track); | 268 this.tracks_.appendChild(track); |
218 | |
219 } | 269 } |
220 | 270 |
221 this.needsViewportReset_ = true; | 271 if (this.isAttachedToDocument_) |
272 this.onResize(); | |
273 else | |
274 this.needsViewportReset_ = true; | |
222 }, | 275 }, |
223 | 276 |
224 viewportChange_: function() { | 277 viewportChange_: function() { |
225 this.invalidate(); | 278 this.invalidate(); |
226 }, | 279 }, |
227 | 280 |
228 invalidate: function() { | 281 invalidate: function() { |
229 if (this.invalidatePending_) | 282 if (this.invalidatePending_) |
230 return; | 283 return; |
231 this.invalidatePending_ = true; | 284 this.invalidatePending_ = true; |
232 window.setTimeout(function() { | 285 if (this.isAttachedToDocument_) |
233 this.invalidatePending_ = false; | 286 window.setTimeout(function() { |
234 this.redrawAllTracks_(); | 287 this.invalidatePending_ = false; |
235 }.bind(this), 0); | 288 this.redrawAllTracks_(); |
289 }.bind(this), 0); | |
290 }, | |
291 | |
292 /** | |
293 * @return {boolean} Whether the current timeline is attached to the | |
294 * document. | |
295 */ | |
296 get isAttachedToDocument_() { | |
297 var cur = this; | |
298 while (cur.parentNode) | |
299 cur = cur.parentNode; | |
300 return cur == this.ownerDocument; | |
236 }, | 301 }, |
237 | 302 |
238 onResize: function() { | 303 onResize: function() { |
304 if (!this.isAttachedToDocument_) | |
305 throw 'Not attached to document!'; | |
239 for (var i = 0; i < this.tracks_.children.length; ++i) { | 306 for (var i = 0; i < this.tracks_.children.length; ++i) { |
240 var track = this.tracks_.children[i]; | 307 var track = this.tracks_.children[i]; |
241 track.onResize(); | 308 track.onResize(); |
242 } | 309 } |
310 if (this.invalidatePending_) { | |
311 this.invalidatePending_ = false; | |
312 this.redrawAllTracks_(); | |
313 } | |
243 }, | 314 }, |
244 | 315 |
245 redrawAllTracks_: function() { | 316 redrawAllTracks_: function() { |
246 if (this.needsViewportReset_ && this.clientWidth != 0) { | 317 if (this.needsViewportReset_ && this.clientWidth != 0) { |
318 if (!this.isAttachedToDocument_) | |
319 throw 'Not attached to document!'; | |
247 this.needsViewportReset_ = false; | 320 this.needsViewportReset_ = false; |
248 /* update viewport */ | 321 /* update viewport */ |
249 var rangeTimestamp = this.model_.maxTimestamp - | 322 var rangeTimestamp = this.model_.maxTimestamp - |
250 this.model_.minTimestamp; | 323 this.model_.minTimestamp; |
251 var w = this.firstCanvas.width; | 324 var w = this.firstCanvas.width; |
252 console.log('viewport was reset with w=', w); | |
253 var scaleX = w / rangeTimestamp; | 325 var scaleX = w / rangeTimestamp; |
254 var panX = -this.model_.minTimestamp; | 326 var panX = -this.model_.minTimestamp; |
255 this.viewport_.setPanAndScale(panX, scaleX); | 327 this.viewport_.setPanAndScale(panX, scaleX); |
256 } | 328 } |
257 for (var i = 0; i < this.tracks_.children.length; ++i) { | 329 for (var i = 0; i < this.tracks_.children.length; ++i) { |
258 this.tracks_.children[i].redraw(); | 330 this.tracks_.children[i].redraw(); |
259 } | 331 } |
260 }, | 332 }, |
261 | 333 |
262 updateChildViewports_: function() { | 334 updateChildViewports_: function() { |
263 for (var cI = 0; cI < this.tracks_.children.length; ++cI) { | 335 for (var cI = 0; cI < this.tracks_.children.length; ++cI) { |
264 var child = this.tracks_.children[cI]; | 336 var child = this.tracks_.children[cI]; |
265 child.setViewport(this.panX, this.scaleX); | 337 child.setViewport(this.panX, this.scaleX); |
266 } | 338 } |
267 }, | 339 }, |
268 | 340 |
341 get listenToKeys_() { | |
342 if (this.parentElement.parentElement.tabIndex >= 0) | |
343 return document.activeElement == this.parentElement.parentElement; | |
344 return true; | |
345 }, | |
346 | |
269 onKeypress_: function(e) { | 347 onKeypress_: function(e) { |
270 var vp = this.viewport_; | 348 var vp = this.viewport_; |
271 if (!this.firstCanvas) | 349 if (!this.firstCanvas) |
272 return; | 350 return; |
351 if (!this.listenToKeys_) | |
352 return; | |
273 var viewWidth = this.firstCanvas.clientWidth; | 353 var viewWidth = this.firstCanvas.clientWidth; |
274 var curMouseV, curCenterW; | 354 var curMouseV, curCenterW; |
275 switch (e.keyCode) { | 355 switch (e.keyCode) { |
276 case 101: // e | 356 case 101: // e |
277 var vX = this.lastMouseViewPos_.x; | 357 var vX = this.lastMouseViewPos_.x; |
278 var wX = vp.xViewToWorld(this.lastMouseViewPos_.x); | 358 var wX = vp.xViewToWorld(this.lastMouseViewPos_.x); |
279 var distFromCenter = vX - (viewWidth / 2); | 359 var distFromCenter = vX - (viewWidth / 2); |
280 var percFromCenter = distFromCenter / viewWidth; | 360 var percFromCenter = distFromCenter / viewWidth; |
281 var percFromCenterSq = percFromCenter * percFromCenter; | 361 var percFromCenterSq = percFromCenter * percFromCenter; |
282 vp.xPanWorldPosToViewPos(wX, 'center', viewWidth); | 362 vp.xPanWorldPosToViewPos(wX, 'center', viewWidth); |
(...skipping 26 matching lines...) Expand all Loading... | |
309 vp.panX += vp.xViewVectorToWorld(viewWidth * 0.5); | 389 vp.panX += vp.xViewVectorToWorld(viewWidth * 0.5); |
310 break; | 390 break; |
311 case 68: // D | 391 case 68: // D |
312 vp.panX -= vp.xViewVectorToWorld(viewWidth * 0.5); | 392 vp.panX -= vp.xViewVectorToWorld(viewWidth * 0.5); |
313 break; | 393 break; |
314 } | 394 } |
315 }, | 395 }, |
316 | 396 |
317 // Not all keys send a keypress. | 397 // Not all keys send a keypress. |
318 onKeydown_: function(e) { | 398 onKeydown_: function(e) { |
399 if (!this.listenToKeys_) | |
400 return; | |
319 switch (e.keyCode) { | 401 switch (e.keyCode) { |
320 case 37: // left arrow | 402 case 37: // left arrow |
321 this.selectPrevious_(e); | 403 this.selectPrevious_(e); |
322 e.preventDefault(); | 404 e.preventDefault(); |
323 break; | 405 break; |
324 case 39: // right arrow | 406 case 39: // right arrow |
325 this.selectNext_(e); | 407 this.selectNext_(e); |
326 e.preventDefault(); | 408 e.preventDefault(); |
327 break; | 409 break; |
328 case 9: // TAB | 410 case 9: // TAB |
329 if (e.shiftKey) | 411 if (this.parentElement.parentElement.tabIndex == -1) { |
330 this.selectPrevious_(e); | 412 if (e.shiftKey) |
331 else | 413 this.selectPrevious_(e); |
332 this.selectNext_(e); | 414 else |
333 e.preventDefault(); | 415 this.selectNext_(e); |
416 e.preventDefault(); | |
417 } | |
334 break; | 418 break; |
335 } | 419 } |
336 }, | 420 }, |
337 | 421 |
338 /** | 422 /** |
339 * Zoom in or out on the timeline by the given scale factor. | 423 * Zoom in or out on the timeline by the given scale factor. |
340 * @param {integer} scale The scale factor to apply. If <1, zooms out. | 424 * @param {integer} scale The scale factor to apply. If <1, zooms out. |
341 */ | 425 */ |
342 zoomBy_: function(scale) { | 426 zoomBy_: function(scale) { |
343 if (!this.firstCanvas) | 427 if (!this.firstCanvas) |
(...skipping 22 matching lines...) Expand all Loading... | |
366 * @param {boolean} forwardp If true, select one forward (next). | 450 * @param {boolean} forwardp If true, select one forward (next). |
367 * Else, select previous. | 451 * Else, select previous. |
368 */ | 452 */ |
369 selectAdjoining_: function(e, forwardp) { | 453 selectAdjoining_: function(e, forwardp) { |
370 var i, track, slice, adjoining; | 454 var i, track, slice, adjoining; |
371 var selection = []; | 455 var selection = []; |
372 // Clear old selection; try and select next. | 456 // Clear old selection; try and select next. |
373 for (i = 0; i < this.selection_.length; ++i) { | 457 for (i = 0; i < this.selection_.length; ++i) { |
374 adjoining = undefined; | 458 adjoining = undefined; |
375 this.selection_[i].slice.selected = false; | 459 this.selection_[i].slice.selected = false; |
376 var track = this.selection_[i].track; | 460 track = this.selection_[i].track; |
377 var slice = this.selection_[i].slice; | 461 slice = this.selection_[i].slice; |
378 if (slice) { | 462 if (slice) { |
379 if (forwardp) | 463 if (forwardp) |
380 adjoining = track.pickNext(slice); | 464 adjoining = track.pickNext(slice); |
381 else | 465 else |
382 adjoining = track.pickPrevious(slice); | 466 adjoining = track.pickPrevious(slice); |
383 } | 467 } |
384 if (adjoining != undefined) | 468 if (adjoining != undefined) |
385 selection.push({track: track, slice: adjoining}); | 469 selection.push({track: track, slice: adjoining}); |
386 } | 470 } |
387 // Activate the new selection. | 471 // Activate the new selection. |
388 this.selection_ = selection; | 472 this.selection_ = selection; |
389 for (i = 0; i < this.selection_.length; ++i) | 473 for (i = 0; i < this.selection_.length; ++i) |
390 this.selection_[i].slice.selected = true; | 474 this.selection_[i].slice.selected = true; |
391 cr.dispatchSimpleEvent(this, 'selectionChange'); | 475 cr.dispatchSimpleEvent(this, 'selectionChange'); |
392 this.invalidate(); // Cause tracks to redraw. | 476 this.invalidate(); // Cause tracks to redraw. |
393 e.preventDefault(); | 477 e.preventDefault(); |
394 }, | 478 }, |
395 | 479 |
396 get keyHelp() { | 480 get keyHelp() { |
397 return 'Keyboard shortcuts:\n' + | 481 var help = 'Keyboard shortcuts:\n' + |
398 ' w/s : Zoom in/out (with shift: go faster)\n' + | 482 ' w/s : Zoom in/out (with shift: go faster)\n' + |
399 ' a/d : Pan left/right\n' + | 483 ' a/d : Pan left/right\n' + |
400 ' e : Center on mouse\n' + | 484 ' e : Center on mouse\n' + |
401 ' g/G : Shows grid at the start/end of the selected task\n' + | 485 ' g/G : Shows grid at the start/end of the selected task\n'; |
402 ' <-,^TAB : Select previous event on current timeline\n' + | |
403 ' ->, TAB : Select next event on current timeline\n' + | |
404 '\n' + | |
405 'Dbl-click to zoom in; Shift dbl-click to zoom out\n'; | |
406 | 486 |
487 if (this.parentElement.parentElement.tabIndex) { | |
488 help += ' <- : Select previous event on current timeline\n' + | |
489 ' -> : Select next event on current timeline\n'; | |
490 } else { | |
491 help += ' <-,^TAB : Select previous event on current timeline\n' + | |
492 ' ->, TAB : Select next event on current timeline\n'; | |
493 } | |
407 | 494 |
495 help += | |
496 '\n' + | |
497 'Dbl-click to zoom in; Shift dbl-click to zoom out\n'; | |
498 return help; | |
408 }, | 499 }, |
409 | 500 |
410 get selection() { | 501 get selection() { |
411 return this.selection_; | 502 return this.selection_; |
412 }, | 503 }, |
413 | 504 |
505 set selection(selection) { | |
506 // Clear old selection. | |
507 for (i = 0; i < this.selection_.length; ++i) | |
James Hawkins
2011/11/11 23:37:40
nit: i++
nduca
2011/11/12 01:28:27
Done.
| |
508 this.selection_[i].slice.selected = false; | |
509 | |
510 this.selection_ = selection; | |
511 | |
512 cr.dispatchSimpleEvent(this, 'selectionChange'); | |
513 for (i = 0; i < this.selection_.length; ++i) | |
James Hawkins
2011/11/11 23:37:40
nit: i++
nduca
2011/11/12 01:28:27
Done.
| |
514 this.selection_[i].slice.selected = true; | |
515 this.invalidate(); // Cause tracks to redraw. | |
516 }, | |
517 | |
414 get firstCanvas() { | 518 get firstCanvas() { |
415 return this.tracks_.firstChild ? | 519 return this.tracks_.firstChild ? |
416 this.tracks_.firstChild.firstCanvas : undefined; | 520 this.tracks_.firstChild.firstCanvas : undefined; |
417 }, | 521 }, |
418 | 522 |
419 showDragBox_: function() { | |
420 this.dragBox_.hidden = false; | |
421 }, | |
422 | |
423 hideDragBox_: function() { | 523 hideDragBox_: function() { |
424 this.dragBox_.style.left = '-1000px'; | 524 this.dragBox_.style.left = '-1000px'; |
425 this.dragBox_.style.top = '-1000px'; | 525 this.dragBox_.style.top = '-1000px'; |
426 this.dragBox_.style.width = 0; | 526 this.dragBox_.style.width = 0; |
427 this.dragBox_.style.height = 0; | 527 this.dragBox_.style.height = 0; |
428 this.dragBox_.hidden = true; | |
429 }, | |
430 | |
431 get dragBoxVisible_() { | |
432 return this.dragBox_.hidden == false; | |
433 }, | 528 }, |
434 | 529 |
435 setDragBoxPosition_: function(eDown, eCur) { | 530 setDragBoxPosition_: function(eDown, eCur) { |
436 var loX = Math.min(eDown.clientX, eCur.clientX); | 531 var loX = Math.min(eDown.clientX, eCur.clientX); |
437 var hiX = Math.max(eDown.clientX, eCur.clientX); | 532 var hiX = Math.max(eDown.clientX, eCur.clientX); |
438 var loY = Math.min(eDown.clientY, eCur.clientY); | 533 var loY = Math.min(eDown.clientY, eCur.clientY); |
439 var hiY = Math.max(eDown.clientY, eCur.clientY); | 534 var hiY = Math.max(eDown.clientY, eCur.clientY); |
440 | 535 |
441 this.dragBox_.style.left = loX + 'px'; | 536 this.dragBox_.style.left = loX + 'px'; |
442 this.dragBox_.style.top = loY + 'px'; | 537 this.dragBox_.style.top = loY + 'px'; |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
483 var canv = this.firstCanvas; | 578 var canv = this.firstCanvas; |
484 var pos = { | 579 var pos = { |
485 x: e.clientX - canv.offsetLeft, | 580 x: e.clientX - canv.offsetLeft, |
486 y: e.clientY - canv.offsetTop | 581 y: e.clientY - canv.offsetTop |
487 }; | 582 }; |
488 | 583 |
489 var wX = this.viewport_.xViewToWorld(pos.x); | 584 var wX = this.viewport_.xViewToWorld(pos.x); |
490 | 585 |
491 this.dragBeginEvent_ = e; | 586 this.dragBeginEvent_ = e; |
492 e.preventDefault(); | 587 e.preventDefault(); |
588 if (this.parentElement.parentElement.tabIndex) | |
589 this.parentElement.parentElement.focus(); | |
493 }, | 590 }, |
494 | 591 |
495 onMouseMove_: function(e) { | 592 onMouseMove_: function(e) { |
496 if (!this.firstCanvas) | 593 if (!this.firstCanvas) |
497 return; | 594 return; |
498 var canv = this.firstCanvas; | 595 var canv = this.firstCanvas; |
499 var pos = { | 596 var pos = { |
500 x: e.clientX - canv.offsetLeft, | 597 x: e.clientX - canv.offsetLeft, |
501 y: e.clientY - canv.offsetTop | 598 y: e.clientY - canv.offsetTop |
502 }; | 599 }; |
503 | 600 |
504 // Remember position. Used during keyboard zooming. | 601 // Remember position. Used during keyboard zooming. |
505 this.lastMouseViewPos_ = pos; | 602 this.lastMouseViewPos_ = pos; |
506 | 603 |
507 // Initiate the drag box if needed. | |
508 if (this.dragBeginEvent_ && !this.dragBoxVisible_) { | |
509 this.showDragBox_(); | |
510 this.setDragBoxPosition_(e, e); | |
511 } | |
512 | |
513 // Update the drag box | 604 // Update the drag box |
514 if (this.dragBeginEvent_) { | 605 if (this.dragBeginEvent_) { |
515 this.setDragBoxPosition_(this.dragBeginEvent_, e); | 606 this.setDragBoxPosition_(this.dragBeginEvent_, e); |
516 } | 607 } |
517 }, | 608 }, |
518 | 609 |
519 onMouseUp_: function(e) { | 610 onMouseUp_: function(e) { |
520 var i; | 611 var i; |
521 if (this.dragBeginEvent_) { | 612 if (this.dragBeginEvent_) { |
522 // Stop the dragging. | 613 // Stop the dragging. |
523 this.hideDragBox_(); | 614 this.hideDragBox_(); |
524 var eDown = this.dragBeginEvent_; | 615 var eDown = this.dragBeginEvent_; |
525 this.dragBeginEvent_ = null; | 616 this.dragBeginEvent_ = null; |
526 | 617 |
527 // Figure out extents of the drag. | 618 // Figure out extents of the drag. |
528 var loX = Math.min(eDown.clientX, e.clientX); | 619 var loX = Math.min(eDown.clientX, e.clientX); |
529 var hiX = Math.max(eDown.clientX, e.clientX); | 620 var hiX = Math.max(eDown.clientX, e.clientX); |
530 var loY = Math.min(eDown.clientY, e.clientY); | 621 var loY = Math.min(eDown.clientY, e.clientY); |
531 var hiY = Math.max(eDown.clientY, e.clientY); | 622 var hiY = Math.max(eDown.clientY, e.clientY); |
532 | 623 |
533 // Convert to worldspace. | 624 // Convert to worldspace. |
534 var canv = this.firstCanvas; | 625 var canv = this.firstCanvas; |
535 var loWX = this.viewport_.xViewToWorld(loX - canv.offsetLeft); | 626 var loWX = this.viewport_.xViewToWorld(loX - canv.offsetLeft); |
536 var hiWX = this.viewport_.xViewToWorld(hiX - canv.offsetLeft); | 627 var hiWX = this.viewport_.xViewToWorld(hiX - canv.offsetLeft); |
537 | 628 |
538 // Clear old selection. | |
539 for (i = 0; i < this.selection_.length; ++i) { | |
540 this.selection_[i].slice.selected = false; | |
541 } | |
542 | |
543 // Figure out what has been hit. | 629 // Figure out what has been hit. |
544 var selection = []; | 630 var selection = []; |
545 function addHit(type, track, slice) { | 631 function addHit(type, track, slice) { |
546 selection.push({track: track, slice: slice}); | 632 selection.push({track: track, slice: slice}); |
547 } | 633 } |
548 for (i = 0; i < this.tracks_.children.length; ++i) { | 634 for (i = 0; i < this.tracks_.children.length; ++i) { |
549 var track = this.tracks_.children[i]; | 635 var track = this.tracks_.children[i]; |
550 | 636 |
551 // Only check tracks that insersect the rect. | 637 // Only check tracks that insersect the rect. |
552 var trackClientRect = track.getBoundingClientRect(); | 638 var trackClientRect = track.getBoundingClientRect(); |
553 var a = Math.max(loY, trackClientRect.top); | 639 var a = Math.max(loY, trackClientRect.top); |
554 var b = Math.min(hiY, trackClientRect.bottom); | 640 var b = Math.min(hiY, trackClientRect.bottom); |
555 if (a <= b) { | 641 if (a <= b) { |
556 track.pickRange(loWX, hiWX, loY, hiY, addHit); | 642 track.pickRange(loWX, hiWX, loY, hiY, addHit); |
557 } | 643 } |
558 } | 644 } |
559 // Activate the new selection. | 645 // Activate the new selection. |
560 this.selection_ = selection; | 646 this.selection = selection; |
561 cr.dispatchSimpleEvent(this, 'selectionChange'); | |
562 for (i = 0; i < this.selection_.length; ++i) { | |
563 this.selection_[i].slice.selected = true; | |
564 } | |
565 this.invalidate(); // Cause tracks to redraw. | |
566 } | 647 } |
567 }, | 648 }, |
568 | 649 |
569 onDblClick_: function(e) { | 650 onDblClick_: function(e) { |
570 var scale = 4; | 651 var scale = 4; |
571 if (e.shiftKey) | 652 if (e.shiftKey) |
572 scale = 1 / scale; | 653 scale = 1 / scale; |
573 this.zoomBy_(scale); | 654 this.zoomBy_(scale); |
574 e.preventDefault(); | 655 e.preventDefault(); |
575 }, | 656 }, |
576 }; | 657 }; |
577 | 658 |
578 /** | 659 /** |
579 * The TimelineModel being viewed by the timeline | 660 * The TimelineModel being viewed by the timeline |
580 * @type {TimelineModel} | 661 * @type {TimelineModel} |
581 */ | 662 */ |
582 cr.defineProperty(Timeline, 'model', cr.PropertyKind.JS); | 663 cr.defineProperty(Timeline, 'model', cr.PropertyKind.JS); |
583 | 664 |
584 return { | 665 return { |
585 Timeline: Timeline | 666 Timeline: Timeline |
586 }; | 667 }; |
587 }); | 668 }); |
OLD | NEW |