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 */
10base.require('base.events');
11base.require('base.guid');
12base.require('base.range');
13
14base.exportTo('tracing', function() {
15
16  function SelectionSliceHit(track, slice) {
17    this.track = track;
18    this.slice = slice;
19  }
20  SelectionSliceHit.prototype = {
21    get modelObject() {
22      return this.slice;
23    },
24
25    get selected() {
26      return this.slice.selected;
27    },
28    set selected(v) {
29      this.slice.selected = v;
30    },
31    addBoundsToRange: function(range) {
32      range.addValue(this.slice.start);
33      range.addValue(this.slice.end);
34    }
35  };
36
37  function SelectionCounterSampleHit(track, counter, sampleIndex) {
38    this.track = track;
39    this.counter = counter;
40    this.sampleIndex = sampleIndex;
41  }
42
43  SelectionCounterSampleHit.prototype = {
44    get modelObject() {
45      return this.sampleIndex;
46    },
47
48    get selected() {
49      return this.track.selectedSamples[this.sampleIndex] == true;
50    },
51
52    set selected(v) {
53      if (v)
54        this.track.selectedSamples[this.sampleIndex] = true;
55      else
56        this.track.selectedSamples[this.sampleIndex] = false;
57    },
58
59    addBoundsToRange: function(range) {
60      if (!this.track.timestamps)
61        return;
62      range.addValue(this.track.timestamps[this.sampleIndex]);
63    }
64  };
65
66  function SelectionObjectSnapshotHit(track, objectSnapshot) {
67    this.track = track;
68    this.objectSnapshot = objectSnapshot;
69  }
70  SelectionObjectSnapshotHit.prototype = {
71    get modelObject() {
72      return this.objectSnapshot;
73    },
74
75    get selected() {
76      return this.objectSnapshot.selected;
77    },
78    set selected(v) {
79      this.objectSnapshot.selected = v;
80    },
81    addBoundsToRange: function(range) {
82      range.addValue(this.objectSnapshot.ts);
83    }
84  };
85
86  function SelectionObjectInstanceHit(track, objectInstance) {
87    this.track = track;
88    this.objectInstance = objectInstance;
89  }
90  SelectionObjectInstanceHit.prototype = {
91    get modelObject() {
92      return this.objectInstance;
93    },
94
95    get selected() {
96      return this.objectInstance.selected;
97    },
98    set selected(v) {
99      this.objectInstance.selected = v;
100    },
101    addBoundsToRange: function(range) {
102      range.addRange(this.objectInstance.bounds);
103    }
104  };
105
106  var HIT_TYPES = [
107    {
108      constructor: SelectionSliceHit,
109      name: 'slice',
110      pluralName: 'slices'
111    },
112    {
113      constructor: SelectionCounterSampleHit,
114      name: 'counterSample',
115      pluralName: 'counterSamples'
116    },
117    {
118      constructor: SelectionObjectSnapshotHit,
119      name: 'objectSnapshot',
120      pluralName: 'objectSnapshots'
121    },
122    {
123      constructor: SelectionObjectInstanceHit,
124      name: 'objectInstance',
125      pluralName: 'objectInstances'
126    }
127  ];
128
129  /**
130   * Represents a selection within a  and its associated set of tracks.
131   * @constructor
132   */
133  function Selection(opt_hits) {
134    this.bounds_dirty_ = true;
135    this.bounds_ = new base.Range();
136    this.length_ = 0;
137    this.guid_ = base.GUID.allocate();
138
139    if (opt_hits)
140      this.pushHits(opt_hits);
141  }
142  Selection.prototype = {
143    __proto__: Object.prototype,
144
145    get bounds() {
146      if (this.bounds_dirty_) {
147        this.bounds_.reset();
148        for (var i = 0; i < this.length_; i++) {
149          var hit = this[i];
150          hit.addBoundsToRange(this.bounds_);
151        }
152        this.bounds_dirty_ = false;
153      }
154      return this.bounds_;
155    },
156
157    get duration() {
158      if (this.bounds_.isEmpty)
159        return 0;
160      return this.bounds_.max - this.bounds_.min;
161    },
162
163    get length() {
164      return this.length_;
165    },
166
167    get guid() {
168      return this.guid_;
169    },
170
171    clear: function() {
172      for (var i = 0; i < this.length_; ++i)
173        delete this[i];
174      this.length_ = 0;
175      this.bounds_dirty_ = true;
176    },
177
178    pushHit: function(hit) {
179      this.push_(hit);
180    },
181
182    pushHits: function(hits) {
183      for (var i = 0; i < hits.length; i++)
184        this.pushHit(hits[i]);
185    },
186
187    push_: function(hit) {
188      this[this.length_++] = hit;
189      this.bounds_dirty_ = true;
190      return hit;
191    },
192
193    addSlice: function(track, slice) {
194      return this.push_(new SelectionSliceHit(track, slice));
195    },
196
197    addCounterSample: function(track, counter, sampleIndex) {
198      return this.push_(
199          new SelectionCounterSampleHit(
200          track, counter, sampleIndex));
201    },
202
203    addObjectSnapshot: function(track, objectSnapshot) {
204      return this.push_(
205          new SelectionObjectSnapshotHit(track, objectSnapshot));
206    },
207
208    addObjectInstance: function(track, objectInstance) {
209      return this.push_(
210          new SelectionObjectInstanceHit(track, objectInstance));
211    },
212
213    addSelection: function(selection) {
214      for (var i = 0; i < selection.length; i++)
215        this.push_(selection[i]);
216    },
217
218    subSelection: function(index, count) {
219      count = count || 1;
220
221      var selection = new Selection();
222      selection.bounds_dirty_ = true;
223      if (index < 0 || index + count > this.length_)
224        throw new Error('Index out of bounds');
225
226      for (var i = index; i < index + count; i++)
227        selection.push_(this[i]);
228
229      return selection;
230    },
231
232    getCounterSampleHitsAsSelection: function() {
233      var selection = new Selection();
234      this.enumHitsOfType(SelectionCounterSampleHit,
235                          selection.push_.bind(selection));
236      return selection;
237    },
238
239    getSliceHitsAsSelection: function() {
240      var selection = new Selection();
241      this.enumHitsOfType(SelectionSliceHit,
242                          selection.push_.bind(selection));
243      return selection;
244    },
245
246    getHitsOrganizedByType: function() {
247      var hits = {};
248      HIT_TYPES.forEach(function(hitType) {
249        hits[hitType.pluralName] = new Selection();
250      });
251      for (var i = 0; i < this.length_; i++) {
252        var hit = this[i];
253        HIT_TYPES.forEach(function(hitType) {
254          if (hit instanceof hitType.constructor)
255            hits[hitType.pluralName].push_(hit);
256        });
257      }
258      return hits;
259    },
260
261    enumHitsOfType: function(type, func) {
262      for (var i = 0; i < this.length_; i++)
263        if (this[i] instanceof type)
264          func(this[i]);
265    },
266
267    getNumSliceHits: function() {
268      var numHits = 0;
269      this.enumHitsOfType(SelectionSliceHit, function(hit) { numHits++; });
270      return numHits;
271    },
272
273    getNumCounterHits: function() {
274      var numHits = 0;
275      this.enumHitsOfType(SelectionCounterSampleHit, function(hit) {
276        numHits++;
277      });
278      return numHits;
279    },
280
281    getNumObjectSnapshotHits: function() {
282      var numHits = 0;
283      this.enumHitsOfType(SelectionObjectSnapshotHit, function(hit) {
284        numHits++;
285      });
286      return numHits;
287    },
288
289    getNumObjectInstanceHits: function() {
290      var numHits = 0;
291      this.enumHitsOfType(SelectionObjectInstanceHit, function(hit) {
292        numHits++;
293      });
294      return numHits;
295    },
296
297    map: function(fn) {
298      for (var i = 0; i < this.length_; i++)
299        fn(this[i]);
300    },
301
302    /**
303     * Helper for selection previous or next.
304     * @param {boolean} forwardp If true, select one forward (next).
305     *   Else, select previous.
306     * @return {boolean} true if current selection changed.
307     */
308    getShiftedSelection: function(offset) {
309      var newSelection = new Selection();
310      for (var i = 0; i < this.length_; i++) {
311        var hit = this[i];
312        hit.track.addItemNearToProvidedHitToSelection(
313            hit, offset, newSelection);
314      }
315
316      if (newSelection.length == 0)
317        return undefined;
318      return newSelection;
319    }
320  };
321
322  function createSelectionFromObjectAndView(obj, opt_view) {
323    // TODO: fill in the track intelligently by finding the TimelineView, then
324    // finding the right track based on the provided object.
325    var track = undefined;
326
327    var selection = new Selection();
328    if (obj instanceof tracing.trace_model.Slice)
329      selection.addSlice(track, obj);
330    else if (obj instanceof tracing.trace_model.ObjectSnapshot)
331      selection.addObjectSnapshot(track, obj);
332    else if (obj instanceof tracing.trace_model.ObjectInstance)
333      selection.addObjectInstance(track, obj);
334    else
335      throw new Error('Unrecognized selection type');
336    return selection;
337  }
338
339  return {
340    SelectionSliceHit: SelectionSliceHit,
341    SelectionCounterSampleHit: SelectionCounterSampleHit,
342    SelectionObjectSnapshotHit: SelectionObjectSnapshotHit,
343    SelectionObjectInstanceHit: SelectionObjectInstanceHit,
344    Selection: Selection,
345    createSelectionFromObjectAndView: createSelectionFromObjectAndView
346  };
347});
348