/* * * @APPLE_LICENSE_HEADER_START@ * * Copyright (c) 1998-2003 Apple Computer, Inc. All Rights Reserved. * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #import "USBLoggerFilterWindowController.h" float timeStampFromLogLine(NSString * line); @implementation USBLoggerFilterWindowController - init { if (self = [super init]) { _currentFilterString = nil; _outputLogLines = [[NSMutableArray alloc] init]; } return self; } - (void)dealloc { [_currentFilterString release]; [_outputLogLines release]; [super dealloc]; } - (void)awakeFromNib { [FilterProgressIndicator setUsesThreadedAnimation:YES]; [FilterOutput setTarget:self]; [FilterOutput setDoubleAction:@selector(itemDoubleClicked:)]; [self setupRecentSearchesMenu]; } - (void)setupRecentSearchesMenu { // we can only do this if we're running on 10.3 or later (where FilterTextField is an NSSearchField instance) if ([FilterTextField respondsToSelector: @selector(setRecentSearches:)]) { NSMenu *cellMenu = [[NSMenu alloc] initWithTitle:@"Search Menu"]; NSMenuItem *recentsTitleItem, *norecentsTitleItem, *recentsItem, *separatorItem, *clearItem; id searchCell = [FilterTextField cell]; [FilterTextField setRecentsAutosaveName:@"logger_output_search"]; [searchCell setMaximumRecents:10]; recentsTitleItem = [[NSMenuItem alloc] initWithTitle:@"Recent Searches" action: nil keyEquivalent:@""]; [recentsTitleItem setTag:NSSearchFieldRecentsTitleMenuItemTag]; [cellMenu insertItem:recentsTitleItem atIndex:0]; [recentsTitleItem release]; norecentsTitleItem = [[NSMenuItem alloc] initWithTitle:@"No recent searches" action: nil keyEquivalent:@""]; [norecentsTitleItem setTag:NSSearchFieldNoRecentsMenuItemTag]; [cellMenu insertItem:norecentsTitleItem atIndex:1]; [norecentsTitleItem release]; recentsItem = [[NSMenuItem alloc] initWithTitle:@"Recents" action: nil keyEquivalent:@""]; [recentsItem setTag:NSSearchFieldRecentsMenuItemTag]; [cellMenu insertItem:recentsItem atIndex:2]; [recentsItem release]; separatorItem = (NSMenuItem *)[NSMenuItem separatorItem]; [separatorItem setTag:NSSearchFieldRecentsTitleMenuItemTag]; [cellMenu insertItem:separatorItem atIndex:3]; clearItem = [[NSMenuItem alloc] initWithTitle:@"Clear" action: nil keyEquivalent:@""]; [clearItem setTag:NSSearchFieldClearRecentsMenuItemTag]; [cellMenu insertItem:clearItem atIndex:4]; [clearItem release]; [searchCell setSearchMenuTemplate:cellMenu]; [cellMenu release]; } } - (IBAction)FilterOutput:(id)sender { NSScroller *scroller = [[FilterOutput enclosingScrollView] verticalScroller]; BOOL isScrolledToEnd = (![scroller isEnabled] || [scroller floatValue] == 1); NSEnumerator *entryEnumerator = [[LoggerController logEntries] objectEnumerator]; LoggerEntry *thisEntry; [_currentFilterString release]; if (![[FilterTextField stringValue] isEqualToString:@""]) { _currentFilterString = [[FilterTextField stringValue] retain]; } else { _currentFilterString = nil; } [_outputLogLines removeAllObjects]; [FilterProgressIndicator startAnimation:self]; while (thisEntry = [entryEnumerator nextObject]) { if ([thisEntry level] <= 0) continue; if (_currentFilterString != nil && [[thisEntry text] rangeOfString:_currentFilterString options:NSCaseInsensitiveSearch].location != NSNotFound) { [_outputLogLines addObject:thisEntry]; } } [FilterProgressIndicator stopAnimation:self]; [FilterOutput reloadData]; if (isScrolledToEnd) { [FilterOutput scrollRowToVisible:[_outputLogLines count]-1]; } } - (int)numberOfRowsInTableView:(NSTableView *)aTableView { return [_outputLogLines count]; } - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex { return [[_outputLogLines objectAtIndex:rowIndex] text]; } - (void)itemDoubleClicked:(id)sender { NSString *lineText = [[sender dataSource] tableView:sender objectValueForTableColumn:[[sender tableColumns] objectAtIndex:0] row:[sender selectedRow]]; NSString *clickedLogString = [lineText stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; // strip the trailing newlines and spaces float clickedTimeStamp = timeStampFromLogLine( clickedLogString ); if (clickedTimeStamp == -1) { return; } NSArray *displayedLines = [LoggerController displayedLogLines]; int numberOfLines = [displayedLines count]; int first = 0, middle = 0, last = numberOfLines-1; float midTimeStamp = 0; while (first <= last) { middle = (first + last) / 2; midTimeStamp = timeStampFromLogLine([displayedLines objectAtIndex:middle]); if (midTimeStamp == -1) { middle++; continue; } if (clickedTimeStamp > midTimeStamp) first = middle + 1; else if (clickedTimeStamp < midTimeStamp) last = middle - 1; else break; } // [displayedLines objectAtIndex:middle] has a time stamp identical or closest to the timestamp // of the clicked line. Now let's try to find the exact line, if we can if (midTimeStamp != clickedTimeStamp) { // timestamps dont match, so the clicked line was likely filtered out (not visible). Just scroll to the // line we found ("middle"), which had the closest timestamp we cound find [LoggerController scrollToVisibleLine:[displayedLines objectAtIndex:middle]]; } else { // walk up and down looking for the exact matching line BOOL found = NO; int lineNo; NSString *thisLine = NULL; NSCharacterSet *whitespaceAndNewlines = [NSCharacterSet whitespaceAndNewlineCharacterSet]; // walk down, starting with (the line we found) lineNo = middle; while (lineNo >= 0) { thisLine = [[displayedLines objectAtIndex:lineNo] stringByTrimmingCharactersInSet:whitespaceAndNewlines]; if ( timeStampFromLogLine(thisLine) != midTimeStamp ) { break; } else if ([thisLine isEqualToString:clickedLogString]) { found = YES; break; } lineNo--; } // if still no exact match found, walk up, starting with (the line we found + 1) if (!found) { lineNo = middle+1; while (lineNo <= numberOfLines-1) { thisLine = [[displayedLines objectAtIndex:lineNo] stringByTrimmingCharactersInSet:whitespaceAndNewlines]; if ( timeStampFromLogLine(thisLine) != midTimeStamp ) { break; } else if ([thisLine isEqualToString:clickedLogString]) { found = YES; break; } lineNo++; } } if (found) { [LoggerController scrollToVisibleLine:thisLine]; } else { // we did not find the exact line, so just scroll to the line we found originally // (which has the same time stamp as what we're looking for) [LoggerController scrollToVisibleLine:[displayedLines objectAtIndex:middle]]; } } } float timeStampFromLogLine(NSString * line) { float timeStamp = -1; int level = -1; sscanf((char *)[line cString],"\t%f [%d]", &timeStamp, &level); return timeStamp; } @end