Skip to content

Commit

Permalink
Change the way result highlighting is done in the "go to database" di…
Browse files Browse the repository at this point in the history
…alog

(Looks a lot less crappy now)
  • Loading branch information
dmoagx committed Sep 26, 2015
1 parent 7cd8cce commit 4aa8ff7
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 58 deletions.
15 changes: 9 additions & 6 deletions Interfaces/English.lproj/GotoDatabaseDialog.xib
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="8.00">
<data>
<int key="IBDocument.SystemTarget">1060</int>
<string key="IBDocument.SystemVersion">13F34</string>
<string key="IBDocument.SystemVersion">13F1096</string>
<string key="IBDocument.InterfaceBuilderVersion">6751</string>
<string key="IBDocument.AppKitVersion">1265.21</string>
<string key="IBDocument.HIToolboxVersion">698.00</string>
Expand Down Expand Up @@ -477,6 +477,14 @@
</object>
<string key="id">O6w-uT-xK7</string>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">delegate</string>
<reference key="source" ref="1032614363"/>
<reference key="destination" ref="1042702399"/>
</object>
<string key="id">6Ur-iZ-LRv</string>
</object>
</array>
<object class="IBMutableOrderedSet" key="objectRecords">
<array key="orderedObjects">
Expand Down Expand Up @@ -720,7 +728,6 @@
<string key="okClicked:">id</string>
<string key="searchChanged:">id</string>
<string key="toggleWordSearch:">id</string>
<string key="toogleWordSearch:">id</string>
</dictionary>
<dictionary class="NSMutableDictionary" key="actionInfosByName">
<object class="IBActionInfo" key="cancelClicked:">
Expand All @@ -739,10 +746,6 @@
<string key="name">toggleWordSearch:</string>
<string key="candidateClassName">id</string>
</object>
<object class="IBActionInfo" key="toogleWordSearch:">
<string key="name">toogleWordSearch:</string>
<string key="candidateClassName">id</string>
</object>
</dictionary>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string>
Expand Down
4 changes: 3 additions & 1 deletion Source/SPGotoDatabaseController.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
* keyboard-based navigation between databases. The dialog also enables
* jumping to a database by C&P-ing its full name.
*/
@interface SPGotoDatabaseController : NSWindowController <NSTableViewDataSource,NSControlTextEditingDelegate,NSUserInterfaceValidations>
@interface SPGotoDatabaseController : NSWindowController <NSTableViewDataSource,NSTableViewDelegate,NSControlTextEditingDelegate,NSUserInterfaceValidations>
{
IBOutlet NSSearchField *searchField;
IBOutlet NSButton *okButton;
Expand All @@ -48,6 +48,8 @@

BOOL isFiltered;
BOOL allowCustomNames;

NSDictionary *highlightAttrs;
}

/**
Expand Down
151 changes: 100 additions & 51 deletions Source/SPGotoDatabaseController.m
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ @interface SPGotoDatabaseController (Private)
* It will neither clear the filteredList first, nor change the isFiltered ivar!
* Search is case insensitive.
*/
- (void)_buildHightlightedFilterList:(NSString *)filter didFindExactMatch:(BOOL *)exactMatch;
- (void)_buildFilterList:(NSString *)filter didFindExactMatch:(BOOL *)exactMatch;

- (IBAction)okClicked:(id)sender;
- (IBAction)cancelClicked:(id)sender;
Expand All @@ -53,7 +53,29 @@ - (BOOL)qualifiesForWordSearch; //takes s from searchField
@end

static BOOL StringQualifiesForWordSearch(NSString *s);
static NSUInteger CountSubMatches(NSAttributedString *s);

#pragma mark -

@interface SPGotoFilteredItem : NSObject {
NSString *string;
NSArray *matches;
BOOL isCustomItem;
}
@property(nonatomic,retain) NSString *string;
@property(nonatomic,retain) NSArray *matches;
@property(nonatomic,assign) BOOL isCustomItem;

+ (SPGotoFilteredItem *)item;
@end

@implementation SPGotoFilteredItem

@synthesize string;
@synthesize matches;
@synthesize isCustomItem;

+ (SPGotoFilteredItem *)item { return [[[SPGotoFilteredItem alloc] init] autorelease]; }
@end

#pragma mark -

