//
// $Id: SPNarrowDownCompletion.m 744 2009-05-22 20:00:00Z bibiko $
//
// SPGrowlController.m
// sequel-pro
//
// Created by Hans-J. Bibiko on May 14, 2009.
//
// This class is based on TextMate's TMDIncrementalPopUp implementation
// (Dialog plugin) written by Joachim Mårtensson, Allan Odgaard, and H.-J. Bibiko.
// see license: http://svn.textmate.org/trunk/LICENSE
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// More info at
#import
#import "SPNarrowDownCompletion.h"
#import "SPArrayAdditions.h"
#import "ImageAndTextCell.h"
#import "SPConstants.h"
#include
@interface NSTableView (MovingSelectedRow)
- (BOOL)SP_NarrowDownCompletion_canHandleEvent:(NSEvent*)anEvent;
@end
@interface SPNarrowDownCompletion (Private)
- (NSRect)rectOfMainScreen;
- (NSString*)filterString;
- (void)setupInterface;
- (void)filter;
- (void)insertCommonPrefix;
- (void)completeAndInsertSnippet;
@end
@implementation NSTableView (MovingSelectedRow)
- (BOOL)SP_NarrowDownCompletion_canHandleEvent:(NSEvent*)anEvent
{
NSInteger visibleRows = (NSInteger)floor(NSHeight([self visibleRect]) / ([self rowHeight]+[self intercellSpacing].height)) - 1;
struct { unichar key; NSInteger rows; } const key_movements[] =
{
{ NSUpArrowFunctionKey, -1 },
{ NSDownArrowFunctionKey, +1 },
{ NSPageUpFunctionKey, -visibleRows },
{ NSPageDownFunctionKey, +visibleRows },
{ NSHomeFunctionKey, -(INT_MAX >> 1) },
{ NSEndFunctionKey, +(INT_MAX >> 1) },
};
unichar keyCode = 0;
if([anEvent type] == NSScrollWheel)
keyCode = [anEvent deltaY] >= 0.0 ? NSUpArrowFunctionKey : NSDownArrowFunctionKey;
else if([anEvent type] == NSKeyDown && [[anEvent characters] length] == 1)
keyCode = [[anEvent characters] characterAtIndex:0];
for(size_t i = 0; i < sizeofA(key_movements); ++i)
{
if(keyCode == key_movements[i].key)
{
NSInteger row = MAX(0, MIN([self selectedRow] + key_movements[i].rows, [self numberOfRows]-1));
[self selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
[self scrollRowToVisible:row];
return YES;
}
}
return NO;
}
@end
@implementation SPNarrowDownCompletion
// =============================
// = Setup/tear-down functions =
// =============================
- (id)init
{
if(self = [super initWithContentRect:NSMakeRect(0,0,450,0) styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO])
{
mutablePrefix = [NSMutableString new];
textualInputCharacters = [[NSMutableCharacterSet alphanumericCharacterSet] retain];
caseSensitive = YES;
filtered = nil;
tableFont = [NSUnarchiver unarchiveObjectWithData:[[NSUserDefaults standardUserDefaults] dataForKey:SPCustomQueryEditorFont]];
[self setupInterface];
}
return self;
}
- (void)dealloc
{
[staticPrefix release];
[mutablePrefix release];
[textualInputCharacters release];
if(suggestions) [suggestions release];
if (filtered) [filtered release];
[super dealloc];
}
- (id)initWithItems:(NSArray*)someSuggestions alreadyTyped:(NSString*)aUserString staticPrefix:(NSString*)aStaticPrefix additionalWordCharacters:(NSString*)someAdditionalWordCharacters caseSensitive:(BOOL)isCaseSensitive charRange:(NSRange)initRange inView:(id)aView dictMode:(BOOL)mode dbMode:(BOOL)dbMode
{
if(self = [self init])
{
BOOL filterStringIsBacktick = ([aUserString isEqualToString:@"`"]) ? YES : NO;
// Set filter string - if aUserString == ` user invoked it via `|` ie show all db/tables/fields etc.
if(aUserString && !filterStringIsBacktick)
[mutablePrefix appendString:aUserString];
dbStructureMode = dbMode;
if(aStaticPrefix)
staticPrefix = [aStaticPrefix retain];
caseSensitive = isCaseSensitive;
theCharRange = initRange;
if(filterStringIsBacktick) {
theCharRange.length = 0;
theCharRange.location++;
}
theView = aView;
dictMode = mode;
if(!dictMode) {
suggestions = [someSuggestions retain];
words = nil;
}
if(someAdditionalWordCharacters)
[textualInputCharacters addCharactersInString:someAdditionalWordCharacters];
}
return self;
}
- (void)setCaretPos:(NSPoint)aPos
{
caretPos = aPos;
isAbove = NO;
NSRect mainScreen = [self rectOfMainScreen];
NSInteger offx = (caretPos.x/mainScreen.size.width) + 1;
if((caretPos.x + [self frame].size.width) > (mainScreen.size.width*offx))
caretPos.x = caretPos.x - [self frame].size.width;
if(caretPos.y>=0 && caretPos.y<[self frame].size.height)
{
caretPos.y = caretPos.y + ([self frame].size.height + [tableFont pointSize]*1.5);
isAbove = YES;
}
if(caretPos.y<0 && (mainScreen.size.height-[self frame].size.height)<(caretPos.y*-1))
{
caretPos.y = caretPos.y + ([self frame].size.height + [tableFont pointSize]*1.5);
isAbove = YES;
}
[self setFrameTopLeftPoint:caretPos];
}
- (void)setupInterface
{
[self setReleasedWhenClosed:YES];
[self setLevel:NSStatusWindowLevel];
[self setHidesOnDeactivate:YES];
[self setHasShadow:YES];
[self setAlphaValue:0.9];
NSScrollView* scrollView = [[[NSScrollView alloc] initWithFrame:NSZeroRect] autorelease];
// [scrollView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
[scrollView setAutohidesScrollers:YES];
[scrollView setHasVerticalScroller:YES];
[scrollView setHasHorizontalScroller:NO];
[[scrollView verticalScroller] setControlSize:NSSmallControlSize];
[[scrollView horizontalScroller] setControlSize:NSSmallControlSize];
theTableView = [[[NSTableView alloc] initWithFrame:NSZeroRect] autorelease];
[theTableView setFocusRingType:NSFocusRingTypeNone];
[theTableView setAllowsEmptySelection:NO];
[theTableView setHeaderView:nil];
// [theTableView setSelectionHighlightStyle:NSTableViewSelectionHighlightStyleSourceList];
NSTableColumn *column0 = [[[NSTableColumn alloc] initWithIdentifier:@"image"] autorelease];
[column0 setDataCell:[[ImageAndTextCell new] autorelease]];
[column0 setEditable:NO];
[theTableView addTableColumn:column0];
[column0 setWidth:20];
NSTableColumn *column1 = [[[NSTableColumn alloc] initWithIdentifier:@"name"] autorelease];
[column1 setEditable:NO];
// [[column1 dataCell] setFont:[NSFont systemFontOfSize:12]];
[theTableView addTableColumn:column1];
[column1 setWidth:180];
NSTableColumn *column2 = [[[NSTableColumn alloc] initWithIdentifier:@"type"] autorelease];
[column2 setEditable:NO];
// [[column2 dataCell] setFont:[NSFont systemFontOfSize:11]];
[[column2 dataCell] setTextColor:[NSColor grayColor]];
[theTableView addTableColumn:column2];
[column2 setWidth:120];
NSTableColumn *column3 = [[[NSTableColumn alloc] initWithIdentifier:@"path"] autorelease];
[column3 setEditable:NO];
// [[column3 dataCell] setFont:[NSFont systemFontOfSize:11]];
[[column3 dataCell] setTextColor:[NSColor grayColor]];
[theTableView addTableColumn:column3];
[column3 setWidth:130];
[theTableView setDataSource:self];
[scrollView setDocumentView:theTableView];
[self setContentView:scrollView];
}
// ========================
// = TableView DataSource =
// ========================
- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
{
return [filtered count];
}
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
NSImage* image = nil;
NSString* imageName = nil;
if([[aTableColumn identifier] isEqualToString:@"image"]) {
if(!dictMode) {
imageName = [[filtered objectAtIndex:rowIndex] objectForKey:@"image"];
if(imageName)
image = [NSImage imageNamed:imageName];
[[aTableColumn dataCell] setImage:image];
return @"";
}
} else if([[aTableColumn identifier] isEqualToString:@"name"]) {
return [[filtered objectAtIndex:rowIndex] objectForKey:@"display"];
} else if([[aTableColumn identifier] isEqualToString:@"type"]) {
return ([[filtered objectAtIndex:rowIndex] objectForKey:@"type"])?[[filtered objectAtIndex:rowIndex] objectForKey:@"type"]:@"";
} else if ([[aTableColumn identifier] isEqualToString:@"path"]) {
return ([[filtered objectAtIndex:rowIndex] objectForKey:@"path"])?[[filtered objectAtIndex:rowIndex] objectForKey:@"path"]:@"";
}
return [filtered objectAtIndex:rowIndex];
}
// ====================
// = Filter the items =
// ====================
- (void)filter
{
// NSRect mainScreen = [self rectOfMainScreen];
NSArray* newFiltered;
if([mutablePrefix length] > 0)
{
if(dictMode) {
newFiltered = [[NSSpellChecker sharedSpellChecker] completionsForPartialWordRange:NSMakeRange(0,[[self filterString] length]) inString:[self filterString] language:nil inSpellDocumentWithTag:0];
} else {
NSPredicate* predicate;
if(caseSensitive)
predicate = [NSPredicate predicateWithFormat:@"match BEGINSWITH %@ OR (match == NULL AND display BEGINSWITH %@)", [self filterString], [self filterString]];
else
predicate = [NSPredicate predicateWithFormat:@"match BEGINSWITH[c] %@ OR (match == NULL AND display BEGINSWITH[c] %@)", [self filterString], [self filterString]];
newFiltered = [suggestions filteredArrayUsingPredicate:predicate];
}
}
else
{
if(dictMode)
newFiltered = nil;
else
newFiltered = suggestions;
}
NSPoint old = NSMakePoint([self frame].origin.x, [self frame].origin.y + [self frame].size.height);
NSInteger displayedRows = [newFiltered count] < SP_NARROWDOWNLIST_MAX_ROWS ? [newFiltered count] : SP_NARROWDOWNLIST_MAX_ROWS;
CGFloat newHeight = ([theTableView rowHeight] + [theTableView intercellSpacing].height) * displayedRows;
// CGFloat maxLen = 1;
// NSString* item;
// NSInteger i;
// BOOL spaceInSuggestion = NO;
// [textualInputCharacters removeCharactersInString:@" "];
// CGFloat maxWidth = [self frame].size.width;
// if([newFiltered count]>0)
// {
// for(i=0; i<[newFiltered count]; i++)
// {
// if(dictMode)
// item = NSArrayObjectAtIndex(newFiltered, i);
// else
// item = [NSArrayObjectAtIndex(newFiltered, i) objectForKey:@"display"];
// // If space in suggestion add space to allowed input chars
// if(!spaceInSuggestion && [item rangeOfString:@" "].length) {
// [textualInputCharacters addCharactersInString:@" "];
// spaceInSuggestion = YES;
// }
//
// if([item length]>maxLen)
// maxLen = [item length];
// }
// maxWidth = maxLen*16;
// maxWidth = (maxWidth>340) ? 340 : maxWidth;
// maxWidth = (maxWidth<20) ? 20 : maxWidth;
// }
// if(caretPos.y>=0 && (isAbove || caretPos.y