Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / third_party / trace-viewer / trace_viewer / tracing / timeline_viewport.js
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 'use strict';
6
7 /**
8  * @fileoverview Code for the viewport.
9  */
10 tvcm.require('tvcm.events');
11 tvcm.require('tracing.draw_helpers');
12 tvcm.require('tracing.timeline_interest_range');
13 tvcm.require('tracing.timeline_display_transform');
14 tvcm.require('tvcm.ui.animation');
15 tvcm.require('tvcm.ui.animation_controller');
16
17 tvcm.exportTo('tracing', function() {
18
19   var TimelineDisplayTransform = tracing.TimelineDisplayTransform;
20   var TimelineInterestRange = tracing.TimelineInterestRange;
21
22   /**
23    * The TimelineViewport manages the transform used for navigating
24    * within the timeline. It is a simple transform:
25    *   x' = (x+pan) * scale
26    *
27    * The timeline code tries to avoid directly accessing this transform,
28    * instead using this class to do conversion between world and viewspace,
29    * as well as the math for centering the viewport in various interesting
30    * ways.
31    *
32    * @constructor
33    * @extends {tvcm.EventTarget}
34    */
35   function TimelineViewport(parentEl) {
36     this.parentEl_ = parentEl;
37     this.modelTrackContainer_ = undefined;
38     this.currentDisplayTransform_ = new TimelineDisplayTransform();
39     this.initAnimationController_();
40
41     // Grid system.
42     this.gridTimebase_ = 0;
43     this.gridStep_ = 1000 / 60;
44     this.gridEnabled_ = false;
45
46     // Init logic.
47     this.hasCalledSetupFunction_ = false;
48
49     this.onResize_ = this.onResize_.bind(this);
50     this.onModelTrackControllerScroll_ =
51         this.onModelTrackControllerScroll_.bind(this);
52
53     // The following code uses an interval to detect when the parent element
54     // is attached to the document. That is a trigger to run the setup function
55     // and install a resize listener.
56     this.checkForAttachInterval_ = setInterval(
57         this.checkForAttach_.bind(this), 250);
58
59     this.majorMarkPositions = [];
60     this.interestRange_ = new TimelineInterestRange(this);
61
62     this.eventToTrackMap_ = {};
63   }
64
65   TimelineViewport.prototype = {
66     __proto__: tvcm.EventTarget.prototype,
67
68     /**
69      * Allows initialization of the viewport when the viewport's parent element
70      * has been attached to the document and given a size.
71      * @param {Function} fn Function to call when the viewport can be safely
72      * initialized.
73      */
74     setWhenPossible: function(fn) {
75       this.pendingSetFunction_ = fn;
76     },
77
78     /**
79      * @return {boolean} Whether the current timeline is attached to the
80      * document.
81      */
82     get isAttachedToDocumentOrInTestMode() {
83       // Allow not providing a parent element, used by tests.
84       if (this.parentEl_ === undefined)
85         return;
86       return tvcm.ui.isElementAttachedToDocument(this.parentEl_);
87     },
88
89     onResize_: function() {
90       this.dispatchChangeEvent();
91     },
92
93     /**
94      * Checks whether the parentNode is attached to the document.
95      * When it is, it installs the iframe-based resize detection hook
96      * and then runs the pendingSetFunction_, if present.
97      */
98     checkForAttach_: function() {
99       if (!this.isAttachedToDocumentOrInTestMode || this.clientWidth == 0)
100         return;
101
102       if (!this.iframe_) {
103         this.iframe_ = document.createElement('iframe');
104         this.iframe_.style.cssText =
105             'position:absolute;width:100%;height:0;border:0;visibility:hidden;';
106         this.parentEl_.appendChild(this.iframe_);
107
108         this.iframe_.contentWindow.addEventListener('resize', this.onResize_);
109       }
110
111       var curSize = this.parentEl_.clientWidth + 'x' +
112           this.parentEl_.clientHeight;
113       if (this.pendingSetFunction_) {
114         this.lastSize_ = curSize;
115         try {
116           this.pendingSetFunction_();
117         } catch (ex) {
118           console.log('While running setWhenPossible:',
119               ex.message ? ex.message + '\n' + ex.stack : ex.stack);
120         }
121         this.pendingSetFunction_ = undefined;
122       }
123
124       window.clearInterval(this.checkForAttachInterval_);
125       this.checkForAttachInterval_ = undefined;
126     },
127
128     /**
129      * Fires the change event on this viewport. Used to notify listeners
130      * to redraw when the underlying model has been mutated.
131      */
132     dispatchChangeEvent: function() {
133       tvcm.dispatchSimpleEvent(this, 'change');
134     },
135
136     detach: function() {
137       if (this.checkForAttachInterval_) {
138         window.clearInterval(this.checkForAttachInterval_);
139         this.checkForAttachInterval_ = undefined;
140       }
141       if (this.iframe_) {
142         this.iframe_.removeEventListener('resize', this.onResize_);
143         this.parentEl_.removeChild(this.iframe_);
144       }
145     },
146
147     initAnimationController_: function() {
148       this.dtAnimationController_ = new tvcm.ui.AnimationController();
149       this.dtAnimationController_.addEventListener(
150           'didtick', function(e) {
151             this.onCurentDisplayTransformChange_(e.oldTargetState);
152           }.bind(this));
153
154       var that = this;
155       this.dtAnimationController_.target = {
156         get panX() {
157           return that.currentDisplayTransform_.panX;
158         },
159
160         set panX(panX) {
161           that.currentDisplayTransform_.panX = panX;
162         },
163
164         get panY() {
165           return that.currentDisplayTransform_.panY;
166         },
167
168         set panY(panY) {
169           that.currentDisplayTransform_.panY = panY;
170         },
171
172         get scaleX() {
173           return that.currentDisplayTransform_.scaleX;
174         },
175
176         set scaleX(scaleX) {
177           that.currentDisplayTransform_.scaleX = scaleX;
178         },
179
180         cloneAnimationState: function() {
181           return that.currentDisplayTransform_.clone();
182         },
183
184         xPanWorldPosToViewPos: function(xWorld, xView) {
185           that.currentDisplayTransform_.xPanWorldPosToViewPos(
186               xWorld, xView, that.modelTrackContainer_.canvas.clientWidth);
187         }
188       };
189     },
190
191     get currentDisplayTransform() {
192       return this.currentDisplayTransform_;
193     },
194
195     setDisplayTransformImmediately: function(displayTransform) {
196       this.dtAnimationController_.cancelActiveAnimation();
197
198       var oldDisplayTransform =
199           this.dtAnimationController_.target.cloneAnimationState();
200       this.currentDisplayTransform_.set(displayTransform);
201       this.onCurentDisplayTransformChange_(oldDisplayTransform);
202     },
203
204     queueDisplayTransformAnimation: function(animation) {
205       if (!(animation instanceof tvcm.ui.Animation))
206         throw new Error('animation must be instanceof tvcm.ui.Animation');
207       this.dtAnimationController_.queueAnimation(animation);
208     },
209
210     onCurentDisplayTransformChange_: function(oldDisplayTransform) {
211       // Ensure panY stays clamped in the track container's scroll range.
212       if (this.modelTrackContainer_) {
213         this.currentDisplayTransform.panY = tvcm.clamp(
214             this.currentDisplayTransform.panY,
215             0,
216             this.modelTrackContainer_.scrollHeight -
217                 this.modelTrackContainer_.clientHeight);
218       }
219
220       var changed = !this.currentDisplayTransform.equals(oldDisplayTransform);
221       var yChanged = this.currentDisplayTransform.panY !==
222           oldDisplayTransform.panY;
223       if (yChanged)
224         this.modelTrackContainer_.scrollTop = this.currentDisplayTransform.panY;
225       if (changed)
226         this.dispatchChangeEvent();
227     },
228
229     onModelTrackControllerScroll_: function(e) {
230       if (this.dtAnimationController_.activeAnimation &&
231           this.dtAnimationController_.activeAnimation.affectsPanY)
232         this.dtAnimationController_.cancelActiveAnimation();
233       var panY = this.modelTrackContainer_.scrollTop;
234       this.currentDisplayTransform_.panY = panY;
235     },
236
237     get modelTrackContainer() {
238       return this.modelTrackContainer_;
239     },
240
241     set modelTrackContainer(m) {
242       if (this.modelTrackContainer_)
243         this.modelTrackContainer_.removeEventListener('scroll',
244             this.onModelTrackControllerScroll_);
245
246       this.modelTrackContainer_ = m;
247       this.modelTrackContainer_.addEventListener('scroll',
248           this.onModelTrackControllerScroll_);
249     },
250
251     get gridEnabled() {
252       return this.gridEnabled_;
253     },
254
255     set gridEnabled(enabled) {
256       if (this.gridEnabled_ == enabled)
257         return;
258
259       this.gridEnabled_ = enabled && true;
260       this.dispatchChangeEvent();
261     },
262
263     get gridTimebase() {
264       return this.gridTimebase_;
265     },
266
267     set gridTimebase(timebase) {
268       if (this.gridTimebase_ == timebase)
269         return;
270       this.gridTimebase_ = timebase;
271       this.dispatchChangeEvent();
272     },
273
274     get gridStep() {
275       return this.gridStep_;
276     },
277
278     get interestRange() {
279       return this.interestRange_;
280     },
281
282     drawMajorMarkLines: function(ctx) {
283       // Apply subpixel translate to get crisp lines.
284       // http://www.mobtowers.com/html5-canvas-crisp-lines-every-time/
285       ctx.save();
286       ctx.translate((Math.round(ctx.lineWidth) % 2) / 2, 0);
287
288       ctx.beginPath();
289       for (var idx in this.majorMarkPositions) {
290         var x = Math.floor(this.majorMarkPositions[idx]);
291         tracing.drawLine(ctx, x, 0, x, ctx.canvas.height);
292       }
293       ctx.strokeStyle = '#ddd';
294       ctx.stroke();
295
296       ctx.restore();
297     },
298
299     drawGridLines: function(ctx, viewLWorld, viewRWorld) {
300       if (!this.gridEnabled)
301         return;
302
303       var dt = this.currentDisplayTransform;
304       var x = this.gridTimebase;
305
306       // Apply subpixel translate to get crisp lines.
307       // http://www.mobtowers.com/html5-canvas-crisp-lines-every-time/
308       ctx.save();
309       ctx.translate((Math.round(ctx.lineWidth) % 2) / 2, 0);
310
311       ctx.beginPath();
312       while (x < viewRWorld) {
313         if (x >= viewLWorld) {
314           // Do conversion to viewspace here rather than on
315           // x to avoid precision issues.
316           var vx = Math.floor(dt.xWorldToView(x));
317           tracing.drawLine(ctx, vx, 0, vx, ctx.canvas.height);
318         }
319
320         x += this.gridStep;
321       }
322       ctx.strokeStyle = 'rgba(255, 0, 0, 0.25)';
323       ctx.stroke();
324
325       ctx.restore();
326     },
327
328     rebuildEventToTrackMap: function() {
329       this.eventToTrackMap_ = undefined;
330
331       var eventToTrackMap = {};
332       eventToTrackMap.addEvent = function(event, track) {
333         if (!track)
334           throw new Error('Must provide a track.');
335         this[event.guid] = track;
336       };
337       this.modelTrackContainer_.addEventsToTrackMap(eventToTrackMap);
338       this.eventToTrackMap_ = eventToTrackMap;
339     },
340
341     trackForEvent: function(event) {
342       return this.eventToTrackMap_[event.guid];
343     }
344   };
345
346   return {
347     TimelineViewport: TimelineViewport
348   };
349 });