Expand All @@ -64,9 +86,14 @@ @implementation SPGotoDatabaseController
- (id)init
{
if ((self = [super initWithWindowNibName:@"GotoDatabaseDialog"])) {
unfilteredList = [[NSMutableArray alloc] init];
unfilteredList = [[NSMutableArray alloc] init];
filteredList = [[NSMutableArray alloc] init];
isFiltered = NO;
highlightAttrs = [@{
NSBackgroundColorAttributeName: [NSColor colorWithCalibratedRed:249/255.0 green:247/255.0 blue:62/255.0 alpha:0.5],
NSUnderlineColorAttributeName: [NSColor colorWithCalibratedRed:246/255.0 green:189/255.0 blue:85/255.0 alpha:1.0],
NSUnderlineStyleAttributeName: [NSNumber numberWithInt:NSUnderlineStyleThick]
} retain];

[self setAllowCustomNames:YES];
}
Expand Down Expand Up @@ -111,7 +138,7 @@ - (IBAction)searchChanged:(id)sender

BOOL exactMatch = NO;

[self _buildHightlightedFilterList:newFilter didFindExactMatch:&exactMatch];
[self _buildFilterList:newFilter didFindExactMatch:&exactMatch];

//always add the search string to the end of the list (in case the user
//wants to switch to a DB not in the list) unless there was an exact match
Expand All @@ -121,19 +148,19 @@ - (IBAction)searchChanged:(id)sender
newFilter = [newFilter substringWithRange:NSMakeRange(1, [newFilter length]-2)];

if([newFilter length]) {
NSMutableAttributedString *searchValue = [[NSMutableAttributedString alloc] initWithString:newFilter];

[searchValue applyFontTraits:NSItalicFontMask range:NSMakeRange(0, [newFilter length])];

[filteredList addObject:[searchValue autorelease]];
SPGotoFilteredItem *customItem = [SPGotoFilteredItem item];
[customItem setString:newFilter];
[customItem setIsCustomItem:YES];
[filteredList addObject:customItem];
}
}
}

[databaseListView reloadData];

// Ensure we have a selection
if ([databaseListView selectedRow] < 0) {
if ([databaseListView selectedRow] < 0 && [self numberOfRowsInTableView:databaseListView]) {
[databaseListView selectRowIndexes:[NSIndexSet indexSetWithIndex:0] byExtendingSelection:NO];
}

Expand Down Expand Up @@ -170,16 +197,12 @@ - (NSString *)selectedDatabase
id attrValue;

if (isFiltered) {
attrValue = [filteredList objectOrNilAtIndex:row];
attrValue = [(SPGotoFilteredItem *)[filteredList objectOrNilAtIndex:row] string];
}
else {
attrValue = [unfilteredList objectOrNilAtIndex:row];
}

if ([attrValue isKindOfClass:[NSAttributedString class]]) {
return [attrValue string];
}

return attrValue;
}

Expand Down Expand Up @@ -209,14 +232,8 @@ - (BOOL)runModal
#pragma mark -
#pragma mark Private

- (void)_buildHightlightedFilterList:(NSString *)filter didFindExactMatch:(BOOL *)exactMatch
- (void)_buildFilterList:(NSString *)filter didFindExactMatch:(BOOL *)exactMatch
{
NSDictionary *attrs = [[NSDictionary alloc] initWithObjectsAndKeys:
[NSColor colorWithCalibratedRed:249/255.0 green:247/255.0 blue:62/255.0 alpha:0.5],NSBackgroundColorAttributeName,
[NSColor colorWithCalibratedRed:180/255.0 green:164/255.0 blue:31/255.0 alpha:1.0],NSUnderlineColorAttributeName,
[NSNumber numberWithInt:NSUnderlineStyleSingle],NSUnderlineStyleAttributeName,
nil];

NSStringCompareOptions opts = NSCaseInsensitiveSearch|NSDiacriticInsensitiveSearch|NSWidthInsensitiveSearch;

BOOL useWordSearch = StringQualifiesForWordSearch(filter);
Expand All @@ -239,9 +256,11 @@ - (void)_buildHightlightedFilterList:(NSString *)filter didFindExactMatch:(BOOL
}
}

NSMutableAttributedString *attrMatch = [[NSMutableAttributedString alloc] initWithString:db];
[attrMatch setAttributes:attrs range:matchRange];
[filteredList addObject:[attrMatch autorelease]];
SPGotoFilteredItem *item = [SPGotoFilteredItem item];
[item setString:db];
[item setMatches:@[[NSValue valueWithRange:matchRange]]];

[filteredList addObject:item];
}
}
// default to a per-character search
Expand All @@ -263,38 +282,34 @@ - (void)_buildHightlightedFilterList:(NSString *)filter didFindExactMatch:(BOOL
}
}

