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 FindControl and FindController. 9 */ 10base.require('tracing.timeline_track_view'); 11base.require('tracing.filter'); 12base.require('ui.overlay'); 13base.exportTo('tracing', function() { 14 15 /** 16 * FindControl 17 * @constructor 18 * @extends {ui.Overlay} 19 */ 20 var FindControl = ui.define('div'); 21 22 FindControl.prototype = { 23 __proto__: ui.Overlay.prototype, 24 25 decorate: function() { 26 ui.Overlay.prototype.decorate.call(this); 27 28 this.className = 'find-control'; 29 30 this.hitCountEl_ = document.createElement('div'); 31 this.hitCountEl_.className = 'hit-count-label'; 32 this.hitCountEl_.textContent = '1 of 7'; 33 34 var findPreviousBn = document.createElement('div'); 35 findPreviousBn.className = 'button find-previous'; 36 findPreviousBn.textContent = '\u2190'; 37 findPreviousBn.addEventListener('click', this.findPrevious_.bind(this)); 38 39 var findNextBn = document.createElement('div'); 40 findNextBn.className = 'button find-next'; 41 findNextBn.textContent = '\u2192'; 42 findNextBn.addEventListener('click', this.findNext_.bind(this)); 43 44 // Filter input element. 45 this.filterEl_ = document.createElement('input'); 46 this.filterEl_.type = 'input'; 47 48 this.filterEl_.addEventListener('input', 49 this.filterTextChanged_.bind(this)); 50 51 this.filterEl_.addEventListener('keydown', function(e) { 52 if (e.keyCode == 13) { 53 if (e.shiftKey) 54 this.findPrevious_(); 55 else 56 this.findNext_(); 57 } else if (e.keyCode == 27) { 58 this.filterEl_.blur(); 59 this.updateHitCountEl_(); 60 } 61 }.bind(this)); 62 63 this.filterEl_.addEventListener('blur', function(e) { 64 this.updateHitCountEl_(); 65 }.bind(this)); 66 67 this.filterEl_.addEventListener('focus', function(e) { 68 this.controller.reset(); 69 this.filterTextChanged_(); 70 this.filterEl_.select(); 71 }.bind(this)); 72 73 // Prevent that the input text is deselected after focusing the find 74 // control with the mouse. 75 this.filterEl_.addEventListener('mouseup', function(e) { 76 e.preventDefault(); 77 }); 78 79 // Attach everything. 80 this.appendChild(this.filterEl_); 81 82 this.appendChild(findPreviousBn); 83 this.appendChild(findNextBn); 84 this.appendChild(this.hitCountEl_); 85 86 this.updateHitCountEl_(); 87 }, 88 89 get controller() { 90 return this.controller_; 91 }, 92 93 set controller(c) { 94 this.controller_ = c; 95 this.updateHitCountEl_(); 96 }, 97 98 focus: function() { 99 this.filterEl_.focus(); 100 }, 101 102 filterTextChanged_: function() { 103 this.controller.filterText = this.filterEl_.value; 104 this.updateHitCountEl_(); 105 }, 106 107 findNext_: function() { 108 if (this.controller) 109 this.controller.findNext(); 110 this.updateHitCountEl_(); 111 }, 112 113 findPrevious_: function() { 114 if (this.controller) 115 this.controller.findPrevious(); 116 this.updateHitCountEl_(); 117 }, 118 119 updateHitCountEl_: function() { 120 if (!this.controller || document.activeElement != this.filterEl_) { 121 this.hitCountEl_.textContent = ''; 122 return; 123 } 124 var i = this.controller.currentHitIndex; 125 var n = this.controller.filterHits.length; 126 if (n == 0) 127 this.hitCountEl_.textContent = '0 of 0'; 128 else 129 this.hitCountEl_.textContent = (i + 1) + ' of ' + n; 130 } 131 }; 132 133 function FindController() { 134 this.timeline_ = undefined; 135 this.model_ = undefined; 136 this.filterText_ = ''; 137 this.filterHits_ = new tracing.Selection(); 138 this.filterHitsDirty_ = true; 139 this.currentHitIndex_ = -1; 140 }; 141 142 FindController.prototype = { 143 __proto__: Object.prototype, 144 145 get timeline() { 146 return this.timeline_; 147 }, 148 149 set timeline(t) { 150 this.timeline_ = t; 151 this.filterHitsDirty_ = true; 152 }, 153 154 get filterText() { 155 return this.filterText_; 156 }, 157 158 set filterText(f) { 159 if (f == this.filterText_) 160 return; 161 this.filterText_ = f; 162 this.filterHitsDirty_ = true; 163 this.showHits_(this.filterHits); 164 }, 165 166 get filterHits() { 167 if (this.filterHitsDirty_) { 168 this.filterHitsDirty_ = false; 169 this.filterHits_.clear(); 170 this.currentHitIndex_ = -1; 171 172 if (this.timeline_ && this.filterText.length) { 173 var filter = new tracing.TitleFilter(this.filterText); 174 this.timeline.addAllObjectsMatchingFilterToSelection( 175 filter, this.filterHits_); 176 } 177 } 178 return this.filterHits_; 179 }, 180 181 get currentHitIndex() { 182 return this.currentHitIndex_; 183 }, 184 185 showHits_: function(selection, zoom, pan) { 186 if (!this.timeline) 187 return; 188 189 this.timeline.selection = selection; 190 191 if (zoom) 192 this.timeline.zoomToSelection(); 193 else if (pan) 194 this.timeline.panToSelection(); 195 }, 196 197 find_: function(dir) { 198 var firstHit = this.currentHitIndex_ === -1; 199 if (firstHit && dir < 0) 200 this.currentHitIndex_ = 0; 201 202 var N = this.filterHits.length; 203 this.currentHitIndex_ = (this.currentHitIndex_ + dir + N) % N; 204 205 // We allow the zoom level to change only on the first hit. But, when 206 // then cycling through subsequent changes, restrict it to panning. 207 var zoom = firstHit; 208 var pan = true; 209 var subSelection = this.filterHits.subSelection(this.currentHitIndex_); 210 this.showHits_(subSelection, zoom, pan); 211 }, 212 213 findNext: function() { 214 this.find_(1); 215 }, 216 217 findPrevious: function() { 218 this.find_(-1); 219 }, 220 221 reset: function() { 222 this.filterText_ = ''; 223 this.filterHitsDirty_ = true; 224 } 225 }; 226 227 return { 228 FindControl: FindControl, 229 FindController: FindController 230 }; 231}); 232