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 |