adium 3140:8943f7e7a446: Render the attributed string off the ma...
commits at adium.im
commits at adium.im
Wed Feb 24 21:18:29 UTC 2010
details: http://hg.adium.im/adium/rev/8943f7e7a446
revision: 3140:8943f7e7a446
author: Stephen Holt <sholt at adium.im>
date: Wed Feb 24 16:18:16 2010 -0500
Render the attributed string off the main thread, since large logs with lots of links (ex: log twitter timelines) can make the app spin for unacceptable lengths of time performing -[NSMutableAttributedString(AIAttributedStringAdditions) addFormattingForLinks].
Keep track of the last operation we started, then cancel it so we don't clobber a newer log, if the user has moved on. This is better than spinning, at least, but the intensive part of the operation (adding blue/underline link styles to link attributes) is uncancellable. So, periods of seemingly unexplained CPU use may result if a user selects another log if the current takes a while to load..
diffs (171 lines):
diff -r 100c1b828c9f -r 8943f7e7a446 Frameworks/AIUtilities Framework/Source/AIAttributedStringAdditions.m
--- a/Frameworks/AIUtilities Framework/Source/AIAttributedStringAdditions.m Wed Feb 24 14:12:12 2010 -0500
+++ b/Frameworks/AIUtilities Framework/Source/AIAttributedStringAdditions.m Wed Feb 24 16:18:16 2010 -0500
@@ -268,15 +268,16 @@
- (void)addFormattingForLinks
{
- NSRange searchRange;
+ NSRange searchRange = NSMakeRange(0,0);
NSUInteger length = [self length];
+ NSColor *calibratedBlueColor = [NSColor blueColor];
+ NSNumber *YESBool = [NSNumber numberWithBool:YES];
- searchRange = NSMakeRange(0,0);
while (searchRange.location < length) {
NSDictionary *attributes = [self attributesAtIndex:searchRange.location effectiveRange:&searchRange];
if ([attributes objectForKey:NSLinkAttributeName] != nil) {
- [self addAttribute:NSForegroundColorAttributeName value:[NSColor blueColor] range:searchRange];
- [self addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithBool:YES] range:searchRange];
+ [self addAttribute:NSForegroundColorAttributeName value:calibratedBlueColor range:searchRange];
+ [self addAttribute:NSUnderlineStyleAttributeName value:YESBool range:searchRange];
}
searchRange.location += searchRange.length;
}
diff -r 100c1b828c9f -r 8943f7e7a446 Source/AILogViewerWindowController.h
--- a/Source/AILogViewerWindowController.h Wed Feb 24 14:12:12 2010 -0500
+++ b/Source/AILogViewerWindowController.h Wed Feb 24 16:18:16 2010 -0500
@@ -153,6 +153,8 @@
SKSearchRef currentSearch;
NSLock *currentSearchLock;
+
+ NSInvocationOperation *displayOperation;
}
+ (id)openForPlugin:(id)inPlugin;
diff -r 100c1b828c9f -r 8943f7e7a446 Source/AILogViewerWindowController.m
--- a/Source/AILogViewerWindowController.m Wed Feb 24 14:12:12 2010 -0500
+++ b/Source/AILogViewerWindowController.m Wed Feb 24 16:18:16 2010 -0500
@@ -42,6 +42,8 @@
#import <AIUtilities/AIApplicationAdditions.h>
#import <AIUtilities/AIDividedAlternatingRowOutlineView.h>
+#import <libkern/OSAtomic.h>
+
#define KEY_LOG_VIEWER_WINDOW_FRAME @"Log Viewer Frame"
#define KEY_LOG_VIEWER_GROUP_STATE @"Log Viewer Group State" //Expand/Collapse state of groups
#define TOOLBAR_LOG_VIEWER @"Log Viewer Toolbar"
@@ -68,10 +70,15 @@
#define IMAGE_TIMESTAMPS_OFF @"timestamp32"
#define IMAGE_TIMESTAMPS_ON @"timestamp32_transparent"
+#define LOG_TEXT_KEY @"logTextKey"
+#define LOG_SCROLL_LOCATION_KEY @"logScrollLocationKey"
+#define LOG_SCROLL_LENGTH_KEY @"logScrollLengthKey"
#define REFRESH_RESULTS_INTERVAL 1.0 //Interval between results refreshes while searching
@interface AILogViewerWindowController ()
++ (NSOperationQueue *)sharedLogViewerQueue;
+
- (id)initWithWindowNibName:(NSString *)windowNibName plugin:(id)inPlugin;
- (void)initLogFiltering;
- (void)displayLog:(AIChatLog *)log;
@@ -97,6 +104,10 @@
- (void)deleteSelection:(id)sender;
+- (void)_displayLogs:(NSArray *)logArray;
+- (void)displayLogTextAndScroll:(NSDictionary *)context;
+- (void)_displayLogText:(NSAttributedString *)logText andScrollTo:(NSRange)scrollRange;
+
- (void)outlineViewSelectionDidChangeDelayed;
- (void)openChatOnDoubleAction:(id)sender;
- (void)deleteLogsAlertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo;
@@ -107,6 +118,20 @@
static AILogViewerWindowController *sharedLogViewerInstance = nil;
static NSInteger toArraySort(id itemA, id itemB, void *context);
++ (NSOperationQueue *)sharedLogViewerQueue
+{
+ static NSOperationQueue *logViewerQueue = nil;
+ if(!logViewerQueue) {
+ NSOperationQueue *newQueue = [[NSOperationQueue alloc] init];
+ if(!OSAtomicCompareAndSwapPtrBarrier(nil, newQueue, (void *)&logViewerQueue))
+ [newQueue release];
+
+ if([logViewerQueue respondsToSelector:@selector(setName:)])
+ [logViewerQueue performSelector:@selector(setName:) withObject:@"sharedLogViewerQueue"];
+ }
+ return logViewerQueue;
+}
+
+ (NSString *)nibName
{
return @"LogViewer";
@@ -679,10 +704,22 @@
}
}
+//Detaches a thread which displays the log after rendering it off the main thread
+- (void)displayLogs:(NSArray *)logArray
+{
+ [displayOperation cancel];
+ [displayOperation autorelease];
+ [self _displayLogText:[NSAttributedString stringWithString:@"Loading..."] andScrollTo:NSMakeRange(0, 0)];
+ displayOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(_displayLogs:) object:logArray];
+ [[[self class] sharedLogViewerQueue] addOperation:displayOperation];
+}
+
//Displays the contents of the specified log in our window
-- (void)displayLogs:(NSArray *)logArray;
-{
- NSMutableAttributedString *displayText = nil;
+- (void)_displayLogs:(NSArray *)logArray
+{
+ NSAutoreleasePool *threadPool = [[NSAutoreleasePool alloc] init];
+ NSInvocationOperation *thisOperation = displayOperation;
+ NSMutableAttributedString *displayText = nil;
NSAttributedString *finalDisplayText = nil;
NSRange scrollRange = NSMakeRange(0,0);
BOOL appendedFirstLog = NO;
@@ -885,22 +922,43 @@
finalDisplayText = displayText;
}
- if (finalDisplayText) {
- [[textView_content textStorage] setAttributedString:finalDisplayText];
+ // only step into this if the current operation is still running.
+ if(![thisOperation isCancelled]) {
+ [self performSelectorOnMainThread:@selector(displayLogTextAndScroll:)
+ withObject:[NSDictionary dictionaryWithObjectsAndKeys:finalDisplayText, LOG_TEXT_KEY,
+ [NSNumber numberWithUnsignedInteger:scrollRange.location], LOG_SCROLL_LOCATION_KEY,
+ [NSNumber numberWithUnsignedInteger:scrollRange.length], LOG_SCROLL_LENGTH_KEY,
+ nil]
+ waitUntilDone:YES];
+ }
+ [displayText release];
+ [threadPool drain];
+}
+
+- (void)displayLogTextAndScroll:(NSDictionary *)context
+{
+ NSAttributedString *logText = [context objectForKey:LOG_TEXT_KEY];
+ NSRange scrollRange = NSMakeRange([(NSNumber *)[context objectForKey:LOG_SCROLL_LOCATION_KEY] unsignedIntegerValue],
+ [(NSNumber *)[context objectForKey:LOG_SCROLL_LENGTH_KEY] unsignedIntegerValue]);
+ [self _displayLogText:logText andScrollTo:scrollRange];
+}
+
+- (void)_displayLogText:(NSAttributedString *)logText andScrollTo:(NSRange)scrollRange
+{
+ if (logText) {
+ [[textView_content textStorage] setAttributedString:logText];
+
//Set this string and scroll to the top/bottom/occurrence
if ((searchMode == LOG_SEARCH_CONTENT) || automaticSearch) {
[textView_content scrollRangeToVisible:scrollRange];
} else {
[textView_content scrollRangeToVisible:NSMakeRange(0,0)];
}
-
} else {
//No log selected, empty the view
[textView_content setString:@""];
}
-
- [displayText release];
}
- (void)displayLog:(AIChatLog *)theLog
More information about the commits
mailing list