diff --git a/GitX.xcodeproj/project.pbxproj b/GitX.xcodeproj/project.pbxproj index a91ba82..c2bd34e 100644 --- a/GitX.xcodeproj/project.pbxproj +++ b/GitX.xcodeproj/project.pbxproj @@ -38,6 +38,7 @@ F58A8F280E043698007E3FC0 /* commits.css in Resources */ = {isa = PBXBuildFile; fileRef = F58A8F270E043698007E3FC0 /* commits.css */; }; F5945E170E02B0C200706420 /* PBGitRepository.m in Sources */ = {isa = PBXBuildFile; fileRef = F5945E160E02B0C200706420 /* PBGitRepository.m */; }; F5B721C40E05CF7E00AF29DC /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = F5B721C20E05CF7E00AF29DC /* MainMenu.xib */; }; + F5C007750E731B48007B84B2 /* PBGitRef.m in Sources */ = {isa = PBXBuildFile; fileRef = F5C007740E731B48007B84B2 /* PBGitRef.m */; }; F5C6F68D0E65FF9300478D97 /* PBGitLane.m in Sources */ = {isa = PBXBuildFile; fileRef = F5C6F68C0E65FF9300478D97 /* PBGitLane.m */; }; F5DFFA6C0E075D8800617813 /* PBEasyFS.m in Sources */ = {isa = PBXBuildFile; fileRef = F5DFFA6B0E075D8800617813 /* PBEasyFS.m */; }; F5FF4E180E0829C20006317A /* PBGitRevList.m in Sources */ = {isa = PBXBuildFile; fileRef = F5FF4E170E0829C20006317A /* PBGitRevList.m */; }; @@ -107,6 +108,8 @@ F5945E150E02B0C200706420 /* PBGitRepository.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PBGitRepository.h; sourceTree = ""; }; F5945E160E02B0C200706420 /* PBGitRepository.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PBGitRepository.m; sourceTree = ""; }; F5B721C30E05CF7E00AF29DC /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/MainMenu.xib; sourceTree = ""; }; + F5C007730E731B48007B84B2 /* PBGitRef.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PBGitRef.h; sourceTree = ""; }; + F5C007740E731B48007B84B2 /* PBGitRef.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PBGitRef.m; sourceTree = ""; }; F5C6F68B0E65FF9300478D97 /* PBGitLane.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PBGitLane.h; sourceTree = ""; }; F5C6F68C0E65FF9300478D97 /* PBGitLane.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PBGitLane.m; sourceTree = ""; }; F5DFFA6A0E075D8800617813 /* PBEasyFS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PBEasyFS.h; sourceTree = ""; }; @@ -305,6 +308,8 @@ F5C6F6750E65FE2B00478D97 /* Graphing */ = { isa = PBXGroup; children = ( + F5C007730E731B48007B84B2 /* PBGitRef.h */, + F5C007740E731B48007B84B2 /* PBGitRef.m */, F50FE0E10E07BE9600854FCD /* PBGitRevisionCell.h */, F50FE0E20E07BE9600854FCD /* PBGitRevisionCell.m */, F56CC7270E65E0AD004307B4 /* PBGitGraphLine.h */, @@ -419,6 +424,7 @@ F56CC7290E65E0AD004307B4 /* PBGitGraphLine.m in Sources */, F56CC7320E65E0E5004307B4 /* PBGraphCellInfo.m in Sources */, F5C6F68D0E65FF9300478D97 /* PBGitLane.m in Sources */, + F5C007750E731B48007B84B2 /* PBGitRef.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/PBGitGrapher.h b/PBGitGrapher.h index 26741a4..2b46430 100644 --- a/PBGitGrapher.h +++ b/PBGitGrapher.h @@ -21,8 +21,10 @@ struct PBGitGraphColumn { @interface PBGitGrapher : NSObject { NSMutableArray* cellsInfo; + PBGitRepository* repository; } +- (id) initWithRepository: (PBGitRepository*) repo; - (void) parseCommits: (NSArray *) array; - (PBGraphCellInfo*) cellInfoForRow: (int) row; @end diff --git a/PBGitGrapher.m b/PBGitGrapher.m index a264f3b..df7d87a 100644 --- a/PBGitGrapher.m +++ b/PBGitGrapher.m @@ -12,12 +12,21 @@ @implementation PBGitGrapher +- (id) initWithRepository: (PBGitRepository*) repo +{ + repository = repo; + return self; +} - (void) parseCommits: (NSArray *) commits { cellsInfo = [NSMutableArray arrayWithCapacity: [commits count]]; int row = 0; + NSDictionary* refs = nil; + if (repository) + refs = repository.refs; + PBGraphCellInfo* previous; NSMutableArray* previousLanes = [NSMutableArray array]; @@ -120,7 +129,10 @@ ++row; previous = [[PBGraphCellInfo alloc] initWithPosition:newPos andLines:lines]; - + + if (refs && [refs objectForKey:commit.sha]) + previous.refs = [refs objectForKey:commit.sha]; + // If a parent was added, we have room to not indent. if (addedParent) previous.numColumns = [currentLanes count] - 1; diff --git a/PBGitRef.h b/PBGitRef.h new file mode 100644 index 0000000..578b56e --- /dev/null +++ b/PBGitRef.h @@ -0,0 +1,22 @@ +// +// PBGitRef.h +// GitX +// +// Created by Pieter de Bie on 06-09-08. +// Copyright 2008 __MyCompanyName__. All rights reserved. +// + +#import + + +@interface PBGitRef : NSObject { + NSString* ref; +} + +- (NSString*) shortName; +- (NSString*) type; ++ (PBGitRef*) refFromString: (NSString*) s; +- (PBGitRef*) initWithString: (NSString*) s; +@property(readonly) NSString* ref; + +@end diff --git a/PBGitRef.m b/PBGitRef.m new file mode 100644 index 0000000..3dadf59 --- /dev/null +++ b/PBGitRef.m @@ -0,0 +1,44 @@ +// +// PBGitRef.m +// GitX +// +// Created by Pieter de Bie on 06-09-08. +// Copyright 2008 __MyCompanyName__. All rights reserved. +// + +#import "PBGitRef.h" + + +@implementation PBGitRef + +@synthesize ref; +- (NSString*) shortName +{ + if ([self type]) + return [ref substringFromIndex:[[self type] length] + 7]; + return ref; +} + +- (NSString*) type +{ + if ([ref hasPrefix:@"refs/heads"]) + return @"head"; + if ([ref hasPrefix:@"refs/tags"]) + return @"tag"; + if ([ref hasPrefix:@"refs/remotes"]) + return @"remote"; + return nil; +} + ++ (PBGitRef*) refFromString: (NSString*) s +{ + return [[PBGitRef alloc] initWithString:s]; +} + +- (PBGitRef*) initWithString: (NSString*) s +{ + ref = s; + return self; +} + +@end diff --git a/PBGitRepository.h b/PBGitRepository.h index 22af9a5..98fe88e 100644 --- a/PBGitRepository.h +++ b/PBGitRepository.h @@ -15,6 +15,7 @@ extern NSString* PBGitRepositoryErrorDomain; PBGitRevList* revisionList; NSArray* branches; NSString* currentBranch; + NSDictionary* refs; } - (NSFileHandle*) handleForCommand:(NSString*) cmd; @@ -22,7 +23,7 @@ extern NSString* PBGitRepositoryErrorDomain; - (NSString*) outputForCommand:(NSString*) cmd; - (NSString*) outputForArguments:(NSArray*) args; -- (void) readBranches; +- (void) readRefs; - (void) readCurrentBranch; - (NSString*) parseSymbolicReference:(NSString*) ref; @@ -34,5 +35,5 @@ extern NSString* PBGitRepositoryErrorDomain; @property (readonly) PBGitRevList* revisionList; @property (assign) NSArray* branches; @property (assign) NSString* currentBranch; - +@property (assign) NSDictionary* refs; @end diff --git a/PBGitRepository.m b/PBGitRepository.m index fc7b66f..584a115 100644 --- a/PBGitRepository.m +++ b/PBGitRepository.m @@ -17,7 +17,7 @@ NSString* PBGitRepositoryErrorDomain = @"GitXErrorDomain"; @implementation PBGitRepository -@synthesize revisionList, branches, currentBranch; +@synthesize revisionList, branches, currentBranch, refs; static NSString* gitPath; + (void) initialize @@ -112,7 +112,7 @@ static NSString* gitPath; } if (success) { - [self readBranches]; + [self readRefs]; [self readCurrentBranch]; revisionList = [[PBGitRevList alloc] initWithRepository:self andRevListParameters:[NSArray array]]; } @@ -139,21 +139,35 @@ static NSString* gitPath; [controller release]; } -- (void) readBranches +- (void) readRefs { - NSString* output = [PBEasyPipe outputForCommand:gitPath withArgs:[NSArray arrayWithObjects:@"for-each-ref", @"refs/heads", nil] inDir: self.fileURL.path]; + NSString* output = [PBEasyPipe outputForCommand:gitPath withArgs:[NSArray arrayWithObjects:@"for-each-ref", @"--format=%(refname) %(objecttype) %(objectname) %(*objectname)", @"refs", nil] inDir: self.fileURL.path]; NSArray* lines = [output componentsSeparatedByString:@"\n"]; + NSMutableDictionary* newRefs = [NSMutableDictionary dictionary]; NSMutableArray* newBranches = [NSMutableArray array]; for (NSString* line in lines) { - NSString* substr = [line substringWithRange: NSMakeRange(40,19)]; - if (![substr isEqualToString:@" commit\trefs/heads/"]) { - NSLog(@"Cannot parse branch %@. (%@)", line, substr); - continue; + NSArray* components = [line componentsSeparatedByString:@" "]; + NSString* ref = [components objectAtIndex:0]; + NSString* type = [components objectAtIndex:1]; + NSString* sha; + if ([type isEqualToString:@"tag"] && [components count] == 4) + sha = [components objectAtIndex:3]; + else + sha = [components objectAtIndex:2]; + + if ([ref length] > 11 && [[ref substringToIndex:11] isEqualToString:@"refs/heads/"]) { + NSString* branch = [ref substringFromIndex:11]; + [newBranches addObject: branch]; } - NSString* branch = [line substringFromIndex:59]; - [newBranches addObject: branch]; + + NSMutableArray* curRefs; + if (curRefs = [newRefs objectForKey:sha]) + [curRefs addObject:ref]; + else + [newRefs setObject:[NSMutableArray arrayWithObject:ref] forKey:sha]; } self.branches = newBranches; + self.refs = newRefs; } - (void) readCurrentBranch diff --git a/PBGitRevList.h b/PBGitRevList.h index 1417cac..60e96bf 100644 --- a/PBGitRevList.h +++ b/PBGitRevList.h @@ -18,7 +18,7 @@ } - initWithRepository:(id)repo andRevListParameters:(NSArray*) params; -- readCommits; +- (void) readCommits; @property(retain) NSArray* commits; @property(retain) id grapher; diff --git a/PBGitRevList.m b/PBGitRevList.m index f07511c..c097320 100644 --- a/PBGitRevList.m +++ b/PBGitRevList.m @@ -103,7 +103,7 @@ [self performSelectorOnMainThread:@selector(setCommits:) withObject:newArray waitUntilDone:YES]; - PBGitGrapher* g = [[PBGitGrapher alloc] init]; + PBGitGrapher* g = [[PBGitGrapher alloc] initWithRepository: repository]; [g parseCommits: self.commits]; [self performSelectorOnMainThread:@selector(setGrapher:) withObject:g waitUntilDone:YES]; [self performSelectorOnMainThread:@selector(setCommits:) withObject:newArray waitUntilDone:YES]; diff --git a/PBGitRevisionCell.m b/PBGitRevisionCell.m index 109a2ac..2e65800 100644 --- a/PBGitRevisionCell.m +++ b/PBGitRevisionCell.m @@ -7,7 +7,54 @@ // #import "PBGitRevisionCell.h" +#import "PBGitRef.h" +@implementation NSBezierPath (RoundedRectangle) ++ (NSBezierPath *)bezierPathWithRoundedRect: (NSRect) aRect cornerRadius: (double) cRadius +{ + double left = aRect.origin.x, bottom = aRect.origin.y, width = aRect.size.width, height = aRect.size.height; + + //now, crop the radius so we don't get weird effects + double lesserDim = width < height ? width : height; + if ( cRadius > lesserDim / 2 ) + { + cRadius = lesserDim / 2; + } + + //these points describe the rectangle as start and stop points of the + //arcs making up its corners --points c, e, & g are implicit endpoints of arcs + //and are unnecessary + NSPoint a = NSMakePoint( 0, cRadius ), b = NSMakePoint( 0, height - cRadius ), + d = NSMakePoint( width - cRadius, height ), f = NSMakePoint( width, cRadius ), + h = NSMakePoint( cRadius, 0 ); + + //these points describe the center points of the corner arcs + NSPoint cA = NSMakePoint( cRadius, height - cRadius ), + cB = NSMakePoint( width - cRadius, height - cRadius ), + cC = NSMakePoint( width - cRadius, cRadius ), + cD = NSMakePoint( cRadius, cRadius ); + + //start + NSBezierPath *bp = [NSBezierPath bezierPath]; + [bp moveToPoint: a ]; + [bp lineToPoint: b ]; + [bp appendBezierPathWithArcWithCenter: cA radius: cRadius startAngle:180 endAngle:90 clockwise: YES]; + [bp lineToPoint: d ]; + [bp appendBezierPathWithArcWithCenter: cB radius: cRadius startAngle:90 endAngle:0 clockwise: YES]; + [bp lineToPoint: f ]; + [bp appendBezierPathWithArcWithCenter: cC radius: cRadius startAngle:0 endAngle:270 clockwise: YES]; + [bp lineToPoint: h ]; + [bp appendBezierPathWithArcWithCenter: cD radius: cRadius startAngle:270 endAngle:180 clockwise: YES]; + [bp closePath]; + + //Transform path to rectangle's origin + NSAffineTransform *transform = [NSAffineTransform transform]; + [transform translateXBy: left yBy: bottom]; + [bp transformUsingAffineTransform: transform]; + + return bp; //it's already been autoreleased +} +@end @implementation PBGitRevisionCell @@ -63,7 +110,11 @@ - (void) drawCircleForColumn: (int) c inRect: (NSRect) r { - [[NSColor blackColor] set]; + if (!cellInfo.refs) + [[NSColor blackColor] set]; + else + [[NSColor redColor] set]; + int columnWidth = 10; NSPoint origin = r.origin; NSPoint columnOrigin = { origin.x + columnWidth * c, origin.y}; @@ -82,6 +133,75 @@ [path fill]; } +- (NSMutableDictionary*) attributesForRefLabelSelected: (BOOL) selected +{ + NSMutableDictionary *attributes = [[[NSMutableDictionary alloc] initWithCapacity:2] autorelease]; + NSMutableParagraphStyle* style = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] autorelease]; + + [style setAlignment:NSCenterTextAlignment]; + [attributes setObject:style forKey:NSParagraphStyleAttributeName]; + [attributes setObject:[NSFont fontWithName:@"Helvetica" size:9] forKey:NSFontAttributeName]; + + //if (selected) + // [attributes setObject:[NSColor alternateSelectedControlTextColor] forKey:NSForegroundColorAttributeName]; + + return attributes; +} + +- (NSColor*) colorForRef: (PBGitRef*) ref +{ + NSString* type = [ref type]; + if ([type isEqualToString:@"head"]) + return [NSColor yellowColor]; + else if ([type isEqualToString:@"remote"]) + return [NSColor greenColor]; + else if ([type isEqualToString:@"tag"]) + return [NSColor cyanColor]; + + return [NSColor yellowColor]; +} + +- (void) drawRefsInRect: (NSRect*) rect +{ + static const float ref_padding = 10.0f; + static const float ref_spacing = 2.0f; + + NSRect refRect = (NSRect){rect->origin, rect->size}; + + if([self isHighlighted]) + [[NSColor whiteColor] setStroke]; + else + [[NSColor blackColor] setStroke]; + + int index; + for (index = 0; index < [cellInfo.refs count]; ++index) { + PBGitRef* ref = [PBGitRef refFromString:[cellInfo.refs objectAtIndex:index]]; + + NSMutableDictionary* attributes = [self attributesForRefLabelSelected:[self isHighlighted]]; + NSSize refSize = [[ref shortName] sizeWithAttributes:attributes]; + + refRect.size.width = refSize.width + ref_padding; + refRect.size.height = refSize.height; + refRect.origin.y += (rect->size.height - refRect.size.height) / 2; + + // Round rects to 0.5 pixels in order to draw only a single pixel + refRect.origin.x = round(refRect.origin.x) - 0.5; + refRect.origin.y = round(refRect.origin.y) - 0.5; + + NSBezierPath *border = [NSBezierPath bezierPathWithRoundedRect:refRect cornerRadius: 2.0]; + [[self colorForRef: ref] set]; + [border fill]; + + [[ref shortName] drawInRect:refRect withAttributes:attributes]; + [border stroke]; + + refRect.origin.x += (int)refRect.size.width + ref_spacing; + } + + rect->size.width -= refRect.origin.x - rect->origin.x; + rect->origin.x = refRect.origin.x; +} + - (void) drawWithFrame: (NSRect) rect inView:(NSView *)view { if (!isReady) @@ -101,6 +221,8 @@ [self drawCircleForColumn: cellInfo.position inRect: ownRect]; + if (cellInfo.refs) + [self drawRefsInRect:&rect]; [super drawWithFrame:rect inView:view]; isReady = NO; diff --git a/PBGraphCellInfo.h b/PBGraphCellInfo.h index 8f4b84b..c399441 100644 --- a/PBGraphCellInfo.h +++ b/PBGraphCellInfo.h @@ -14,8 +14,10 @@ int position; NSArray* lines; int numColumns; + NSArray* refs; } @property(readonly) NSArray* lines; +@property(retain) NSArray* refs; @property(assign) int position, numColumns; - (id)initWithPosition: (int) p andLines: (NSArray*) l; diff --git a/PBGraphCellInfo.m b/PBGraphCellInfo.m index f5e96b5..8beff21 100644 --- a/PBGraphCellInfo.m +++ b/PBGraphCellInfo.m @@ -10,7 +10,7 @@ @implementation PBGraphCellInfo -@synthesize lines, position, numColumns; +@synthesize lines, position, numColumns, refs; - (id)initWithPosition: (int) p andLines: (NSArray*) l { position = p;