1<!DOCTYPE html>
2<!--
3Copyright (c) 2012 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/task.html">
9<link rel="import" href="/tracing/core/filter.html">
10<link rel="import" href="/tracing/model/event_set.html">
11
12<script>
13'use strict';
14
15/**
16 * @fileoverview FindController.
17 */
18tr.exportTo('tr.ui', function() {
19  var Task = tr.b.Task;
20
21  function FindController(brushingStateController) {
22    this.brushingStateController_ = brushingStateController;
23    this.filterHits_ = new tr.model.EventSet();
24    this.currentHitIndex_ = -1;
25    this.activePromise_ = Promise.resolve();
26    this.activeTask_ = undefined;
27  };
28
29  FindController.prototype = {
30    __proto__: Object.prototype,
31
32    get model() {
33      return this.brushingStateController_.model;
34    },
35
36    get brushingStateController() {
37      return this.brushingStateController_;
38    },
39
40    enqueueOperation_: function(operation) {
41      var task;
42      if (operation instanceof tr.b.Task)
43        task = operation;
44      else
45        task = new tr.b.Task(operation, this);
46      if (this.activeTask_) {
47        this.activeTask_ = this.activeTask_.enqueue(task);
48      } else {
49        // We're enqueuing the first task, schedule it.
50        this.activeTask_ = task;
51        this.activePromise_ = Task.RunWhenIdle(this.activeTask_);
52        this.activePromise_.then(function() {
53          this.activePromise_ = undefined;
54          this.activeTask_ = undefined;
55        }.bind(this));
56      }
57    },
58
59    /**
60     * Updates the filter hits based on the provided |filterText|. Returns a
61     * promise which resolves when |filterHits| has been refreshed.
62     */
63    startFiltering: function(filterText) {
64      var sc = this.brushingStateController_;
65      if (!sc)
66        return;
67
68      // TODO(beaudoin): Cancel anything left in the task queue, without
69      // invalidating the promise.
70      this.enqueueOperation_(function() {
71        this.filterHits_ = new tr.model.EventSet();
72        this.currentHitIndex_ = -1;
73      }.bind(this));
74
75      // Try constructing a UIState from the filterText.
76      // UIState.fromUserFriendlyString will throw an error only if the string
77      // is syntactically correct to a UI state string but with invalid values.
78      // It will return undefined if there is no syntactic match.
79      var stateFromString;
80      try {
81        stateFromString = sc.uiStateFromString(filterText);
82      } catch (e) {
83        this.enqueueOperation_(function() {
84          var overlay = new tr.ui.b.Overlay();
85          overlay.textContent = e.message;
86          overlay.title = 'UI State Navigation Error';
87          overlay.visible = true;
88        });
89        return this.activePromise_;
90      }
91
92      if (stateFromString !== undefined) {
93        this.enqueueOperation_(
94            sc.navToPosition.bind(this, stateFromString, true));
95      } else {
96        // filterText is not a navString here -- proceed with find and filter.
97        if (filterText.length === 0) {
98          this.enqueueOperation_(sc.findTextCleared.bind(sc));
99        } else {
100          var filter = new tr.c.FullTextFilter(filterText);
101          var filterHits = new tr.model.EventSet();
102          this.enqueueOperation_(sc.addAllEventsMatchingFilterToSelectionAsTask(
103              filter, filterHits));
104          this.enqueueOperation_(function() {
105            this.filterHits_ = filterHits;
106            sc.findTextChangedTo(filterHits);
107          }.bind(this));
108        }
109      }
110      return this.activePromise_;
111    },
112
113    /**
114     * Returns the most recent filter hits as a tr.model.EventSet. Call
115     * |startFiltering| to ensure this is up to date after the filter settings
116     * have been changed.
117     */
118    get filterHits() {
119      return this.filterHits_;
120    },
121
122    get currentHitIndex() {
123      return this.currentHitIndex_;
124    },
125
126    find_: function(dir) {
127      var firstHit = this.currentHitIndex_ === -1;
128      if (firstHit && dir < 0)
129        this.currentHitIndex_ = 0;
130
131      var N = this.filterHits.length;
132      this.currentHitIndex_ = (this.currentHitIndex_ + dir + N) % N;
133
134      if (!this.brushingStateController_)
135        return;
136
137      this.brushingStateController_.findFocusChangedTo(
138          this.filterHits.subEventSet(this.currentHitIndex_, 1));
139    },
140
141    findNext: function() {
142      this.find_(1);
143    },
144
145    findPrevious: function() {
146      this.find_(-1);
147    }
148  };
149
150  return {
151    FindController: FindController
152  };
153});
154</script>
155