5 // Created by Pieter de Bie on 19-09-08.
6 // Copyright 2008 __MyCompanyName__. All rights reserved.
9 #import "PBGitHistoryController.h"
10 #import "CWQuickLook.h"
11 #import "PBGitGrapher.h"
12 #import "PBGitRevisionCell.h"
13 #import "PBCommitList.h"
14 #define QLPreviewPanel NSClassFromString(@"QLPreviewPanel")
17 @implementation PBGitHistoryController
18 @synthesize selectedTab, webCommit, rawCommit, gitTree, commitController;
22 self.selectedTab = [[NSUserDefaults standardUserDefaults] integerForKey:@"Repository Window Selected Tab Index"];;
23 [commitController addObserver:self forKeyPath:@"selection" options:(NSKeyValueObservingOptionNew,NSKeyValueObservingOptionOld) context:@"commitChange"];
24 [treeController addObserver:self forKeyPath:@"selection" options:0 context:@"treeChange"];
25 [repository addObserver:self forKeyPath:@"currentBranch" options:0 context:@"branchChange"];
26 NSSize cellSpacing = [commitList intercellSpacing];
27 cellSpacing.height = 0;
28 [commitList setIntercellSpacing:cellSpacing];
29 [fileBrowser setTarget:self];
30 [fileBrowser setDoubleAction:@selector(openSelectedFile:)];
32 if (!repository.currentBranch) {
33 [repository reloadRefs];
34 [repository readCurrentBranch];
37 [repository lazyReload];
39 // Set a sort descriptor for the subject column in the history list, as
40 // It can't be sorted by default (because it's bound to a PBGitCommit)
41 [[commitList tableColumnWithIdentifier:@"subject"] setSortDescriptorPrototype:[[NSSortDescriptor alloc] initWithKey:@"subject" ascending:YES]];
42 // Add a menu that allows a user to select which columns to view
43 [[commitList headerView] setMenu:[self tableColumnMenu]];
44 [historySplitView setTopMin:33.0 andBottomMin:100.0];
45 [historySplitView uncollapse];
51 NSArray* selection = [commitController selectedObjects];
53 // Remove any references in the QLPanel
54 //[[QLPreviewPanel sharedPreviewPanel] setURLs:[NSArray array] currentIndex:0 preservingDisplayState:YES];
55 // We have to do this manually, as NSTreeController leaks memory?
56 //[treeController setSelectionIndexPaths:[NSArray array]];
58 if ([selection count] > 0)
59 realCommit = [selection objectAtIndex:0];
67 switch (self.selectedTab) {
68 case 0: self.webCommit = realCommit; break;
69 case 1: self.rawCommit = realCommit; break;
70 case 2: self.gitTree = realCommit.tree; break;
75 - (void) setSelectedTab: (int) number
78 [[NSUserDefaults standardUserDefaults] setInteger:selectedTab forKey:@"Repository Window Selected Tab Index"];
82 - (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
84 if ([(NSString *)context isEqualToString: @"commitChange"]) {
88 else if ([(NSString *)context isEqualToString: @"treeChange"]) {
89 [self updateQuicklookForce: NO];
91 else if([(NSString *)context isEqualToString:@"branchChange"]) {
93 commitController.sortDescriptors = [NSArray array];
96 [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
100 - (IBAction) openSelectedFile: sender
102 NSArray* selectedFiles = [treeController selectedObjects];
103 if ([selectedFiles count] == 0)
105 PBGitTree* tree = [selectedFiles objectAtIndex:0];
106 NSString* name = [tree tmpFileNameForContents];
107 [[NSWorkspace sharedWorkspace] openTempFile:name];
110 - (IBAction) setDetailedView: sender {
111 self.selectedTab = 0;
113 - (IBAction) setRawView: sender {
114 self.selectedTab = 1;
116 - (IBAction) setTreeView: sender {
117 self.selectedTab = 2;
120 - (void)keyDown:(NSEvent*)event
122 if ([[event charactersIgnoringModifiers] isEqualToString: @"f"] && [event modifierFlags] & NSAlternateKeyMask && [event modifierFlags] & NSCommandKeyMask)
123 [superController.window makeFirstResponder: searchField];
125 [super keyDown: event];
128 - (void) copyCommitInfo
130 PBGitCommit *commit = [[commitController selectedObjects] objectAtIndex:0];
133 NSString *info = [NSString stringWithFormat:@"%@ (%@)", [[commit realSha] substringToIndex:10], [commit subject]];
135 NSPasteboard *a =[NSPasteboard generalPasteboard];
136 [a declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:self];
137 [a setString:info forType: NSStringPboardType];
141 - (IBAction) toggleQuickView: sender
143 id panel = [QLPreviewPanel sharedPreviewPanel];
144 if ([panel isOpen]) {
147 [[QLPreviewPanel sharedPreviewPanel] makeKeyAndOrderFrontWithEffect:1];
148 [self updateQuicklookForce: YES];
152 - (void) updateQuicklookForce: (BOOL) force
154 if (!force && ![[QLPreviewPanel sharedPreviewPanel] isOpen])
157 NSArray* selectedFiles = [treeController selectedObjects];
159 if ([selectedFiles count] == 0)
162 NSMutableArray* fileNames = [NSMutableArray array];
163 for (PBGitTree* tree in selectedFiles) {
164 NSString* s = [tree tmpFileNameForContents];
166 [fileNames addObject:[NSURL fileURLWithPath: s]];
169 [[QLPreviewPanel sharedPreviewPanel] setURLs:fileNames currentIndex:0 preservingDisplayState:YES];
173 - (IBAction) refresh: sender
175 [repository reloadRefs];
176 [repository.revisionList reload];
184 - (NSResponder *)firstResponder;
189 - (void) selectCommit: (NSString*) commit
191 NSPredicate* selection = [NSPredicate predicateWithFormat:@"realSha == %@", commit];
192 NSArray* selectedCommits = [repository.revisionList.commits filteredArrayUsingPredicate:selection];
193 [commitController setSelectedObjects: selectedCommits];
194 int index = [[commitController selectionIndexes] firstIndex];
195 [commitList scrollRowToVisible: index];
198 - (BOOL) hasNonlinearPath
200 return [commitController filterPredicate] || [[commitController sortDescriptors] count] > 0;
206 [commitController removeObserver:self forKeyPath:@"selection"];
207 [treeController removeObserver:self forKeyPath:@"selection"];
208 [repository removeObserver:self forKeyPath:@"currentBranch"];
213 #pragma mark Table Column Methods
214 - (NSMenu *)tableColumnMenu
216 NSMenu *menu = [[NSMenu alloc] initWithTitle:@"Table columns menu"];
217 for (NSTableColumn *column in [commitList tableColumns]) {
218 NSMenuItem *item = [[NSMenuItem alloc] init];
219 [item setTitle:[[column headerCell] stringValue]];
222 withKeyPath:@"hidden"
223 options:[NSDictionary dictionaryWithObject:@"NSNegateBoolean" forKey:NSValueTransformerNameBindingOption]];
229 #pragma mark Tree Context Menu Methods
231 - (void)showCommitsFromTree:(id)sender
233 // TODO: Enable this from webview as well!
235 NSMutableArray *filePaths = [NSMutableArray arrayWithObjects:@"HEAD", @"--", NULL];
236 [filePaths addObjectsFromArray:[sender representedObject]];
238 PBGitRevSpecifier *revSpec = [[PBGitRevSpecifier alloc] initWithParameters:filePaths];
240 repository.currentBranch = [repository addBranch:revSpec];
243 - (void)showInFinderAction:(id)sender
245 NSString *workingDirectory = [[repository workingDirectory] stringByAppendingString:@"/"];
247 NSWorkspace *ws = [NSWorkspace sharedWorkspace];
249 for (NSString *filePath in [sender representedObject]) {
250 path = [workingDirectory stringByAppendingPathComponent:filePath];
251 [ws selectFile: path inFileViewerRootedAtPath:path];
256 - (void)openFilesAction:(id)sender
258 NSString *workingDirectory = [[repository workingDirectory] stringByAppendingString:@"/"];
260 NSWorkspace *ws = [NSWorkspace sharedWorkspace];
262 for (NSString *filePath in [sender representedObject]) {
263 path = [workingDirectory stringByAppendingPathComponent:filePath];
269 - (NSMenu *)contextMenuForTreeView
271 NSArray *filePaths = [[treeController selectedObjects] valueForKey:@"fullPath"];
273 NSMenu *menu = [[NSMenu alloc] init];
274 for (NSMenuItem *item in [self menuItemsForPaths:filePaths])
279 - (NSArray *)menuItemsForPaths:(NSArray *)paths
281 BOOL multiple = [paths count] != 1;
282 NSMenuItem *historyItem = [[NSMenuItem alloc] initWithTitle:multiple? @"Show history of files" : @"Show history of file"
283 action:@selector(showCommitsFromTree:)
285 NSMenuItem *finderItem = [[NSMenuItem alloc] initWithTitle:@"Show in Finder"
286 action:@selector(showInFinderAction:)
288 NSMenuItem *openFilesItem = [[NSMenuItem alloc] initWithTitle:multiple? @"Open Files" : @"Open File"
289 action:@selector(openFilesAction:)
292 NSArray *menuItems = [NSArray arrayWithObjects:historyItem, finderItem, openFilesItem, nil];
293 for (NSMenuItem *item in menuItems) {
294 [item setTarget:self];
295 [item setRepresentedObject:paths];
301 - (BOOL)splitView:(NSSplitView *)sender canCollapseSubview:(NSView *)subview {
305 - (BOOL)splitView:(NSSplitView *)splitView shouldCollapseSubview:(NSView *)subview forDoubleClickOnDividerAtIndex:(NSInteger)dividerIndex {
306 int index = [[splitView subviews] indexOfObject:subview];
307 // this method (and canCollapse) are called by the splitView to decide how to collapse on double-click
308 // we compare our two subviews, so that always the smaller one is collapsed.
309 if([[[splitView subviews] objectAtIndex:index] frame].size.height < [[[splitView subviews] objectAtIndex:((index+1)%2)] frame].size.height) {
315 - (CGFloat)splitView:(NSSplitView *)sender constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)offset {
316 return proposedMin + historySplitView.topViewMin;
319 - (CGFloat)splitView:(NSSplitView *)sender constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)offset {
321 return proposedMax - historySplitView.bottomViewMin;
322 return [sender frame].size.height;