Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / third_party / trace-viewer / src / tracing / timing_tool.js
1 // Copyright (c) 2013 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 tvcm.require('tvcm.range');
8 tvcm.require('tracing.constants');
9 tvcm.require('tracing.selection');
10 tvcm.require('tracing.trace_model.slice');
11
12 /**
13  * @fileoverview Provides the TimingTool class.
14  */
15 tvcm.exportTo('tracing', function() {
16
17   var constants = tracing.constants;
18
19   /**
20    * Tool for taking time measurements in the TimelineTrackView using
21    * Viewportmarkers.
22    * @constructor
23    */
24   var TimingTool = function(viewport, targetElement) {
25     this.viewport = viewport;
26
27     this.rangeStartMarker_ = viewport.createMarker(0);
28     this.rangeEndMarker_ = viewport.createMarker(0);
29     this.cursorMarker_ = viewport.createMarker(0);
30     this.activeMarker_ = this.cursorMarker_;
31
32     // Prepare the event handlers to be added and removed repeatedly.
33     this.onMouseMove_ = this.onMouseMove_.bind(this);
34     this.onDblClick_ = this.onDblClick_.bind(this);
35     this.targetElement_ = targetElement;
36   };
37
38   TimingTool.prototype = {
39
40     getWorldXFromEvent_: function(e) {
41       var pixelRatio = window.devicePixelRatio || 1;
42       var modelTrackContainer = this.viewport.modelTrackContainer;
43       var viewX = (e.clientX -
44                    modelTrackContainer.offsetLeft -
45                    constants.HEADING_WIDTH) * pixelRatio;
46       return this.viewport.currentDisplayTransform.xViewToWorld(viewX);
47     },
48
49     onEnterTiming: function(e) {
50       // Show the cursor marker if it was the active marker, otherwise the two
51       // range markers should be left.
52       if (this.activeMarker_ === this.cursorMarker_)
53         this.viewport.addMarker(this.cursorMarker_);
54
55       this.targetElement_.addEventListener('mousemove', this.onMouseMove_);
56       this.targetElement_.addEventListener('dblclick', this.onDblClick_);
57     },
58
59     onBeginTiming: function(e) {
60       var worldX = this.getWorldXFromEvent_(e);
61
62       // Check if click was on a range marker that can be moved.
63       if (!this.activeMarker_) {
64         var marker = this.viewport.findMarkerNear(worldX, 6);
65         if (marker === this.rangeStartMarker_ ||
66             marker === this.rangeEndMarker_) {
67           // Set the clicked marker as active marker so it will be moved.
68           this.activeMarker_ = marker;
69           marker.selected = true;
70           return;
71         }
72       } else {
73         // Otherwise start selecting a new range by hiding the cursor marker and
74         // adding the end marker. This is skipped if there was already a range
75         // on screen.
76         this.viewport.removeMarker(this.cursorMarker_);
77         this.viewport.addMarker(this.rangeEndMarker_);
78       }
79
80       // Set the range markers to the mouse or snapped position and select them.
81       var snapPos = this.getSnappedToEventPosition_(e);
82       this.updateMarkerToSnapPosition_(this.rangeStartMarker_, snapPos);
83       this.updateMarkerToSnapPosition_(this.rangeEndMarker_, snapPos);
84
85       this.rangeStartMarker_.selected = true;
86       this.rangeEndMarker_.selected = true;
87
88       // The end marker is the one that is moved.
89       this.activeMarker_ = this.rangeEndMarker_;
90     },
91
92     onUpdateTiming: function(e) {
93       if (!this.activeMarker_ || this.activeMarker_ === this.cursorMarker_)
94         return;
95
96       // Update the position of the active marker to the cursor position.
97       // This is either the end marker when creating a range, or one of the
98       // range markers when they are moved.
99       var snapPos = this.getSnappedToEventPosition_(e);
100       this.updateMarkerToSnapPosition_(this.activeMarker_, snapPos);
101
102       // When creating a range, only show the start marker after the range
103       // exceeds a certain amount. This prevents a short flicker showing the
104       // dimmed areas left and right of the range when clicking.
105       if (this.rangeStartMarker_.selected && this.rangeEndMarker_.selected) {
106         var rangeX = Math.abs(this.rangeStartMarker_.positionView -
107                               this.rangeEndMarker_.positionView);
108         if (rangeX >= constants.MIN_MOUSE_SELECTION_DISTANCE)
109           this.viewport.addMarker(this.rangeStartMarker_);
110       }
111     },
112
113     onEndTiming: function(e) {
114       if (!this.activeMarker_ || !this.activeMarker_.selected)
115         return;
116
117       e.preventDefault();
118
119       // Check if a range selection is finished now.
120       if (this.rangeStartMarker_.selected && this.rangeEndMarker_.selected) {
121         var rangeX = Math.abs(this.rangeStartMarker_.positionView -
122                               this.rangeEndMarker_.positionView);
123
124         // The range is only valid when it exceeds the minimum mouse selection
125         // distance, otherwise it could have been just a click.
126         if (rangeX >= constants.MIN_MOUSE_SELECTION_DISTANCE) {
127           this.rangeStartMarker_.selected = false;
128           this.rangeEndMarker_.selected = false;
129           this.activeMarker_ = null;
130         } else {
131           // If the range is not valid, hide it and activate the cursor marker.
132           this.viewport.removeMarker(this.rangeStartMarker_);
133           this.viewport.removeMarker(this.rangeEndMarker_);
134
135           this.viewport.addMarker(this.cursorMarker_);
136           this.cursorMarker_.positionWorld =
137               this.getWorldXFromEvent_(e);
138           this.activeMarker_ = this.cursorMarker_;
139           e.preventDefault();
140         }
141         return;
142       }
143
144       // Deselect and deactivate a range marker that was moved.
145       this.activeMarker_.selected = false;
146       this.activeMarker_ = null;
147     },
148
149     onExitTiming: function(e) {
150       // If there is a selected range the markers are left on screen, but the
151       // cursor marker gets removed.
152       if (this.activeMarker_ === this.cursorMarker_)
153         this.viewport.removeMarker(this.cursorMarker_);
154
155       this.targetElement_.removeEventListener('mousemove', this.onMouseMove_);
156       this.targetElement_.removeEventListener('dblclick', this.onDblClick_);
157     },
158
159     onMouseMove_: function(e) {
160       var worldX = this.getWorldXFromEvent_(e);
161
162       if (this.activeMarker_) {
163         // Update the position of the cursor marker.
164         if (this.activeMarker_ === this.cursorMarker_) {
165           var snapPos = this.getSnappedToEventPosition_(e);
166           this.updateMarkerToSnapPosition_(this.cursorMarker_, snapPos);
167         }
168         return;
169       }
170
171       // If there is no active marker then look for a marker close to the cursor
172       // and indicate that it can be moved by displaying it selected.
173       var marker = this.viewport.findMarkerNear(worldX, 6);
174       if (marker === this.rangeStartMarker_ ||
175           marker === this.rangeEndMarker_) {
176         marker.selected = true;
177       } else {
178         // Otherwise deselect markers that may have been selected before.
179         this.rangeEndMarker_.selected = false;
180         this.rangeStartMarker_.selected = false;
181       }
182     },
183
184     onDblClick_: function(e) {
185       var modelTrackContainer = this.viewport.modelTrackContainer;
186       var modelTrackContainerRect = modelTrackContainer.getBoundingClientRect();
187
188       var eventWorldX = this.getWorldXFromEvent_(e);
189       var y = e.clientY;
190
191       var selection = new tracing.Selection();
192       modelTrackContainer.addClosestEventToSelection(
193           eventWorldX, Infinity, y, y, selection);
194
195       if (!selection.length)
196         return;
197
198       var slice = selection[0];
199
200       if (!(slice instanceof tracing.trace_model.Slice))
201         return;
202
203       if (slice.start > eventWorldX || slice.end < eventWorldX)
204         return;
205
206       var track = this.viewport.trackForEvent(slice);
207       var trackRect = track.getBoundingClientRect();
208
209       var snapPos = {
210         x: slice.start,
211         y: trackRect.top +
212             modelTrackContainer.scrollTop - modelTrackContainerRect.top,
213         height: trackRect.height,
214         snapped: true
215       };
216       this.updateMarkerToSnapPosition_(this.rangeStartMarker_, snapPos);
217       snapPos.x = slice.end;
218       this.updateMarkerToSnapPosition_(this.rangeEndMarker_, snapPos);
219
220       this.viewport.addMarker(this.rangeStartMarker_);
221       this.viewport.addMarker(this.rangeEndMarker_);
222       this.viewport.removeMarker(this.cursorMarker_);
223       this.activeMarker_ = null;
224     },
225
226     /**
227      * Get the closest position of an event within a vertical range of the mouse
228      * position if possible, otherwise use the position of the mouse pointer.
229      * @param {MouseEvent} e Mouse event with the current mouse coordinates.
230      * @return {
231      *   {Number} x, The x coordinate in world space.
232      *   {Number} y, The y coordinate in world space.
233      *   {Number} height, The height of the event.
234      *   {boolean} snapped Whether the coordinates are from a snapped event or
235      *     the mouse position.
236      * }
237      */
238     getSnappedToEventPosition_: function(e) {
239       var pixelRatio = window.devicePixelRatio || 1;
240       var EVENT_SNAP_RANGE = 16 * pixelRatio;
241
242       var modelTrackContainer = this.viewport.modelTrackContainer;
243       var modelTrackContainerRect = modelTrackContainer.getBoundingClientRect();
244
245       var viewport = this.viewport;
246       var dt = viewport.currentDisplayTransform;
247       var worldMaxDist = dt.xViewVectorToWorld(EVENT_SNAP_RANGE);
248
249       var worldX = this.getWorldXFromEvent_(e);
250       var mouseY = e.clientY;
251
252       var selection = new tracing.Selection();
253
254       // Look at the track under mouse position first for better performance.
255       modelTrackContainer.addClosestEventToSelection(
256           worldX, worldMaxDist, mouseY, mouseY, selection);
257
258       // Look at all tracks visible on screen.
259       if (!selection.length) {
260         modelTrackContainer.addClosestEventToSelection(
261             worldX, worldMaxDist,
262             modelTrackContainerRect.top, modelTrackContainerRect.bottom,
263             selection);
264       }
265
266       var minDistX = worldMaxDist;
267       var minDistY = Infinity;
268       var pixWidth = dt.xViewVectorToWorld(1);
269
270       // Create result object with the mouse coordinates.
271       var result = {
272         x: worldX,
273         y: mouseY - modelTrackContainerRect.top,
274         height: 0,
275         snapped: false
276       };
277
278       var eventBounds = new tvcm.Range();
279       for (var i = 0; i < selection.length; i++) {
280         var event = selection[i];
281         var track = viewport.trackForEvent(event);
282         var trackRect = track.getBoundingClientRect();
283
284         eventBounds.reset();
285         event.addBoundsToRange(eventBounds);
286         var eventX;
287         if (Math.abs(eventBounds.min - worldX) <
288             Math.abs(eventBounds.max - worldX)) {
289           eventX = eventBounds.min;
290         } else {
291           eventX = eventBounds.max;
292         }
293
294         var distX = eventX - worldX;
295
296         var eventY = trackRect.top;
297         var eventHeight = trackRect.height;
298         var distY = Math.abs(eventY + eventHeight / 2 - mouseY);
299
300         // Prefer events with a closer y position if their x difference is below
301         // the width of a pixel.
302         if ((distX <= minDistX || Math.abs(distX - minDistX) < pixWidth) &&
303             distY < minDistY) {
304           minDistX = distX;
305           minDistY = distY;
306
307           // Retrieve the event position from the hit.
308           result.x = eventX;
309           result.y = eventY +
310               modelTrackContainer.scrollTop - modelTrackContainerRect.top;
311           result.height = eventHeight;
312           result.snapped = true;
313         }
314       }
315
316       return result;
317     },
318
319     /**
320      * Update the marker to the snapped position.
321      * @param {ViewportMarker} marker The marker to be updated.
322      * @param {
323      *   {Number} x, The new positionWorld of the marker.
324      *   {Number} y, The new indicatorY of the marker.
325      *   {Number} height, The new indicatorHeight of the marker.
326      *   {boolean} snapped Whether the coordinates are from a snapped event or
327      *     the mouse position.
328      * } snapPos
329      */
330     updateMarkerToSnapPosition_: function(marker, snapPos) {
331       marker.setSnapIndicator(snapPos.snapped, snapPos.y, snapPos.height);
332       marker.positionWorld = snapPos.x;
333     }
334   };
335
336   return {
337     TimingTool: TimingTool
338   };
339 });