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