blob: 3b53496d5019782c67d236e4e13eb668712ea83f [file] [log] [blame]
<!DOCTYPE html>
<!--
Copyright (c) 2015 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/event_target.html">
<link rel="import" href="/base/task.html">
<link rel="import" href="/ui/base/brushing_state.html">
<link rel="import" href="/ui/timeline_viewport.html">
<link rel="import" href="/ui/base/ui_state.html">
<link rel="import" href="/model/event_set.html">
<link rel="import" href="/model/selection_state.html">
<script>
'use strict';
tr.exportTo('tr.c', function() {
var BrushingState = tr.ui.b.BrushingState;
var EventSet = tr.model.EventSet;
var SelectionState = tr.model.SelectionState;
var Viewport = tr.ui.TimelineViewport;
function BrushingStateController(timelineView) {
tr.b.EventTarget.call(this);
this.timelineView_ = timelineView;
this.currentBrushingState_ = new BrushingState();
this.onPopState_ = this.onPopState_.bind(this);
this.historyEnabled_ = false;
this.selections_ = {};
}
BrushingStateController.prototype = {
__proto__: tr.b.EventTarget.prototype,
dispatchChangeEvent_: function() {
var e = new tr.b.Event('change', false, false);
this.dispatchEvent(e);
},
get model() {
if (!this.timelineView_)
return undefined;
return this.timelineView_.model;
},
get trackView() {
if (!this.timelineView_)
return undefined;
return this.timelineView_.trackView;
},
get viewport() {
if (!this.timelineView_)
return undefined;
if (!this.timelineView_.trackView)
return undefined;
return this.timelineView_.trackView.viewport;
},
/* History system */
get historyEnabled() {
return this.historyEnabled_;
},
set historyEnabled(historyEnabled) {
this.historyEnabled_ = !!historyEnabled;
if (historyEnabled)
window.addEventListener('popstate', this.onPopState_);
else
window.removeEventListener('popstate', this.onPopState_);
},
modelWillChange: function() {
if (this.currentBrushingState_.isAppliedToModel)
this.currentBrushingState_.unapplyFromModelSelectionState();
},
modelDidChange: function() {
this.selections_ = {};
this.currentBrushingState_ = new BrushingState();
this.currentBrushingState_.applyToModelSelectionState(this.model);
var e = new tr.b.Event('model-changed', false, false);
this.dispatchEvent(e);
this.dispatchChangeEvent_();
},
onUserInitiatedSelectionChange_: function() {
var selection = this.selection;
if (this.historyEnabled) {
// Save the selection so that when back button is pressed,
// it could be retrieved.
this.selections_[selection.guid] = selection;
var state = {
selection_guid: selection.guid
};
window.history.pushState(state, document.title);
}
},
onPopState_: function(e) {
if (e.state === null)
return;
var selection = this.selections_[e.state.selection_guid];
if (selection) {
var newState = this.currentBrushingState_.clone();
newState.selection = selection;
this.currentBrushingState = newState;
}
e.stopPropagation();
},
get selection() {
return this.currentBrushingState_.selection;
},
get findMatches() {
return this.currentBrushingState_.findMatches;
},
get selectionOfInterest() {
return this.currentBrushingState_.selectionOfInterest;
},
get currentBrushingState() {
return this.currentBrushingState_;
},
set currentBrushingState(newBrushingState) {
if (newBrushingState.isAppliedToModel)
throw new Error('Cannot apply this state, it is applied');
// This function uses value-equality on the states so that state can
// changed to a clone of itself without causing a change event, while
// still having the actual state object change to the new clone.
var hasValueChanged = !this.currentBrushingState_.equals(
newBrushingState);
if (newBrushingState !== this.currentBrushingState_ && !hasValueChanged) {
if (this.currentBrushingState_.isAppliedToModel) {
this.currentBrushingState_.transferModelOwnershipToClone(
newBrushingState);
}
this.currentBrushingState_ = newBrushingState;
return;
}
if (this.currentBrushingState_.isAppliedToModel)
this.currentBrushingState_.unapplyFromModelSelectionState();
this.currentBrushingState_ = newBrushingState;
if (this.model)
this.currentBrushingState_.applyToModelSelectionState(this.model);
this.dispatchChangeEvent_();
},
/**
* @param {Filter} filter The filter to use for finding matches.
* @param {Selection} selection The selection to add matches to.
* @return {Task} which performs the filtering.
*/
addAllEventsMatchingFilterToSelectionAsTask: function(filter, selection) {
var timelineView = this.timelineView_.trackView;
if (!timelineView)
return new tr.b.Task();
return timelineView.addAllEventsMatchingFilterToSelectionAsTask(
filter, selection);
},
findTextChangedTo: function(allPossibleMatches) {
var newBrushingState = this.currentBrushingState_.clone();
newBrushingState.findMatches = allPossibleMatches;
this.currentBrushingState = newBrushingState;
},
findFocusChangedTo: function(currentFocus) {
var newBrushingState = this.currentBrushingState_.clone();
newBrushingState.selection = currentFocus;
this.currentBrushingState = newBrushingState;
this.onUserInitiatedSelectionChange_();
},
findTextCleared: function() {
if (this.xNavStringMarker_ !== undefined) {
this.model.removeAnnotation(this.xNavStringMarker_);
this.xNavStringMarker_ = undefined;
}
if (this.guideLineAnnotation_ !== undefined) {
this.model.removeAnnotation(this.guideLineAnnotation_);
this.guideLineAnnotation_ = undefined;
}
var newBrushingState = this.currentBrushingState_.clone();
newBrushingState.selection = new EventSet();
newBrushingState.findMatches = new EventSet();
this.currentBrushingState = newBrushingState;
this.onUserInitiatedSelectionChange_();
},
uiStateFromString: function(string) {
return tr.ui.b.UIState.fromUserFriendlyString(
this.model, this.viewport, string);
},
navToPosition: function(uiState, showNavLine) {
this.trackView.navToPosition(uiState, showNavLine);
},
changeSelectionFromTimeline: function(selection) {
var newBrushingState = this.currentBrushingState_.clone();
newBrushingState.selection = selection;
newBrushingState.findMatches = new EventSet();
this.currentBrushingState = newBrushingState;
this.onUserInitiatedSelectionChange_();
},
showScriptControlSelection: function(selection) {
var newBrushingState = this.currentBrushingState_.clone();
newBrushingState.selection = selection;
newBrushingState.findMatches = new EventSet();
this.currentBrushingState = newBrushingState;
},
changeSelectionFromRequestSelectionChangeEvent: function(selection) {
var newBrushingState = this.currentBrushingState_.clone();
newBrushingState.selection = selection;
newBrushingState.findMatches = new EventSet();
this.currentBrushingState = newBrushingState;
this.onUserInitiatedSelectionChange_();
},
changeAnalysisViewRelatedEvents: function(eventSet) {
var newBrushingState = this.currentBrushingState_.clone();
newBrushingState.analysisViewRelatedEvents = eventSet;
this.currentBrushingState = newBrushingState;
},
changeAnalysisLinkHoveredEvents: function(eventSet) {
var newBrushingState = this.currentBrushingState_.clone();
newBrushingState.analysisLinkHoveredEvents = eventSet;
this.currentBrushingState = newBrushingState;
},
getViewSpecificBrushingState: function(viewId) {
return this.currentBrushingState.viewSpecificBrushingStates[viewId];
},
changeViewSpecificBrushingState: function(viewId, newState) {
var oldStates = this.currentBrushingState_.viewSpecificBrushingStates;
var newStates = {};
for (var id in oldStates)
newStates[id] = oldStates[id];
if (newState === undefined)
delete newStates[viewId];
else
newStates[viewId] = newState;
var newBrushingState = this.currentBrushingState_.clone();
newBrushingState.viewSpecificBrushingStates = newStates;
this.currentBrushingState = newBrushingState;
}
};
BrushingStateController.getControllerForElement = function(element) {
if (tr.isHeadless)
throw new Error('Unsupported');
var currentElement = element;
while (currentElement) {
if (currentElement.brushingStateController)
return currentElement.brushingStateController;
// Walk up the DOM.
if (currentElement.parentElement) {
currentElement = currentElement.parentElement;
continue;
}
// Possibly inside a shadow DOM.
var currentNode = currentElement;
while (currentNode.parentNode)
currentNode = currentNode.parentNode;
currentElement = currentNode.host;
}
return undefined;
};
return {
BrushingStateController: BrushingStateController
};
});
</script>