Chromium Code Reviews| 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 |