| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2011 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 /** | |
| 6 * EventsView displays a filtered list of all events sharing a source, and | |
| 7 * a details pane for the selected sources. | |
| 8 * | |
| 9 * +----------------------++----------------+ | |
| 10 * | filter box || | | |
| 11 * +----------------------+| | | |
| 12 * | || | | |
| 13 * | || | | |
| 14 * | || | | |
| 15 * | || | | |
| 16 * | source list || details | | |
| 17 * | || view | | |
| 18 * | || | | |
| 19 * | || | | |
| 20 * | || | | |
| 21 * | || | | |
| 22 * +----------------------++ | | |
| 23 * | action bar || | | |
| 24 * +----------------------++----------------+ | |
| 25 * | |
| 26 * @constructor | |
| 27 */ | |
| 28 function EventsView() { | |
| 29 const tableBodyId = 'eventsListTableBody'; | |
| 30 const filterInputId = 'filterInput'; | |
| 31 const filterCountId = 'filterCount'; | |
| 32 const deleteSelectedId = 'deleteSelected'; | |
| 33 const deleteAllId = 'deleteAll'; | |
| 34 const selectAllId = 'selectAll'; | |
| 35 const sortByIdId = 'sortById'; | |
| 36 const sortBySourceTypeId = 'sortBySource'; | |
| 37 const sortByDescriptionId = 'sortByDescription'; | |
| 38 const tabHandlesContainerId = 'detailsTabHandles'; | |
| 39 const logTabId = 'detailsLogTab'; | |
| 40 const timelineTabId = 'detailsTimelineTab'; | |
| 41 const detailsLogBoxId = 'detailsLogBox'; | |
| 42 const detailsTimelineBoxId = 'detailsTimelineBox'; | |
| 43 const topbarId = 'filterBox'; | |
| 44 const middleboxId = 'eventsBox'; | |
| 45 const bottombarId = 'actionBox'; | |
| 46 const sizerId = 'splitterBoxForEventDetails'; | |
| 47 | |
| 48 View.call(this); | |
| 49 | |
| 50 // Initialize the sub-views. | |
| 51 var leftPane = new TopMidBottomView(new DivView(topbarId), | |
| 52 new DivView(middleboxId), | |
| 53 new DivView(bottombarId)); | |
| 54 | |
| 55 this.detailsView_ = new DetailsView(tabHandlesContainerId, | |
| 56 logTabId, | |
| 57 timelineTabId, | |
| 58 detailsLogBoxId, | |
| 59 detailsTimelineBoxId); | |
| 60 | |
| 61 this.splitterView_ = new ResizableVerticalSplitView( | |
| 62 leftPane, this.detailsView_, new DivView(sizerId)); | |
| 63 | |
| 64 g_browser.sourceTracker.addObserver(this); | |
| 65 | |
| 66 this.tableBody_ = $(tableBodyId); | |
| 67 | |
| 68 this.filterInput_ = $(filterInputId); | |
| 69 this.filterCount_ = $(filterCountId); | |
| 70 | |
| 71 this.filterInput_.addEventListener('search', | |
| 72 this.onFilterTextChanged_.bind(this), true); | |
| 73 | |
| 74 $(deleteSelectedId).onclick = this.deleteSelected_.bind(this); | |
| 75 | |
| 76 $(deleteAllId).onclick = g_browser.sourceTracker.deleteAllSourceEntries.bind( | |
| 77 g_browser.sourceTracker); | |
| 78 | |
| 79 $(selectAllId).addEventListener('click', this.selectAll_.bind(this), true); | |
| 80 | |
| 81 $(sortByIdId).addEventListener('click', this.sortById_.bind(this), true); | |
| 82 | |
| 83 $(sortBySourceTypeId).addEventListener( | |
| 84 'click', this.sortBySourceType_.bind(this), true); | |
| 85 | |
| 86 $(sortByDescriptionId).addEventListener( | |
| 87 'click', this.sortByDescription_.bind(this), true); | |
| 88 | |
| 89 // Sets sort order and filter. | |
| 90 this.setFilter_(''); | |
| 91 | |
| 92 this.initializeSourceList_(); | |
| 93 } | |
| 94 | |
| 95 inherits(EventsView, View); | |
| 96 | |
| 97 /** | |
| 98 * Initializes the list of source entries. If source entries are already, | |
| 99 * being displayed, removes them all in the process. | |
| 100 */ | |
| 101 EventsView.prototype.initializeSourceList_ = function() { | |
| 102 this.currentSelectedRows_ = []; | |
| 103 this.sourceIdToRowMap_ = {}; | |
| 104 this.tableBody_.innerHTML = ''; | |
| 105 this.numPrefilter_ = 0; | |
| 106 this.numPostfilter_ = 0; | |
| 107 this.invalidateFilterCounter_(); | |
| 108 this.invalidateDetailsView_(); | |
| 109 }; | |
| 110 | |
| 111 // How soon after updating the filter list the counter should be updated. | |
| 112 EventsView.REPAINT_FILTER_COUNTER_TIMEOUT_MS = 0; | |
| 113 | |
| 114 EventsView.prototype.setGeometry = function(left, top, width, height) { | |
| 115 EventsView.superClass_.setGeometry.call(this, left, top, width, height); | |
| 116 this.splitterView_.setGeometry(left, top, width, height); | |
| 117 }; | |
| 118 | |
| 119 EventsView.prototype.show = function(isVisible) { | |
| 120 EventsView.superClass_.show.call(this, isVisible); | |
| 121 this.splitterView_.show(isVisible); | |
| 122 }; | |
| 123 | |
| 124 EventsView.prototype.getFilterText_ = function() { | |
| 125 return this.filterInput_.value; | |
| 126 }; | |
| 127 | |
| 128 EventsView.prototype.setFilterText_ = function(filterText) { | |
| 129 this.filterInput_.value = filterText; | |
| 130 this.onFilterTextChanged_(); | |
| 131 }; | |
| 132 | |
| 133 EventsView.prototype.onFilterTextChanged_ = function() { | |
| 134 this.setFilter_(this.getFilterText_()); | |
| 135 }; | |
| 136 | |
| 137 /** | |
| 138 * Updates text in the details view when security stripping is toggled. | |
| 139 */ | |
| 140 EventsView.prototype.onSecurityStrippingChanged = function() { | |
| 141 this.invalidateDetailsView_(); | |
| 142 } | |
| 143 | |
| 144 /** | |
| 145 * Sorts active entries first. If both entries are inactive, puts the one | |
| 146 * that was active most recently first. If both are active, uses source ID, | |
| 147 * which puts longer lived events at the top, and behaves better than using | |
| 148 * duration or time of first event. | |
| 149 */ | |
| 150 EventsView.compareActive_ = function(source1, source2) { | |
| 151 if (!source1.isInactive() && source2.isInactive()) | |
| 152 return -1; | |
| 153 if (source1.isInactive() && !source2.isInactive()) | |
| 154 return 1; | |
| 155 if (source1.isInactive()) { | |
| 156 var deltaEndTime = source1.getEndTime() - source2.getEndTime(); | |
| 157 if (deltaEndTime != 0) { | |
| 158 // The one that ended most recently (Highest end time) should be sorted | |
| 159 // first. | |
| 160 return -deltaEndTime; | |
| 161 } | |
| 162 // If both ended at the same time, then odds are they were related events, | |
| 163 // started one after another, so sort in the opposite order of their | |
| 164 // source IDs to get a more intuitive ordering. | |
| 165 return -EventsView.compareSourceId_(source1, source2); | |
| 166 } | |
| 167 return EventsView.compareSourceId_(source1, source2); | |
| 168 }; | |
| 169 | |
| 170 EventsView.compareDescription_ = function(source1, source2) { | |
| 171 var source1Text = source1.getDescription().toLowerCase(); | |
| 172 var source2Text = source2.getDescription().toLowerCase(); | |
| 173 var compareResult = source1Text.localeCompare(source2Text); | |
| 174 if (compareResult != 0) | |
| 175 return compareResult; | |
| 176 return EventsView.compareSourceId_(source1, source2); | |
| 177 }; | |
| 178 | |
| 179 EventsView.compareDuration_ = function(source1, source2) { | |
| 180 var durationDifference = source2.getDuration() - source1.getDuration(); | |
| 181 if (durationDifference) | |
| 182 return durationDifference; | |
| 183 return EventsView.compareSourceId_(source1, source2); | |
| 184 }; | |
| 185 | |
| 186 /** | |
| 187 * For the purposes of sorting by source IDs, entries without a source | |
| 188 * appear right after the SourceEntry with the highest source ID received | |
| 189 * before the sourceless entry. Any ambiguities are resolved by ordering | |
| 190 * the entries without a source by the order in which they were received. | |
| 191 */ | |
| 192 EventsView.compareSourceId_ = function(source1, source2) { | |
| 193 var sourceId1 = source1.getSourceId(); | |
| 194 if (sourceId1 < 0) | |
| 195 sourceId1 = source1.getMaxPreviousEntrySourceId(); | |
| 196 var sourceId2 = source2.getSourceId(); | |
| 197 if (sourceId2 < 0) | |
| 198 sourceId2 = source2.getMaxPreviousEntrySourceId(); | |
| 199 | |
| 200 if (sourceId1 != sourceId2) | |
| 201 return sourceId1 - sourceId2; | |
| 202 | |
| 203 // One or both have a negative ID. In either case, the source with the | |
| 204 // highest ID should be sorted first. | |
| 205 return source2.getSourceId() - source1.getSourceId(); | |
| 206 }; | |
| 207 | |
| 208 EventsView.compareSourceType_ = function(source1, source2) { | |
| 209 var source1Text = source1.getSourceTypeString(); | |
| 210 var source2Text = source2.getSourceTypeString(); | |
| 211 var compareResult = source1Text.localeCompare(source2Text); | |
| 212 if (compareResult != 0) | |
| 213 return compareResult; | |
| 214 return EventsView.compareSourceId_(source1, source2); | |
| 215 }; | |
| 216 | |
| 217 EventsView.prototype.comparisonFuncWithReversing_ = function(a, b) { | |
| 218 var result = this.comparisonFunction_(a, b); | |
| 219 if (this.doSortBackwards_) | |
| 220 result *= -1; | |
| 221 return result; | |
| 222 }; | |
| 223 | |
| 224 EventsView.comparisonFunctionTable_ = { | |
| 225 // sort: and sort:- are allowed | |
| 226 '': EventsView.compareSourceId_, | |
| 227 'active': EventsView.compareActive_, | |
| 228 'desc': EventsView.compareDescription_, | |
| 229 'description': EventsView.compareDescription_, | |
| 230 'duration': EventsView.compareDuration_, | |
| 231 'id': EventsView.compareSourceId_, | |
| 232 'source': EventsView.compareSourceType_, | |
| 233 'type': EventsView.compareSourceType_ | |
| 234 }; | |
| 235 | |
| 236 EventsView.prototype.Sort_ = function() { | |
| 237 var sourceEntries = []; | |
| 238 for (var id in this.sourceIdToRowMap_) { | |
| 239 sourceEntries.push(this.sourceIdToRowMap_[id].getSourceEntry()); | |
| 240 } | |
| 241 sourceEntries.sort(this.comparisonFuncWithReversing_.bind(this)); | |
| 242 | |
| 243 // Reposition source rows from back to front. | |
| 244 for (var i = sourceEntries.length - 2; i >= 0; --i) { | |
| 245 var sourceRow = this.sourceIdToRowMap_[sourceEntries[i].getSourceId()]; | |
| 246 var nextSourceId = sourceEntries[i + 1].getSourceId(); | |
| 247 if (sourceRow.getNextNodeSourceId() != nextSourceId) { | |
| 248 var nextSourceRow = this.sourceIdToRowMap_[nextSourceId]; | |
| 249 sourceRow.moveBefore(nextSourceRow); | |
| 250 } | |
| 251 } | |
| 252 }; | |
| 253 | |
| 254 /** | |
| 255 * Looks for the first occurence of |directive|:parameter in |sourceText|. | |
| 256 * Parameter can be an empty string. | |
| 257 * | |
| 258 * On success, returns an object with two fields: | |
| 259 * |remainingText| - |sourceText| with |directive|:parameter removed, | |
| 260 and excess whitespace deleted. | |
| 261 * |parameter| - the parameter itself. | |
| 262 * | |
| 263 * On failure, returns null. | |
| 264 */ | |
| 265 EventsView.prototype.parseDirective_ = function(sourceText, directive) { | |
| 266 // Adding a leading space allows a single regexp to be used, regardless of | |
| 267 // whether or not the directive is at the start of the string. | |
| 268 sourceText = ' ' + sourceText; | |
| 269 regExp = new RegExp('\\s+' + directive + ':(\\S*)\\s*', 'i'); | |
| 270 matchInfo = regExp.exec(sourceText); | |
| 271 if (matchInfo == null) | |
| 272 return null; | |
| 273 | |
| 274 return {'remainingText': sourceText.replace(regExp, ' ').trim(), | |
| 275 'parameter': matchInfo[1]}; | |
| 276 }; | |
| 277 | |
| 278 /** | |
| 279 * Just like parseDirective_, except can optionally be a '-' before or | |
| 280 * the parameter, to negate it. Before is more natural, after | |
| 281 * allows more convenient toggling. | |
| 282 * | |
| 283 * Returned value has the additional field |isNegated|, and a leading | |
| 284 * '-' will be removed from |parameter|, if present. | |
| 285 */ | |
| 286 EventsView.prototype.parseNegatableDirective_ = function(sourceText, | |
| 287 directive) { | |
| 288 var matchInfo = this.parseDirective_(sourceText, directive); | |
| 289 if (matchInfo == null) | |
| 290 return null; | |
| 291 | |
| 292 // Remove any leading or trailing '-' from the directive. | |
| 293 var negationInfo = /^(-?)(\S*?)$/.exec(matchInfo.parameter); | |
| 294 matchInfo.parameter = negationInfo[2]; | |
| 295 matchInfo.isNegated = (negationInfo[1] == '-'); | |
| 296 return matchInfo; | |
| 297 }; | |
| 298 | |
| 299 /** | |
| 300 * Parse any "sort:" directives, and update |comparisonFunction_| and | |
| 301 * |doSortBackwards_|as needed. Note only the last valid sort directive | |
| 302 * is used. | |
| 303 * | |
| 304 * Returns |filterText| with all sort directives removed, including | |
| 305 * invalid ones. | |
| 306 */ | |
| 307 EventsView.prototype.parseSortDirectives_ = function(filterText) { | |
| 308 this.comparisonFunction_ = EventsView.compareSourceId_; | |
| 309 this.doSortBackwards_ = false; | |
| 310 | |
| 311 while (true) { | |
| 312 var sortInfo = this.parseNegatableDirective_(filterText, 'sort'); | |
| 313 if (sortInfo == null) | |
| 314 break; | |
| 315 var comparisonName = sortInfo.parameter.toLowerCase(); | |
| 316 if (EventsView.comparisonFunctionTable_[comparisonName] != null) { | |
| 317 this.comparisonFunction_ = | |
| 318 EventsView.comparisonFunctionTable_[comparisonName]; | |
| 319 this.doSortBackwards_ = sortInfo.isNegated; | |
| 320 } | |
| 321 filterText = sortInfo.remainingText; | |
| 322 } | |
| 323 | |
| 324 return filterText; | |
| 325 }; | |
| 326 | |
| 327 /** | |
| 328 * Parse any "is:" directives, and update |filter| accordingly. | |
| 329 * | |
| 330 * Returns |filterText| with all "is:" directives removed, including | |
| 331 * invalid ones. | |
| 332 */ | |
| 333 EventsView.prototype.parseRestrictDirectives_ = function(filterText, filter) { | |
| 334 while (true) { | |
| 335 var filterInfo = this.parseNegatableDirective_(filterText, 'is'); | |
| 336 if (filterInfo == null) | |
| 337 break; | |
| 338 if (filterInfo.parameter == 'active') { | |
| 339 if (!filterInfo.isNegated) { | |
| 340 filter.isActive = true; | |
| 341 } else { | |
| 342 filter.isInactive = true; | |
| 343 } | |
| 344 } | |
| 345 if (filterInfo.parameter == 'error') { | |
| 346 if (!filterInfo.isNegated) { | |
| 347 filter.isError = true; | |
| 348 } else { | |
| 349 filter.isNotError = true; | |
| 350 } | |
| 351 } | |
| 352 filterText = filterInfo.remainingText; | |
| 353 } | |
| 354 return filterText; | |
| 355 }; | |
| 356 | |
| 357 /** | |
| 358 * Parses all directives that take arbitrary strings as input, | |
| 359 * and updates |filter| accordingly. Directives of these types | |
| 360 * are stored as lists. | |
| 361 * | |
| 362 * Returns |filterText| with all recognized directives removed. | |
| 363 */ | |
| 364 EventsView.prototype.parseStringDirectives_ = function(filterText, filter) { | |
| 365 var directives = ['type', 'id']; | |
| 366 for (var i = 0; i < directives.length; ++i) { | |
| 367 while (true) { | |
| 368 var directive = directives[i]; | |
| 369 var filterInfo = this.parseDirective_(filterText, directive); | |
| 370 if (filterInfo == null) | |
| 371 break; | |
| 372 if (!filter[directive]) | |
| 373 filter[directive] = []; | |
| 374 filter[directive].push(filterInfo.parameter); | |
| 375 filterText = filterInfo.remainingText; | |
| 376 } | |
| 377 } | |
| 378 return filterText; | |
| 379 }; | |
| 380 | |
| 381 /* | |
| 382 * Converts |filterText| into an object representing the filter. | |
| 383 */ | |
| 384 EventsView.prototype.createFilter_ = function(filterText) { | |
| 385 var filter = {}; | |
| 386 filterText = filterText.toLowerCase(); | |
| 387 filterText = this.parseRestrictDirectives_(filterText, filter); | |
| 388 filterText = this.parseStringDirectives_(filterText, filter); | |
| 389 filter.text = filterText.trim(); | |
| 390 return filter; | |
| 391 }; | |
| 392 | |
| 393 EventsView.prototype.setFilter_ = function(filterText) { | |
| 394 var lastComparisonFunction = this.comparisonFunction_; | |
| 395 var lastDoSortBackwards = this.doSortBackwards_; | |
| 396 | |
| 397 filterText = this.parseSortDirectives_(filterText); | |
| 398 | |
| 399 if (lastComparisonFunction != this.comparisonFunction_ || | |
| 400 lastDoSortBackwards != this.doSortBackwards_) { | |
| 401 this.Sort_(); | |
| 402 } | |
| 403 | |
| 404 this.currentFilter_ = this.createFilter_(filterText); | |
| 405 | |
| 406 // Iterate through all of the rows and see if they match the filter. | |
| 407 for (var id in this.sourceIdToRowMap_) { | |
| 408 var entry = this.sourceIdToRowMap_[id]; | |
| 409 entry.setIsMatchedByFilter(entry.matchesFilter(this.currentFilter_)); | |
| 410 } | |
| 411 }; | |
| 412 | |
| 413 /** | |
| 414 * Repositions |sourceRow|'s in the table using an insertion sort. | |
| 415 * Significantly faster than sorting the entire table again, when only | |
| 416 * one entry has changed. | |
| 417 */ | |
| 418 EventsView.prototype.InsertionSort_ = function(sourceRow) { | |
| 419 // SourceRow that should be after |sourceRow|, if it needs | |
| 420 // to be moved earlier in the list. | |
| 421 var sourceRowAfter = sourceRow; | |
| 422 while (true) { | |
| 423 var prevSourceId = sourceRowAfter.getPreviousNodeSourceId(); | |
| 424 if (prevSourceId == null) | |
| 425 break; | |
| 426 var prevSourceRow = this.sourceIdToRowMap_[prevSourceId]; | |
| 427 if (this.comparisonFuncWithReversing_( | |
| 428 sourceRow.getSourceEntry(), | |
| 429 prevSourceRow.getSourceEntry()) >= 0) { | |
| 430 break; | |
| 431 } | |
| 432 sourceRowAfter = prevSourceRow; | |
| 433 } | |
| 434 if (sourceRowAfter != sourceRow) { | |
| 435 sourceRow.moveBefore(sourceRowAfter); | |
| 436 return; | |
| 437 } | |
| 438 | |
| 439 var sourceRowBefore = sourceRow; | |
| 440 while (true) { | |
| 441 var nextSourceId = sourceRowBefore.getNextNodeSourceId(); | |
| 442 if (nextSourceId == null) | |
| 443 break; | |
| 444 var nextSourceRow = this.sourceIdToRowMap_[nextSourceId]; | |
| 445 if (this.comparisonFuncWithReversing_( | |
| 446 sourceRow.getSourceEntry(), | |
| 447 nextSourceRow.getSourceEntry()) <= 0) { | |
| 448 break; | |
| 449 } | |
| 450 sourceRowBefore = nextSourceRow; | |
| 451 } | |
| 452 if (sourceRowBefore != sourceRow) | |
| 453 sourceRow.moveAfter(sourceRowBefore); | |
| 454 }; | |
| 455 | |
| 456 EventsView.prototype.onSourceEntryUpdated = function(sourceEntry) { | |
| 457 // Lookup the row. | |
| 458 var sourceRow = this.sourceIdToRowMap_[sourceEntry.getSourceId()]; | |
| 459 | |
| 460 if (!sourceRow) { | |
| 461 sourceRow = new SourceRow(this, sourceEntry); | |
| 462 this.sourceIdToRowMap_[sourceEntry.getSourceId()] = sourceRow; | |
| 463 } | |
| 464 | |
| 465 sourceRow.onSourceUpdated(); | |
| 466 | |
| 467 if (sourceRow.isSelected()) | |
| 468 this.invalidateDetailsView_(); | |
| 469 | |
| 470 // TODO(mmenke): Fix sorting when sorting by duration. | |
| 471 // Duration continuously increases for all entries that are | |
| 472 // still active. This can result in incorrect sorting, until | |
| 473 // Sort_ is called. | |
| 474 this.InsertionSort_(sourceRow); | |
| 475 }; | |
| 476 | |
| 477 /** | |
| 478 * Returns the SourceRow with the specified ID, if there is one. | |
| 479 * Otherwise, returns undefined. | |
| 480 */ | |
| 481 EventsView.prototype.getSourceRow = function(id) { | |
| 482 return this.sourceIdToRowMap_[id]; | |
| 483 }; | |
| 484 | |
| 485 /** | |
| 486 * Called whenever some log events are deleted. |sourceIds| lists | |
| 487 * the source IDs of all deleted log entries. | |
| 488 */ | |
| 489 EventsView.prototype.onSourceEntriesDeleted = function(sourceIds) { | |
| 490 for (var i = 0; i < sourceIds.length; ++i) { | |
| 491 var id = sourceIds[i]; | |
| 492 var sourceRow = this.sourceIdToRowMap_[id]; | |
| 493 if (sourceRow) { | |
| 494 sourceRow.remove(); | |
| 495 delete this.sourceIdToRowMap_[id]; | |
| 496 this.incrementPrefilterCount(-1); | |
| 497 } | |
| 498 } | |
| 499 }; | |
| 500 | |
| 501 /** | |
| 502 * Called whenever all log events are deleted. | |
| 503 */ | |
| 504 EventsView.prototype.onAllSourceEntriesDeleted = function() { | |
| 505 this.initializeSourceList_(); | |
| 506 }; | |
| 507 | |
| 508 /** | |
| 509 * Called when either a log file is loaded, after clearing the old entries, | |
| 510 * but before getting any new ones. | |
| 511 */ | |
| 512 EventsView.prototype.onLoadLogStart = function() { | |
| 513 // Needed to sort new sourceless entries correctly. | |
| 514 this.maxReceivedSourceId_ = 0; | |
| 515 }; | |
| 516 | |
| 517 EventsView.prototype.onLoadLogFinish = function(data) { | |
| 518 return true; | |
| 519 }; | |
| 520 | |
| 521 EventsView.prototype.incrementPrefilterCount = function(offset) { | |
| 522 this.numPrefilter_ += offset; | |
| 523 this.invalidateFilterCounter_(); | |
| 524 }; | |
| 525 | |
| 526 EventsView.prototype.incrementPostfilterCount = function(offset) { | |
| 527 this.numPostfilter_ += offset; | |
| 528 this.invalidateFilterCounter_(); | |
| 529 }; | |
| 530 | |
| 531 EventsView.prototype.onSelectionChanged = function() { | |
| 532 this.invalidateDetailsView_(); | |
| 533 }; | |
| 534 | |
| 535 EventsView.prototype.clearSelection = function() { | |
| 536 var prevSelection = this.currentSelectedRows_; | |
| 537 this.currentSelectedRows_ = []; | |
| 538 | |
| 539 // Unselect everything that is currently selected. | |
| 540 for (var i = 0; i < prevSelection.length; ++i) { | |
| 541 prevSelection[i].setSelected(false); | |
| 542 } | |
| 543 | |
| 544 this.onSelectionChanged(); | |
| 545 }; | |
| 546 | |
| 547 EventsView.prototype.deleteSelected_ = function() { | |
| 548 var sourceIds = []; | |
| 549 for (var i = 0; i < this.currentSelectedRows_.length; ++i) { | |
| 550 var sourceRow = this.currentSelectedRows_[i]; | |
| 551 sourceIds.push(sourceRow.getSourceEntry().getSourceId()); | |
| 552 } | |
| 553 g_browser.sourceTracker.deleteSourceEntries(sourceIds); | |
| 554 }; | |
| 555 | |
| 556 EventsView.prototype.selectAll_ = function(event) { | |
| 557 for (var id in this.sourceIdToRowMap_) { | |
| 558 var sourceRow = this.sourceIdToRowMap_[id]; | |
| 559 if (sourceRow.isMatchedByFilter()) { | |
| 560 sourceRow.setSelected(true); | |
| 561 } | |
| 562 } | |
| 563 event.preventDefault(); | |
| 564 }; | |
| 565 | |
| 566 EventsView.prototype.unselectAll_ = function() { | |
| 567 var entries = this.currentSelectedRows_.slice(0); | |
| 568 for (var i = 0; i < entries.length; ++i) { | |
| 569 entries[i].setSelected(false); | |
| 570 } | |
| 571 }; | |
| 572 | |
| 573 /** | |
| 574 * If |params| includes a query, replaces the current filter and unselects. | |
| 575 * all items. | |
| 576 */ | |
| 577 EventsView.prototype.setParameters = function(params) { | |
| 578 if (params.q) { | |
| 579 this.unselectAll_(); | |
| 580 this.setFilterText_(params.q); | |
| 581 } | |
| 582 }; | |
| 583 | |
| 584 /** | |
| 585 * If already using the specified sort method, flips direction. Otherwise, | |
| 586 * removes pre-existing sort parameter before adding the new one. | |
| 587 */ | |
| 588 EventsView.prototype.toggleSortMethod_ = function(sortMethod) { | |
| 589 // Remove old sort directives, if any. | |
| 590 var filterText = this.parseSortDirectives_(this.getFilterText_()); | |
| 591 | |
| 592 // If already using specified sortMethod, sort backwards. | |
| 593 if (!this.doSortBackwards_ && | |
| 594 EventsView.comparisonFunctionTable_[sortMethod] == | |
| 595 this.comparisonFunction_) | |
| 596 sortMethod = '-' + sortMethod; | |
| 597 | |
| 598 filterText = 'sort:' + sortMethod + ' ' + filterText; | |
| 599 this.setFilterText_(filterText.trim()); | |
| 600 }; | |
| 601 | |
| 602 EventsView.prototype.sortById_ = function(event) { | |
| 603 this.toggleSortMethod_('id'); | |
| 604 }; | |
| 605 | |
| 606 EventsView.prototype.sortBySourceType_ = function(event) { | |
| 607 this.toggleSortMethod_('source'); | |
| 608 }; | |
| 609 | |
| 610 EventsView.prototype.sortByDescription_ = function(event) { | |
| 611 this.toggleSortMethod_('desc'); | |
| 612 }; | |
| 613 | |
| 614 EventsView.prototype.modifySelectionArray = function( | |
| 615 sourceRow, addToSelection) { | |
| 616 // Find the index for |sourceEntry| in the current selection list. | |
| 617 var index = -1; | |
| 618 for (var i = 0; i < this.currentSelectedRows_.length; ++i) { | |
| 619 if (this.currentSelectedRows_[i] == sourceRow) { | |
| 620 index = i; | |
| 621 break; | |
| 622 } | |
| 623 } | |
| 624 | |
| 625 if (index != -1 && !addToSelection) { | |
| 626 // Remove from the selection. | |
| 627 this.currentSelectedRows_.splice(index, 1); | |
| 628 } | |
| 629 | |
| 630 if (index == -1 && addToSelection) { | |
| 631 this.currentSelectedRows_.push(sourceRow); | |
| 632 } | |
| 633 }; | |
| 634 | |
| 635 EventsView.prototype.getSelectedSourceEntries_ = function() { | |
| 636 var sourceEntries = []; | |
| 637 for (var id in this.currentSelectedRows_) { | |
| 638 sourceEntries.push(this.currentSelectedRows_[id].getSourceEntry()); | |
| 639 } | |
| 640 return sourceEntries; | |
| 641 }; | |
| 642 | |
| 643 EventsView.prototype.invalidateDetailsView_ = function() { | |
| 644 this.detailsView_.setData(this.getSelectedSourceEntries_()); | |
| 645 }; | |
| 646 | |
| 647 EventsView.prototype.invalidateFilterCounter_ = function() { | |
| 648 if (!this.outstandingRepaintFilterCounter_) { | |
| 649 this.outstandingRepaintFilterCounter_ = true; | |
| 650 window.setTimeout(this.repaintFilterCounter_.bind(this), | |
| 651 EventsView.REPAINT_FILTER_COUNTER_TIMEOUT_MS); | |
| 652 } | |
| 653 }; | |
| 654 | |
| 655 EventsView.prototype.repaintFilterCounter_ = function() { | |
| 656 this.outstandingRepaintFilterCounter_ = false; | |
| 657 this.filterCount_.innerHTML = ''; | |
| 658 addTextNode(this.filterCount_, | |
| 659 this.numPostfilter_ + ' of ' + this.numPrefilter_); | |
| 660 }; | |
| OLD | NEW |