From d956e8d7c039a451e27ca6ecb8bd945672dbfc29 Mon Sep 17 00:00:00 2001 From: Allan Odgaard Date: Mon, 27 Aug 2012 18:05:19 +0200 Subject: [PATCH] Rewrote OakFileIconImage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There are mainly two reasons for this: 1. Unexplained crashes (see issue #73). 2. We were using lockFocus when getting the symbolic link arrow (from IconRef, as there seems to be no “modern” API to get this arrow) which caused a rasterization of the image. The new implementation is much simpler, as all work happens in the image representation which does not have any (weak) pointer to its parent image. --- Frameworks/OakAppKit/src/OakFileIconImage.h | 13 - Frameworks/OakAppKit/src/OakFileIconImage.mm | 261 +++++++++---------- 2 files changed, 120 insertions(+), 154 deletions(-) diff --git a/Frameworks/OakAppKit/src/OakFileIconImage.h b/Frameworks/OakAppKit/src/OakFileIconImage.h index 6e8c84e6..6e2aae2b 100644 --- a/Frameworks/OakAppKit/src/OakFileIconImage.h +++ b/Frameworks/OakAppKit/src/OakFileIconImage.h @@ -1,20 +1,7 @@ -#import - @interface OakFileIconImage : NSImage { - scm::info_ptr scmDriver; - - NSString* path; - BOOL isModified; - BOOL existsOnDisk; - scm::status::type scmStatus; - - NSImage* base; - NSImage* badge; } + (id)fileIconImageWithPath:(NSString*)aPath isModified:(BOOL)flag size:(NSSize)aSize; + (id)fileIconImageWithPath:(NSString*)aPath isModified:(BOOL)flag; + (id)fileIconImageWithPath:(NSString*)aPath size:(NSSize)aSize; -@property (nonatomic, retain) NSString* path; -@property (nonatomic, assign) BOOL isModified; @end diff --git a/Frameworks/OakAppKit/src/OakFileIconImage.mm b/Frameworks/OakAppKit/src/OakFileIconImage.mm index 524e6e17..c662a08e 100644 --- a/Frameworks/OakAppKit/src/OakFileIconImage.mm +++ b/Frameworks/OakAppKit/src/OakFileIconImage.mm @@ -1,74 +1,18 @@ #import "OakFileIconImage.h" #import "NSImage Additions.h" #import +#import #import -@interface OakFileIconImage () -- (void)refreshFileStatus; -@property (nonatomic, retain) NSImage* base; -@property (nonatomic, retain) NSImage* badge; -@end +// ======================== +// = Obtain Various Icons = +// ======================== -// =============================== -// = Custom Image Representation = -// =============================== - -@interface OakFileIconImageRep : NSImageRep +static NSImage* CustomIconForPath (NSString* path, struct stat const& buf) { - OakFileIconImage* image; -} -- (id)initWithImage:(OakFileIconImage*)anImage; -@end + if(!S_ISREG(buf.st_mode) && !S_ISLNK(buf.st_mode)) + return nil; -@implementation OakFileIconImageRep -- (id)initWithImage:(OakFileIconImage*)anImage -{ - if((self = [super init])) - { - image = anImage; - self.size = anImage.size; - } - return self; -} - -- (id)copyWithZone:(NSZone*)zone -{ - OakFileIconImageRep* copy = [super copyWithZone:zone]; - copy->image = image; - return copy; -} - -- (void)dealloc -{ - [super dealloc]; -} - -- (BOOL)draw -{ - [image refreshFileStatus]; - - NSImage* buffer = nil; - if(image.isModified) - { - buffer = [[[NSImage alloc] initWithSize:[self size]] autorelease]; - [buffer lockFocus]; - } - - [image.base drawInRect:(NSRect){ NSZeroPoint, self.size } fromRect:NSZeroRect operation:NSCompositeCopy fraction:1]; - [image.badge drawInRect:(NSRect){ NSZeroPoint, self.size } fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1]; - - if(image.isModified) - { - [buffer unlockFocus]; - [buffer drawAtPoint:NSZeroPoint fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:0.4]; - } - - return YES; -} -@end - -static NSImage* IconImageForPath (NSString* path) -{ std::multimap ordering; NSDictionary* bindings = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle bundleForClass:[OakFileIconImage class]] pathForResource:@"bindings" ofType:@"plist"]]; iterate(pair, bindings) @@ -79,50 +23,30 @@ static NSImage* IconImageForPath (NSString* path) ordering.insert(std::make_pair(rank, pair->first)); } } + return ordering.empty() ? nil : [NSImage imageNamed:ordering.begin()->second inSameBundleAsClass:[OakFileIconImage class]]; +} - if(!ordering.empty()) - { - struct stat buf; - if(lstat([path fileSystemRepresentation], &buf) == 0) - { - if(S_ISREG(buf.st_mode) || S_ISLNK(buf.st_mode)) - { - NSImage* res = [NSImage imageNamed:ordering.begin()->second inSameBundleAsClass:[OakFileIconImage class]]; - - IconRef iconRef; - if(S_ISLNK(buf.st_mode) && GetIconRef(kOnSystemDisk, kSystemIconsCreator, kAliasBadgeIcon, &iconRef) == noErr) - { - NSImage* badge = [[[NSImage alloc] initWithIconRef:iconRef] autorelease]; - ReleaseIconRef(iconRef); - - res = [[res copy] autorelease]; - [res setSize:NSMakeSize(16, 16)]; - [res lockFocus]; - [badge drawInRect:(NSRect){ NSZeroPoint, res.size } fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1]; - [res unlockFocus]; - } - return res; - } - } - } - +static NSImage* IconBadgeForPath (NSString* path, struct stat const& buf) +{ IconRef iconRef; - FSRef fileRef; - - bool didGetIconRef = false; - if([path isEqualToString:@"/"]) // this is an optimization — not sure why, but it takes a second or so to get this icon on my system - didGetIconRef = GetIconRef(kOnSystemDisk, 0, kGenericHardDiskIcon, &iconRef) == noErr; - else if(FSPathMakeRefWithOptions((UInt8 const*)[path fileSystemRepresentation], kFSPathMakeRefDoNotFollowLeafSymlink, &fileRef, NULL) == noErr) - didGetIconRef = GetIconRefFromFileInfo(&fileRef, 0, NULL, 0, NULL, kIconServicesNormalUsageFlag, &iconRef, NULL) == noErr; - - NSImage* image = nil; - if(didGetIconRef) + if(S_ISLNK(buf.st_mode) && GetIconRef(kOnSystemDisk, kSystemIconsCreator, kAliasBadgeIcon, &iconRef) == noErr) { - image = [[[NSImage alloc] initWithIconRef:iconRef] autorelease]; // the reason we use IconRef is that it adds a badge to symbolic links + NSImage* badge = [[[NSImage alloc] initWithIconRef:iconRef] autorelease]; ReleaseIconRef(iconRef); + return badge; } + return nil; +} - return image ?: [[NSWorkspace sharedWorkspace] iconForFile:path]; +static NSImage* SystemIconForPath (NSString* path, struct stat const& buf) +{ + NSImage* image; + return [[NSURL fileURLWithPath:path isDirectory:S_ISDIR(buf.st_mode)] getResourceValue:&image forKey:NSURLEffectiveIconKey error:NULL] ? image : [[NSWorkspace sharedWorkspace] iconForFile:path]; +} + +static NSImage* SystemIconForHFSType (OSType hfsFileTypeCode) +{ + return [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(hfsFileTypeCode)]; } static NSImage* BadgeForSCMStatus (scm::status::type scmStatus) @@ -139,70 +63,125 @@ static NSImage* BadgeForSCMStatus (scm::status::type scmStatus) return nil; } -@implementation OakFileIconImage -@synthesize path, isModified, base, badge; +// ================================= +// = Create Image Stack for a Path = +// ================================= -- (void)setExistsOnDisk:(BOOL)flag +static NSArray* ImageStackForPath (NSString* path) { - if(existsOnDisk != flag) + NSMutableArray* res = [NSMutableArray array]; + + struct stat buf; + if(lstat([path fileSystemRepresentation], &buf) == 0) { - existsOnDisk = flag; - self.base = existsOnDisk ? IconImageForPath(path) : [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUnknownFSObjectIcon)]; + if(NSImage* customImage = CustomIconForPath(path, buf)) + { + [res addObject:customImage]; + if(NSImage* imageBadge = IconBadgeForPath(path, buf)) + [res addObject:imageBadge]; + } + else if(NSImage* image = SystemIconForPath(path, buf)) + { + [res addObject:image]; + } } -} -- (void)setScmStatus:(scm::status::type)newScmStatus -{ - if(scmStatus != newScmStatus) + if([res count] == 0) + [res addObject:SystemIconForHFSType(kUnknownFSObjectIcon)]; + + if(auto scmDriver = scm::info([path fileSystemRepresentation])) { - scmStatus = newScmStatus; - self.badge = [[BadgeForSCMStatus(scmStatus) copy] autorelease]; + if(NSImage* scmStatusImage = BadgeForSCMStatus(scmDriver->status([path fileSystemRepresentation]))) + [res addObject:scmStatusImage]; } + + return res; } -- (void)refreshFileStatus +// =============================== +// = Custom Image Representation = +// =============================== + +@interface OakFileIconImageRep : NSImageRep { - if(!path) - return; - - if(!scmDriver) - scmDriver = scm::info([path fileSystemRepresentation]); - - [self setExistsOnDisk:lstat([path fileSystemRepresentation], &(struct stat){ 0 }) == 0]; - [self setScmStatus:scmDriver ? scmDriver->status([path fileSystemRepresentation]) : scm::status::none]; + NSString* path; + BOOL isModified; + NSArray* imageStack; } +- (id)initWithPath:(NSString*)aPath isModified:(BOOL)flag; +@end -- (void)setSize:(NSSize)aSize +@implementation OakFileIconImageRep +- (id)initWithPath:(NSString*)aPath isModified:(BOOL)flag { - for(NSImageRep* imageRep in [self representations]) - [imageRep setSize:aSize]; - [super setSize:aSize]; -} - -- (id)initWithWithPath:(NSString*)aPath isModified:(BOOL)flag size:(NSSize)aSize -{ - if((self = [super initWithSize:aSize])) + if((self = [super init])) { - self.path = aPath; - isModified = flag; - existsOnDisk = NO; - scmStatus = scm::status::none; - - self.base = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUnknownFSObjectIcon)]; - - [self addRepresentation:[[[OakFileIconImageRep alloc] initWithImage:self] autorelease]]; + path = [aPath retain]; + isModified = flag; } return self; } +- (id)copyWithZone:(NSZone*)zone +{ + OakFileIconImageRep* copy = [super copyWithZone:zone]; + copy->path = [path retain]; + copy->isModified = isModified; + copy->imageStack = [imageStack retain]; + return copy; +} + - (void)dealloc { [path release]; - [base release]; - [badge release]; + [imageStack release]; [super dealloc]; } +- (BOOL)draw +{ + if(!imageStack) + imageStack = [ImageStackForPath(path) retain]; + + NSImage* buffer = nil; + if(isModified) + { + buffer = [[[NSImage alloc] initWithSize:[self size]] autorelease]; + [buffer lockFocus]; + } + + NSCompositingOperation op = NSCompositeCopy; + for(NSImage* image in imageStack) + { + [image drawInRect:(NSRect){ NSZeroPoint, self.size } fromRect:NSZeroRect operation:op fraction:1]; + op = NSCompositeSourceOver; + } + + if(isModified) + { + [buffer unlockFocus]; + [buffer drawAtPoint:NSZeroPoint fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:0.4]; + } + + return YES; +} +@end + +// ==================== +// = OakFileIconImage = +// ==================== + +@implementation OakFileIconImage +- (id)initWithWithPath:(NSString*)aPath isModified:(BOOL)flag size:(NSSize)aSize +{ + if((self = [super initWithSize:aSize])) + { + [self addRepresentation:[[[OakFileIconImageRep alloc] initWithPath:aPath isModified:flag] autorelease]]; + [self setSize:aSize]; + } + return self; +} + + (id)fileIconImageWithPath:(NSString*)aPath isModified:(BOOL)flag size:(NSSize)aSize { return [[[self alloc] initWithWithPath:aPath isModified:flag size:aSize] autorelease]; } + (id)fileIconImageWithPath:(NSString*)aPath isModified:(BOOL)flag { return [self fileIconImageWithPath:aPath isModified:flag size:NSMakeSize(16, 16)]; } + (id)fileIconImageWithPath:(NSString*)aPath size:(NSSize)aSize { return [self fileIconImageWithPath:aPath isModified:NO size:aSize]; }