blob: 0885fddad6edc48987c51f38eb8ff6d6eecf504b [file] [log] [blame]
<!DOCTYPE html>
<!--
Copyright (c) 2012 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<link rel="import" href="/base/events.html">
<link rel="import" href="/base/guid.html">
<link rel="import" href="/base/range.html">
<link rel="import" href="/core/trace_model/trace_model.html">
<script>
'use strict';
/**
* @fileoverview Code for the viewport.
*/
tv.exportTo('tv.c', function() {
var EventRegistry = tv.c.trace_model.EventRegistry;
var RequestSelectionChangeEvent = tv.b.Event.bind(
undefined, 'requestSelectionChange', true, false);
/**
* Represents a selection within a and its associated set of tracks.
* @constructor
*/
function Selection(opt_events) {
// sunburst_zoom_level is used by sunburst chart to remember
// zoom level across selection changes.
// TODO(gholap): get rid of this eventually when
// selections support frames.
this.sunburst_zoom_level = undefined;
this.bounds_dirty_ = true;
this.bounds_ = new tv.b.Range();
this.length_ = 0;
this.guid_ = tv.b.GUID.allocate();
this.pushed_guids_ = {};
if (opt_events) {
for (var i = 0; i < opt_events.length; i++)
this.push(opt_events[i]);
}
}
Selection.prototype = {
__proto__: Object.prototype,
get bounds() {
if (this.bounds_dirty_) {
this.bounds_.reset();
for (var i = 0; i < this.length_; i++)
this[i].addBoundsToRange(this.bounds_);
this.bounds_dirty_ = false;
}
return this.bounds_;
},
get duration() {
if (this.bounds_.isEmpty)
return 0;
return this.bounds_.max - this.bounds_.min;
},
get length() {
return this.length_;
},
get guid() {
return this.guid_;
},
clear: function() {
for (var i = 0; i < this.length_; ++i)
delete this[i];
this.length_ = 0;
this.bounds_dirty_ = true;
},
// push pushes only unique events.
// If an event has been already pushed, do nothing.
push: function(event) {
if (event.guid == undefined)
throw new Error('Event must have a GUID');
if (this.pushed_guids_[event.guid])
return event;
this.pushed_guids_[event.guid] = true;
this[this.length_++] = event;
this.bounds_dirty_ = true;
return event;
},
addSelection: function(selection) {
for (var i = 0; i < selection.length; i++)
this.push(selection[i]);
},
subSelection: function(index, count) {
count = count || 1;
var selection = new Selection();
selection.bounds_dirty_ = true;
if (index < 0 || index + count > this.length_)
throw new Error('Index out of bounds');
for (var i = index; i < index + count; i++)
selection.push(this[i]);
return selection;
},
getEventsOrganizedByBaseType: function(opt_pruneEmpty) {
var events = {};
var allTypeInfos = EventRegistry.getAllRegisteredTypeInfos();
allTypeInfos.forEach(function(eventTypeInfo) {
events[eventTypeInfo.metadata.name] = new Selection();
if (this.sunburst_zoom_level !== undefined)
events[eventTypeInfo.metadata.name].sunburst_zoom_level =
this.sunburst_zoom_level;
}, this);
this.forEach(function(event, i) {
var maxEventIndex = -1;
var maxEventTypeInfo = undefined;
allTypeInfos.forEach(function(eventTypeInfo, eventIndex) {
if (!(event instanceof eventTypeInfo.constructor))
return;
if (eventIndex > maxEventIndex) {
maxEventIndex = eventIndex;
maxEventTypeInfo = eventTypeInfo;
}
});
if (maxEventIndex == -1)
throw new Error('Unrecgonized event type');
events[maxEventTypeInfo.metadata.name].push(event);
});
if (opt_pruneEmpty) {
var prunedEvents = {};
for (var eventType in events) {
if (events[eventType].length > 0)
prunedEvents[eventType] = events[eventType];
}
return prunedEvents;
} else {
return events;
}
},
enumEventsOfType: function(type, func) {
for (var i = 0; i < this.length_; i++)
if (this[i] instanceof type)
func(this[i]);
},
/**
* Helper for selection previous or next.
* @param {boolean} offset If positive, select one forward (next).
* Else, select previous.
*
* @param {TimelineViewport} viewport The viewport to use to determine what
* is near to the current selection.
*
* @return {boolean} true if current selection changed.
*/
getShiftedSelection: function(viewport, offset) {
var newSelection = new Selection();
for (var i = 0; i < this.length_; i++) {
var event = this[i];
var addEventToNewSelection = function(event) {
};
// If this is a flow event, and we have a next/prev item in the chain
// then we use that as the item to move too. Otherwise, we let the
// normal movement for a slice kick in and use that.
if (event instanceof tv.c.trace_model.FlowEvent) {
if ((offset > 0) && event.nextFlowEvent) {
newSelection.push(event.nextFlowEvent);
continue;
} else if ((offset < 0) && event.previousFlowEvent) {
newSelection.push(event.previousFlowEvent);
continue;
}
}
var track = viewport.trackForEvent(event);
track.addItemNearToProvidedEventToSelection(
event, offset, newSelection);
}
if (newSelection.length == 0)
return undefined;
return newSelection;
},
forEach: function(fn, opt_this) {
for (var i = 0; i < this.length; i++)
fn.call(opt_this, this[i], i);
},
map: function(fn, opt_this) {
var res = [];
for (var i = 0; i < this.length; i++)
res.push(fn.call(opt_this, this[i], i));
return res;
},
every: function(fn, opt_this) {
for (var i = 0; i < this.length; i++)
if (!fn.call(opt_this, this[i], i))
return false;
return true;
},
some: function(fn, opt_this) {
for (var i = 0; i < this.length; i++)
if (fn.call(opt_this, this[i], i))
return true;
return false;
}
};
return {
Selection: Selection,
RequestSelectionChangeEvent: RequestSelectionChangeEvent
};
});
</script>