From 4fad6b60a383463530f3854783e80f7a789a7263 Mon Sep 17 00:00:00 2001 From: Nathan Kinsinger Date: Sun, 29 Aug 2010 00:00:34 -0600 Subject: [PATCH] Improve search UI Previously searching would filter the commits in the commit tableview to only show the commits that matched the search. However the context of where those commits exist in the history is lost. With this patch all the commits are shown but the commits that match the search are highlighted with a light blue background. In addition there is a forward/back button to step through the matches. A new search controller: - keeps track of the matching results - finds the next or previous result - displays the number of matches found or "Not found" - shows/hides the # of matches text and the next/last stepper button - shows a small bezel style window with a rewind icon indicating that the selection has cycled (pressing next when at the last match or previous when at the first) - sets up the search predicate which covers Subject, Author and SHA (previously this was three different searches) - stores search results in an NSIndexSet to make finding if a row is in the set faster (needed at drawing time) Highlighting of search result rows is done in PBCommitList -drawRow:clipRect: PBGitTextFieldCell is a subclass of NSTextFieldCell that disables the cell's selection highlighting. Supporting Find Next and Find Previous (cmd-g and cmd-shift-g) menu commands required changing the action method of the menu items because NSTextFields (seem to) actively disable items in the Find menu. rewindImage.pdf created by Nathan Kinsinger --- English.lproj/MainMenu.xib | 170 +++++++++-- GitX.xcodeproj/project.pbxproj | 20 ++ GitXTextFieldCell.h | 16 ++ GitXTextFieldCell.m | 20 ++ Images/rewindImage.pdf | Bin 0 -> 7819 bytes PBCommitList.h | 2 + PBCommitList.m | 63 ++++ PBGitHistoryController.h | 9 + PBGitHistoryController.m | 31 +- PBGitHistoryView.xib | 512 +++++++++++++++++++++++++-------- PBGitRevisionCell.m | 3 +- PBHistorySearchController.h | 46 +++ PBHistorySearchController.m | 313 ++++++++++++++++++++ 13 files changed, 1057 insertions(+), 148 deletions(-) create mode 100644 GitXTextFieldCell.h create mode 100644 GitXTextFieldCell.m create mode 100644 Images/rewindImage.pdf create mode 100644 PBHistorySearchController.h create mode 100644 PBHistorySearchController.m diff --git a/English.lproj/MainMenu.xib b/English.lproj/MainMenu.xib index df9ae8e..dd75fb2 100644 --- a/English.lproj/MainMenu.xib +++ b/English.lproj/MainMenu.xib @@ -3,16 +3,15 @@ 1050 10F569 - 804 + 788 1038.29 461.00 com.apple.InterfaceBuilder.CocoaPlugin - 804 + 788 YES - YES @@ -1104,22 +1103,6 @@ 199 - - - performFindPanelAction: - - - - 200 - - - - performFindPanelAction: - - - - 201 - performFindPanelAction: @@ -1368,6 +1351,22 @@ 966 + + + selectNext: + + + + 967 + + + + selectPrevious: + + + + 968 + @@ -2332,7 +2331,7 @@ com.apple.InterfaceBuilder.CocoaPlugin - {{864, 473}, {238, 103}} + {{762, 747}, {238, 103}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin @@ -2498,7 +2497,7 @@ - 966 + 968 @@ -2665,6 +2664,13 @@ + + NSApplication + + IBProjectSource + NSApplication+GitXScripting.h + + NSOutlineView @@ -2688,12 +2694,14 @@ YES controller + searchController webController webView YES PBGitHistoryController + PBHistorySearchController PBWebHistoryController WebView @@ -2703,6 +2711,7 @@ YES controller + searchController webController webView @@ -2712,6 +2721,10 @@ controller PBGitHistoryController + + searchController + PBHistorySearchController + webController PBWebHistoryController @@ -3148,6 +3161,7 @@ rebaseButton refController scopeBarView + searchController searchField selectedBranchFilterItem treeController @@ -3168,6 +3182,7 @@ NSButton PBRefController PBGitGradientBarView + PBHistorySearchController NSSearchField NSButton NSTreeController @@ -3191,6 +3206,7 @@ rebaseButton refController scopeBarView + searchController searchField selectedBranchFilterItem treeController @@ -3244,6 +3260,10 @@ scopeBarView PBGitGradientBarView + + searchController + PBHistorySearchController + searchField NSSearchField @@ -3617,6 +3637,78 @@ + + PBHistorySearchController + NSObject + + stepperPressed: + id + + + stepperPressed: + + stepperPressed: + id + + + + YES + + YES + commitController + historyController + numberOfMatchesField + searchField + stepper + + + YES + NSArrayController + PBGitHistoryController + NSTextField + NSSearchField + NSSegmentedControl + + + + YES + + YES + commitController + historyController + numberOfMatchesField + searchField + stepper + + + YES + + commitController + NSArrayController + + + historyController + PBGitHistoryController + + + numberOfMatchesField + NSTextField + + + searchField + NSSearchField + + + stepper + NSSegmentedControl + + + + + IBProjectSource + PBHistorySearchController.h + + PBNiceSplitView NSSplitView @@ -4542,6 +4634,34 @@ Foundation.framework/Headers/NSURLDownload.h + + NSObject + + IBFrameworkSource + QuartzCore.framework/Headers/CAAnimation.h + + + + NSObject + + IBFrameworkSource + QuartzCore.framework/Headers/CALayer.h + + + + NSObject + + IBFrameworkSource + QuartzCore.framework/Headers/CIImageProvider.h + + + + NSObject + + IBFrameworkSource + ScriptingBridge.framework/Headers/SBApplication.h + + NSObject @@ -4678,6 +4798,14 @@ AppKit.framework/Headers/NSSearchField.h + + NSSegmentedControl + NSControl + + IBFrameworkSource + AppKit.framework/Headers/NSSegmentedControl.h + + NSSplitView NSView diff --git a/GitX.xcodeproj/project.pbxproj b/GitX.xcodeproj/project.pbxproj index 5cb9bd1..f95d9c4 100644 --- a/GitX.xcodeproj/project.pbxproj +++ b/GitX.xcodeproj/project.pbxproj @@ -68,18 +68,22 @@ D858108411274D28007F254B /* RemoteBranch.png in Resources */ = {isa = PBXBuildFile; fileRef = D858108111274D28007F254B /* RemoteBranch.png */; }; D858108511274D28007F254B /* Tag.png in Resources */ = {isa = PBXBuildFile; fileRef = D858108211274D28007F254B /* Tag.png */; }; D85B939310E3D8B4007F3C28 /* PBCreateBranchSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = D85B939210E3D8B4007F3C28 /* PBCreateBranchSheet.xib */; }; + D87127011229A21C00012334 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D87127001229A21C00012334 /* QuartzCore.framework */; }; + D8712A00122B14EC00012334 /* GitXTextFieldCell.m in Sources */ = {isa = PBXBuildFile; fileRef = D87129FF122B14EC00012334 /* GitXTextFieldCell.m */; }; D889EB3110E6BCBB00F08413 /* PBCreateTagSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = D889EB3010E6BCBB00F08413 /* PBCreateTagSheet.xib */; }; D89E9B141218BA260097A90B /* ScriptingBridge.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D89E9AB21218A9DA0097A90B /* ScriptingBridge.framework */; }; D8A4BB6F11337D5C00E92D51 /* PBGitGradientBarView.m in Sources */ = {isa = PBXBuildFile; fileRef = D8A4BB6E11337D5C00E92D51 /* PBGitGradientBarView.m */; }; D8A4BD071134AD2900E92D51 /* CherryPickTemplate.png in Resources */ = {isa = PBXBuildFile; fileRef = D8A4BD041134AD2900E92D51 /* CherryPickTemplate.png */; }; D8A4BD081134AD2900E92D51 /* MergeTemplate.png in Resources */ = {isa = PBXBuildFile; fileRef = D8A4BD051134AD2900E92D51 /* MergeTemplate.png */; }; D8A4BD091134AD2900E92D51 /* RebaseTemplate.png in Resources */ = {isa = PBXBuildFile; fileRef = D8A4BD061134AD2900E92D51 /* RebaseTemplate.png */; }; + D8B4DC571220D1E4004166D6 /* PBHistorySearchController.m in Sources */ = {isa = PBXBuildFile; fileRef = D8B4DC561220D1E4004166D6 /* PBHistorySearchController.m */; }; D8E105471157C18200FC28A4 /* PBQLTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = D8E105461157C18200FC28A4 /* PBQLTextView.m */; }; D8E3B2B810DC9FB2001096A3 /* ScriptingBridge.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D8E3B2B710DC9FB2001096A3 /* ScriptingBridge.framework */; }; D8E3B34D10DCA958001096A3 /* PBCreateTagSheet.m in Sources */ = {isa = PBXBuildFile; fileRef = D8E3B34C10DCA958001096A3 /* PBCreateTagSheet.m */; }; D8EB616A122F643E00FCCAF4 /* GitXRelativeDateFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = D8EB6169122F643E00FCCAF4 /* GitXRelativeDateFormatter.m */; }; D8F01C4B12182F19007F729F /* GitX.sdef in Resources */ = {isa = PBXBuildFile; fileRef = D8F01C4A12182F19007F729F /* GitX.sdef */; }; D8F01D531218A164007F729F /* NSApplication+GitXScripting.m in Sources */ = {isa = PBXBuildFile; fileRef = D8F01D521218A164007F729F /* NSApplication+GitXScripting.m */; }; + D8F4AB7912298CE200D6D53C /* rewindImage.pdf in Resources */ = {isa = PBXBuildFile; fileRef = D8F4AB7812298CE200D6D53C /* rewindImage.pdf */; }; D8FBCF19115FA20C0098676A /* PBGitSHA.m in Sources */ = {isa = PBXBuildFile; fileRef = D8FBCF18115FA20C0098676A /* PBGitSHA.m */; }; D8FDD9F711432A12005647F6 /* PBCloneRepositoryPanel.xib in Resources */ = {isa = PBXBuildFile; fileRef = D8FDD9F511432A12005647F6 /* PBCloneRepositoryPanel.xib */; }; D8FDDA6A114335E8005647F6 /* PBGitSVBranchItem.m in Sources */ = {isa = PBXBuildFile; fileRef = D8FDDA5D114335E8005647F6 /* PBGitSVBranchItem.m */; }; @@ -307,6 +311,9 @@ D858108111274D28007F254B /* RemoteBranch.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = RemoteBranch.png; path = Images/RemoteBranch.png; sourceTree = ""; }; D858108211274D28007F254B /* Tag.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Tag.png; path = Images/Tag.png; sourceTree = ""; }; D85B93F610E51279007F3C28 /* PBGitRefish.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PBGitRefish.h; sourceTree = ""; }; + D87127001229A21C00012334 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; + D87129FE122B14EC00012334 /* GitXTextFieldCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GitXTextFieldCell.h; sourceTree = ""; }; + D87129FF122B14EC00012334 /* GitXTextFieldCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GitXTextFieldCell.m; sourceTree = ""; }; D89E9AB21218A9DA0097A90B /* ScriptingBridge.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ScriptingBridge.framework; path = System/Library/Frameworks/ScriptingBridge.framework; sourceTree = SDKROOT; }; D89E9B4F1218C2750097A90B /* GitXScriptingConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GitXScriptingConstants.h; sourceTree = ""; }; D8A4BB6D11337D5C00E92D51 /* PBGitGradientBarView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PBGitGradientBarView.h; sourceTree = ""; }; @@ -314,6 +321,8 @@ D8A4BD041134AD2900E92D51 /* CherryPickTemplate.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = CherryPickTemplate.png; path = Images/CherryPickTemplate.png; sourceTree = ""; }; D8A4BD051134AD2900E92D51 /* MergeTemplate.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = MergeTemplate.png; path = Images/MergeTemplate.png; sourceTree = ""; }; D8A4BD061134AD2900E92D51 /* RebaseTemplate.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = RebaseTemplate.png; path = Images/RebaseTemplate.png; sourceTree = ""; }; + D8B4DC551220D1E4004166D6 /* PBHistorySearchController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PBHistorySearchController.h; sourceTree = ""; }; + D8B4DC561220D1E4004166D6 /* PBHistorySearchController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PBHistorySearchController.m; sourceTree = ""; }; D8C1B77210E875CF009B7F8B /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/PBRemoteProgressSheet.xib; sourceTree = ""; }; D8E105451157C18200FC28A4 /* PBQLTextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PBQLTextView.h; sourceTree = ""; }; D8E105461157C18200FC28A4 /* PBQLTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PBQLTextView.m; sourceTree = ""; }; @@ -327,6 +336,7 @@ D8F01D511218A164007F729F /* NSApplication+GitXScripting.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSApplication+GitXScripting.h"; sourceTree = ""; }; D8F01D521218A164007F729F /* NSApplication+GitXScripting.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSApplication+GitXScripting.m"; sourceTree = ""; }; D8F01D841218A406007F729F /* GitX.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GitX.h; sourceTree = ""; }; + D8F4AB7812298CE200D6D53C /* rewindImage.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; name = rewindImage.pdf; path = Images/rewindImage.pdf; sourceTree = ""; }; D8FBCF17115FA20C0098676A /* PBGitSHA.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PBGitSHA.h; sourceTree = ""; }; D8FBCF18115FA20C0098676A /* PBGitSHA.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PBGitSHA.m; sourceTree = ""; }; D8FDD9F611432A12005647F6 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/PBCloneRepositoryPanel.xib; sourceTree = ""; }; @@ -477,6 +487,7 @@ F5E4DBFB0EAB58D90013FAFC /* SystemConfiguration.framework in Frameworks */, F5C580E50EDA250900995434 /* libgit2.a in Frameworks */, D8E3B2B810DC9FB2001096A3 /* ScriptingBridge.framework in Frameworks */, + D87127011229A21C00012334 /* QuartzCore.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -534,6 +545,7 @@ 29B97325FDCFA39411CA2CEA /* Foundation.framework */, 29B97324FDCFA39411CA2CEA /* AppKit.framework */, D8E3B2B710DC9FB2001096A3 /* ScriptingBridge.framework */, + D87127001229A21C00012334 /* QuartzCore.framework */, ); name = "Other Frameworks"; sourceTree = ""; @@ -580,6 +592,7 @@ D858108011274D28007F254B /* Branch.png */, D858108111274D28007F254B /* RemoteBranch.png */, D858108211274D28007F254B /* Tag.png */, + D8F4AB7812298CE200D6D53C /* rewindImage.pdf */, D85810541127476E007F254B /* StageView.png */, D8FDDBF31143F318005647F6 /* AddRemote.png */, D828A5EF1128AE7200F09D11 /* FetchTemplate.png */, @@ -825,6 +838,8 @@ D8A4BB6E11337D5C00E92D51 /* PBGitGradientBarView.m */, D8EB6168122F643E00FCCAF4 /* GitXRelativeDateFormatter.h */, D8EB6169122F643E00FCCAF4 /* GitXRelativeDateFormatter.m */, + D87129FE122B14EC00012334 /* GitXTextFieldCell.h */, + D87129FF122B14EC00012334 /* GitXTextFieldCell.m */, ); name = Aux; sourceTree = ""; @@ -952,6 +967,8 @@ F52BCE060E84211300AA3741 /* PBGitHistoryController.m */, F574A2830EAE2EAC003F2CB1 /* PBRefController.h */, F574A2840EAE2EAC003F2CB1 /* PBRefController.m */, + D8B4DC551220D1E4004166D6 /* PBHistorySearchController.h */, + D8B4DC561220D1E4004166D6 /* PBHistorySearchController.m */, ); name = History; sourceTree = ""; @@ -1154,6 +1171,7 @@ D828AEEC112F411100F09D11 /* CloneRepositoryTemplate.png in Resources */, D8022FE811E124A0003C21F6 /* PBGitXMessageSheet.xib in Resources */, D8F01C4B12182F19007F729F /* GitX.sdef in Resources */, + D8F4AB7912298CE200D6D53C /* rewindImage.pdf in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1301,6 +1319,8 @@ D8022FED11E124C8003C21F6 /* PBGitXMessageSheet.m in Sources */, D8EB616A122F643E00FCCAF4 /* GitXRelativeDateFormatter.m in Sources */, D8F01D531218A164007F729F /* NSApplication+GitXScripting.m in Sources */, + D8B4DC571220D1E4004166D6 /* PBHistorySearchController.m in Sources */, + D8712A00122B14EC00012334 /* GitXTextFieldCell.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/GitXTextFieldCell.h b/GitXTextFieldCell.h new file mode 100644 index 0000000..4bd3ec7 --- /dev/null +++ b/GitXTextFieldCell.h @@ -0,0 +1,16 @@ +// +// GitXTextFieldCell.h +// GitX +// +// Created by Nathan Kinsinger on 8/27/10. +// Copyright 2010 Nathan Kinsinger. All rights reserved. +// + +#import + + +@interface GitXTextFieldCell : NSTextFieldCell { + +} + +@end diff --git a/GitXTextFieldCell.m b/GitXTextFieldCell.m new file mode 100644 index 0000000..bc95397 --- /dev/null +++ b/GitXTextFieldCell.m @@ -0,0 +1,20 @@ +// +// GitXTextFieldCell.m +// GitX +// +// Created by Nathan Kinsinger on 8/27/10. +// Copyright 2010 Nathan Kinsinger. All rights reserved. +// + +#import "GitXTextFieldCell.h" + + +@implementation GitXTextFieldCell + +- (NSColor *)highlightColorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView +{ + // disables the cell's selection highlight + return nil; +} + +@end diff --git a/Images/rewindImage.pdf b/Images/rewindImage.pdf new file mode 100644 index 0000000000000000000000000000000000000000..40066caad4c3dc16c4d64cac03d1f4ab1a9a0166 GIT binary patch literal 7819 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 Q;8GANdLbbd9aZ}O0mYj=rT_o{ literal 0 HcmV?d00001 diff --git a/PBCommitList.h b/PBCommitList.h index d83dd80..3829a41 100644 --- a/PBCommitList.h +++ b/PBCommitList.h @@ -16,6 +16,8 @@ IBOutlet WebView* webView; IBOutlet PBWebHistoryController *webController; IBOutlet PBGitHistoryController *controller; + IBOutlet PBHistorySearchController *searchController; + BOOL useAdjustScroll; NSPoint mouseDownPoint; } diff --git a/PBCommitList.m b/PBCommitList.m index d5ea35d..dbf33b4 100644 --- a/PBCommitList.m +++ b/PBCommitList.m @@ -9,6 +9,7 @@ #import "PBCommitList.h" #import "PBGitRevisionCell.h" #import "PBWebHistoryController.h" +#import "PBHistorySearchController.h" @implementation PBCommitList @@ -122,4 +123,66 @@ return newImage; } + + +#pragma mark Row highlighting + +- (NSColor *)searchResultHighlightColorForRow:(NSInteger)rowIndex +{ + // if the row is selected use default colors + if ([self isRowSelected:rowIndex]) { + if ([[self window] isKeyWindow]) { + if ([[self window] firstResponder] == self) { + return [NSColor alternateSelectedControlColor]; + } + return [NSColor selectedControlColor]; + } + return [NSColor secondarySelectedControlColor]; + } + + // light blue color highlighting search results + return [NSColor colorWithCalibratedRed:0.751f green:0.831f blue:0.943f alpha:0.800f]; +} + +- (NSColor *)searchResultHighlightStrokeColorForRow:(NSInteger)rowIndex +{ + if ([self isRowSelected:rowIndex]) + return [NSColor colorWithCalibratedWhite:0.0f alpha:0.30f]; + + return [NSColor colorWithCalibratedWhite:0.0f alpha:0.05f]; +} + +- (void)drawRow:(NSInteger)rowIndex clipRect:(NSRect)tableViewClipRect +{ + NSRect rowRect = [self rectOfRow:rowIndex]; + BOOL isRowVisible = NSIntersectsRect(rowRect, tableViewClipRect); + + // draw special highlighting if the row is part of search results + if (isRowVisible && [searchController isRowInSearchResults:rowIndex]) { + NSRect highlightRect = NSInsetRect(rowRect, 1.0f, 1.0f); + float radius = highlightRect.size.height / 2.0f; + + NSBezierPath *highlightPath = [NSBezierPath bezierPathWithRoundedRect:highlightRect xRadius:radius yRadius:radius]; + + [[self searchResultHighlightColorForRow:rowIndex] set]; + [highlightPath fill]; + + [[self searchResultHighlightStrokeColorForRow:rowIndex] set]; + [highlightPath stroke]; + } + + // draws the content inside the row + [super drawRow:rowIndex clipRect:tableViewClipRect]; +} + +- (void)highlightSelectionInClipRect:(NSRect)tableViewClipRect +{ + // disable highlighting if the selected row is part of search results + // instead do the highlighting in drawRow:clipRect: above + if ([searchController isRowInSearchResults:[self selectedRow]]) + return; + + [super highlightSelectionInClipRect:tableViewClipRect]; +} + @end diff --git a/PBGitHistoryController.h b/PBGitHistoryController.h index f848c4c..8905277 100644 --- a/PBGitHistoryController.h +++ b/PBGitHistoryController.h @@ -20,6 +20,8 @@ @class PBCommitList; @class PBGitSHA; +@class PBHistorySearchController; + @interface PBGitHistoryController : PBViewController { IBOutlet PBRefController *refController; IBOutlet NSSearchField *searchField; @@ -31,6 +33,7 @@ IBOutlet PBCollapsibleSplitView *historySplitView; IBOutlet PBWebHistoryController *webHistoryController; QLPreviewPanel* previewPanel; + IBOutlet PBHistorySearchController *searchController; IBOutlet PBGitGradientBarView *upperToolbarView; IBOutlet NSButton *mergeButton; @@ -56,6 +59,8 @@ @property (retain) PBGitTree* gitTree; @property (readonly) NSArrayController *commitController; @property (readonly) PBRefController *refController; +@property (readonly) PBHistorySearchController *searchController; +@property (readonly) PBCommitList *commitList; - (IBAction) setDetailedView:(id)sender; - (IBAction) setTreeView:(id)sender; @@ -82,6 +87,10 @@ - (IBAction) cherryPick:(id)sender; - (IBAction) rebase:(id)sender; +// Find/Search methods +- (IBAction)selectNext:(id)sender; +- (IBAction)selectPrevious:(id)sender; + - (void) copyCommitInfo; - (void) copyCommitSHA; diff --git a/PBGitHistoryController.m b/PBGitHistoryController.m index 3f08f65..1de0764 100644 --- a/PBGitHistoryController.m +++ b/PBGitHistoryController.m @@ -20,7 +20,9 @@ #import "PBDiffWindowController.h" #import "PBGitDefaults.h" #import "PBGitRevList.h" +#import "PBHistorySearchController.h" #define QLPreviewPanel NSClassFromString(@"QLPreviewPanel") +#import "PBQLTextView.h" #define kHistorySelectedDetailIndexKey @"PBHistorySelectedDetailIndex" @@ -38,6 +40,8 @@ @implementation PBGitHistoryController @synthesize selectedCommitDetailsIndex, webCommit, gitTree, commitController, refController; +@synthesize searchController; +@synthesize commitList; - (void)awakeFromNib { @@ -298,6 +302,30 @@ [super keyDown: event]; } +// NSSearchField (actually textfields in general) prevent the normal Find operations from working. Setup custom actions for the +// next and previous menuitems (in MainMenu.nib) so they will work when the search field is active. When searching for text in +// a file make sure to call the Find panel's action method instead. +- (IBAction)selectNext:(id)sender +{ + NSResponder *firstResponder = [[[self view] window] firstResponder]; + if ([firstResponder isKindOfClass:[PBQLTextView class]]) { + [(PBQLTextView *)firstResponder performFindPanelAction:sender]; + return; + } + + [searchController selectNextResult]; +} +- (IBAction)selectPrevious:(id)sender +{ + NSResponder *firstResponder = [[[self view] window] firstResponder]; + if ([firstResponder isKindOfClass:[PBQLTextView class]]) { + [(PBQLTextView *)firstResponder performFindPanelAction:sender]; + return; + } + + [searchController selectPreviousResult]; +} + - (void) copyCommitInfo { PBGitCommit *commit = [[commitController selectedObjects] objectAtIndex:0]; @@ -436,8 +464,7 @@ NSArray *selectedCommits = [self selectedObjectsForSHA:commitSHA]; [commitController setSelectedObjects:selectedCommits]; - if (repository.currentBranchFilter != kGitXSelectedBranchFilter) - [self scrollSelectionToTopOfViewFrom:oldIndex]; + [self scrollSelectionToTopOfViewFrom:oldIndex]; forceSelectionUpdate = NO; } diff --git a/PBGitHistoryView.xib b/PBGitHistoryView.xib index 37d329e..4e83049 100644 --- a/PBGitHistoryView.xib +++ b/PBGitHistoryView.xib @@ -21,7 +21,7 @@ YES - + YES @@ -702,31 +702,6 @@ 266 YES - - - 268 - {{49, 2}, {57, 17}} - - 1 - YES - - 67239424 - 134348800 - Remote - - LucidaGrande-Bold - 11 - 16 - - - -1232846593 - 173 - - - 400 - 75 - - 265 @@ -737,7 +712,6 @@ 343014976 268567552 - Subject YES 1 @@ -788,6 +762,88 @@ 255 + + + 265 + {{611, 3}, {43, 18}} + + YES + + 67239424 + 131072 + + LucidaGrande + 9 + 16 + + + + YES + + 18 + + Previous search result + 0 + + + 18 + + Next search result + 1 + 0 + + + 1 + 2 + 3 + + + + + 265 + {{527, 5}, {80, 14}} + + YES + + 68288064 + 71435264 + # of matches + + + + 6 + System + controlColor + + + + + + + + 268 + {{49, 2}, {57, 17}} + + 1 + YES + + 67239424 + 134348800 + Remote + + LucidaGrande-Bold + 11 + 16 + + + -1232846593 + 173 + + + 400 + 75 + + 268 @@ -912,12 +968,7 @@ {852, 232} Details - - 6 - System - controlColor - - + @@ -1328,6 +1379,9 @@ GitXRelativeDateFormatter + + PBHistorySearchController + @@ -1709,95 +1763,6 @@ 291 - - - predicate: filterPredicate - - - - - - predicate: filterPredicate - predicate - filterPredicate - - YES - - YES - NSDisplayName - NSPredicateFormat - - - YES - Subject - subject contains[c] $value - - - 2 - - - 301 - - - - predicate2: filterPredicate - - - - - - predicate2: filterPredicate - predicate2 - filterPredicate - - YES - - YES - NSDisplayName - NSPredicateFormat - - - YES - Author - author contains[c] $value - - - - 2 - - - 304 - - - - predicate3: filterPredicate - - - - - - predicate3: filterPredicate - predicate3 - filterPredicate - - YES - - YES - NSDisplayName - NSPredicateFormat - - - YES - SHA - realSha contains[c] $value - - - - 2 - - - 308 - searchField @@ -2050,6 +2015,86 @@ 422 + + + commitController + + + + 424 + + + + historyController + + + + 425 + + + + searchField + + + + 426 + + + + stepper + + + + 429 + + + + numberOfMatchesField + + + + 432 + + + + delegate + + + + 433 + + + + updateSearch: + + + + 434 + + + + stepperPressed: + + + + 435 + + + + searchController + + + + 436 + + + + searchController + + + + 437 + @@ -2387,10 +2432,12 @@ YES - + + + Commits Scope Bar @@ -2614,6 +2661,39 @@ + + 423 + + + + + 427 + + + YES + + + + + + 428 + + + + + 430 + + + YES + + + + + + 431 + + + @@ -2650,6 +2730,7 @@ 28.IBPluginDependency 28.IBShouldRemoveOnLegacySave 287.IBPluginDependency + 288.CustomClassName 288.IBPluginDependency 29.IBPluginDependency 29.IBShouldRemoveOnLegacySave @@ -2680,6 +2761,7 @@ 337.IBAttributePlaceholdersKey 337.IBPluginDependency 338.IBPluginDependency + 34.CustomClassName 34.IBPluginDependency 340.IBAttributePlaceholdersKey 340.IBPluginDependency @@ -2695,6 +2777,7 @@ 356.IBPluginDependency 359.IBAttributePlaceholdersKey 359.IBPluginDependency + 36.CustomClassName 36.IBPluginDependency 36.ImportedFromIB2 360.IBPluginDependency @@ -2707,11 +2790,18 @@ 4.IBAttributePlaceholdersKey 4.IBPluginDependency 409.IBPluginDependency + 410.CustomClassName 410.IBPluginDependency 414.IBDateFormatterBehaviorMetadataKey 414.IBPluginDependency 418.IBPluginDependency + 419.CustomClassName 419.IBPluginDependency + 427.IBPluginDependency + 428.IBPluginDependency + 428.IBSegmentedControlInspectorSelectedSegmentMetadataKey + 430.IBPluginDependency + 431.IBPluginDependency 46.IBEditorWindowLastContentRect 46.IBPluginDependency 48.IBPluginDependency @@ -2762,6 +2852,7 @@ com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin + GitXTextFieldCell com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin @@ -2813,6 +2904,7 @@ com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin + GitXTextFieldCell com.apple.InterfaceBuilder.CocoaPlugin ToolTip @@ -2856,6 +2948,7 @@ com.apple.InterfaceBuilder.CocoaPlugin + GitXTextFieldCell com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin @@ -2881,10 +2974,17 @@ com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin + GitXTextFieldCell com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin + GitXTextFieldCell + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{9, 486}, {852, 432}} com.apple.InterfaceBuilder.CocoaPlugin @@ -2922,7 +3022,7 @@ - 422 + 437 @@ -2935,6 +3035,21 @@ GitXRelativeDateFormatter.h + + GitXTextFieldCell + NSTextFieldCell + + IBProjectSource + GitXTextFieldCell.h + + + + NSApplication + + IBProjectSource + NSApplication+GitXScripting.h + + NSOutlineView @@ -2958,12 +3073,14 @@ YES controller + searchController webController webView YES PBGitHistoryController + PBHistorySearchController PBWebHistoryController WebView @@ -2973,6 +3090,7 @@ YES controller + searchController webController webView @@ -2982,6 +3100,10 @@ controller PBGitHistoryController + + searchController + PBHistorySearchController + webController PBWebHistoryController @@ -3028,6 +3150,8 @@ openSelectedFile: rebase: refresh: + selectNext: + selectPrevious: setBranchFilter: setDetailedView: setTreeView: @@ -3053,6 +3177,8 @@ id id id + id + id @@ -3067,6 +3193,8 @@ openSelectedFile: rebase: refresh: + selectNext: + selectPrevious: setBranchFilter: setDetailedView: setTreeView: @@ -3109,6 +3237,14 @@ refresh: id + + selectNext: + id + + + selectPrevious: + id + setBranchFilter: id @@ -3154,6 +3290,7 @@ rebaseButton refController scopeBarView + searchController searchField selectedBranchFilterItem treeController @@ -3174,6 +3311,7 @@ NSButton PBRefController PBGitGradientBarView + PBHistorySearchController NSSearchField NSButton NSTreeController @@ -3197,6 +3335,7 @@ rebaseButton refController scopeBarView + searchController searchField selectedBranchFilterItem treeController @@ -3250,6 +3389,10 @@ scopeBarView PBGitGradientBarView + + searchController + PBHistorySearchController + searchField NSSearchField @@ -3329,6 +3472,99 @@ PBGitRevisionCell.h + + PBHistorySearchController + NSObject + + YES + + YES + stepperPressed: + updateSearch: + + + YES + id + id + + + + YES + + YES + stepperPressed: + updateSearch: + + + YES + + stepperPressed: + id + + + updateSearch: + id + + + + + YES + + YES + commitController + historyController + numberOfMatchesField + searchField + stepper + + + YES + NSArrayController + PBGitHistoryController + NSTextField + NSSearchField + NSSegmentedControl + + + + YES + + YES + commitController + historyController + numberOfMatchesField + searchField + stepper + + + YES + + commitController + NSArrayController + + + historyController + PBGitHistoryController + + + numberOfMatchesField + NSTextField + + + searchField + NSSearchField + + + stepper + NSSegmentedControl + + + + + IBProjectSource + PBHistorySearchController.h + + PBNiceSplitView NSSplitView @@ -4102,6 +4338,34 @@ Foundation.framework/Headers/NSURLDownload.h + + NSObject + + IBFrameworkSource + QuartzCore.framework/Headers/CAAnimation.h + + + + NSObject + + IBFrameworkSource + QuartzCore.framework/Headers/CALayer.h + + + + NSObject + + IBFrameworkSource + QuartzCore.framework/Headers/CIImageProvider.h + + + + NSObject + + IBFrameworkSource + ScriptingBridge.framework/Headers/SBApplication.h + + NSObject diff --git a/PBGitRevisionCell.m b/PBGitRevisionCell.m index 0e77c22..65889a6 100644 --- a/PBGitRevisionCell.m +++ b/PBGitRevisionCell.m @@ -9,6 +9,7 @@ #import "PBGitRevisionCell.h" #import "PBGitRef.h" #import "RoundedRectangle.h" +#import "GitXTextFieldCell.h" @implementation PBGitRevisionCell @@ -16,7 +17,7 @@ - (id) initWithCoder: (id) coder { self = [super initWithCoder:coder]; - textCell = [[NSTextFieldCell alloc] initWithCoder:coder]; + textCell = [[GitXTextFieldCell alloc] initWithCoder:coder]; return self; } diff --git a/PBHistorySearchController.h b/PBHistorySearchController.h new file mode 100644 index 0000000..75bd250 --- /dev/null +++ b/PBHistorySearchController.h @@ -0,0 +1,46 @@ +// +// PBHistorySearchController.h +// GitX +// +// Created by Nathan Kinsinger on 8/21/10. +// Copyright 2010 Nathan Kinsinger. All rights reserved. +// + +#import + + +@class PBGitHistoryController; + + +@interface PBHistorySearchController : NSObject { + PBGitHistoryController *historyController; + NSArrayController *commitController; + + NSIndexSet *results; + + NSSearchField *searchField; + NSSegmentedControl *stepper; + NSTextField *numberOfMatchesField; + + NSPanel *rewindPanel; +} + +@property (assign) IBOutlet PBGitHistoryController *historyController; +@property (assign) IBOutlet NSArrayController *commitController; + +@property (assign) IBOutlet NSSearchField *searchField; +@property (assign) IBOutlet NSSegmentedControl *stepper; +@property (assign) IBOutlet NSTextField *numberOfMatchesField; + + +- (BOOL)isRowInSearchResults:(NSInteger)rowIndex; +- (BOOL)hasSearchResults; + +- (void)selectNextResult; +- (void)selectPreviousResult; +- (IBAction)stepperPressed:(id)sender; + +- (void)clearSearch; +- (IBAction)updateSearch:(id)sender; + +@end diff --git a/PBHistorySearchController.m b/PBHistorySearchController.m new file mode 100644 index 0000000..99ea7a8 --- /dev/null +++ b/PBHistorySearchController.m @@ -0,0 +1,313 @@ +// +// PBHistorySearchController.m +// GitX +// +// Created by Nathan Kinsinger on 8/21/10. +// Copyright 2010 Nathan Kinsinger. All rights reserved. +// + +#import "PBHistorySearchController.h" +#import "PBGitHistoryController.h" +#import "PBGitRepository.h" +#import + + +@interface PBHistorySearchController () + +- (void)selectNextResultInDirection:(NSInteger)direction; + +- (void)updateUI; + +- (void)startBasicSearch; + +- (void)showSearchRewindPanelReverse:(BOOL)isReversed; + +@end + + +#define kGitXSearchDirectionNext 1 +#define kGitXSearchDirectionPrevious -1 + +#define kGitXBasicSearchLabel @"Subject, Author, SHA" + +#define kGitXSearchArrangedObjectsContext @"GitXSearchArrangedObjectsContext" + + +@implementation PBHistorySearchController + +@synthesize historyController; +@synthesize commitController; + +@synthesize searchField; +@synthesize stepper; +@synthesize numberOfMatchesField; + + + +#pragma mark - +#pragma mark Public methods + +- (BOOL)isRowInSearchResults:(NSInteger)rowIndex +{ + return [results containsIndex:rowIndex]; +} + +- (BOOL)hasSearchResults +{ + return ([results count] > 0); +} + +- (void)selectNextResult +{ + [self selectNextResultInDirection:kGitXSearchDirectionNext]; +} + +- (void)selectPreviousResult +{ + [self selectNextResultInDirection:kGitXSearchDirectionPrevious]; +} + +- (IBAction)stepperPressed:(id)sender +{ + NSInteger selectedSegment = [sender selectedSegment]; + + if (selectedSegment == 0) + [self selectPreviousResult]; + else + [self selectNextResult]; +} + +- (void)clearSearch +{ + [searchField setStringValue:@""]; + if (results) { + results = nil; + [historyController.commitList reloadData]; + } + [self updateUI]; +} + +- (IBAction)updateSearch:(id)sender +{ + [self startBasicSearch]; +} + +- (void)awakeFromNib +{ + [[searchField cell] setPlaceholderString:@"Subject, Author, SHA"]; + + [self updateUI]; + + [commitController addObserver:self forKeyPath:@"arrangedObjects" options:0 context:kGitXSearchArrangedObjectsContext]; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context +{ + if ([(NSString *)context isEqualToString:kGitXSearchArrangedObjectsContext]) { + // the objects in the commitlist changed so the result indexes are no longer valid + [self clearSearch]; + return; + } + + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; +} + + + +#pragma mark - +#pragma mark Private methods + +- (void)selectIndex:(NSUInteger)index +{ + if ([[commitController arrangedObjects] count] > index) { + PBGitCommit *commit = [[commitController arrangedObjects] objectAtIndex:index]; + [historyController selectCommit:[commit sha]]; + } +} + +- (void)selectNextResultInDirection:(NSInteger)direction +{ + if (![results count]) + return; + + NSUInteger selectedRow = [historyController.commitList selectedRow]; + if (selectedRow == NSNotFound) { + [self selectIndex:[results firstIndex]]; + return; + } + + NSUInteger currentResult = NSNotFound; + if (direction == kGitXSearchDirectionNext) + currentResult = [results indexGreaterThanIndex:selectedRow]; + else + currentResult = [results indexLessThanIndex:selectedRow]; + + if (currentResult == NSNotFound) { + if (direction == kGitXSearchDirectionNext) + currentResult = [results firstIndex]; + else + currentResult = [results lastIndex]; + + [self showSearchRewindPanelReverse:(direction != kGitXSearchDirectionNext)]; + } + + [self selectIndex:currentResult]; +} + +- (NSString *)numberOfMatchesString +{ + NSUInteger numberOfMatches = [results count]; + + if (numberOfMatches == 0) + return @"Not found"; + + if (numberOfMatches == 1) + return @"1 match"; + + return [NSString stringWithFormat:@"%d matches", numberOfMatches]; +} + +- (void)updateUI +{ + if ([[searchField stringValue] isEqualToString:@""]) { + [numberOfMatchesField setHidden:YES]; + [stepper setHidden:YES]; + } + else { + [numberOfMatchesField setStringValue:[self numberOfMatchesString]]; + [numberOfMatchesField setHidden:NO]; + [stepper setHidden:NO]; + [historyController.commitList reloadData]; + } +} + +// changes the selection to the next match after the current selected row unless the current row is already a match +- (void)updateSelectedResult +{ + NSString *searchString = [searchField stringValue]; + if ([searchString isEqualToString:@""]) { + [self clearSearch]; + return; + } + + if (![self isRowInSearchResults:[historyController.commitList selectedRow]]) + [self selectNextResult]; + + [self updateUI]; +} + + + +#pragma mark Basic Search + +- (void)startBasicSearch +{ + NSString *searchString = [searchField stringValue]; + if ([searchString isEqualToString:@""]) { + [self clearSearch]; + return; + } + + NSMutableIndexSet *indexes = [NSMutableIndexSet indexSet]; + NSPredicate *searchPredicate = [NSPredicate predicateWithFormat:@"subject CONTAINS[cd] %@ OR author CONTAINS[cd] %@ OR realSha BEGINSWITH[c] %@", searchString, searchString, searchString]; + + NSUInteger index = 0; + for (PBGitCommit *commit in [commitController arrangedObjects]) { + if ([searchPredicate evaluateWithObject:commit]) + [indexes addIndex:index]; + index++; + } + + results = indexes; + + [self updateSelectedResult]; +} + + + +#pragma mark - +#pragma mark Rewind Panel + +#define kRewindPanelSize 125.0f + +- (void)closeRewindPanel +{ + [[[historyController view] window] removeChildWindow:rewindPanel]; + [rewindPanel close]; + rewindPanel = nil; +} + +- (NSPanel *)rewindPanelReverse:(BOOL)isReversed +{ + NSRect windowFrame = [[[historyController view] window] frame]; + NSRect historyFrame = [[historyController view] convertRectToBase:[[historyController view] frame]]; + NSRect panelRect = NSMakeRect(0.0f, 0.0f, kRewindPanelSize, kRewindPanelSize); + panelRect.origin.x = windowFrame.origin.x + historyFrame.origin.x + ((historyFrame.size.width - kRewindPanelSize) / 2.0f); + panelRect.origin.y = windowFrame.origin.y + historyFrame.origin.y + ((historyFrame.size.height - kRewindPanelSize) / 2.0f); + + NSPanel *panel = [[NSPanel alloc] initWithContentRect:panelRect + styleMask:NSBorderlessWindowMask + backing:NSBackingStoreBuffered + defer:YES]; + [panel setIgnoresMouseEvents:YES]; + [panel setOneShot:YES]; + [panel setOpaque:NO]; + [panel setBackgroundColor:[NSColor clearColor]]; + [panel setHasShadow:NO]; + [panel useOptimizedDrawing:YES]; + [panel setAlphaValue:0.0f]; + + NSBox *box = [[NSBox alloc] initWithFrame:[[panel contentView] frame]]; + [box setBoxType:NSBoxCustom]; + [box setBorderType:NSLineBorder]; + [box setFillColor:[NSColor colorWithCalibratedWhite:0.0f alpha:0.5f]]; + [box setBorderColor:[NSColor colorWithCalibratedWhite:0.5f alpha:0.5f]]; + [box setCornerRadius:12.0f]; + [[panel contentView] addSubview:box]; + + NSImage *rewindImage = [[NSImage imageNamed:@"rewindImage"] copy]; + [rewindImage setFlipped:isReversed]; + NSSize imageSize = [rewindImage size]; + NSRect imageViewFrame = NSMakeRect(21.0f, 5.0f, imageSize.width, imageSize.height); + NSImageView *rewindImageView = [[NSImageView alloc] initWithFrame:imageViewFrame]; + [rewindImageView setImage:rewindImage]; + [[box contentView] addSubview:rewindImageView]; + + return panel; +} + +- (CAKeyframeAnimation *)rewindPanelFadeOutAnimation +{ + CAKeyframeAnimation *animation = [CAKeyframeAnimation animation]; + animation.duration = 1.0f; + animation.values = [NSArray arrayWithObjects: + [NSNumber numberWithFloat:1.0f], + [NSNumber numberWithFloat:1.0f], + [NSNumber numberWithFloat:0.0f], + [NSNumber numberWithFloat:0.0f], nil]; + animation.keyTimes = [NSArray arrayWithObjects: + [NSNumber numberWithFloat:0.1f], + [NSNumber numberWithFloat:0.3f], + [NSNumber numberWithFloat:0.7f], + [NSNumber numberWithFloat:animation.duration], nil]; + + return animation; +} + +- (void)showSearchRewindPanelReverse:(BOOL)isReversed +{ + if (rewindPanel) + [self closeRewindPanel]; + + rewindPanel = [self rewindPanelReverse:isReversed]; + + [[[historyController view] window] addChildWindow:rewindPanel ordered:NSWindowAbove]; + + CAKeyframeAnimation *alphaAnimation = [self rewindPanelFadeOutAnimation]; + [rewindPanel setAnimations:[NSDictionary dictionaryWithObject:alphaAnimation forKey:@"alphaValue"]]; + [[rewindPanel animator] setAlphaValue:0.0f]; + + [self performSelector:@selector(closeRewindPanel) withObject:nil afterDelay:0.7f]; +} + +@end