Bug fix: Make QuickLook panel work by using the now public API.

Also implements Finder's fade transitions, handles preview icon creation
through GCD block dispatch and does the right thing regarding events:
Mouse events go to the panel, key events to the fileBrowser outline view.
This commit is contained in:
André Berg
2009-10-20 04:12:06 +02:00
parent dca051c520
commit 75e8a5b9b1
10 changed files with 275 additions and 94 deletions
+3
View File
@@ -7,6 +7,7 @@
//
#import <Cocoa/Cocoa.h>
#import <Quartz/Quartz.h> /* for QLPreviewPanel */
#import "PBGitRepository.h"
@class PBCLIProxy;
@@ -35,4 +36,6 @@
- (IBAction)saveAction:sender;
- (IBAction) showHelp:(id) sender;
- (IBAction)togglePreviewPanel:(id)previewPanel;
@end
+18 -5
View File
@@ -27,9 +27,11 @@
#endif
if(self = [super init]) {
if(![[NSBundle bundleWithPath:@"/System/Library/PrivateFrameworks/QuickLookUI.framework"] load])
NSLog(@"Could not load QuickLook");
/* Location of QuickLookUI.framework - it's public now */
if(![[NSBundle bundleWithPath:@"/System/Library/Frameworks/Quartz.framework/Frameworks/QuickLookUI.framework"] load])
{
NSLog(@"Could not load QuickLook");
}
self.cliProxy = [PBCLIProxy new];
}
@@ -191,7 +193,7 @@
return managedObjectModel;
}
managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];
managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
return managedObjectModel;
}
@@ -217,7 +219,7 @@
fileManager = [NSFileManager defaultManager];
applicationSupportFolder = [self applicationSupportFolder];
if ( ![fileManager fileExistsAtPath:applicationSupportFolder isDirectory:NULL] ) {
[fileManager createDirectoryAtPath:applicationSupportFolder attributes:nil];
[fileManager createDirectoryAtPath:applicationSupportFolder withIntermediateDirectories:YES attributes:nil error:nil];
}
url = [NSURL fileURLWithPath: [applicationSupportFolder stringByAppendingPathComponent: @"GitTest.xml"]];
@@ -324,6 +326,17 @@
return reply;
}
// QuickLook preview panel
- (IBAction)togglePreviewPanel:(id)previewPanel
{
if ([QLPreviewPanel sharedPreviewPanelExists] && [[QLPreviewPanel sharedPreviewPanel] isVisible]) {
[[QLPreviewPanel sharedPreviewPanel] orderOut:nil];
} else {
[[QLPreviewPanel sharedPreviewPanel] makeKeyAndOrderFront:nil];
}
}
/**
Implementation of dealloc, to release the retained variables.
+17 -14
View File
@@ -11,22 +11,25 @@
#import "PBGitTree.h"
#import "PBViewController.h"
#import "PBCollapsibleSplitView.h"
#import <Quartz/Quartz.h> /* for the QLPreviewPanelDataSource et al. stuff */
@interface PBGitHistoryController : PBViewController {
IBOutlet NSSearchField *searchField;
IBOutlet NSArrayController* commitController;
IBOutlet NSTreeController* treeController;
IBOutlet NSOutlineView* fileBrowser;
IBOutlet NSTableView* commitList;
IBOutlet PBCollapsibleSplitView *historySplitView;
@interface PBGitHistoryController : PBViewController <QLPreviewPanelDataSource, QLPreviewPanelDelegate> {
IBOutlet NSSearchField *searchField;
IBOutlet NSArrayController* commitController;
IBOutlet NSTreeController* treeController;
IBOutlet NSOutlineView* fileBrowser;
IBOutlet NSTableView* commitList;
IBOutlet PBCollapsibleSplitView *historySplitView;
IBOutlet id webView;
int selectedTab;
PBGitTree* gitTree;
PBGitCommit* webCommit;
PBGitCommit* rawCommit;
PBGitCommit* realCommit;
IBOutlet id webView;
int selectedTab;
PBGitTree* gitTree;
PBGitCommit* webCommit;
PBGitCommit* rawCommit;
PBGitCommit* realCommit;
QLPreviewPanel * previewPanel;
}
@property (assign) int selectedTab;
+133 -45
View File
@@ -7,16 +7,96 @@
//
#import "PBGitHistoryController.h"
#import "CWQuickLook.h"
#import "PBGitGrapher.h"
#import "PBGitRevisionCell.h"
#import "PBCommitList.h"
#define QLPreviewPanel NSClassFromString(@"QLPreviewPanel")
#import "ApplicationController.h"
@implementation PBGitHistoryController
@synthesize selectedTab, webCommit, rawCommit, gitTree, commitController;
// MARK: Quick Look panel support
- (BOOL)acceptsPreviewPanelControl:(QLPreviewPanel *)panel;
{
return YES;
}
- (void)beginPreviewPanelControl:(QLPreviewPanel *)panel
{
// This document is now responsible of the preview panel
// It is allowed to set the delegate, data source and refresh panel.
previewPanel = panel;
panel.delegate = self;
panel.dataSource = self;
}
- (void)endPreviewPanelControl:(QLPreviewPanel *)panel
{
// This document loses its responsisibility on the preview panel
// Until the next call to -beginPreviewPanelControl: it must not
// change the panel's delegate, data source or refresh it.
[previewPanel release];
previewPanel = nil;
}
// MARK: Quick Look panel data source
- (NSInteger)numberOfPreviewItemsInPreviewPanel:(QLPreviewPanel *)panel
{
return [[fileBrowser selectedRowIndexes] count];
}
- (id <QLPreviewItem>)previewPanel:(QLPreviewPanel *)panel previewItemAtIndex:(NSInteger)index
{
return [[treeController selectedObjects] objectAtIndex:index];
}
// MARK: Quick Look panel delegate
- (BOOL)previewPanel:(QLPreviewPanel *)panel handleEvent:(NSEvent *)event
{
// redirect all key down events to the table view
if ([event type] == NSKeyDown) {
[fileBrowser keyDown:event];
return YES;
}
return NO;
}
// This delegate method provides the rect on screen from which the panel will zoom.
- (NSRect)previewPanel:(QLPreviewPanel *)panel sourceFrameOnScreenForPreviewItem:(id <QLPreviewItem>)item
{
NSInteger index = [[treeController selectedObjects] indexOfObject:item];
if (index == NSNotFound) {
return NSZeroRect;
}
NSRect iconRect = [fileBrowser frameOfOutlineCellAtRow:index];
// check that the icon rect is visible on screen
// NSRect visibleRect = [fileBrowser visibleRect];
//
// if (!NSIntersectsRect(visibleRect, iconRect)) {
// return NSZeroRect;
// }
// convert icon rect to screen coordinates
iconRect = [fileBrowser convertRectToBase:iconRect];
iconRect.origin = [[fileBrowser window] convertBaseToScreen:iconRect.origin];
return iconRect;
}
// This delegate method provides a transition image between the table view and the preview panel
- (id)previewPanel:(QLPreviewPanel *)panel transitionImageForPreviewItem:(id <QLPreviewItem>)item contentRect:(NSRect *)contentRect
{
PBGitTree * treeItem = (PBGitTree *)item;
return treeItem.iconImage;
}
// MARK: PBGitHistoryController
- (void)awakeFromNib
{
self.selectedTab = [[NSUserDefaults standardUserDefaults] integerForKey:@"Repository Window Selected Tab Index"];;
@@ -28,14 +108,14 @@
[commitList setIntercellSpacing:cellSpacing];
[fileBrowser setTarget:self];
[fileBrowser setDoubleAction:@selector(openSelectedFile:)];
if (!repository.currentBranch) {
[repository reloadRefs];
[repository readCurrentBranch];
}
else
[repository lazyReload];
// Set a sort descriptor for the subject column in the history list, as
// It can't be sorted by default (because it's bound to a PBGitCommit)
[[commitList tableColumnWithIdentifier:@"subject"] setSortDescriptorPrototype:[[NSSortDescriptor alloc] initWithKey:@"subject" ascending:YES]];
@@ -103,7 +183,7 @@
return;
PBGitTree* tree = [selectedFiles objectAtIndex:0];
NSString* name = [tree tmpFileNameForContents];
[[NSWorkspace sharedWorkspace] openTempFile:name];
[[NSWorkspace sharedWorkspace] openFile:name];
}
- (IBAction) setDetailedView: sender {
@@ -118,10 +198,22 @@
- (void)keyDown:(NSEvent*)event
{
if ([[event charactersIgnoringModifiers] isEqualToString: @"f"] && [event modifierFlags] & NSAlternateKeyMask && [event modifierFlags] & NSCommandKeyMask)
[superController.window makeFirstResponder: searchField];
else
[super keyDown: event];
if ([[event charactersIgnoringModifiers] isEqualToString: @"f"]
&& [event modifierFlags] & NSAlternateKeyMask
&& [event modifierFlags] & NSCommandKeyMask)
{
// command+alt+f
[superController.window makeFirstResponder: searchField];
}
else if ([[event charactersIgnoringModifiers] isEqualToString: @" "])
{
// space
[[NSApp delegate] togglePreviewPanel:self];
}
else
{
[super keyDown: event];
}
}
- (void) copyCommitInfo
@@ -130,7 +222,7 @@
if (!commit)
return;
NSString *info = [NSString stringWithFormat:@"%@ (%@)", [[commit realSha] substringToIndex:10], [commit subject]];
NSPasteboard *a =[NSPasteboard generalPasteboard];
[a declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:self];
[a setString:info forType: NSStringPboardType];
@@ -139,34 +231,29 @@
- (IBAction) toggleQuickView: sender
{
id panel = [QLPreviewPanel sharedPreviewPanel];
if ([panel isOpen]) {
[panel closePanel];
} else {
[[QLPreviewPanel sharedPreviewPanel] makeKeyAndOrderFrontWithEffect:1];
[self updateQuicklookForce: YES];
}
[[NSApp delegate] togglePreviewPanel:sender];
}
- (void) updateQuicklookForce: (BOOL) force
{
if (!force && ![[QLPreviewPanel sharedPreviewPanel] isOpen])
return;
NSArray* selectedFiles = [treeController selectedObjects];
if ([selectedFiles count] == 0)
return;
NSMutableArray* fileNames = [NSMutableArray array];
for (PBGitTree* tree in selectedFiles) {
NSString* s = [tree tmpFileNameForContents];
if (s)
[fileNames addObject:[NSURL fileURLWithPath: s]];
}
[[QLPreviewPanel sharedPreviewPanel] setURLs:fileNames currentIndex:0 preservingDisplayState:YES];
if ((!force && ![[QLPreviewPanel sharedPreviewPanel] isVisible])
|| ![QLPreviewPanel sharedPreviewPanelExists])
{
return;
}
// NSArray* selectedFiles = [treeController selectedObjects];
//
// if ([selectedFiles count] == 0)
// return;
//
// NSMutableArray* fileNames = [NSMutableArray array];
// for (PBGitTree* tree in selectedFiles) {
// NSString* s = [tree tmpFileNameForContents];
// if (s)
// [fileNames addObject:[NSURL fileURLWithPath: s]];
// }
[[QLPreviewPanel sharedPreviewPanel] reloadData];
}
- (IBAction) refresh: sender
@@ -205,7 +292,7 @@
[commitController removeObserver:self forKeyPath:@"selection"];
[treeController removeObserver:self forKeyPath:@"selection"];
[repository removeObserver:self forKeyPath:@"currentBranch"];
[super removeView];
}
@@ -230,12 +317,12 @@
- (void)showCommitsFromTree:(id)sender
{
// TODO: Enable this from webview as well!
NSMutableArray *filePaths = [NSMutableArray arrayWithObjects:@"HEAD", @"--", NULL];
[filePaths addObjectsFromArray:[sender representedObject]];
PBGitRevSpecifier *revSpec = [[PBGitRevSpecifier alloc] initWithParameters:filePaths];
repository.currentBranch = [repository addBranch:revSpec];
}
@@ -244,12 +331,12 @@
NSString *workingDirectory = [[repository workingDirectory] stringByAppendingString:@"/"];
NSString *path;
NSWorkspace *ws = [NSWorkspace sharedWorkspace];
for (NSString *filePath in [sender representedObject]) {
path = [workingDirectory stringByAppendingPathComponent:filePath];
[ws selectFile: path inFileViewerRootedAtPath:path];
}
}
- (void)openFilesAction:(id)sender
@@ -257,7 +344,7 @@
NSString *workingDirectory = [[repository workingDirectory] stringByAppendingString:@"/"];
NSString *path;
NSWorkspace *ws = [NSWorkspace sharedWorkspace];
for (NSString *filePath in [sender representedObject]) {
path = [workingDirectory stringByAppendingPathComponent:filePath];
[ws openFile:path];
@@ -268,7 +355,7 @@
- (NSMenu *)contextMenuForTreeView
{
NSArray *filePaths = [[treeController selectedObjects] valueForKey:@"fullPath"];
NSMenu *menu = [[NSMenu alloc] init];
for (NSMenuItem *item in [self menuItemsForPaths:filePaths])
[menu addItem:item];
@@ -280,20 +367,20 @@
BOOL multiple = [paths count] != 1;
NSMenuItem *historyItem = [[NSMenuItem alloc] initWithTitle:multiple? @"Show history of files" : @"Show history of file"
action:@selector(showCommitsFromTree:)
keyEquivalent:@""];
keyEquivalent:@""];
NSMenuItem *finderItem = [[NSMenuItem alloc] initWithTitle:@"Show in Finder"
action:@selector(showInFinderAction:)
keyEquivalent:@""];
NSMenuItem *openFilesItem = [[NSMenuItem alloc] initWithTitle:multiple? @"Open Files" : @"Open File"
action:@selector(openFilesAction:)
keyEquivalent:@""];
NSArray *menuItems = [NSArray arrayWithObjects:historyItem, finderItem, openFilesItem, nil];
for (NSMenuItem *item in menuItems) {
[item setTarget:self];
[item setRepresentedObject:paths];
}
return menuItems;
}
@@ -322,3 +409,4 @@
}
@end
+2
View File
@@ -61,6 +61,7 @@ extern NSString* PBGitRepositoryErrorDomain;
+ (NSURL*)gitDirForURL:(NSURL*)repositoryURL;
+ (NSURL*)baseDirForURL:(NSURL*)repositoryURL;
+ (NSString *) basePath;
- (id) initWithURL: (NSURL*) path;
- (void) setup;
@@ -72,4 +73,5 @@ extern NSString* PBGitRepositoryErrorDomain;
@property (assign) NSMutableArray* branches;
@property (assign) PBGitRevSpecifier *currentBranch;
@property (retain) NSMutableDictionary* refs;
@end
+28 -16
View File
@@ -17,6 +17,7 @@
#import "PBGitRevSpecifier.h"
NSString* PBGitRepositoryErrorDomain = @"GitXErrorDomain";
static NSString * repositoryBasePath = nil;
@implementation PBGitRepository
@@ -37,12 +38,23 @@ NSString* PBGitRepositoryErrorDomain = @"GitXErrorDomain";
return [[PBEasyPipe outputForCommand:[PBGitBinary path] withArgs:[NSArray arrayWithObjects:@"rev-parse", @"--is-bare-repository", nil] inDir:path] isEqualToString:@"true"];
}
+ (NSString *) basePath {
if (repositoryBasePath) {
return repositoryBasePath;
}
return nil;
}
+ (NSURL*)gitDirForURL:(NSURL*)repositoryURL;
{
if (![PBGitBinary path])
return nil;
NSString* repositoryPath = [repositoryURL path];
// save base path
repositoryBasePath = [repositoryPath stringByDeletingLastPathComponent];
if ([self isBareRepository:repositoryPath])
return repositoryURL;
@@ -103,7 +115,7 @@ NSString* PBGitRepositoryErrorDomain = @"GitXErrorDomain";
NSURL* gitDirURL = [PBGitRepository gitDirForURL:[self fileURL]];
if (!gitDirURL) {
if (outError) {
NSDictionary* userInfo = [NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"%@ does not appear to be a git repository.", [self fileName]]
NSDictionary* userInfo = [NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"%@ does not appear to be a git repository.", [[self fileURL] path]]
forKey:NSLocalizedRecoverySuggestionErrorKey];
*outError = [NSError errorWithDomain:PBGitRepositoryErrorDomain code:0 userInfo:userInfo];
}
@@ -118,7 +130,7 @@ NSString* PBGitRepositoryErrorDomain = @"GitXErrorDomain";
- (void) setup
{
config = [[PBGitConfig alloc] initWithRepository:self.fileURL.path];
config = [[PBGitConfig alloc] initWithRepository:[[self fileURL] path]];
self.branches = [NSMutableArray array];
[self reloadRefs];
revisionList = [[PBGitRevList alloc] initWithRepository:self];
@@ -133,20 +145,20 @@ NSString* PBGitRepositoryErrorDomain = @"GitXErrorDomain";
if (!gitDirURL)
return nil;
self = [self init];
[self setFileURL: gitDirURL];
[self setup];
// We don't want the window controller to display anything yet..
// We'll leave that to the caller of this method.
#ifndef CLI
[self addWindowController:[[PBGitWindowController alloc] initWithRepository:self displayDefault:NO]];
#endif
[self showWindows];
return self;
if (self = [super init]) {
[self setFileURL: gitDirURL];
[self setup];
// We don't want the window controller to display anything yet..
// We'll leave that to the caller of this method.
#ifndef CLI
[self addWindowController:[[PBGitWindowController alloc] initWithRepository:self displayDefault:NO]];
#endif
[self showWindows];
}
return self;
}
// The fileURL the document keeps is to the .git dir, but thats pretty
+7 -1
View File
@@ -7,9 +7,11 @@
//
#import <Cocoa/Cocoa.h>
#import <Quartz/Quartz.h>
#import "PBGitRepository.h"
@interface PBGitTree : NSObject {
long long _fileSize;
NSString* sha;
@@ -21,8 +23,10 @@
NSString* localFileName;
NSDate* localMtime;
NSString * absolutePath;
NSImage * iconImage;
}
+ (PBGitTree*) rootForCommit: (id) commit;
+ (PBGitTree*) treeForTree: (PBGitTree*) tree andPath: (NSString*) path;
- (void) saveToFolder: (NSString *) directory;
@@ -40,4 +44,6 @@
@property(readonly) NSString* fullPath;
@property(readonly) NSString* contents;
@property (copy) NSString *absolutePath;
@property (retain) NSImage *iconImage;
@end
+65 -11
View File
@@ -11,10 +11,37 @@
#import "NSFileHandleExt.h"
#import "PBEasyPipe.h"
#import "PBEasyFS.h"
//#import "PBGitTreePreviewItem.h"
#define ICON_SIZE 48.0
static NSDictionary* quickLookOptions = nil;
static NSOperationQueue* treeIconQueue = nil;
@interface PBGitTree (QLPreviewItemProtocol) <QLPreviewItem>
- (NSURL *) previewItemURL;
- (NSString *) previewItemTitle;
@end
@implementation PBGitTree (QLPreviewItemProtocol)
- (NSURL *) previewItemURL {
NSString * absPath = self.absolutePath;
if ([absPath isEqualToString:[PBGitRepository basePath]]) {
absPath = [absPath stringByAppendingFormat:@"/%@", [self fullPath]];
}
return [NSURL fileURLWithPath:absPath];
}
- (NSString *) previewItemTitle {
return [self fullPath];
}
@end
@implementation PBGitTree
@synthesize sha, path, repository, leaf, parent;
@synthesize sha, path, repository, leaf, parent, iconImage, absolutePath;
+ (PBGitTree*) rootForCommit:(id) commit
{
@@ -40,12 +67,39 @@
- init
{
children = nil;
localFileName = nil;
leaf = YES;
if (self = [super init]) {
children = nil;
localFileName = nil;
leaf = YES;
absolutePath = [PBGitRepository basePath];
}
return self;
}
- (NSImage *) iconImage {
if (!iconImage) {
iconImage = [[NSWorkspace sharedWorkspace] iconForFile:self.absolutePath ];
[iconImage setSize:NSMakeSize(ICON_SIZE, ICON_SIZE)];
if (!treeIconQueue) {
treeIconQueue = [[NSOperationQueue alloc] init];
[treeIconQueue setMaxConcurrentOperationCount:2];
quickLookOptions = [[NSDictionary alloc] initWithObjectsAndKeys:
(id)kCFBooleanTrue, (id)kQLThumbnailOptionIconModeKey,
nil];
}
[treeIconQueue addOperationWithBlock:^{
CGImageRef quickLookIcon = QLThumbnailImageCreate(NULL, (CFURLRef)[NSURL fileURLWithPath:self.absolutePath], CGSizeMake(ICON_SIZE, ICON_SIZE), (CFDictionaryRef)quickLookOptions);
if (quickLookIcon != NULL) {
NSImage* betterIcon = [[NSImage alloc] initWithCGImage:quickLookIcon size:NSMakeSize(ICON_SIZE, ICON_SIZE)];
[self performSelectorOnMainThread:@selector(setIconImage:) withObject:betterIcon waitUntilDone:NO];
CFRelease(quickLookIcon);
}
}];
}
return iconImage;
}
- (NSString*) refSpec
{
return [NSString stringWithFormat:@"%@:%@", self.sha, self.fullPath];
@@ -157,7 +211,7 @@
NSData* data = [handle readDataToEndOfFile];
[data writeToFile:newName atomically:YES];
} else { // Directory
[[NSFileManager defaultManager] createDirectoryAtPath:newName attributes:nil];
[[NSFileManager defaultManager] createDirectoryAtPath:newName withIntermediateDirectories:YES attributes:nil error:nil];
for (PBGitTree* child in [self children])
[child saveToFolder: newName];
}
@@ -215,10 +269,10 @@
NSMutableArray* c = [NSMutableArray array];
NSString* p = [handle readLine];
while (p.length > 0) {
BOOL isLeaf = ([p characterAtIndex:p.length - 1] != '/');
while ([p length] > 0) {
BOOL isLeaf = ([p characterAtIndex:[p length]- 1] != '/');
if (!isLeaf)
p = [p substringToIndex:p.length -1];
p = [p substringToIndex:[p length]-1];
PBGitTree* child = [PBGitTree treeForTree:self andPath:p];
child.leaf = isLeaf;
@@ -235,16 +289,16 @@
if (!parent)
return @"";
if ([parent.fullPath isEqualToString:@""])
if ([[parent fullPath] isEqualToString:@""])
return self.path;
return [parent.fullPath stringByAppendingPathComponent: self.path];
return [[parent fullPath] stringByAppendingPathComponent: self.path];
}
- (void) finalize
{
if (localFileName)
[[NSFileManager defaultManager] removeFileAtPath:localFileName handler:nil];
[[NSFileManager defaultManager] removeItemAtPath:localFileName error:nil];
[super finalize];
}
@end
+1 -1
View File
@@ -34,7 +34,7 @@
- (void)windowWillClose:(NSNotification *)notification
{
NSLog(@"Window will close!");
//NSLog(@"Window will close!");
if (historyViewController)
[historyViewController removeView];
if (commitViewController)
+1 -1
View File
@@ -79,7 +79,7 @@
/* Implemented to satisfy datasourcee protocol */
- (BOOL) outlineView: (NSOutlineView *)ov
isItemExpandable: (id)item { return NO; }
isItemExpandable: (id)item { return YES; }
- (NSInteger) outlineView: (NSOutlineView *)ov
numberOfChildrenOfItem:(id)item { return 0; }