1<!DOCTYPE html>
2<!--
3Copyright (c) 2015 The Chromium Authors. All rights reserved.
4Use of this source code is governed by a BSD-style license that can be
5found in the LICENSE file.
6-->
7
8<link rel="import" href="/tracing/base/event_target.html">
9<link rel="import" href="/tracing/base/task.html">
10<link rel="import" href="/tracing/ui/brushing_state.html">
11<link rel="import" href="/tracing/ui/timeline_viewport.html">
12<link rel="import" href="/tracing/ui/base/ui_state.html">
13<link rel="import" href="/tracing/model/event_set.html">
14<link rel="import" href="/tracing/model/selection_state.html">
15
16<script>
17'use strict';
18
19tr.exportTo('tr.c', function() {
20  var BrushingState = tr.ui.b.BrushingState;
21  var EventSet = tr.model.EventSet;
22  var SelectionState = tr.model.SelectionState;
23  var Viewport = tr.ui.TimelineViewport;
24
25  function BrushingStateController(timelineView) {
26    tr.b.EventTarget.call(this);
27
28    this.timelineView_ = timelineView;
29    this.currentBrushingState_ = new BrushingState();
30
31    this.onPopState_ = this.onPopState_.bind(this);
32    this.historyEnabled_ = false;
33    this.selections_ = {};
34  }
35
36  BrushingStateController.prototype = {
37    __proto__: tr.b.EventTarget.prototype,
38
39    dispatchChangeEvent_: function() {
40      var e = new tr.b.Event('change', false, false);
41      this.dispatchEvent(e);
42    },
43
44    get model() {
45      if (!this.timelineView_)
46        return undefined;
47      return this.timelineView_.model;
48    },
49
50    get trackView() {
51      if (!this.timelineView_)
52        return undefined;
53      return this.timelineView_.trackView;
54    },
55
56    get viewport() {
57      if (!this.timelineView_)
58        return undefined;
59      if (!this.timelineView_.trackView)
60        return undefined;
61      return this.timelineView_.trackView.viewport;
62    },
63
64    /* History system */
65    get historyEnabled() {
66      return this.historyEnabled_;
67    },
68
69    set historyEnabled(historyEnabled) {
70      this.historyEnabled_ = !!historyEnabled;
71      if (historyEnabled)
72        window.addEventListener('popstate', this.onPopState_);
73      else
74        window.removeEventListener('popstate', this.onPopState_);
75    },
76
77    modelWillChange: function() {
78      if (this.currentBrushingState_.isAppliedToModel)
79        this.currentBrushingState_.unapplyFromModelSelectionState();
80    },
81
82    modelDidChange: function() {
83      this.selections_ = {};
84
85      this.currentBrushingState_ = new BrushingState();
86      this.currentBrushingState_.applyToModelSelectionState(this.model);
87
88      var e = new tr.b.Event('model-changed', false, false);
89      this.dispatchEvent(e);
90
91      this.dispatchChangeEvent_();
92    },
93
94    onUserInitiatedSelectionChange_: function() {
95      var selection = this.selection;
96      if (this.historyEnabled) {
97        // Save the selection so that when back button is pressed,
98        // it could be retrieved.
99        this.selections_[selection.guid] = selection;
100        var state = {
101          selection_guid: selection.guid
102        };
103
104        window.history.pushState(state, document.title);
105      }
106    },
107
108    onPopState_: function(e) {
109      if (e.state === null)
110        return;
111
112      var selection = this.selections_[e.state.selection_guid];
113      if (selection) {
114        var newState = this.currentBrushingState_.clone();
115        newState.selection = selection;
116        this.currentBrushingState = newState;
117      }
118      e.stopPropagation();
119    },
120
121    get selection() {
122      return this.currentBrushingState_.selection;
123    },
124    get findMatches() {
125      return this.currentBrushingState_.findMatches;
126    },
127
128    get selectionOfInterest() {
129      return this.currentBrushingState_.selectionOfInterest;
130    },
131
132    get currentBrushingState() {
133      return this.currentBrushingState_;
134    },
135
136    set currentBrushingState(newBrushingState) {
137      if (newBrushingState.isAppliedToModel)
138        throw new Error('Cannot apply this state, it is applied');
139
140      // This function uses value-equality on the states so that state can
141      // changed to a clone of itself without causing a change event, while
142      // still having the actual state object change to the new clone.
143      var hasValueChanged = !this.currentBrushingState_.equals(
144          newBrushingState);
145
146      if (newBrushingState !== this.currentBrushingState_ && !hasValueChanged) {
147        if (this.currentBrushingState_.isAppliedToModel) {
148          this.currentBrushingState_.transferModelOwnershipToClone(
149              newBrushingState);
150        }
151        this.currentBrushingState_ = newBrushingState;
152        return;
153      }
154
155      if (this.currentBrushingState_.isAppliedToModel)
156        this.currentBrushingState_.unapplyFromModelSelectionState();
157
158      this.currentBrushingState_ = newBrushingState;
159
160      if (this.model)
161        this.currentBrushingState_.applyToModelSelectionState(this.model);
162
163      this.dispatchChangeEvent_();
164    },
165
166    /**
167     * @param {Filter} filter The filter to use for finding matches.
168     * @param {Selection} selection The selection to add matches to.
169     * @return {Task} which performs the filtering.
170     */
171    addAllEventsMatchingFilterToSelectionAsTask: function(filter, selection) {
172      var timelineView = this.timelineView_.trackView;
173      if (!timelineView)
174        return new tr.b.Task();
175      return timelineView.addAllEventsMatchingFilterToSelectionAsTask(
176          filter, selection);
177    },
178
179    findTextChangedTo: function(allPossibleMatches) {
180      var newBrushingState = this.currentBrushingState_.clone();
181      newBrushingState.findMatches = allPossibleMatches;
182      this.currentBrushingState = newBrushingState;
183    },
184
185    findFocusChangedTo: function(currentFocus) {
186      var newBrushingState = this.currentBrushingState_.clone();
187      newBrushingState.selection = currentFocus;
188      this.currentBrushingState = newBrushingState;
189
190      this.onUserInitiatedSelectionChange_();
191    },
192
193    findTextCleared: function() {
194      if (this.xNavStringMarker_ !== undefined) {
195        this.model.removeAnnotation(this.xNavStringMarker_);
196        this.xNavStringMarker_ = undefined;
197      }
198
199      if (this.guideLineAnnotation_ !== undefined) {
200        this.model.removeAnnotation(this.guideLineAnnotation_);
201        this.guideLineAnnotation_ = undefined;
202      }
203
204      var newBrushingState = this.currentBrushingState_.clone();
205      newBrushingState.selection = new EventSet();
206      newBrushingState.findMatches = new EventSet();
207      this.currentBrushingState = newBrushingState;
208
209      this.onUserInitiatedSelectionChange_();
210    },
211
212    uiStateFromString: function(string) {
213      return tr.ui.b.UIState.fromUserFriendlyString(
214          this.model, this.viewport, string);
215    },
216
217    navToPosition: function(uiState, showNavLine) {
218      this.trackView.navToPosition(uiState, showNavLine);
219    },
220
221    changeSelectionFromTimeline: function(selection) {
222      var newBrushingState = this.currentBrushingState_.clone();
223      newBrushingState.selection = selection;
224      newBrushingState.findMatches = new EventSet();
225      this.currentBrushingState = newBrushingState;
226
227      this.onUserInitiatedSelectionChange_();
228    },
229
230    showScriptControlSelection: function(selection) {
231      var newBrushingState = this.currentBrushingState_.clone();
232      newBrushingState.selection = selection;
233      newBrushingState.findMatches = new EventSet();
234      this.currentBrushingState = newBrushingState;
235    },
236
237    changeSelectionFromRequestSelectionChangeEvent: function(selection) {
238      var newBrushingState = this.currentBrushingState_.clone();
239      newBrushingState.selection = selection;
240      newBrushingState.findMatches = new EventSet();
241      this.currentBrushingState = newBrushingState;
242
243      this.onUserInitiatedSelectionChange_();
244    },
245
246    changeAnalysisViewRelatedEvents: function(eventSet) {
247      var newBrushingState = this.currentBrushingState_.clone();
248      newBrushingState.analysisViewRelatedEvents = eventSet;
249      this.currentBrushingState = newBrushingState;
250    },
251
252    changeAnalysisLinkHoveredEvents: function(eventSet) {
253      var newBrushingState = this.currentBrushingState_.clone();
254      newBrushingState.analysisLinkHoveredEvents = eventSet;
255      this.currentBrushingState = newBrushingState;
256    },
257
258    getViewSpecificBrushingState: function(viewId) {
259      return this.currentBrushingState.viewSpecificBrushingStates[viewId];
260    },
261
262    changeViewSpecificBrushingState: function(viewId, newState) {
263      var oldStates = this.currentBrushingState_.viewSpecificBrushingStates;
264      var newStates = {};
265      for (var id in oldStates)
266        newStates[id] = oldStates[id];
267      if (newState === undefined)
268        delete newStates[viewId];
269      else
270        newStates[viewId] = newState;
271
272      var newBrushingState = this.currentBrushingState_.clone();
273      newBrushingState.viewSpecificBrushingStates = newStates;
274      this.currentBrushingState = newBrushingState;
275    }
276  };
277
278  BrushingStateController.getControllerForElement = function(element) {
279    if (tr.isHeadless)
280      throw new Error('Unsupported');
281    var currentElement = element;
282    while (currentElement) {
283      if (currentElement.brushingStateController)
284        return currentElement.brushingStateController;
285
286      // Walk up the DOM.
287      if (currentElement.parentElement) {
288        currentElement = currentElement.parentElement;
289        continue;
290      }
291
292      // Possibly inside a shadow DOM.
293      var currentNode = currentElement;
294      while (currentNode.parentNode)
295        currentNode = currentNode.parentNode;
296      currentElement = currentNode.host;
297    }
298    return undefined;
299  };
300
301  return {
302    BrushingStateController: BrushingStateController
303  };
304});
305</script>
306