aboutsummaryrefslogtreecommitdiffstats
path: root/Source
diff options
context:
space:
mode:
Diffstat (limited to 'Source')
-rw-r--r--Source/NoodleLineNumberView.h49
-rw-r--r--Source/NoodleLineNumberView.m483
-rw-r--r--Source/SPDatabaseDocument.m1
-rw-r--r--Source/SPDatabaseViewController.m6
-rw-r--r--Source/SPFieldMapperController.h2
-rw-r--r--Source/SPFieldMapperController.m111
-rw-r--r--Source/SPTextView.m55
7 files changed, 399 insertions, 308 deletions
diff --git a/Source/NoodleLineNumberView.h b/Source/NoodleLineNumberView.h
index 45f33c00..1d600720 100644
--- a/Source/NoodleLineNumberView.h
+++ b/Source/NoodleLineNumberView.h
@@ -29,37 +29,42 @@
#import <Cocoa/Cocoa.h>
-@class NoodleLineNumberMarker;
-
@interface NoodleLineNumberView : NSRulerView
{
- // Array of character indices for the beginning of each line
- NSMutableArray *lineIndices;
- NSFont *font;
- NSColor *textColor;
- NSColor *alternateTextColor;
- NSColor *backgroundColor;
- // Add support for selection by clicking/dragging
- NSUInteger dragSelectionStartLine;
-}
+ // Array of character indices for the beginning of each line
+ NSMutableArray *lineIndices;
-- (id)initWithScrollView:(NSScrollView *)aScrollView;
+ NSFont *font;
+ NSColor *textColor;
+ NSColor *alternateTextColor;
+ NSColor *backgroundColor;
+ CGFloat maxWidthOfGlyph;
+ CGFloat maxWidthOfGlyph1;
+ CGFloat maxWidthOfGlyph2;
+ CGFloat maxWidthOfGlyph3;
+ CGFloat maxWidthOfGlyph4;
+ CGFloat maxWidthOfGlyph5;
+ CGFloat maxWidthOfGlyph6;
+ CGFloat maxWidthOfGlyph7;
+ CGFloat maxWidthOfGlyph8;
+ NSDictionary *textAttributes;
-- (void)setFont:(NSFont *)aFont;
-- (NSFont *)font;
+ // Add support for selection by clicking/dragging
+ NSUInteger dragSelectionStartLine;
-- (void)setTextColor:(NSColor *)color;
-- (NSColor *)textColor;
+}
-- (void)setAlternateTextColor:(NSColor *)color;
-- (NSColor *)alternateTextColor;
+@property(retain) NSColor *alternateTextColor;
+@property(retain) NSColor *backgroundColor;
-- (void)setBackgroundColor:(NSColor *)color;
-- (NSColor *)backgroundColor;
+- (NSFont*)font;
+- (void)setFont:(NSFont*)aFont;
+- (NSColor*)textColor;
+- (void)setTextColor:(NSColor*)color;
+- (id)initWithScrollView:(NSScrollView *)aScrollView;
- (NSUInteger)lineNumberForLocation:(CGFloat)location;
-
-- (NSUInteger)lineNumberForCharacterIndex:(NSUInteger)index inText:(NSString *)text;
+- (NSUInteger)lineNumberForCharacterIndex:(NSUInteger)index;
@end
diff --git a/Source/NoodleLineNumberView.m b/Source/NoodleLineNumberView.m
index 3df2b423..ebca542d 100644
--- a/Source/NoodleLineNumberView.m
+++ b/Source/NoodleLineNumberView.m
@@ -28,32 +28,58 @@
//
// This version of the NoodleLineNumberView for Sequel Pro removes marker
-// functionality and adds selection by clicking on the ruler.
+// functionality and adds selection by clicking on the ruler. Furthermore
+// the code was optimized.
#import "NoodleLineNumberView.h"
#include <tgmath.h>
-#define DEFAULT_THICKNESS 22.0
-#define RULER_MARGIN 5.0
+#pragma mark NSCoding methods
+
+#define NOODLE_FONT_CODING_KEY @"font"
+#define NOODLE_TEXT_COLOR_CODING_KEY @"textColor"
+#define NOODLE_ALT_TEXT_COLOR_CODING_KEY @"alternateTextColor"
+#define NOODLE_BACKGROUND_COLOR_CODING_KEY @"backgroundColor"
+
+#pragma mark -
+
+#define DEFAULT_THICKNESS 22.0
+#define RULER_MARGIN 5.0
+#define RULER_MARGIN2 RULER_MARGIN * 2
+
+typedef NSRange (*RangeOfLineIMP)(id object, SEL selector, NSRange range);
+
+#pragma mark -
@interface NoodleLineNumberView (Private)
- (NSMutableArray *)lineIndices;
- (void)invalidateLineIndices;
- (void)calculateLines;
-- (NSDictionary *)textAttributes;
+- (void)updateGutterThicknessConstants;
@end
@implementation NoodleLineNumberView
+@synthesize alternateTextColor;
+@synthesize backgroundColor;
+
- (id)initWithScrollView:(NSScrollView *)aScrollView
{
+
if ((self = [super initWithScrollView:aScrollView orientation:NSVerticalRuler]) != nil)
{
[self setClientView:[aScrollView documentView]];
+ [self setAlternateTextColor:[NSColor whiteColor]];
lineIndices = nil;
+ textAttributes = [[NSDictionary dictionaryWithObjectsAndKeys:
+ [self font], NSFontAttributeName,
+ [self textColor], NSForegroundColorAttributeName,
+ nil] retain];
+ maxWidthOfGlyph = [[NSString stringWithString:@"8"] sizeWithAttributes:textAttributes].width;
+ [self updateGutterThicknessConstants];
}
return self;
@@ -69,27 +95,36 @@
[[NSNotificationCenter defaultCenter] removeObserver:self];
if (lineIndices) [lineIndices release];
- [font release];
-
+ if (textAttributes) [textAttributes release];
+ if (font) [font release];
+ if (textColor) [textColor release];
[super dealloc];
}
+#pragma mark -
+
- (void)setFont:(NSFont *)aFont
{
if (font != aFont)
{
[font autorelease];
font = [aFont retain];
+ if (textAttributes) [textAttributes release];
+ textAttributes = [[NSDictionary dictionaryWithObjectsAndKeys:
+ font, NSFontAttributeName,
+ [self textColor], NSForegroundColorAttributeName,
+ nil] retain];
+ maxWidthOfGlyph = [[NSString stringWithString:@"8"] sizeWithAttributes:textAttributes].width;
+ [self updateGutterThicknessConstants];
}
}
- (NSFont *)font
{
if (font == nil)
- {
return [NSFont labelFontOfSize:[NSFont systemFontSizeForControlSize:NSMiniControlSize]];
- }
- return font;
+
+ return font;
}
- (void)setTextColor:(NSColor *)color
@@ -98,101 +133,68 @@
{
[textColor autorelease];
textColor = [color retain];
+ if (textAttributes) [textAttributes release];
+ textAttributes = [[NSDictionary dictionaryWithObjectsAndKeys:
+ [self font], NSFontAttributeName,
+ textColor, NSForegroundColorAttributeName,
+ nil] retain];
+ maxWidthOfGlyph = [[NSString stringWithString:@"8"] sizeWithAttributes:textAttributes].width;
+ [self updateGutterThicknessConstants];
}
}
- (NSColor *)textColor
{
if (textColor == nil)
- {
return [NSColor colorWithCalibratedWhite:0.42 alpha:1.0];
- }
- return textColor;
-}
-
-- (void)setAlternateTextColor:(NSColor *)color
-{
- if (alternateTextColor != color)
- {
- [alternateTextColor autorelease];
- alternateTextColor = [color retain];
- }
-}
-
-- (NSColor *)alternateTextColor
-{
- if (alternateTextColor == nil)
- {
- return [NSColor whiteColor];
- }
- return alternateTextColor;
-}
-
-- (void)setBackgroundColor:(NSColor *)color
-{
- if (backgroundColor != color)
- {
- [backgroundColor autorelease];
- backgroundColor = [color retain];
- }
-}
-- (NSColor *)backgroundColor
-{
- return backgroundColor;
+ return textColor;
}
- (void)setClientView:(NSView *)aView
{
- id oldClientView;
-
- oldClientView = [self clientView];
+ id oldClientView = [self clientView];
if ((oldClientView != aView) && [oldClientView isKindOfClass:[NSTextView class]])
- {
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSTextStorageDidProcessEditingNotification object:[(NSTextView *)oldClientView textStorage]];
- }
+
[super setClientView:aView];
+
if ((aView != nil) && [aView isKindOfClass:[NSTextView class]])
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textDidChange:) name:NSTextStorageDidProcessEditingNotification object:[(NSTextView *)aView textStorage]];
-
[self invalidateLineIndices];
}
}
-- (NSMutableArray *)lineIndices
-{
- if (lineIndices == nil)
- {
- [self calculateLines];
- }
- return lineIndices;
-}
-
-- (void)invalidateLineIndices
-{
- if (lineIndices) [lineIndices release], lineIndices = nil;
-}
+#pragma mark -
- (void)textDidChange:(NSNotification *)notification
{
- // Invalidate the line indices. They will be recalculated and recached on demand.
- [self invalidateLineIndices];
+
+ if(![self clientView]) return;
+
+ NSUInteger editMask = [[(NSTextView *)[self clientView] textStorage] editedMask];
+
+ // Invalidate the line indices only if text view was changed in length but not if the font was changed.
+ // They will be recalculated and recached on demand.
+ if(editMask != 1)
+ [self invalidateLineIndices];
[self setNeedsDisplay:YES];
+
}
- (NSUInteger)lineNumberForLocation:(CGFloat)location
{
- NSUInteger line, count, index, rectCount, i;
- NSRectArray rects;
- NSRect visibleRect;
- NSLayoutManager *layoutManager;
- NSTextContainer *container;
- NSRange nullRange;
- NSArray *lines;
- id view;
+ NSUInteger line, count, rectCount, i;
+ NSRectArray rects;
+ NSRect visibleRect;
+ NSLayoutManager *layoutManager;
+ NSTextContainer *container;
+ NSRange nullRange;
+ NSArray *lines;
+ id view;
view = [self clientView];
visibleRect = [[[self scrollView] contentView] bounds];
@@ -216,148 +218,83 @@
// It doesn't show up in the glyphs so would not be accounted for.
range.length++;
- for (line = [self lineNumberForCharacterIndex:range.location inText:@""]; line < count; line++)
- {
+ // Cache loop methods for speed
+ SEL lineNumberForCharacterIndexSel = @selector(lineNumberForCharacterIndex:);
+ IMP lineNumberForCharacterIndexIMP = [self methodForSelector:lineNumberForCharacterIndexSel];
- index = [NSArrayObjectAtIndex(lines, line) unsignedIntegerValue];
+ for (line = (NSUInteger)(*lineNumberForCharacterIndexIMP)(self, lineNumberForCharacterIndexSel, range.location); line < count; line++)
+ {
- rects = [layoutManager rectArrayForCharacterRange:NSMakeRange(index, 0)
+ rects = [layoutManager rectArrayForCharacterRange:NSMakeRange([NSArrayObjectAtIndex(lines, line) unsignedIntegerValue], 0)
withinSelectedCharacterRange:nullRange
inTextContainer:container
rectCount:&rectCount];
- for (i = 0; i < rectCount; i++)
- if ((location >= NSMinY(rects[i])) && (location < NSMaxY(rects[i])))
- return line + 1;
+ if(!rectCount) return NSNotFound;
+
+ if ((location >= NSMinY(rects[0])) && (location < NSMaxY(rects[0])))
+ return line + 1;
}
}
return NSNotFound;
}
-- (void)calculateLines
+- (NSUInteger)lineNumberForCharacterIndex:(NSUInteger)index
{
- id view = [self clientView];
-
- if ([view isKindOfClass:[NSTextView class]])
- {
- NSUInteger index, stringLength, lineEnd, contentEnd;
- NSString *text;
- CGFloat oldThickness, newThickness;
-
- text = [view string];
- stringLength = [text length];
-
- // Switch off line numbering if text larger than 6MB
- // for performance reasons.
- // TODO improve performance maybe via threading
- if(stringLength>6000000)
- return;
+ NSUInteger left, right, mid, lineStart;
+ NSMutableArray *lines;
- if (lineIndices) [lineIndices release], lineIndices = nil;
- lineIndices = [[NSMutableArray alloc] initWithCapacity:1];
-
- index = 0;
+ lines = [self lineIndices];
- do
- {
- [lineIndices addObject:[NSNumber numberWithUnsignedInteger:index]];
- index = NSMaxRange([text lineRangeForRange:NSMakeRange(index, 0)]);
- }
- while (index < stringLength);
+ // Binary search
+ left = 0;
+ right = [lines count];
- // Check if text ends with a new line.
- [text getLineStart:NULL end:&lineEnd contentsEnd:&contentEnd forRange:NSMakeRange([[lineIndices lastObject] unsignedIntegerValue], 0)];
- if (contentEnd < lineEnd)
- {
- [lineIndices addObject:[NSNumber numberWithUnsignedInteger:index]];
- }
+ while ((right - left) > 1)
+ {
- oldThickness = [self ruleThickness];
- newThickness = [self requiredThickness];
- if (fabs(oldThickness - newThickness) > 1)
- {
- NSInvocation *invocation;
+ mid = (right + left) >> 1;
+ lineStart = [NSArrayObjectAtIndex(lines, mid) unsignedIntegerValue];
- // Not a good idea to resize the view during calculations (which can happen during
- // display). Do a delayed perform (using NSInvocation since arg is a float).
- invocation = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:@selector(setRuleThickness:)]];
- [invocation setSelector:@selector(setRuleThickness:)];
- [invocation setTarget:self];
- [invocation setArgument:&newThickness atIndex:2];
+ if (index < lineStart)
+ right = mid;
+ else if (index > lineStart)
+ left = mid;
+ else
+ return mid;
- [invocation performSelector:@selector(invoke) withObject:nil afterDelay:0.0];
- }
}
-}
-
-- (NSUInteger)lineNumberForCharacterIndex:(NSUInteger)index inText:(NSString *)text
-{
- NSUInteger left, right, mid, lineStart;
- NSMutableArray *lines;
-
- lines = [self lineIndices];
-
- // Binary search
- left = 0;
- right = [lines count];
-
- while ((right - left) > 1)
- {
- mid = (right + left) / 2;
- lineStart = [NSArrayObjectAtIndex(lines, mid) unsignedIntegerValue];
-
- if (index < lineStart)
- {
- right = mid;
- }
- else if (index > lineStart)
- {
- left = mid;
- }
- else
- {
- return mid;
- }
- }
- return left;
-}
-
-- (NSDictionary *)textAttributes
-{
- return [NSDictionary dictionaryWithObjectsAndKeys:
- [self font], NSFontAttributeName,
- [self textColor], NSForegroundColorAttributeName,
- nil];
+ return left;
}
- (CGFloat)requiredThickness
{
NSUInteger lineCount = [[self lineIndices] count];
if(lineCount < 10)
- return ceil(MAX(DEFAULT_THICKNESS, [[NSString stringWithString:@"8"] sizeWithAttributes:[self textAttributes]].width + RULER_MARGIN * 2));
+ return maxWidthOfGlyph1;
else if(lineCount < 100)
- return ceil(MAX(DEFAULT_THICKNESS, [[NSString stringWithString:@"88"] sizeWithAttributes:[self textAttributes]].width + RULER_MARGIN * 2));
+ return maxWidthOfGlyph2;
else if(lineCount < 1000)
- return ceil(MAX(DEFAULT_THICKNESS, [[NSString stringWithString:@"888"] sizeWithAttributes:[self textAttributes]].width + RULER_MARGIN * 2));
+ return maxWidthOfGlyph3;
else if(lineCount < 10000)
- return ceil(MAX(DEFAULT_THICKNESS, [[NSString stringWithString:@"8888"] sizeWithAttributes:[self textAttributes]].width + RULER_MARGIN * 2));
+ return maxWidthOfGlyph4;
else if(lineCount < 100000)
- return ceil(MAX(DEFAULT_THICKNESS, [[NSString stringWithString:@"88888"] sizeWithAttributes:[self textAttributes]].width + RULER_MARGIN * 2));
+ return maxWidthOfGlyph5;
else if(lineCount < 1000000)
- return ceil(MAX(DEFAULT_THICKNESS, [[NSString stringWithString:@"888888"] sizeWithAttributes:[self textAttributes]].width + RULER_MARGIN * 2));
+ return maxWidthOfGlyph6;
else if(lineCount < 10000000)
- return ceil(MAX(DEFAULT_THICKNESS, [[NSString stringWithString:@"8888888"] sizeWithAttributes:[self textAttributes]].width + RULER_MARGIN * 2));
+ return maxWidthOfGlyph7;
else if(lineCount < 100000000)
- return ceil(MAX(DEFAULT_THICKNESS, [[NSString stringWithString:@"88888888"] sizeWithAttributes:[self textAttributes]].width + RULER_MARGIN * 2));
+ return maxWidthOfGlyph8;
else
return 100;
}
- (void)drawHashMarksAndLabelsInRect:(NSRect)aRect
{
- id view;
- NSRect bounds;
+ id view;
+ NSRect bounds;
bounds = [self bounds];
@@ -374,45 +311,45 @@
if ([view isKindOfClass:[NSTextView class]])
{
- NSLayoutManager *layoutManager;
- NSTextContainer *container;
- NSRect visibleRect;
- NSRange range, glyphRange, nullRange;
- NSString *text, *labelText;
- NSUInteger rectCount, index, line, count;
- NSRectArray rects;
- CGFloat ypos, yinset;
- NSDictionary *textAttributes;
- NSSize stringSize;
- NSArray *lines;
-
- layoutManager = [view layoutManager];
- container = [view textContainer];
- text = [view string];
- nullRange = NSMakeRange(NSNotFound, 0);
-
- yinset = [view textContainerInset].height;
- visibleRect = [[[self scrollView] contentView] bounds];
-
- textAttributes = [self textAttributes];
-
- lines = [self lineIndices];
+ NSLayoutManager *layoutManager;
+ NSTextContainer *container;
+ NSRect visibleRect;
+ NSRange range, nullRange;
+ NSString *labelText;
+ NSUInteger rectCount, index, line, count;
+ NSRectArray rects;
+ CGFloat ypos, yinset;
+ NSSize stringSize;
+ NSArray *lines;
+
+ layoutManager = [view layoutManager];
+ container = [view textContainer];
+ nullRange = NSMakeRange(NSNotFound, 0);
+
+ yinset = [view textContainerInset].height;
+ visibleRect = [[[self scrollView] contentView] bounds];
+
+ lines = [self lineIndices];
// Find the characters that are currently visible
- glyphRange = [layoutManager glyphRangeForBoundingRect:visibleRect inTextContainer:container];
- range = [layoutManager characterRangeForGlyphRange:glyphRange actualGlyphRange:NULL];
+ range = [layoutManager characterRangeForGlyphRange:[layoutManager glyphRangeForBoundingRect:visibleRect inTextContainer:container] actualGlyphRange:NULL];
// Fudge the range a tad in case there is an extra new line at end.
// It doesn't show up in the glyphs so would not be accounted for.
range.length++;
count = [lines count];
-
- CGFloat boundsRULERMargin2 = NSWidth(bounds) - RULER_MARGIN * 2.0;
+
+ CGFloat boundsRULERMargin2 = NSWidth(bounds) - RULER_MARGIN2;
CGFloat boundsWidthRULER = NSWidth(bounds) - RULER_MARGIN;
CGFloat yinsetMinY = yinset - NSMinY(visibleRect);
+ CGFloat rectHeight;
- for (line = [self lineNumberForCharacterIndex:range.location inText:text]; line < count; line++)
+ // Cache loop methods for speed
+ SEL lineNumberForCharacterIndexSel = @selector(lineNumberForCharacterIndex:);
+ IMP lineNumberForCharacterIndexIMP = [self methodForSelector:lineNumberForCharacterIndexSel];
+
+ for (line = (NSUInteger)(*lineNumberForCharacterIndexIMP)(self, lineNumberForCharacterIndexSel, range.location); line < count; line++)
{
index = [NSArrayObjectAtIndex(lines, line) unsignedIntegerValue];
@@ -433,35 +370,35 @@
stringSize = [labelText sizeWithAttributes:textAttributes];
+ rectHeight = NSHeight(rects[0]);
// Draw string flush right, centered vertically within the line
[labelText drawInRect:
NSMakeRect(boundsWidthRULER - stringSize.width,
- yinsetMinY + NSMinY(rects[0]) + ((NSHeight(rects[0]) - stringSize.height) / 2.0),
- boundsRULERMargin2, NSHeight(rects[0]))
+ yinsetMinY + NSMinY(rects[0]) + ((NSInteger)(rectHeight - stringSize.height) >> 1),
+ boundsRULERMargin2, rectHeight)
withAttributes:textAttributes];
}
}
+
if (index > NSMaxRange(range))
- {
break;
- }
+
}
}
}
- (void)mouseDown:(NSEvent *)theEvent
{
- NSPoint location;
- NSUInteger line;
- NSTextView *view;
-
- if (![[self clientView] isKindOfClass:[NSTextView class]]) return;
+
+ NSUInteger line;
+ NSTextView *view;
+
+ if (![[self clientView] isKindOfClass:[NSTextView class]]) return;
view = (NSTextView *)[self clientView];
-
- location = [self convertPoint:[theEvent locationInWindow] fromView:nil];
- line = [self lineNumberForLocation:location.y];
+
+ line = [self lineNumberForLocation:[self convertPoint:[theEvent locationInWindow] fromView:nil].y];
dragSelectionStartLine = line;
-
+
if (line != NSNotFound)
{
NSUInteger selectionStart, selectionEnd;
@@ -479,15 +416,14 @@
- (void)mouseDragged:(NSEvent *)theEvent
{
- NSPoint location;
- NSUInteger line, startLine, endLine;
- NSTextView *view;
-
- if (![[self clientView] isKindOfClass:[NSTextView class]] || dragSelectionStartLine == NSNotFound) return;
+
+ NSUInteger line, startLine, endLine;
+ NSTextView *view;
+
+ if (![[self clientView] isKindOfClass:[NSTextView class]] || dragSelectionStartLine == NSNotFound) return;
view = (NSTextView *)[self clientView];
-
- location = [self convertPoint:[theEvent locationInWindow] fromView:nil];
- line = [self lineNumberForLocation:location.y];
+
+ line = [self lineNumberForLocation:[self convertPoint:[theEvent locationInWindow] fromView:nil].y];
if (line != NSNotFound)
{
@@ -509,16 +445,11 @@
}
[view setSelectedRange:NSMakeRange(selectionStart, selectionEnd - selectionStart)];
}
-
+
[view autoscroll:theEvent];
}
-#pragma mark NSCoding methods
-
-#define NOODLE_FONT_CODING_KEY @"font"
-#define NOODLE_TEXT_COLOR_CODING_KEY @"textColor"
-#define NOODLE_ALT_TEXT_COLOR_CODING_KEY @"alternateTextColor"
-#define NOODLE_BACKGROUND_COLOR_CODING_KEY @"backgroundColor"
+#pragma mark -
- (id)initWithCoder:(NSCoder *)decoder
{
@@ -562,4 +493,94 @@
}
}
+#pragma mark -
+#pragma mark PrivateAPI
+
+- (NSMutableArray *)lineIndices
+{
+
+ if (lineIndices == nil)
+ [self calculateLines];
+
+ return lineIndices;
+
+}
+
+- (void)invalidateLineIndices
+{
+
+ if (lineIndices) [lineIndices release], lineIndices = nil;
+
+}
+
+- (void)calculateLines
+{
+ id view = [self clientView];
+
+ if ([view isKindOfClass:[NSTextView class]])
+ {
+ NSUInteger index, stringLength, lineEnd, contentEnd;
+ NSString *textString;
+ CGFloat newThickness;
+
+ textString = [view string];
+ stringLength = [textString length];
+
+ // Switch off line numbering if text larger than 6MB
+ // for performance reasons.
+ // TODO improve performance maybe via threading
+ if(stringLength>6000000)
+ return;
+
+ if (lineIndices) [lineIndices release], lineIndices = nil;
+ // Init lineIndices with text length / 16 + 1
+ lineIndices = [[NSMutableArray alloc] initWithCapacity:((NSUInteger)stringLength>>4)+1];
+
+ index = 0;
+
+ // Cache loop methods for speed
+ SEL lineRangeForRangeSel = @selector(lineRangeForRange:);
+ SEL addObjectSel = @selector(addObject:);
+ RangeOfLineIMP rangeOfLineIMP = (RangeOfLineIMP)[textString methodForSelector:lineRangeForRangeSel];
+ IMP addObjectIMP = [lineIndices methodForSelector:addObjectSel];
+
+ do
+ {
+ (void*)(*addObjectIMP)(lineIndices, addObjectSel, [NSNumber numberWithUnsignedInteger:index]);
+ index = NSMaxRange((*rangeOfLineIMP)(textString, lineRangeForRangeSel, NSMakeRange(index, 0)));
+ }
+ while (index < stringLength);
+
+ // Check if text ends with a new line.
+ [textString getLineStart:NULL end:&lineEnd contentsEnd:&contentEnd forRange:NSMakeRange([[lineIndices lastObject] unsignedIntegerValue], 0)];
+ if (contentEnd < lineEnd)
+ (void*)(*addObjectIMP)(lineIndices, addObjectSel, [NSNumber numberWithUnsignedInteger:index]);
+
+ newThickness = [self requiredThickness];
+ if (fabs([self ruleThickness] - newThickness) > 1)
+ {
+ // Not a good idea to resize the view during calculations (which can happen during
+ // display). Do a delayed perform (using NSInvocation since arg is a float).
+ NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:@selector(setRuleThickness:)]];
+ [invocation setSelector:@selector(setRuleThickness:)];
+ [invocation setTarget:self];
+ [invocation setArgument:&newThickness atIndex:2];
+
+ [invocation performSelector:@selector(invoke) withObject:nil afterDelay:0.0];
+ }
+ }
+}
+
+- (void)updateGutterThicknessConstants
+{
+ maxWidthOfGlyph1 = ceil(MAX(DEFAULT_THICKNESS, maxWidthOfGlyph + RULER_MARGIN2));
+ maxWidthOfGlyph2 = ceil(MAX(DEFAULT_THICKNESS, maxWidthOfGlyph * 2 + RULER_MARGIN2));
+ maxWidthOfGlyph3 = ceil(MAX(DEFAULT_THICKNESS, maxWidthOfGlyph * 3 + RULER_MARGIN2));
+ maxWidthOfGlyph4 = ceil(MAX(DEFAULT_THICKNESS, maxWidthOfGlyph * 4 + RULER_MARGIN2));
+ maxWidthOfGlyph5 = ceil(MAX(DEFAULT_THICKNESS, maxWidthOfGlyph * 5 + RULER_MARGIN2));
+ maxWidthOfGlyph6 = ceil(MAX(DEFAULT_THICKNESS, maxWidthOfGlyph * 6 + RULER_MARGIN2));
+ maxWidthOfGlyph7 = ceil(MAX(DEFAULT_THICKNESS, maxWidthOfGlyph * 7 + RULER_MARGIN2));
+ maxWidthOfGlyph8 = ceil(MAX(DEFAULT_THICKNESS, maxWidthOfGlyph * 8 + RULER_MARGIN2));
+}
+
@end
diff --git a/Source/SPDatabaseDocument.m b/Source/SPDatabaseDocument.m
index 1d4b4fc6..4bc82a21 100644
--- a/Source/SPDatabaseDocument.m
+++ b/Source/SPDatabaseDocument.m
@@ -3795,7 +3795,6 @@
// Add the progress window to this window
[self centerTaskWindow];
- [taskProgressWindow orderFront:self];
[parentWindow addChildWindow:taskProgressWindow ordered:NSWindowAbove];
}
diff --git a/Source/SPDatabaseViewController.m b/Source/SPDatabaseViewController.m
index a7867c53..03153230 100644
--- a/Source/SPDatabaseViewController.m
+++ b/Source/SPDatabaseViewController.m
@@ -426,6 +426,12 @@
if (changeEncoding) [mySQLConnection restoreStoredEncoding];
+ // Cache table information on the working thread
+ if (selectedTableType == SPTableTypeView)
+ [tableDataInstance updateInformationForCurrentView];
+ else
+ [tableDataInstance updateInformationForCurrentTable];
+
// Notify listeners of the table change now that the state is fully set up.
[[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:SPTableChangedNotification object:self];
diff --git a/Source/SPFieldMapperController.h b/Source/SPFieldMapperController.h
index 8a70df90..813a3b3e 100644
--- a/Source/SPFieldMapperController.h
+++ b/Source/SPFieldMapperController.h
@@ -76,6 +76,7 @@
IBOutlet id highPriorityCheckBox;
IBOutlet id skipexistingRowsCheckBox;
IBOutlet SPTextView *onupdateTextView;
+ IBOutlet id gobackButton;
IBOutlet id advancedButton;
@@ -171,6 +172,7 @@
- (IBAction)openAdvancedSheet:(id)sender;
- (IBAction)closeSheet:(id)sender;
- (IBAction)goBackToFileChooser:(id)sender;
+- (IBAction)goBackToFileChooserFromPathControl:(id)sender;
- (IBAction)addGlobalValue:(id)sender;
- (IBAction)removeGlobalValue:(id)sender;
diff --git a/Source/SPFieldMapperController.m b/Source/SPFieldMapperController.m
index e70a7c25..b77ca98e 100644
--- a/Source/SPFieldMapperController.m
+++ b/Source/SPFieldMapperController.m
@@ -29,6 +29,7 @@
#import "SPTextView.h"
#import "SPTableView.h"
#import "SPCategoryAdditions.h"
+#import "RegexKitLite.h"
#define SP_NUMBER_OF_RECORDS_STRING NSLocalizedString(@"%ld of %@%lu records", @"Label showing the index of the selected CSV row")
@@ -120,7 +121,7 @@ static const NSString *SPTableViewSqlColumnID = @"sql";
[pc setURL:[NSURL fileURLWithPath:sourcePath]];
if([pc pathComponentCells])
[fileSourcePath setPathComponentCells:[pc pathComponentCells]];
- [fileSourcePath setDoubleAction:@selector(goBackToFileChooser:)];
+ [fileSourcePath setDoubleAction:@selector(goBackToFileChooserFromPathControl:)];
[onupdateTextView setDelegate:theDelegate];
windowMinWidth = [[self window] minSize].width;
@@ -700,15 +701,24 @@ static const NSString *SPTableViewSqlColumnID = @"sql";
if(possibleImports < 1) return;
+ // Set all operators to doNotImport
+ [fieldMappingOperatorArray removeAllObjects];
+ for(i=0; i < [fieldMappingTableColumnNames count]; i++)
+ [fieldMappingOperatorArray addObject:doNotImport];
+
switch([[alignByPopup selectedItem] tag]) {
case 0: // file order
- for(i=0; i<possibleImports; i++)
+ for(i=0; i<possibleImports; i++) {
[fieldMappingArray replaceObjectAtIndex:i withObject:[NSNumber numberWithInteger:i]];
+ [fieldMappingOperatorArray replaceObjectAtIndex:i withObject:doImport];
+ }
break;
case 1: // reversed file order
possibleImports--;
- for(i=possibleImports; i>=0; i--)
+ for(i=possibleImports; i>=0; i--) {
[fieldMappingArray replaceObjectAtIndex:possibleImports-i withObject:[NSNumber numberWithInteger:i]];
+ [fieldMappingOperatorArray replaceObjectAtIndex:possibleImports-i withObject:doImport];
+ }
break;
case 2: // try to align header and table target field names via Levenshtein distance
[self matchHeaderNames];
@@ -763,14 +773,21 @@ static const NSString *SPTableViewSqlColumnID = @"sql";
}
}
+- (IBAction)goBackToFileChooserFromPathControl:(id)sender
+{
+ [gobackButton performSelector:@selector(performClick:) withObject:nil afterDelay:0.0f];
+}
+
- (IBAction)goBackToFileChooser:(id)sender
{
+
[NSApp endSheet:[self window] returnCode:[sender tag]];
- if([sourcePath hasPrefix:SPImportClipboardTempFileNamePrefix]) {
+
+ if([sourcePath hasPrefix:SPImportClipboardTempFileNamePrefix])
[theDelegate importFromClipboard];
- } else {
+ else
[theDelegate importFile];
- }
+
}
- (IBAction)newTable:(id)sender
@@ -1365,34 +1382,84 @@ static const NSString *SPTableViewSqlColumnID = @"sql";
NSMutableArray *tableHeaderNames = [NSMutableArray array];
[tableHeaderNames setArray:fieldMappingTableColumnNames];
+ // Create a distance matrix for each file-table name
+ // distance will be calculated by using Levenshtein distance minus common prefix and suffix length
+ // and minus the length of a fuzzy regex search for a common sequence of characters
NSInteger i,j;
- NSMutableArray *matchedHeaderNames = [NSMutableArray array];
+ NSMutableArray *distMatrix = [NSMutableArray array];
for(i=0; i < [tableHeaderNames count]; i++) {
- CGFloat minDist = 1e6;
+ CGFloat minDist = 1e6;
NSInteger minIndex = 0;
+ CGFloat dist = 1e6;
for(j=0; j < [fileHeaderNames count]; j++) {
id fileHeaderName = NSArrayObjectAtIndex(fileHeaderNames,j);
if([fileHeaderName isKindOfClass:[NSNull class]] || [fileHeaderName isSPNotLoaded]) continue;
NSString *headerName = [(NSString*)fileHeaderName lowercaseString];
- CGFloat dist = [[NSArrayObjectAtIndex(tableHeaderNames,i) lowercaseString] levenshteinDistanceWithWord:headerName];
- if(dist < minDist && ![matchedHeaderNames containsObject:headerName]) {
- minDist = dist;
- minIndex = j;
+ NSString *tableHeadName = [NSArrayObjectAtIndex(tableHeaderNames,i) lowercaseString];
+ dist = [tableHeadName levenshteinDistanceWithWord:headerName];
+
+ // if dist > 0 subtract the length of common prefixes, suffixes, and in common sequence characters
+ if(dist > 0.0) {
+ dist -= [[tableHeadName commonPrefixWithString:headerName options:NSCaseInsensitiveSearch] length];
+ dist -= [[tableHeadName commonPrefixWithString:headerName options:NSCaseInsensitiveSearch|NSBackwardsSearch] length];
+
+ NSMutableString *fuzzyRegexp = [[NSMutableString alloc] initWithCapacity:3];
+ NSInteger i;
+ unichar c;
+
+ for(i=0; i<[headerName length]; i++) {
+ c = [headerName characterAtIndex:i];
+ if (c == '.' || c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}')
+ [fuzzyRegexp appendFormat:@".*?\\%c",c];
+ else
+ [fuzzyRegexp appendFormat:@".*?%c",c];
+ }
+ dist -= [tableHeadName rangeOfRegex:fuzzyRegexp].length;
+ [fuzzyRegexp release];
+
+ } else {
+ // Levenshtein distance == 0 means that both names are equal set dist to
+ // a large negative number since dist can be negative due to search for in common chars
+ dist = -1e6;
}
- if(dist == 0.0f) [matchedHeaderNames addObject:headerName];
+
+ [distMatrix addObject:[NSDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithFloat:dist], @"dist",
+ NSStringFromRange(NSMakeRange(i,j)), @"match",
+ (NSString*)fileHeaderName, @"file",
+ NSArrayObjectAtIndex(tableHeaderNames,i), @"table",
+ nil]];
+
}
- [fieldMappingArray replaceObjectAtIndex:i withObject:[NSNumber numberWithInteger:minIndex]];
- [fieldMappingOperatorArray replaceObjectAtIndex:i withObject:doImport];
+
}
- // If a pair with distance 0 was found set doNotImport to those fields which are still mapped
- // to such csv file header name
- if([matchedHeaderNames count])
- for(i=0; i < [tableHeaderNames count]; i++) {
- NSString *mappedFileHeaderName = [NSArrayObjectAtIndex(fileHeaderNames, [[fieldMappingArray objectAtIndex:i] integerValue]) lowercaseString];
- if([matchedHeaderNames containsObject:mappedFileHeaderName] && ![mappedFileHeaderName isEqualToString:[NSArrayObjectAtIndex(tableHeaderNames, i) lowercaseString]])
- [fieldMappingOperatorArray replaceObjectAtIndex:i withObject:doNotImport];
+ // Sort the matrix according distance
+ NSSortDescriptor *sortByDistance = [[[NSSortDescriptor alloc] initWithKey:@"dist" ascending:TRUE] autorelease];
+ [distMatrix sortUsingDescriptors:[NSArray arrayWithObjects:sortByDistance, nil]];
+
+ NSMutableArray *matchedFile = [NSMutableArray array];
+ NSMutableArray *matchedTable = [NSMutableArray array];
+ NSInteger cnt = 0;
+ for(NSDictionary* m in distMatrix) {
+ if(![matchedFile containsObject:[m objectForKey:@"file"]] && ![matchedTable containsObject:[m objectForKey:@"table"]]) {
+
+ NSRange match = NSRangeFromString([m objectForKey:@"match"]);
+
+ // Set best match
+ [fieldMappingArray replaceObjectAtIndex:match.location withObject:[NSNumber numberWithInteger:match.length]];
+ [fieldMappingOperatorArray replaceObjectAtIndex:match.location withObject:doImport];
+
+ // Remember matched pair
+ [matchedTable addObject:[m objectForKey:@"table"]];
+ [matchedFile addObject:[m objectForKey:@"file"]];
+ cnt++;
}
+
+ // break if all file names are mapped
+ if(cnt >= [fileHeaderNames count]) break;
+
+ }
}
/*
diff --git a/Source/SPTextView.m b/Source/SPTextView.m
index 82bfdedf..705f2a8a 100644
--- a/Source/SPTextView.m
+++ b/Source/SPTextView.m
@@ -269,15 +269,16 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
// If caret is not inside backticks add keywords and all words coming from the view.
if(!dbBrowseMode)
{
- // Only parse for words if text size is less than 6MB
- if([[self string] length] && [[self string] length]<6000000)
+ // Only parse for words if text size is less than 1MB
+ if([[self string] length] && [[self string] length]<1000000)
{
NSMutableSet *uniqueArray = [NSMutableSet setWithCapacity:5];
for(id w in [[self textStorage] words])
- [uniqueArray addObject:[w string]];
- // Remove current word from list
+ if([[w string] hasPrefix:currentWord])
+ [uniqueArray addObject:[w string]];
+ // Remove current word from list
[uniqueArray removeObject:currentWord];
NSInteger reverseSort = NO;
@@ -863,7 +864,7 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
*/
- (NSUInteger) getLineNumberForCharacterIndex:(NSUInteger)anIndex
{
- return [lineNumberView lineNumberForCharacterIndex:anIndex inText:[self string]]+1;
+ return [lineNumberView lineNumberForCharacterIndex:anIndex]+1;
}
/**
@@ -2153,12 +2154,6 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
}
// Note: switch(insertedCharacter) {} does not work instead use charactersIgnoringModifiers
- if([charactersIgnMod isEqualToString:@"c"]) // ^C copy as RTF
- if(curFlags==(NSControlKeyMask))
- {
- [self copyAsRTF];
- return;
- }
if([charactersIgnMod isEqualToString:@"h"]) // ^H show MySQL Help
if(curFlags==(NSControlKeyMask))
{
@@ -2690,10 +2685,6 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
tokenColor = quoteColor;
allowToCheckForUpperCase = NO;
break;
- case SPT_BACKTICK_QUOTED_TEXT:
- tokenColor = backtickColor;
- allowToCheckForUpperCase = NO;
- break;
case SPT_RESERVED_WORD:
tokenColor = keywordColor;
break;
@@ -2701,6 +2692,10 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
tokenColor = numericColor;
allowToCheckForUpperCase = NO;
break;
+ case SPT_BACKTICK_QUOTED_TEXT:
+ tokenColor = backtickColor;
+ allowToCheckForUpperCase = NO;
+ break;
case SPT_COMMENT:
tokenColor = commentColor;
allowToCheckForUpperCase = NO;
@@ -2726,31 +2721,28 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
if (!tokenRange.length) continue;
// If the current token is marked as SQL keyword, uppercase it if required.
- tokenEnd = tokenRange.location+tokenRange.length-1;
+ tokenEnd = NSMaxRange(tokenRange) - 1;
// Check the end of the token
- if (textBufferSizeIncreased
+ if (autouppercaseKeywordsEnabled
&& allowToCheckForUpperCase
- && autouppercaseKeywordsEnabled
+ && textBufferSizeIncreased
&& !delBackwardsWasPressed
+ && (tokenEnd+1) < strlength
&& [(NSString*)NSMutableAttributedStringAttributeAtIndex(textStore, kSQLkeyword, tokenEnd, nil) length])
// check if next char is not a kSQLkeyword or current kSQLkeyword is at the end;
// if so then upper case keyword if not already done
- // @try catch() for catching valid index esp. after deleteBackward:
{
- NSString* curTokenString = [selfstr substringWithRange:tokenRange];
- BOOL doIt = NO;
- @try
- {
- doIt = ![(NSString*)NSMutableAttributedStringAttributeAtIndex(textStore, kSQLkeyword,tokenEnd+1,nil) length];
- } @catch(id ae) { doIt = NO; }
-
- if(doIt)
+ NSString* curTokenString = [selfstr substringWithRange:tokenRange];
+ if(![(NSString*)NSMutableAttributedStringAttributeAtIndex(textStore, kSQLkeyword,tokenEnd+1,nil) length])
{
- // Register it for undo works only partly for now, at least the uppercased keyword will be selected
- [self shouldChangeTextInRange:tokenRange replacementString:curTokenString];
- [self replaceCharactersInRange:tokenRange withString:[curTokenString uppercaseString]];
+ NSString *curTokenStringUP = [curTokenString uppercaseString];
+ if(![curTokenString isEqualToString:curTokenStringUP]) {
+ // Register it for undo works only partly for now, at least the uppercased keyword will be selected
+ [self shouldChangeTextInRange:tokenRange replacementString:curTokenStringUP];
+ [self replaceCharactersInRange:tokenRange withString:curTokenStringUP];
+ }
}
}
@@ -2986,9 +2978,8 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
}
if ([[[self class] defaultMenu] itemWithTag:SP_CQ_COPY_AS_RTF_MENU_ITEM_TAG] == nil)
{
- NSMenuItem *copyAsRTFMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Copy as RTF", @"Copy as RTF") action:@selector(copyAsRTF) keyEquivalent:@"c"];
+ NSMenuItem *copyAsRTFMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Copy as RTF", @"Copy as RTF") action:@selector(copyAsRTF) keyEquivalent:@""];
[copyAsRTFMenuItem setTag:SP_CQ_COPY_AS_RTF_MENU_ITEM_TAG];
- [copyAsRTFMenuItem setKeyEquivalentModifierMask:NSControlKeyMask];
[menu insertItem:copyAsRTFMenuItem atIndex:2];
[copyAsRTFMenuItem release];
}