NSMutableAttributedString *attrMatch = [[NSMutableAttributedString alloc] initWithString:db];
SPGotoFilteredItem *item = [SPGotoFilteredItem item];
[item setString:db];
[item setMatches:matches];

for (NSValue *matchValue in matches) {
[attrMatch setAttributes:attrs range:[matchValue rangeValue]];
}

[filteredList addObject:[attrMatch autorelease]];
[filteredList addObject:item];
}
}

//sort the filtered list
[filteredList sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
// word search produces only 1 match, skip.
if(!useWordSearch) {
// All the items are NSAttributedStrings.
// First we want to sort by number of match groups.
// Less match groups -> better result:
// Search string: abc
// Matches: schema_abc, tablecloth
// => First only has 1 match group, is more likely to be the desired result
NSUInteger mgc1 = CountSubMatches(obj1);
NSUInteger mgc2 = CountSubMatches(obj2);
NSUInteger mgc1 = [[(SPGotoFilteredItem *)obj1 matches] count];
NSUInteger mgc2 = [[(SPGotoFilteredItem *)obj2 matches] count];
if(mgc1 < mgc2)
return NSOrderedAscending;
if(mgc2 > mgc1)
return NSOrderedDescending;
}
// For strings with the same number of match groups we just sort alphabetically
return [[(NSAttributedString *)obj1 string] compare:[(NSAttributedString *)obj2 string]];
return [[(SPGotoFilteredItem *)obj1 string] compare:[(SPGotoFilteredItem *)obj2 string]];
}];

[attrs release];

}

- (BOOL)qualifiesForWordSearch
Expand All @@ -321,10 +336,56 @@ - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColu
return [unfilteredList objectAtIndex:rowIndex];
}
else {
return [filteredList objectAtIndex:rowIndex];
return [(SPGotoFilteredItem *)[filteredList objectAtIndex:rowIndex] string];
}
}

#pragma mark -
#pragma mark NSTableViewDelegate

- (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
//nothing to do here, unless the list is filtered
if(!isFiltered) return;

// The styling of source list table views is basically done by Apple by replacing
// the cell's string with an attributedstring. But if the data source were to
// already return an attributedstring, most of the other attributes Apple sets
// would not get applied. So we have to add our attributes after Apple has already
// modified the string returned by the data source.

id cellValue = [cell objectValue];
//turn the cell value into something we can work with
NSMutableAttributedString *attrString;
if([cellValue isKindOfClass:[NSMutableAttributedString class]]) {
attrString = cellValue;
}
else if([cellValue isKindOfClass:[NSAttributedString class]]) {
attrString = [[[NSMutableAttributedString alloc] initWithAttributedString:cellValue] autorelease];
}
else if([cellValue isKindOfClass:[NSString class]]) {
attrString = [[[NSMutableAttributedString alloc] initWithString:cellValue] autorelease];
}
else {
SPLog(@"Unknown object for cellValue (type=%@)",[cellValue className]);
return;
}

SPGotoFilteredItem *item = [filteredList objectAtIndex:row];

if([item isCustomItem]) {
[[attrString mutableString] appendString:@""];
[attrString addAttribute:NSUnderlineStyleAttributeName value:@(NSUnderlineStyleSingle) range:NSMakeRange([attrString length]-1, 1)];
}
else {
for (NSValue *matchValue in [item matches]) {
[attrString addAttributes:highlightAttrs range:[matchValue rangeValue]];
}
}

[cell setObjectValue:attrString];
}

#pragma mark -
#pragma mark NSControlTextEditingDelegate

Expand Down Expand Up @@ -373,6 +434,7 @@ - (void)dealloc
{
SPClear(unfilteredList);
SPClear(filteredList);
SPClear(highlightAttrs);

[super dealloc];
}
Expand All @@ -385,16 +447,3 @@ BOOL StringQualifiesForWordSearch(NSString *s)
{
return (s && ([s length] > 1) && (([s hasPrefix:@"\""] && [s hasSuffix:@"\""]) || ([s hasPrefix:@"'"] && [s hasSuffix:@"'"])));
}

NSUInteger CountSubMatches(NSAttributedString *s)
{
__block NSUInteger matches = 0;
[s enumerateAttribute:NSBackgroundColorAttributeName
inRange:NSMakeRange(0, [s length])
options:0
usingBlock:^(id value, NSRange range, BOOL *stop) {
if(value)
matches++;
}];
return matches;
}

0 comments on commit 4aa8ff7

Please sign in to comment.