diff options
Diffstat (limited to 'Source')
64 files changed, 3307 insertions, 1809 deletions
diff --git a/Source/NoodleLineNumberView.h b/Source/NoodleLineNumberView.h index 855f75ee..e3e96873 100644 --- a/Source/NoodleLineNumberView.h +++ b/Source/NoodleLineNumberView.h @@ -34,11 +34,13 @@ // Array of character indices for the beginning of each line NSMutableArray *lineIndices; + NSUInteger currentNumberOfLines; NSFont *font; NSColor *textColor; NSColor *alternateTextColor; NSColor *backgroundColor; + CGFloat maxHeightOfGlyph; CGFloat maxWidthOfGlyph; CGFloat maxWidthOfGlyph1; CGFloat maxWidthOfGlyph2; @@ -61,9 +63,13 @@ IMP numberWithUnsignedIntegerIMP; SEL addObjectSel; IMP addObjectIMP; + SEL rangeOfLineSel; + Class numberClass; NSLayoutManager *layoutManager; NSTextContainer *container; + NSTextView *clientView; + } diff --git a/Source/NoodleLineNumberView.m b/Source/NoodleLineNumberView.m index 0213c151..193c9ada 100644 --- a/Source/NoodleLineNumberView.m +++ b/Source/NoodleLineNumberView.m @@ -80,7 +80,9 @@ typedef NSRange (*RangeOfLineIMP)(id object, SEL selector, NSRange range); [self font], NSFontAttributeName, [self textColor], NSForegroundColorAttributeName, nil] retain]; - maxWidthOfGlyph = [[NSString stringWithString:@"8"] sizeWithAttributes:textAttributes].width; + NSSize s = [[NSString stringWithString:@"8"] sizeWithAttributes:textAttributes]; + maxWidthOfGlyph = s.width; + maxHeightOfGlyph = s.height; [self updateGutterThicknessConstants]; currentRuleThickness = 0.0f; @@ -91,7 +93,10 @@ typedef NSRange (*RangeOfLineIMP)(id object, SEL selector, NSRange range); addObjectSel = @selector(addObject:); numberWithUnsignedIntegerSel = @selector(numberWithUnsignedInteger:); numberWithUnsignedIntegerIMP = [NSNumber methodForSelector:numberWithUnsignedIntegerSel]; + rangeOfLineSel = @selector(getLineStart:end:contentsEnd:forRange:); + currentNumberOfLines = 1; + numberClass = [NSNumber class]; } @@ -127,7 +132,9 @@ typedef NSRange (*RangeOfLineIMP)(id object, SEL selector, NSRange range); font, NSFontAttributeName, [self textColor], NSForegroundColorAttributeName, nil] retain]; - maxWidthOfGlyph = [[NSString stringWithString:@"8"] sizeWithAttributes:textAttributes].width; + NSSize s = [[NSString stringWithString:@"8"] sizeWithAttributes:textAttributes]; + maxWidthOfGlyph = s.width; + maxHeightOfGlyph = s.height; [self updateGutterThicknessConstants]; } } @@ -151,7 +158,9 @@ typedef NSRange (*RangeOfLineIMP)(id object, SEL selector, NSRange range); [self font], NSFontAttributeName, textColor, NSForegroundColorAttributeName, nil] retain]; - maxWidthOfGlyph = [[NSString stringWithString:@"8"] sizeWithAttributes:textAttributes].width; + NSSize s = [[NSString stringWithString:@"8"] sizeWithAttributes:textAttributes]; + maxWidthOfGlyph = s.width; + maxHeightOfGlyph = s.height; [self updateGutterThicknessConstants]; } } @@ -177,8 +186,9 @@ typedef NSRange (*RangeOfLineIMP)(id object, SEL selector, NSRange range); { layoutManager = [(NSTextView*)aView layoutManager]; container = [(NSTextView*)aView textContainer]; + clientView = (NSTextView*)[self clientView]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textDidChange:) name:NSTextStorageDidProcessEditingNotification object:[(NSTextView *)aView textStorage]]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textDidChange:) name:NSTextStorageDidProcessEditingNotification object:[clientView textStorage]]; [self invalidateLineIndices]; } @@ -189,16 +199,14 @@ typedef NSRange (*RangeOfLineIMP)(id object, SEL selector, NSRange range); - (void)textDidChange:(NSNotification *)notification { - if(![self clientView]) return; - - NSUInteger editMask = [[(NSTextView *)[self clientView] textStorage] editedMask]; + if(!clientView) return; // 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) + if([[clientView textStorage] editedMask] != 1) [self invalidateLineIndices]; - [self setNeedsDisplay:YES]; + [self setNeedsDisplayInRect:[self bounds]]; } @@ -279,7 +287,7 @@ typedef NSRange (*RangeOfLineIMP)(id object, SEL selector, NSRange range); - (void)drawHashMarksAndLabelsInRect:(NSRect)aRect { - id view; + NSRect bounds; bounds = [self bounds]; @@ -293,9 +301,7 @@ typedef NSRange (*RangeOfLineIMP)(id object, SEL selector, NSRange range); // [NSBezierPath strokeLineFromPoint:NSMakePoint(NSMaxX(bounds) - 0.5, NSMinY(bounds)) toPoint:NSMakePoint(NSMaxX(bounds) - 0.5, NSMaxY(bounds))]; // } - view = [self clientView]; - - if ([view isKindOfClass:[NSTextView class]]) + if ([clientView isKindOfClass:[NSTextView class]]) { NSRect visibleRect; NSRange range, nullRange; @@ -303,15 +309,17 @@ typedef NSRange (*RangeOfLineIMP)(id object, SEL selector, NSRange range); NSUInteger rectCount, lineIndex, line, count; NSRectArray rects; CGFloat yinset; - NSSize stringSize; NSArray *lines; nullRange = NSMakeRange(NSNotFound, 0); - yinset = [view textContainerInset].height; + yinset = [clientView textContainerInset].height; visibleRect = [[[self scrollView] contentView] bounds]; lines = [self lineIndices]; + count = [lines count]; + + if(!count) return; // Find the characters that are currently visible range = [layoutManager characterRangeForGlyphRange:[layoutManager glyphRangeForBoundingRect:visibleRect inTextContainer:container] actualGlyphRange:NULL]; @@ -320,14 +328,11 @@ typedef NSRange (*RangeOfLineIMP)(id object, SEL selector, NSRange range); // It doesn't show up in the glyphs so would not be accounted for. range.length++; - count = [lines count]; - CGFloat boundsRULERMargin2 = NSWidth(bounds) - RULER_MARGIN2; CGFloat boundsWidthRULER = NSWidth(bounds) - RULER_MARGIN; CGFloat yinsetMinY = yinset - NSMinY(visibleRect); CGFloat rectHeight; - for (line = (NSUInteger)(*lineNumberForCharacterIndexIMP)(self, lineNumberForCharacterIndexSel, range.location); line < count; line++) { lineIndex = [NSArrayObjectAtIndex(lines, line) unsignedIntegerValue]; @@ -347,13 +352,16 @@ typedef NSRange (*RangeOfLineIMP)(id object, SEL selector, NSRange range); // Line numbers are internally stored starting at 0 labelText = [NSString stringWithFormat:@"%lu", (NSUInteger)(line + 1)]; - stringSize = [labelText sizeWithAttributes:textAttributes]; + // How many digits has the current line number? + NSUInteger idx = line + 1; + NSInteger numOfDigits = 0; + while(idx) { numOfDigits++; idx/=10; } rectHeight = NSHeight(rects[0]); // Draw string flush right, centered vertically within the line [labelText drawInRect: - NSMakeRect(boundsWidthRULER - stringSize.width, - yinsetMinY + NSMinY(rects[0]) + ((NSInteger)(rectHeight - stringSize.height) >> 1), + NSMakeRect(boundsWidthRULER - (maxWidthOfGlyph * numOfDigits), + yinsetMinY + NSMinY(rects[0]) + ((NSInteger)(rectHeight - maxHeightOfGlyph) >> 1), boundsRULERMargin2, rectHeight) withAttributes:textAttributes]; } @@ -494,51 +502,45 @@ typedef NSRange (*RangeOfLineIMP)(id object, SEL selector, NSRange range); - (void)calculateLines { - id view = [self clientView]; - if ([view isKindOfClass:[NSTextView class]]) + if ([clientView isKindOfClass:[NSTextView class]]) { - NSUInteger anIndex, stringLength, lineEnd, contentEnd, lastLine; + NSUInteger anIndex, stringLength, lineEnd, contentEnd; NSString *textString; CGFloat newThickness; - textString = [view string]; + textString = [clientView string]; stringLength = [textString length]; - // Switch off line numbering if text larger than 6MB + // Switch off line numbering if text larger than 3MB // for performance reasons. // TODO improve performance maybe via threading - if(stringLength>6000000) + if(stringLength>3000000) return; - if (lineIndices) [lineIndices release], lineIndices = nil; - // Init lineIndices with text length / 16 + 1 - lineIndices = [[NSMutableArray alloc] initWithCapacity:((NSUInteger)stringLength>>4)+1]; + lineIndices = [[NSMutableArray alloc] initWithCapacity:currentNumberOfLines]; anIndex = 0; // Cache loop methods for speed - RangeOfLineIMP rangeOfLineIMP = (RangeOfLineIMP)[textString methodForSelector:lineRangeForRangeSel]; + IMP rangeOfLineIMP = [textString methodForSelector:rangeOfLineSel]; addObjectIMP = [lineIndices methodForSelector:addObjectSel]; do { - (void)(*addObjectIMP)(lineIndices, addObjectSel, (*numberWithUnsignedIntegerIMP)([NSNumber class], numberWithUnsignedIntegerSel, anIndex)); - lastLine = anIndex; - anIndex = NSMaxRange((*rangeOfLineIMP)(textString, lineRangeForRangeSel, NSMakeRange(anIndex, 0))); + (void)(*addObjectIMP)(lineIndices, addObjectSel, (*numberWithUnsignedIntegerIMP)(numberClass, numberWithUnsignedIntegerSel, anIndex)); + (*rangeOfLineIMP)(textString, rangeOfLineSel, NULL, &anIndex, NULL, NSMakeRange(anIndex, 0)); } while (anIndex < stringLength); // Check if text ends with a new line. - [textString getLineStart:NULL end:&lineEnd contentsEnd:&contentEnd forRange:NSMakeRange(lastLine, 0)]; + (*rangeOfLineIMP)(textString, rangeOfLineSel, NULL, &lineEnd, &contentEnd, NSMakeRange([[lineIndices lastObject] unsignedIntValue], 0)); if (contentEnd < lineEnd) - (void)(*addObjectIMP)(lineIndices, addObjectSel, (*numberWithUnsignedIntegerIMP)([NSNumber class], numberWithUnsignedIntegerSel, anIndex)); + (void)(*addObjectIMP)(lineIndices, addObjectSel, (*numberWithUnsignedIntegerIMP)(numberClass, numberWithUnsignedIntegerSel, anIndex)); NSUInteger lineCount = [lineIndices count]; - if(lineCount < 10) - newThickness = maxWidthOfGlyph1; - else if(lineCount < 100) + if(lineCount < 100) newThickness = maxWidthOfGlyph2; else if(lineCount < 1000) newThickness = maxWidthOfGlyph3; @@ -555,7 +557,9 @@ typedef NSRange (*RangeOfLineIMP)(id object, SEL selector, NSRange range); else newThickness = 100; - if (fabs(currentRuleThickness - newThickness) > 1) + currentNumberOfLines = lineCount; + + if (currentRuleThickness != newThickness) { currentRuleThickness = newThickness; diff --git a/Source/SPAppController.m b/Source/SPAppController.m index d151a6e6..332bbbef 100644 --- a/Source/SPAppController.m +++ b/Source/SPAppController.m @@ -1221,7 +1221,6 @@ YY_BUFFER_STATE yy_scan_string (const char *); // Create a new window controller, and set up a new connection view within it. SPWindowController *newWindowController = [[SPWindowController alloc] initWithWindowNibName:@"MainWindow"]; - [newWindowController addNewConnection:self]; NSWindow *newWindow = [newWindowController window]; // Cascading defaults to on - retrieve the window origin automatically assigned by cascading, @@ -1237,6 +1236,9 @@ YY_BUFFER_STATE yy_scan_string (const char *); [newWindow setFrameUsingName:@"DBView"]; } + // Add the connection view + [newWindowController addNewConnection:self]; + // Cascade according to the statically stored cascade location. cascadeLocation = [newWindow cascadeTopLeftFromPoint:cascadeLocation]; diff --git a/Source/SPBundleEditorController.m b/Source/SPBundleEditorController.m index 5d3555fa..44768479 100644 --- a/Source/SPBundleEditorController.m +++ b/Source/SPBundleEditorController.m @@ -110,6 +110,7 @@ if(commandBundleTree) [commandBundleTree release], commandBundleTree = nil; if(sortDescriptor) [sortDescriptor release], sortDescriptor = nil; if(bundlePath) [bundlePath release], bundlePath = nil; + if (esUndoManager) [esUndoManager release], esUndoManager = nil; [super dealloc]; @@ -1239,8 +1240,8 @@ - (id)outlineView:(id)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item { - if(item && [item respondsToSelector:@selector(objectForKey:)]) - return [item objectForKey:kBundleNameKey]; + if(item && [[item representedObject] respondsToSelector:@selector(objectForKey:)]) + return [[item representedObject] objectForKey:kBundleNameKey]; return @""; } diff --git a/Source/SPCSVParser.h b/Source/SPCSVParser.h index a230d69b..1691ba47 100644 --- a/Source/SPCSVParser.h +++ b/Source/SPCSVParser.h @@ -73,6 +73,7 @@ NSScanner *csvScanner; BOOL escapeStringIsFieldQuoteString; + BOOL useStrictEscapeMatching; } /* Retrieving data from the CSV string */ @@ -96,6 +97,7 @@ - (void) setFieldQuoteString:(NSString *)theString convertDisplayStrings:(BOOL)convertString; - (void) setEscapeString:(NSString *)theString convertDisplayStrings:(BOOL)convertString; - (void) setNullReplacementString:(NSString *)nullString; +- (void) setEscapeStringsAreMatchedStrictly:(BOOL)strictMatching; /* Init and internal update methods */ - (void) _initialiseCSVParserDefaults; diff --git a/Source/SPCSVParser.m b/Source/SPCSVParser.m index 5429896e..e012e323 100644 --- a/Source/SPCSVParser.m +++ b/Source/SPCSVParser.m @@ -85,6 +85,7 @@ NSUInteger startingParserPosition, nextQuoteDistance, nextFieldEndDistance, nextLineEndDistance; NSInteger skipLength, j; BOOL fieldIsQuoted, isEscaped; + BOOL nonStrictEscapeMatchingFallback = NO; BOOL lineEndingEncountered = NO; if (fieldCount == NSNotFound) @@ -120,6 +121,7 @@ if (escapeLength && nextQuoteDistance != NSNotFound) { j = 1; isEscaped = NO; + nonStrictEscapeMatchingFallback = NO; if (!escapeStringIsFieldQuoteString) { while (j * escapeLength <= (NSInteger)nextQuoteDistance && ([[csvString substringWithRange:NSMakeRange((parserPosition + nextQuoteDistance - (j*escapeLength)), escapeLength)] isEqualToString:escapeString])) @@ -128,7 +130,13 @@ j++; } skipLength = fieldQuoteLength; - } else { + if (!useStrictEscapeMatching && !isEscaped) nonStrictEscapeMatchingFallback = YES; + } + + // If the escape string is the field quote string, check for doubled (Excel-style) usage. + // Also, if the parser is in loose mode, also support field end strings quoted by using + // another field end string, as used by Excel + if (escapeStringIsFieldQuoteString || nonStrictEscapeMatchingFallback) { if (parserPosition + nextQuoteDistance + (2 * fieldQuoteLength) <= csvStringLength && [[csvString substringWithRange:NSMakeRange(parserPosition + nextQuoteDistance + fieldQuoteLength, fieldQuoteLength)] isEqualToString:fieldQuoteString]) { @@ -143,7 +151,7 @@ // Append the matched string, together with the field quote character // which has been determined to be within the string - but append the // field end character unescaped to avoid later processing. - if (escapeStringIsFieldQuoteString) { + if (escapeStringIsFieldQuoteString || nonStrictEscapeMatchingFallback) { [csvCellString appendString:[csvString substringWithRange:NSMakeRange(parserPosition, nextQuoteDistance+fieldQuoteLength)]]; } else { [csvCellString appendString:[csvString substringWithRange:NSMakeRange(parserPosition, nextQuoteDistance - escapeLength)]]; @@ -461,6 +469,17 @@ if (nullString) nullReplacementString = [[NSString alloc] initWithString:nullString]; } +/** + * By default, field end strings aren't matched strictly - as well as the defined escape + * character, the class will automatically match doubled-up field quote strings, as exported + * by Excel and in common use (eg "field contains ""quotes"""). To switch escaping to strict + * mode, set this to YES. + */ +- (void) setEscapeStringsAreMatchedStrictly:(BOOL)strictMatching +{ + useStrictEscapeMatching = strictMatching; +} + #pragma mark - #pragma mark Init and internal update methods @@ -486,6 +505,7 @@ escapedLineEndString = [[NSString alloc] initWithString:@"\\\n"]; escapedFieldQuoteString = [[NSString alloc] initWithString:@"\\\""]; escapedEscapeString = [[NSString alloc] initWithString:@"\\\\"]; + useStrictEscapeMatching = NO; fieldEndLength = [fieldEndString length]; lineEndLength = [lineEndString length]; fieldQuoteLength = [fieldQuoteString length]; diff --git a/Source/SPConnectionController.h b/Source/SPConnectionController.h index 83c38172..5a90c985 100644 --- a/Source/SPConnectionController.h +++ b/Source/SPConnectionController.h @@ -68,11 +68,12 @@ #ifndef SP_REFACTOR /* ivars */ NSView *databaseConnectionSuperview; NSSplitView *databaseConnectionView; + NSOpenPanel *keySelectionPanel; +#endif NSUserDefaults *prefs; NSMutableArray *favorites; BOOL automaticFavoriteSelection; -#endif BOOL cancellingConnection; BOOL isConnecting; #ifndef SP_REFACTOR /* ivars */ @@ -223,6 +224,8 @@ - (IBAction)chooseKeyLocation:(id)sender; - (IBAction)showHelp:(id)sender; - (IBAction)updateSSLInterface:(id)sender; +- (IBAction)updateKeyLocationFileVisibility:(id)sender; +- (void)resizeTabViewToConnectionType:(NSUInteger)theType animating:(BOOL)animate; - (IBAction)sortFavorites:(id)sender; - (IBAction)reverseSortFavorites:(id)sender; diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index f5d02a61..2e6790f6 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -47,6 +47,10 @@ static NSString *SPImportFavorites = @"ImportFavorites"; static NSString *SPExportFavorites = @"ExportFavorites"; static NSString *SPExportFavoritesFilename = @"SequelProFavorites.plist"; +@interface NSSavePanel (NSSavePanel_unpublishedUntilSnowLeopardAPI) +- (void)setShowsHiddenFiles:(BOOL)flag; +@end + @interface SPConnectionController () - (BOOL)_checkHost; @@ -237,7 +241,13 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v * connection proxies in use. */ - (IBAction)initiateConnection:(id)sender -{ +{ + // If this action was triggered via a double-click on the favorites outline view, + // ensure that one of the connections was double-clicked, not the area above or below +#ifndef SP_REFACTOR + if (sender == favoritesOutlineView && [favoritesOutlineView clickedRow] <= 0) return; +#endif + // Ensure that host is not empty if this is a TCP/IP or SSH connection if (([self type] == SPTCPIPConnection || [self type] == SPSSHTunnelConnection) && ![[self host] length]) { SPBeginAlertSheet(NSLocalizedString(@"Insufficient connection details", @"insufficient details message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [dbDocument parentWindow], self, nil, nil, NSLocalizedString(@"Insufficient details provided to establish a connection. Please enter at least the hostname.", @"insufficient details informative message")); @@ -396,7 +406,8 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v NSString *directoryPath = nil; NSString *filePath = nil; NSArray *permittedFileTypes = nil; - NSOpenPanel *openPanel = [NSOpenPanel openPanel]; + keySelectionPanel = [NSOpenPanel openPanel]; + [keySelectionPanel setShowsHiddenFiles:[prefs boolForKey:SPHiddenKeyFileVisibilityKey]]; // Switch details by sender. // First, SSH keys: @@ -415,7 +426,7 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v } permittedFileTypes = [NSArray arrayWithObjects:@"pem", @"", nil]; - [openPanel setAccessoryView:sshKeyLocationHelp]; + [keySelectionPanel setAccessoryView:sshKeyLocationHelp]; // SSL key file location: } else if (sender == standardSSLKeyFileButton || sender == socketSSLKeyFileButton) { @@ -424,7 +435,7 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v return; } permittedFileTypes = [NSArray arrayWithObjects:@"pem", @"key", @"", nil]; - [openPanel setAccessoryView:sslKeyFileLocationHelp]; + [keySelectionPanel setAccessoryView:sslKeyFileLocationHelp]; // SSL certificate file location: } else if (sender == standardSSLCertificateButton || sender == socketSSLCertificateButton) { @@ -433,7 +444,7 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v return; } permittedFileTypes = [NSArray arrayWithObjects:@"pem", @"cert", @"crt", @"", nil]; - [openPanel setAccessoryView:sslCertificateLocationHelp]; + [keySelectionPanel setAccessoryView:sslCertificateLocationHelp]; // SSL CA certificate file location: } else if (sender == standardSSLCACertButton || sender == socketSSLCACertButton) { @@ -442,10 +453,10 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v return; } permittedFileTypes = [NSArray arrayWithObjects:@"pem", @"cert", @"crt", @"", nil]; - [openPanel setAccessoryView:sslCACertLocationHelp]; + [keySelectionPanel setAccessoryView:sslCACertLocationHelp]; } - [openPanel beginSheetForDirectory:directoryPath + [keySelectionPanel beginSheetForDirectory:directoryPath file:filePath types:permittedFileTypes modalForWindow:[dbDocument parentWindow] @@ -470,6 +481,14 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v [self resizeTabViewToConnectionType:[self type] animating:YES]; } +/** + * Toggle hidden file visiblity in response to accessory view changes + */ +- (IBAction)updateKeyLocationFileVisibility:(id)sender +{ + [keySelectionPanel setShowsHiddenFiles:[prefs boolForKey:SPHiddenKeyFileVisibilityKey]]; +} + #pragma mark - #pragma mark Connection details interaction and display diff --git a/Source/SPConstants.h b/Source/SPConstants.h index 502f0d81..41cad9ec 100644 --- a/Source/SPConstants.h +++ b/Source/SPConstants.h @@ -223,9 +223,7 @@ typedef enum #define SPLOCALIZEDURL_CONNECTIONHELP NSLocalizedString(@"http://www.sequelpro.com/docs/Getting_Connected", @"Localized connection help page - do not localize if no translated webpage is available") #define SPLOCALIZEDURL_TRANSLATIONFEEDBACK NSLocalizedString(@"http://dev.sequelpro.com/translate/feedback", @"Localized translation feedback page - do not localize if no translated webpage is available") #define SPLOCALIZEDURL_BUNDLEEDITORHELP NSLocalizedString(@"http://www.sequelpro.com/docs/Bundle_Editor", @"Localized help page for bundle editor - do not localize if no translated webpage is available") - -// Long running notification time for Growl messages -extern const CGFloat SPLongRunningNotificationTime; +#define SPLOCALIZEDURL_CONTENTFILTERHELP NSLocalizedString(@"http://www.sequelpro.com/docs/Content_Filters", @"Localized help page for content filter - do not localize if no translated webpage is available") // Narrow down completion max rows extern const NSUInteger SPNarrowDownCompletionMaxRows; @@ -286,7 +284,6 @@ extern NSString *SPNullValue; extern NSString *SPGlobalResultTableFont; extern NSString *SPFilterTableDefaultOperator; extern NSString *SPFilterTableDefaultOperatorLastItems; -extern NSString *SPAlphabeticalTableSorting; // Favorites Prefpane extern NSString *SPFavorites; @@ -356,6 +353,8 @@ extern NSString *SPResetAutoIncrementAfterDeletionOfAllRows; // Hidden Prefs extern NSString *SPPrintWarningRowLimit; extern NSString *SPDisplayServerVersionInWindowTitle; +extern NSString *SPLongRunningQueryNotificationTime; +extern NSString *SPAlphabeticalTableSorting; // Import and export extern NSString *SPCSVImportFieldTerminator; @@ -391,6 +390,7 @@ extern NSString *SPLastImportIntoNewTableEncoding; extern NSString *SPLastImportIntoNewTableType; extern NSString *SPGlobalValueHistory; extern NSString *SPBundleDeletedDefaultBundlesKey; +extern NSString *SPHiddenKeyFileVisibilityKey; // URLs extern NSString *SPDonationsURL; diff --git a/Source/SPConstants.m b/Source/SPConstants.m index ecb91e41..1ce6181b 100644 --- a/Source/SPConstants.m +++ b/Source/SPConstants.m @@ -25,9 +25,6 @@ #import "SPConstants.h" -// Long running notification time for Growl messages -const CGFloat SPLongRunningNotificationTime = 3.0f; - // Narrow down completion max rows const NSUInteger SPNarrowDownCompletionMaxRows = 15; @@ -92,7 +89,6 @@ NSString *SPNullValue = @"SPNullValue"; NSString *SPGlobalResultTableFont = @"GlobalResultTableFont"; NSString *SPFilterTableDefaultOperator = @"FilterTableDefaultOperator"; NSString *SPFilterTableDefaultOperatorLastItems = @"FilterTableDefaultOperatorLastItems"; -NSString *SPAlphabeticalTableSorting = @"AlphabeticalTableSorting"; // Favorites Prefpane NSString *SPFavorites = @"favorites"; @@ -162,6 +158,8 @@ NSString *SPResetAutoIncrementAfterDeletionOfAllRows = @"ResetAutoIncrementAfter // Hidden Prefs NSString *SPPrintWarningRowLimit = @"PrintWarningRowLimit"; NSString *SPDisplayServerVersionInWindowTitle = @"DisplayServerVersionInWindowTitle"; +NSString *SPLongRunningQueryNotificationTime = @"LongRunningQueryNotificationTime"; +NSString *SPAlphabeticalTableSorting = @"AlphabeticalTableSorting"; // Import and export NSString *SPCSVImportFieldEnclosedBy = @"CSVImportFieldEnclosedBy"; @@ -197,9 +195,10 @@ NSString *SPLastImportIntoNewTableEncoding = @"LastImportIntoNewTableEncod NSString *SPLastImportIntoNewTableType = @"LastImportIntoNewTableType"; NSString *SPGlobalValueHistory = @"GlobalValueHistory"; NSString *SPBundleDeletedDefaultBundlesKey = @"deletedDefaultBundles"; +NSString *SPHiddenKeyFileVisibilityKey = @"KeySelectionHiddenFilesVisibility"; // URLs -NSString *SPDonationsURL = @"http://www.sequelpro.com/donate.html"; +NSString *SPDonationsURL = @"http://www.sequelpro.com/donate/"; NSString *SPMySQLSearchURL = @"http://search.mysql.com/search/query/search?q=%@&group=refman-%@"; NSString *SPDevURL = @"http://code.google.com/p/sequel-pro/"; diff --git a/Source/SPContentFilterManager.m b/Source/SPContentFilterManager.m index 57ff7dac..0d4b7c18 100644 --- a/Source/SPContentFilterManager.m +++ b/Source/SPContentFilterManager.m @@ -27,6 +27,7 @@ #import "ImageAndTextCell.h" #import "RegexKitLite.h" #import "SPQueryController.h" +#import "SPQueryDocumentsController.h" #import "SPTableContent.h" #import "SPConnectionController.h" #ifndef SP_REFACTOR /* headers */ @@ -53,16 +54,16 @@ contentFilters = [[NSMutableArray alloc] init]; - if(managerDelegate == nil) { + if (managerDelegate == nil) { NSBeep(); NSLog(@"ContentFilterManager was called without a delegate."); return nil; } + tableDocumentInstance = [managerDelegate valueForKeyPath:@"tableDocumentInstance"]; delegatesFileURL = [tableDocumentInstance fileURL]; filterType = [NSString stringWithString:compareType]; - } return self; @@ -71,6 +72,7 @@ - (void)dealloc { [contentFilters release]; + [super dealloc]; } @@ -79,7 +81,6 @@ */ - (void)awakeFromNib { - // Add global group row to contentFilters [contentFilters addObject:[NSDictionary dictionaryWithObjectsAndKeys: NSLocalizedString(@"Global",@"Content Filter Manager : Filter Entry List: 'Global' Header"), @"MenuLabel", @@ -107,6 +108,7 @@ [delegatesFileURL absoluteString], @"headerOfFileURL", @"", @"Clause", nil]]; + if([[SPQueryController sharedQueryController] contentFilterForFileURL:delegatesFileURL]) { id filters = [[SPQueryController sharedQueryController] contentFilterForFileURL:delegatesFileURL]; if([filters objectForKey:filterType]) @@ -117,6 +119,7 @@ // Select the first query if any NSUInteger i = 0; + for(i=0; i < [contentFilters count]; i++ ) if(![[contentFilters objectAtIndex:i] objectForKey:@"headerOfFileURL"]) break; @@ -174,11 +177,13 @@ i++; break; } + i++; } // Take all content filters until the next header or end of all content filters NSUInteger numOfArgs; + for ( ; i<[contentFilters count]; i++) { if(![[contentFilters objectAtIndex:i] objectForKey:@"headerOfFileURL"]) { @@ -215,7 +220,6 @@ return [tableDocumentInstance valueForKey:@"customQueryInstance"]; } - #pragma mark - #pragma mark IBAction methods @@ -224,7 +228,6 @@ */ - (IBAction)addContentFilter:(id)sender { - NSMutableDictionary *filter; NSUInteger insertIndex; @@ -255,7 +258,6 @@ [removeButton setEnabled:([contentFilterTableView numberOfSelectedRows] > 0)]; [[self window] makeFirstResponder:contentFilterNameTextField]; - } /** @@ -421,12 +423,13 @@ */ - (void)tableViewSelectionDidChange:(NSNotification *)aNotification { - if([contentFilterTableView selectedRow] > -1) { + NSInteger row = [contentFilterTableView selectedRow]; + + if ((row > -1) && (row < (NSInteger)[contentFilters count])) { + NSString *newName = [[contentFilters objectAtIndex:[contentFilterTableView selectedRow]] objectForKey:@"MenuLabel"]; - if(newName) - [contentFilterNameTextField setStringValue:newName]; - else - [contentFilterNameTextField setStringValue:@""]; + + [contentFilterNameTextField setStringValue:(newName) ? newName : @""]; } } @@ -442,8 +445,8 @@ * Returns the value for the requested table column and row index. */ - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex -{ - if(![[contentFilters objectAtIndex:rowIndex] objectForKey:[aTableColumn identifier]]) return @""; +{ + if (![[contentFilters objectAtIndex:rowIndex] objectForKey:[aTableColumn identifier]]) return @""; return [[contentFilters objectAtIndex:rowIndex] objectForKey:[aTableColumn identifier]]; } @@ -791,28 +794,28 @@ */ - (void)sheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo { - // Is disabled - do we need that? - // if ([contextInfo isEqualToString:@"removeAllFavorites"]) { - // if (returnCode == NSAlertAlternateReturn) { - // [favorites removeObjects:[queryFavoritesController arrangedObjects]]; - // } - // } - if([contextInfo isEqualToString:@"removeSelectedFilters"]) { + if ([contextInfo isEqualToString:@"removeSelectedFilters"]) { if (returnCode == NSAlertDefaultReturn) { NSIndexSet *indexes = [contentFilterTableView selectedRowIndexes]; - // get last index + // Get last index NSUInteger currentIndex = [indexes lastIndex]; - while (currentIndex != NSNotFound) { + while (currentIndex != NSNotFound) + { [contentFilters removeObjectAtIndex:currentIndex]; - // get next index (beginning from the end) + + // Get next index (beginning from the end) currentIndex = [indexes indexLessThanIndex:currentIndex]; } - + + if ([contentFilters count] == 2) { + [contentFilterNameTextField setStringValue:@""]; + } + [contentFilterArrayController rearrangeObjects]; [contentFilterTableView reloadData]; - + // Set focus to filter list to avoid an unstable state [[self window] makeFirstResponder:contentFilterTableView]; @@ -826,7 +829,6 @@ */ - (void)importPanelDidEnd:(NSOpenPanel *)panel returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo { - if (returnCode == NSOKButton) { NSString *filename = [[panel filenames] objectAtIndex:0]; diff --git a/Source/SPCopyTable.h b/Source/SPCopyTable.h index 87c4ec8c..a8a80d09 100644 --- a/Source/SPCopyTable.h +++ b/Source/SPCopyTable.h @@ -171,6 +171,17 @@ - (BOOL)isCellEditingMode; - (BOOL)isCellComplex; +/*! + @method shouldUseFieldEditorForRow:column: + @abstract Determine whether to trigger sheet editing or in-cell editing for a cell + @discussion Checks the column data type, and the cell contents if necessary, to check + the most appropriate editing type. + @param rowIndex The row in the table the cell is present in + @param colIndex The *original* column in the table the cell is present in (ie pre-reordering) + @result YES if sheet editing should be used, NO otherwise. +*/ +- (BOOL)shouldUseFieldEditorForRow:(NSUInteger)rowIndex column:(NSUInteger)colIndex; + - (IBAction)executeBundleItemForDataTable:(id)sender; - (void)selectTableRows:(NSArray*)rowIndices; diff --git a/Source/SPCopyTable.m b/Source/SPCopyTable.m index e1b7e773..36f1f6b3 100644 --- a/Source/SPCopyTable.m +++ b/Source/SPCopyTable.m @@ -161,7 +161,7 @@ NSInteger kBlobAsImageFile = 4; // Create an array of table column mappings for fast iteration NSUInteger *columnMappings = malloc(numColumns * sizeof(NSUInteger)); for ( c = 0; c < numColumns; c++ ) - columnMappings[c] = [[NSArrayObjectAtIndex(columns, c) identifier] unsignedIntValue]; + columnMappings[c] = (NSUInteger)[[NSArrayObjectAtIndex(columns, c) identifier] integerValue]; // Loop through the rows, adding their descriptive contents NSUInteger rowIndex = [selectedRows firstIndex]; @@ -299,7 +299,7 @@ NSInteger kBlobAsImageFile = 4; // Create an array of table column mappings for fast iteration NSUInteger *columnMappings = malloc(numColumns * sizeof(NSUInteger)); for ( c = 0; c < numColumns; c++ ) - columnMappings[c] = [[NSArrayObjectAtIndex(columns, c) identifier] unsignedIntValue]; + columnMappings[c] = (NSUInteger)[[NSArrayObjectAtIndex(columns, c) identifier] integerValue]; // Loop through the rows, adding their descriptive contents NSUInteger rowIndex = [selectedRows firstIndex]; @@ -443,7 +443,7 @@ NSInteger kBlobAsImageFile = 4; NSUInteger *columnMappings = malloc(numColumns * sizeof(NSUInteger)); NSUInteger *columnTypes = malloc(numColumns * sizeof(NSUInteger)); for ( c = 0; c < numColumns; c++) { - columnMappings[c] = [[NSArrayObjectAtIndex(columns, c) identifier] unsignedIntValue]; + columnMappings[c] = (NSUInteger)[[NSArrayObjectAtIndex(columns, c) identifier] integerValue]; NSString *t = [NSArrayObjectAtIndex(columnDefinitions, columnMappings[c]) objectForKey:@"typegrouping"]; @@ -614,7 +614,7 @@ NSInteger kBlobAsImageFile = 4; // Create an array of table column mappings for fast iteration NSUInteger *columnMappings = malloc(numColumns * sizeof(NSUInteger)); for ( c = 0; c < numColumns; c++ ) - columnMappings[c] = [[NSArrayObjectAtIndex(columns, c) identifier] unsignedIntValue]; + columnMappings[c] = (NSUInteger)[[NSArrayObjectAtIndex(columns, c) identifier] integerValue]; // Loop through the rows, adding their descriptive contents NSUInteger rowIndex = [selectedRows firstIndex]; @@ -714,7 +714,7 @@ NSInteger kBlobAsImageFile = 4; if ([[NSThread currentThread] isCancelled]) return nil; columnWidth = [self autodetectWidthForColumnDefinition:columnDefinition maxRows:100]; - [columnWidths setObject:[NSNumber numberWithUnsignedInteger:columnWidth] forKey:[columnDefinition objectForKey:@"datacolumnindex"]]; + [columnWidths setObject:[NSString stringWithFormat:@"%llu", (unsigned long long)columnWidth] forKey:[columnDefinition objectForKey:@"datacolumnindex"]]; allColumnWidths += columnWidth; } @@ -724,7 +724,7 @@ NSInteger kBlobAsImageFile = 4; // Look for columns that are wider than the multi-column max for (NSString *columnIdentifier in columnWidths) { - columnWidth = [[columnWidths objectForKey:columnIdentifier] unsignedIntegerValue]; + columnWidth = [[columnWidths objectForKey:columnIdentifier] integerValue]; if (columnWidth > SP_MAX_CELL_WIDTH_MULTICOLUMN) availableWidthToReduce += columnWidth - SP_MAX_CELL_WIDTH_MULTICOLUMN; } @@ -736,7 +736,7 @@ NSInteger kBlobAsImageFile = 4; if (widthToReduce) { NSArray *columnIdentifiers = [columnWidths allKeys]; for (NSString *columnIdentifier in columnIdentifiers) { - columnWidth = [[columnWidths objectForKey:columnIdentifier] unsignedIntegerValue]; + columnWidth = [[columnWidths objectForKey:columnIdentifier] integerValue]; if (columnWidth > SP_MAX_CELL_WIDTH_MULTICOLUMN) { columnWidth -= ceil((double)(columnWidth - SP_MAX_CELL_WIDTH_MULTICOLUMN) / availableWidthToReduce * widthToReduce); [columnWidths setObject:[NSNumber numberWithUnsignedInteger:columnWidth] forKey:columnIdentifier]; @@ -759,12 +759,13 @@ NSInteger kBlobAsImageFile = 4; NSUInteger cellWidth, maxCellWidth, i; NSRange linebreakRange; double rowStep; + unichar breakChar; #ifndef SP_REFACTOR /* patch */ NSFont *tableFont = [NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPGlobalResultTableFont]]; #else NSFont *tableFont = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]; #endif - NSUInteger columnIndex = [[columnDefinition objectForKey:@"datacolumnindex"] unsignedIntegerValue]; + NSUInteger columnIndex = (NSUInteger)[[columnDefinition objectForKey:@"datacolumnindex"] integerValue]; NSDictionary *stringAttributes = [NSDictionary dictionaryWithObject:tableFont forKey:NSFontAttributeName]; Class mcpGeometryData = [MCPGeometryData class]; @@ -807,10 +808,26 @@ NSInteger kBlobAsImageFile = 4; contentString = [contentString substringToIndex:500]; } - // If any linebreaks are present, use only the visible part of the string - linebreakRange = [contentString rangeOfCharacterFromSet:[NSCharacterSet newlineCharacterSet]]; + // If any linebreaks are present, they are displayed as single characters; replace them with pilcrow/ + // reverse pilcrow to match display output width. + linebreakRange = [contentString rangeOfCharacterFromSet:[NSCharacterSet newlineCharacterSet] options:NSLiteralSearch]; if (linebreakRange.location != NSNotFound) { - contentString = [contentString substringToIndex:linebreakRange.location]; + NSMutableString *singleLineString = [[[NSMutableString alloc] initWithString:contentString] autorelease]; + while (linebreakRange.location != NSNotFound) { + breakChar = [singleLineString characterAtIndex:linebreakRange.location]; + switch (breakChar) { + case '\n': + [singleLineString replaceCharactersInRange:linebreakRange withString:@"¶"]; + break; + default: + [singleLineString replaceCharactersInRange:linebreakRange withString:@"⁋"]; + if (breakChar == '\r' && NSMaxRange(linebreakRange) < [singleLineString length] && [singleLineString characterAtIndex:linebreakRange.location+1] == '\n') { + [singleLineString deleteCharactersInRange:NSMakeRange(linebreakRange.location+1, 1)]; + } + } + linebreakRange = [singleLineString rangeOfCharacterFromSet:[NSCharacterSet newlineCharacterSet] options:NSLiteralSearch]; + } + contentString = singleLineString; } } @@ -1050,6 +1067,11 @@ NSInteger kBlobAsImageFile = 4; if([self isCellComplex]) return NO; + // Check whether the editor is multiline - if so, allow the arrow down to change selection if it's not + // on the final line + if (NSMaxRange([[textView string] lineRangeForRange:[textView selectedRange]]) < [[textView string] length]) + return NO; + NSInteger newRow = row+1; #ifndef SP_REFACTOR if (newRow>=[[self delegate] numberOfRowsInTableView:self]) return YES; //check if we're already at the end of the list @@ -1078,7 +1100,12 @@ NSInteger kBlobAsImageFile = 4; { // If enum field is edited ARROW key navigates through the popup list - if([self isCellComplex]) + if ([self isCellComplex]) + return NO; + + // Check whether the editor is multiline - if so, allow the arrow up to change selection if it's not + // on the first line + if ([[textView string] lineRangeForRange:[textView selectedRange]].location > 0) return NO; if (row==0) return YES; //already at the beginning of the list @@ -1137,6 +1164,45 @@ NSInteger kBlobAsImageFile = 4; } #pragma mark - +#pragma mark Field editing checks + +/** + * Determine whether to use the sheet for editing; do so if the multipleLineEditingButton is enabled, + * or if the column was a blob or a text, or if it contains linebreaks. + */ +- (BOOL)shouldUseFieldEditorForRow:(NSUInteger)rowIndex column:(NSUInteger)colIndex +{ + + // Retrieve the column definition + NSDictionary *columnDefinition = [[[self delegate] dataColumnDefinitions] objectAtIndex:colIndex]; + NSString *columnType = [columnDefinition objectForKey:@"typegrouping"]; + + // Return YES if the multiple line editing button is enabled - triggers sheet editing on all cells. +#ifndef SP_REFACTOR + if ([prefs boolForKey:SPEditInSheetEnabled]) return YES; +#endif + + // If the column is a BLOB or TEXT column, and not an enum, trigger sheet editing + BOOL isBlob = ([columnType isEqualToString:@"textdata"] || [columnType isEqualToString:@"blobdata"]); + if (isBlob && ![columnType isEqualToString:@"enum"]) return YES; + + // Otherwise, check the cell value for newlines. + id cellValue = [tableStorage cellDataAtRow:rowIndex column:colIndex]; + if ([cellValue isKindOfClass:[NSData class]]) { + cellValue = [[[NSString alloc] initWithData:cellValue encoding:[mySQLConnection stringEncoding]] autorelease]; + } + if (![cellValue isNSNull] + && [columnType isEqualToString:@"string"] + && [cellValue rangeOfCharacterFromSet:[NSCharacterSet newlineCharacterSet] options:NSLiteralSearch].location != NSNotFound) + { + return YES; + } + + // Otherwise, use standard editing + return NO; +} + +#pragma mark - #pragma mark Bundle Command Support - (IBAction)executeBundleItemForDataTable:(id)sender @@ -1273,7 +1339,7 @@ NSInteger kBlobAsImageFile = 4; NSUInteger *columnMappings = malloc(numColumns * sizeof(NSUInteger)); NSUInteger c; for ( c = 0; c < numColumns; c++ ) - columnMappings[c] = [[NSArrayObjectAtIndex(columns, c) identifier] unsignedIntValue]; + columnMappings[c] = (NSUInteger)[[NSArrayObjectAtIndex(columns, c) identifier] integerValue]; NSMutableString *tableMetaData = [NSMutableString string]; if([[self delegate] isKindOfClass:[SPCustomQuery class]]) { diff --git a/Source/SPCustomQuery.h b/Source/SPCustomQuery.h index 8d289f70..aa78a250 100644 --- a/Source/SPCustomQuery.h +++ b/Source/SPCustomQuery.h @@ -238,6 +238,7 @@ // Accessors - (NSArray *)currentResult; +- (NSArray *)currentDataResultWithNULLs:(BOOL)includeNULLs; - (void)processResultIntoDataStorage:(MCPStreamingResult *)theResult; // Retrieving and setting table state @@ -278,7 +279,8 @@ - (void)commentOutCurrentQueryTakingSelection:(BOOL)takeSelection; - (NSString *)usedQuery; - (NSString *)argumentForRow:(NSUInteger)rowIndex ofTable:(NSString *)tableForColumn andDatabase:(NSString *)database includeBlobs:(BOOL)includeBlobs; -- (NSArray*)fieldEditStatusForRow:(NSInteger)rowIndex andColumn:(NSNumber *)columnIndex; +- (void)saveCellValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSUInteger)rowIndex; +- (NSArray*)fieldEditStatusForRow:(NSInteger)rowIndex andColumn:(NSInteger)columnIndex; - (NSUInteger)numberOfQueries; - (NSRange)currentQueryRange; - (NSString *)buildHistoryString; diff --git a/Source/SPCustomQuery.m b/Source/SPCustomQuery.m index 26810323..272c0ebf 100644 --- a/Source/SPCustomQuery.m +++ b/Source/SPCustomQuery.m @@ -37,6 +37,7 @@ #import "SPTooltip.h" #import "SPQueryFavoriteManager.h" #import "SPQueryController.h" +#import "SPQueryDocumentsController.h" #import "SPEncodingPopupAccessory.h" #import "SPDataStorage.h" #import "SPAlertSheets.h" @@ -49,8 +50,23 @@ #import <BWToolkitFramework/BWToolkitFramework.h> #endif +@interface SPCustomQuery (PrivateAPI) + +- (id)_resultDataItemAtRow:(NSInteger)row columnIndex:(NSUInteger)column; +- (id)_convertResultDataValueToDisplayableRepresentation:(id)value whilePreservingNULLs:(BOOL)preserveNULLs; + +@end + @implementation SPCustomQuery +#ifdef SP_REFACTOR +@synthesize textView; +@synthesize customQueryView; +@synthesize runAllButton; +@synthesize tableDocumentInstance; +@synthesize tablesListInstance; +#endif + @synthesize textViewWasChanged; #pragma mark IBAction methods @@ -110,9 +126,8 @@ [self performQueries:queries withCallback:@selector(runAllQueriesCallback)]; } -- (void) runAllQueriesCallback +- (void)runAllQueriesCallback { - // If no error was selected, reconstruct a given selection. This // may no longer be valid if the query text has changed in the // meantime, so error-checking is required. @@ -310,7 +325,7 @@ } } -/* +/** * Closes the sheet */ - (IBAction)closeSheet:(id)sender @@ -319,7 +334,7 @@ [[sender window] orderOut:self]; } -/* +/** * Perform simple actions (which don't require their own method), triggered by selecting the appropriate menu item * in the "gear" action menu displayed beneath the cusotm query view. */ @@ -466,7 +481,6 @@ - (IBAction)copyQueryHistory:(id)sender { - NSPasteboard *pb = [NSPasteboard generalPasteboard]; [pb declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil]; @@ -474,10 +488,11 @@ } -// "Clear History" menu item - clear query history +/** + * 'Clear History' menu item - clear query history + */ - (IBAction)clearQueryHistory:(id)sender { - NSString *infoString; #ifndef SP_REFACTOR /* if ([tableDocumentInstance isUntitled]) */ @@ -511,7 +526,7 @@ } -/* +/* * * Set font panel's valid modes */ - (NSUInteger)validModesForFontPanel:(NSFontPanel *)fontPanel @@ -532,18 +547,21 @@ #pragma mark - #pragma mark Query actions -/* +/** * Performs the mysql-query given by the user * sets the tableView columns corresponding to the mysql-result */ - (void)performQueries:(NSArray *)queries withCallback:(SEL)customQueryCallbackMethod; { NSString *taskString; + if ([queries count] > 1) { taskString = [NSString stringWithFormat:NSLocalizedString(@"Running query %i of %lu...", @"Running multiple queries string"), 1, (unsigned long)[queries count]]; - } else { + } + else { taskString = NSLocalizedString(@"Running query...", @"Running single query string"); } + [tableDocumentInstance startTaskWithDescription:taskString]; [errorText setString:taskString]; [affectedRowsText setStringValue:@""]; @@ -556,7 +574,8 @@ // If a helper thread is already running, execute inline - otherwise detach a new thread for the queries if ([NSThread isMainThread]) { [NSThread detachNewThreadSelector:@selector(performQueriesTask:) toTarget:self withObject:taskArguments]; - } else { + } + else { [self performQueriesTask:taskArguments]; } } @@ -584,11 +603,7 @@ #endif // Notify listeners that a query has started -#ifndef SP_REFACTOR [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryWillBePerformed" object:tableDocumentInstance]; -#else - [[NSNotificationCenter defaultCenter] sequelProPostNotificationOnMainThreadWithName:@"SMySQLQueryWillBePerformed" object:tableDocumentInstance]; -#endif #ifndef SP_REFACTOR /* growl */ // Start the notification timer to allow notifications to be shown even if frontmost for long queries @@ -855,11 +870,7 @@ [customQueryView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:YES]; // Notify any listeners that the query has completed -#ifndef SP_REFACTOR [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; -#else - [[NSNotificationCenter defaultCenter] sequelProPostNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; -#endif #ifndef SP_REFACTOR /* growl */ // Perform the Growl notification for query completion @@ -892,11 +903,7 @@ } //query finished -#ifndef SP_REFACTOR [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; -#else - [[NSNotificationCenter defaultCenter] sequelProPostNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; -#endif #ifndef SP_REFACTOR /* growl */ // Query finished Growl notification @@ -922,10 +929,9 @@ [[tableDocumentInstance parentWindow] makeFirstResponder:customQueryView]; [queryRunningPool release]; - } -/* +/** * Processes a supplied streaming result set, loading it into the data array. */ - (void)processResultIntoDataStorage:(MCPStreamingResult *)theResult @@ -978,7 +984,7 @@ [dataLoadingPool drain]; } -/* +/** * Retrieve the range of the query at a position specified * within the custom query text view. */ @@ -1103,7 +1109,7 @@ return queryRange; } -/* +/** * Retrieve the range of the query for the passed index seen from a start position * specified within the custom query text view. */ @@ -1144,7 +1150,7 @@ return theQueryRange; } -/* +/** * Retrieve the query at a position specified within the custom query * text view. This will return nil if the position specified is beyond * the available string or if an empty query would be returned. @@ -1167,13 +1173,12 @@ [textView setSelectedRange:currentQueryRange]; } -/* +/** * Add or remove "⁄* *⁄" for each line in the current query * a given selection */ - (void)commentOutCurrentQueryTakingSelection:(BOOL)takeSelection { - BOOL isUncomment = NO; NSRange oldRange = [textView selectedRange]; @@ -1216,10 +1221,9 @@ // something like /*!400000 or similar if(!isUncomment) [textView setSelectedRange:NSMakeRange(workingRange.location+2,0)]; - } -/* +/** * Add or remove "-- " for each line in the current query or selection, * if the selection is in-line wrap selection into ⁄* block comments and * place the caret after ⁄* to allow to enter !xxxxxx e.g. @@ -1269,12 +1273,10 @@ // allow a fast (un)commenting of lines [textView setSelectedRange:lineRange]; [textView insertText:n]; - } - } -/* +/** * Update the interface to reflect the query error state. * Should be performed on the main thread. */ @@ -1375,6 +1377,7 @@ - (void) initQueryLoadTimer { if (queryLoadTimer) [self clearQueryLoadTimer]; + queryLoadInterfaceUpdateInterval = 1; queryLoadLastRowCount = 0; queryLoadTimerTicksSinceLastUpdate = 0; @@ -1435,48 +1438,65 @@ queryLoadInterfaceUpdateInterval = 25; break; } + queryLoadTimerTicksSinceLastUpdate = 0; } #pragma mark - #pragma mark Accessors -/* - * Returns the current result (as shown in custom result view) as array, - * the first object containing the field names as array, - * the following objects containing the rows as array +/** + * Returns the current result (as shown in custom result view) as an array, the first object containing + * the field names as an array and the following objects containing the rows as arrays. */ - (NSArray *)currentResult +{ + return [self currentDataResultWithNULLs:NO]; +} + +/** + * Returns the current result (as shown in custom result view) as an array, the first object containing + * the field names as an array and the following objects containing the rows as arrays. + * + * @param includeNULLs Indicates whether to include NULLs as a native type + * or use the user's NULL string representation preference. + */ +- (NSArray *)currentDataResultWithNULLs:(BOOL)includeNULLs { - NSArray *tableColumns = [customQueryView tableColumns]; - NSEnumerator *enumerator = [tableColumns objectEnumerator]; + NSInteger i; id tableColumn; - NSMutableArray *currentResult = [NSMutableArray array]; - NSMutableArray *tempRow = [NSMutableArray array]; - NSInteger i; - - //set field names as first line - while ( (tableColumn = [enumerator nextObject]) ) { + NSMutableArray *tempRow = [[NSMutableArray alloc] init]; + + // Set field names as first line + for (tableColumn in [customQueryView tableColumns]) + { [tempRow addObject:[[tableColumn headerCell] stringValue]]; } - [currentResult addObject:[NSArray arrayWithArray:tempRow]]; + NSMutableArray *currentResult = [NSMutableArray array]; + + [currentResult addObject:[NSArray arrayWithArray:tempRow]]; + //add rows for ( i = 0 ; i < [self numberOfRowsInTableView:customQueryView] ; i++) { [tempRow removeAllObjects]; - enumerator = [tableColumns objectEnumerator]; + NSEnumerator *enumerator = [[customQueryView tableColumns] objectEnumerator]; while ( (tableColumn = [enumerator nextObject]) ) { - [tempRow addObject:[self tableView:customQueryView objectValueForTableColumn:tableColumn row:i]]; + id value = [self _resultDataItemAtRow:i columnIndex:[[tableColumn identifier] integerValue]]; + + [tempRow addObject:[self _convertResultDataValueToDisplayableRepresentation:value whilePreservingNULLs:YES]]; } [currentResult addObject:[NSArray arrayWithArray:tempRow]]; } + [tempRow release]; + return currentResult; } #pragma mark - #pragma mark Additional methods -/* +/** * Sets the connection (received from SPDatabaseDocument) and makes things that have to be done only once */ - (void)setConnection:(MCPConnection *)theConnection @@ -1511,7 +1531,7 @@ [runSelectionMenuItem setEnabled:NO]; } -/* +/** * Inserts the query in the textView and performs query */ - (void)doPerformQueryService:(NSString *)query @@ -1522,6 +1542,7 @@ [textView scrollRangeToVisible:NSMakeRange([query length], 0)]; [self runAllQueries:self]; } + - (void)doPerformLoadQueryService:(NSString *)query { [textView shouldChangeTextInRange:NSMakeRange(0, [[textView string] length]) replacementString:query]; @@ -1609,21 +1630,12 @@ [customQueryView addTableColumn:theCol]; [theCol release]; } - - [customQueryView sizeLastColumnToFit]; - - //tries to fix problem with last row (otherwise to small) - //sets last column to width of the first if smaller than 30 - //problem not fixed for resizing window - if ( [[customQueryView tableColumnWithIdentifier:[NSNumber numberWithInteger:[theColumns count]-1]] width] < 30 ) - [[customQueryView tableColumnWithIdentifier:[NSNumber numberWithInteger:[theColumns count]-1]] - setWidth:[[customQueryView tableColumnWithIdentifier:[NSNumber numberWithInteger:0]] width]]; } /** * Provide a getter for the custom query result table's selected rows index set */ -- (NSIndexSet *) resultSelectedRowIndexes +- (NSIndexSet *)resultSelectedRowIndexes { return [customQueryView selectedRowIndexes]; } @@ -1631,7 +1643,7 @@ /** * Provide a getter for the custom query result table's current viewport */ -- (NSRect) resultViewport +- (NSRect)resultViewport { return [customQueryView visibleRect]; } @@ -1647,7 +1659,7 @@ /** * Set the selected row indexes to restore on next custom query result table load */ -- (void) setResultSelectedRowIndexesToRestore:(NSIndexSet *)theIndexSet +- (void)setResultSelectedRowIndexesToRestore:(NSIndexSet *)theIndexSet { if (selectionIndexToRestore) [selectionIndexToRestore release], selectionIndexToRestore = nil; @@ -1657,7 +1669,7 @@ /** * Set the viewport to restore on next table load */ -- (void) setResultViewportToRestore:(NSRect)theViewport +- (void)setResultViewportToRestore:(NSRect)theViewport { selectionViewportToRestore = theViewport; } @@ -1665,7 +1677,7 @@ /** * Convenience method for storing all current settings for restoration */ -- (void) storeCurrentResultViewForRestoration +- (void)storeCurrentResultViewForRestoration { [self setResultSelectedRowIndexesToRestore:[self resultSelectedRowIndexes]]; [self setResultViewportToRestore:[self resultViewport]]; @@ -1674,7 +1686,7 @@ /** * Convenience method for clearing any settings to restore */ -- (void) clearResultViewDetailsToRestore +- (void)clearResultViewDetailsToRestore { [self setResultSelectedRowIndexesToRestore:nil]; [self setResultViewportToRestore:NSZeroRect]; @@ -1699,33 +1711,26 @@ // Otherwise set the column width NSTableColumn *aTableColumn = [customQueryView tableColumnWithIdentifier:[columnDefinition objectForKey:@"datacolumnindex"]]; - NSUInteger targetWidth = [[columnWidths objectForKey:[columnDefinition objectForKey:@"datacolumnindex"]] unsignedIntegerValue]; + NSUInteger targetWidth = [[columnWidths objectForKey:[columnDefinition objectForKey:@"datacolumnindex"]] integerValue]; [aTableColumn setWidth:targetWidth]; } + [customQueryView setDelegate:self]; } #pragma mark - #pragma mark Field Editing -/* +/** * Check if table cell is editable * Returns as array the minimum number of possible changes or * -1 if no table name can be found or multiple table origins * -2 for other errors * and the used WHERE clause to identify */ -- (NSArray*)fieldEditStatusForRow:(NSInteger)rowIndex andColumn:(NSNumber*)columnIndex +- (NSArray*)fieldEditStatusForRow:(NSInteger)rowIndex andColumn:(NSInteger)columnIndex { - NSDictionary *columnDefinition = nil; - - // Retrieve the column defintion - for(id c in cqColumnDefinition) { - if([[c objectForKey:@"datacolumnindex"] isEqualToNumber:columnIndex]) { - columnDefinition = [NSDictionary dictionaryWithDictionary:c]; - break; - } - } + NSDictionary *columnDefinition = [NSDictionary dictionaryWithDictionary:[cqColumnDefinition objectAtIndex:[[[[customQueryView tableColumns] objectAtIndex:columnIndex] identifier] integerValue]]]; if(!columnDefinition) return [NSArray arrayWithObjects:[NSNumber numberWithInteger:-2], @"", nil]; @@ -1786,7 +1791,6 @@ [tableDocumentInstance endTask]; return [NSArray arrayWithObjects:[NSNumber numberWithInteger:-1], @"", nil]; } - } [tableDocumentInstance endTask]; @@ -1795,10 +1799,9 @@ fieldIDQueryStr = @""; return [NSArray arrayWithObjects:[NSNumber numberWithInteger:[[tempRow objectAtIndex:0] integerValue]], fieldIDQueryStr, nil]; - } -/* +/** * Collect all columns for a given 'tableForColumn' table and * return a WHERE clause for identifying the field in question. */ @@ -1807,12 +1810,13 @@ NSArray *dataRow; NSDictionary *theRow; id field; + NSMutableArray *argumentParts = [NSMutableArray array]; - //Look for all columns which are coming from "tableForColumn" - NSMutableArray *columnsForFieldTableName = [NSMutableArray array]; + // Check the table/view columns and select only those coming from the supplied database and table + NSMutableArray *columnsInSpecifiedTable = [NSMutableArray array]; for(field in cqColumnDefinition) { - if([[field objectForKey:@"org_table"] isEqualToString:tableForColumn]) - [columnsForFieldTableName addObject:field]; + if([[field objectForKey:@"db"] isEqualToString:database] && [[field objectForKey:@"org_table"] isEqualToString:tableForColumn]) + [columnsInSpecifiedTable addObject:field]; } // Try to identify the field bijectively @@ -1822,34 +1826,32 @@ // --- Build WHERE clause --- dataRow = [resultData rowContentsAtIndex:rowIndex]; - // Get the primary key if there is one + // Get the primary key if there is one, using any columns present within it MCPResult *theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW COLUMNS FROM %@.%@", [database backtickQuotedString], [tableForColumn backtickQuotedString]]]; [theResult setReturnDataAsStrings:YES]; if ([theResult numOfRows]) [theResult dataSeek:0]; + NSMutableArray *primaryColumnsInSpecifiedTable = [NSMutableArray array]; NSUInteger i; for ( i = 0 ; i < [theResult numOfRows] ; i++ ) { theRow = [theResult fetchRowAsDictionary]; if ( [[theRow objectForKey:@"Key"] isEqualToString:@"PRI"] ) { - for(field in columnsForFieldTableName) { - id aValue = [dataRow objectAtIndex:[[field objectForKey:@"datacolumnindex"] integerValue]]; + for (field in columnsInSpecifiedTable) { if([[field objectForKey:@"org_name"] isEqualToString:[theRow objectForKey:@"Field"]]) { - [fieldIDQueryStr appendFormat:@"%@.%@.%@ = %@)", - [database backtickQuotedString], - [tableForColumn backtickQuotedString], - [[theRow objectForKey:@"Field"] backtickQuotedString], - [aValue description]]; - return fieldIDQueryStr; + [primaryColumnsInSpecifiedTable addObject:field]; } } } } - // If there is no primary key, all found fields belonging to the same table are used in the argument - for(field in columnsForFieldTableName) { + // Determine whether to use the primary keys list or fall back to all fields when building the query string + NSMutableArray *columnsToQuery = [primaryColumnsInSpecifiedTable count] ? primaryColumnsInSpecifiedTable : columnsInSpecifiedTable; + + // Build up the argument + for (field in columnsToQuery) { id aValue = [dataRow objectAtIndex:[[field objectForKey:@"datacolumnindex"] integerValue]]; if ([aValue isKindOfClass:[NSNull class]] || [aValue isNSNull]) { - [fieldIDQueryStr appendFormat:@"%@ IS NULL AND ", [[field objectForKey:@"org_name"] backtickQuotedString]]; + [argumentParts addObject:[NSString stringWithFormat:@"%@ IS NULL", [[field objectForKey:@"org_name"] backtickQuotedString]]]; } else { if ([[field objectForKey:@"typegrouping"] isEqualToString:@"textdata"]) { if(includeBlobs) { @@ -1862,25 +1864,126 @@ } } else if ([[field objectForKey:@"typegrouping"] isEqualToString:@"bit"]) { - [fieldIDQueryStr appendFormat:@"%@=b'%@' AND ", [[field objectForKey:@"org_name"] backtickQuotedString], [aValue description]]; + [argumentParts addObject:[NSString stringWithFormat:@"%@=b'%@'", [[field objectForKey:@"org_name"] backtickQuotedString], [aValue description]]]; } else if ([[field objectForKey:@"typegrouping"] isEqualToString:@"integer"]) { [fieldIDQueryStr appendFormat:@"%@=%@ AND ", [[field objectForKey:@"org_name"] backtickQuotedString], [aValue description]]; } else if ([[field objectForKey:@"typegrouping"] isEqualToString:@"geometry"]) { - [fieldIDQueryStr appendFormat:@"%@=X'%@' AND ", [[field objectForKey:@"org_name"] backtickQuotedString], [mySQLConnection prepareBinaryData:[aValue data]]]; + [argumentParts addObject:[NSString stringWithFormat:@"%@=X'%@'", [[field objectForKey:@"org_name"] backtickQuotedString], [mySQLConnection prepareBinaryData:[aValue data]]]]; + } + // BLOB/TEXT data + else if ([aValue isKindOfClass:[NSData class]]) { + [argumentParts addObject:[NSString stringWithFormat:@"%@=X'%@'", [[field objectForKey:@"org_name"] backtickQuotedString], [mySQLConnection prepareBinaryData:aValue]]]; } else { - [fieldIDQueryStr appendFormat:@"%@='%@' AND ", [[field objectForKey:@"org_name"] backtickQuotedString], [mySQLConnection prepareString:aValue]]; + [argumentParts addObject:[NSString stringWithFormat:@"%@='%@'", [[field objectForKey:@"org_name"] backtickQuotedString], [mySQLConnection prepareString:aValue]]]; } } } - // Remove last " AND " - if([fieldIDQueryStr length]>12) - [fieldIDQueryStr replaceCharactersInRange:NSMakeRange([fieldIDQueryStr length]-5,5) withString:@")"]; + // Check for empty strings + if (![argumentParts count]) return nil; - return fieldIDQueryStr; + return [NSString stringWithFormat:@"WHERE (%@)", [argumentParts componentsJoinedByString:@" AND "]]; +} + +- (void)saveCellValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSUInteger)rowIndex +{ + NSDictionary *columnDefinition = [cqColumnDefinition objectAtIndex:[[aTableColumn identifier] integerValue]]; + NSString *columnTypeGroup = [columnDefinition objectForKey:@"typegrouping"]; + + // Resolve the original table name for current column if AS was used + NSString *tableForColumn = [columnDefinition objectForKey:@"org_table"]; + + if(!tableForColumn || ![tableForColumn length]) { + [errorText setString:[NSString stringWithFormat:NSLocalizedString(@"Couldn't identify field origin unambiguously. The column '%@' contains data from more than one table.", @"Custom Query result editing error - could not identify a corresponding column"), [columnDefinition objectForKey:@"name"]]]; + NSBeep(); + return; + } + + // Resolve the original column name if AS was used + NSString *columnName = [columnDefinition objectForKey:@"org_name"]; + + // Check if the IDstring identifies the current field bijectively and get the WHERE clause + NSArray *editStatus = [self fieldEditStatusForRow:rowIndex andColumn:[[aTableColumn identifier] integerValue]]; + fieldIDQueryString = [editStatus objectAtIndex:1]; + NSInteger numberOfPossibleUpdateRows = [[editStatus objectAtIndex:0] integerValue]; + + if(numberOfPossibleUpdateRows == 1) { + + NSString *newObject = nil; + if ( [anObject isKindOfClass:[NSCalendarDate class]] ) { + newObject = [NSString stringWithFormat:@"'%@'", [mySQLConnection prepareString:[anObject description]]]; + } else if ( [anObject isKindOfClass:[NSNumber class]] ) { + newObject = [anObject stringValue]; + } else if ( [anObject isKindOfClass:[NSData class]] ) { + newObject = [NSString stringWithFormat:@"X'%@'", [mySQLConnection prepareBinaryData:anObject]]; + } else { + if ( [[anObject description] isEqualToString:@"CURRENT_TIMESTAMP"] ) { + newObject = @"CURRENT_TIMESTAMP"; + } else if ([anObject isEqualToString:[prefs stringForKey:SPNullValue]] + || (([columnTypeGroup isEqualToString:@"float"] || [columnTypeGroup isEqualToString:@"integer"]) + && [[anObject description] isEqualToString:@""])) + { + newObject = @"NULL"; + } else if ([columnTypeGroup isEqualToString:@"geometry"]) { + newObject = [(NSString*)anObject getGeomFromTextString]; + } else if ([columnTypeGroup isEqualToString:@"bit"]) { + newObject = [NSString stringWithFormat:@"b'%@'", ((![[anObject description] length] || [[anObject description] isEqualToString:@"0"]) ? @"0" : [anObject description])]; + } else if ([columnTypeGroup isEqualToString:@"date"] + && [[anObject description] isEqualToString:@"NOW()"]) { + newObject = @"NOW()"; + } else { + newObject = [NSString stringWithFormat:@"'%@'", [mySQLConnection prepareString:[anObject description]]]; + } + } + + [mySQLConnection queryString: + [NSString stringWithFormat:@"UPDATE %@.%@ SET %@.%@.%@ = %@ %@ LIMIT 1", + [[columnDefinition objectForKey:@"db"] backtickQuotedString], [[columnDefinition objectForKey:@"org_table"] backtickQuotedString], + [[columnDefinition objectForKey:@"db"] backtickQuotedString], [[columnDefinition objectForKey:@"org_table"] backtickQuotedString], [columnName backtickQuotedString], newObject, fieldIDQueryString]]; + + // Check for errors while UPDATE + if ([mySQLConnection queryErrored]) { + SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), NSLocalizedString(@"Cancel", @"cancel button"), nil, [tableDocumentInstance parentWindow], self, nil, nil, + [NSString stringWithFormat:NSLocalizedString(@"Couldn't write field.\nMySQL said: %@", @"message of panel when error while updating field to db"), [mySQLConnection getLastErrorMessage]]); + + return; + } + + // This shouldn't happen – for safety reasons + if ( ![mySQLConnection affectedRows] ) { +#ifndef SP_REFACTOR + if ( [prefs boolForKey:SPShowNoAffectedRowsError] ) { + SPBeginAlertSheet(NSLocalizedString(@"Warning", @"warning"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, + NSLocalizedString(@"The row was not written to the MySQL database. You probably haven't changed anything.\nReload the table to be sure that the row exists and use a primary key for your table.\n(This error can be turned off in the preferences.)", @"message of panel when no rows have been affected after writing to the db")); + } else { + NSBeep(); + } +#endif + return; + } + + // On success reload table data by executing the last query if reloading is enabled +#ifndef SP_REFACTOR + if ([prefs boolForKey:SPReloadAfterEditingRow]) { + reloadingExistingResult = YES; + [self storeCurrentResultViewForRestoration]; + [self performQueries:[NSArray arrayWithObject:lastExecutedQuery] withCallback:NULL]; + } else { +#endif + // otherwise, just update the data in the data storage + SPDataStorageReplaceObjectAtRowAndColumn(resultData, rowIndex, [[aTableColumn identifier] intValue], anObject); +#ifndef SP_REFACTOR + } +#endif + } else { + SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, + [NSString stringWithFormat:NSLocalizedString(@"Updating field content failed. Couldn't identify field origin unambiguously (%ld match%@). It's very likely that while editing this field of table `%@` was changed.", @"message of panel when error while updating field to db after enabling it"), + (numberOfPossibleUpdateRows<1)?0:numberOfPossibleUpdateRows, (numberOfPossibleUpdateRows>1)?@"es":@"", [columnDefinition objectForKey:@"org_table"]]); + + } } #pragma mark - @@ -1891,12 +1994,7 @@ */ - (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView { - if (aTableView == customQueryView) { - return (resultData == nil) ? 0 : resultDataCount; - } - else { - return 0; - } + return (aTableView == customQueryView) ? (resultData == nil) ? 0 : resultDataCount : 0; } /** @@ -1908,28 +2006,32 @@ // For NULL cell's display the user's NULL value placeholder in grey to easily distinguish it from other values if ([cell respondsToSelector:@selector(setTextColor:)]) { + + id value = nil; NSUInteger columnIndex = [[aTableColumn identifier] integerValue]; - id theValue = nil; // While the table is being loaded, additional validation is required - data // locks must be used to avoid crashes, and indexes higher than the available // rows or columns may be requested. Use gray to show loading in these cases. if (isWorking) { pthread_mutex_lock(&resultDataLock); + if (rowIndex < resultDataCount && columnIndex < [resultData columnCount]) { - theValue = SPDataStorageObjectAtRowAndColumn(resultData, rowIndex, columnIndex); + value = SPDataStorageObjectAtRowAndColumn(resultData, rowIndex, columnIndex); } + pthread_mutex_unlock(&resultDataLock); - if (!theValue) { + if (!value) { [cell setTextColor:[NSColor lightGrayColor]]; return; } - } else { - theValue = SPDataStorageObjectAtRowAndColumn(resultData, rowIndex, columnIndex); + } + else { + value = SPDataStorageObjectAtRowAndColumn(resultData, rowIndex, columnIndex); } - [cell setTextColor:[theValue isNSNull] ? [NSColor lightGrayColor] : [NSColor blackColor]]; + [cell setTextColor:[value isNSNull] ? [NSColor lightGrayColor] : [NSColor blackColor]]; } } } @@ -1937,160 +2039,36 @@ /** * Returns the object for the requested column and row index. */ -- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex +- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex { if (aTableView == customQueryView) { - NSUInteger columnIndex = [[aTableColumn identifier] integerValue]; - id theValue = nil; - - // While the table is being loaded, additional validation is required - data - // locks must be used to avoid crashes, and indexes higher than the available - // rows or columns may be requested. Return "..." to indicate loading in these - // cases. - if (isWorking) { - pthread_mutex_lock(&resultDataLock); - if (rowIndex < resultDataCount && columnIndex < [resultData columnCount]) { - theValue = [[SPDataStorageObjectAtRowAndColumn(resultData, rowIndex, columnIndex) copy] autorelease]; - } - pthread_mutex_unlock(&resultDataLock); - - if (!theValue) return @"..."; - } else { - theValue = SPDataStorageObjectAtRowAndColumn(resultData, rowIndex, columnIndex); - } - - if ([theValue isKindOfClass:[NSData class]]) - return [theValue shortStringRepresentationUsingEncoding:[mySQLConnection stringEncoding]]; - - if ([theValue isNSNull]) - return [prefs objectForKey:SPNullValue]; - - if ([theValue isKindOfClass:[MCPGeometryData class]]) - return [theValue wktString]; - - return theValue; - } - else { - return @""; + + return [self _convertResultDataValueToDisplayableRepresentation:[self _resultDataItemAtRow:rowIndex columnIndex:[[tableColumn identifier] integerValue]] whilePreservingNULLs:NO]; } + + return @""; } - (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex { if (aTableView == customQueryView) { - NSDictionary *columnDefinition; - NSString *columnTypeGroup; - - // Retrieve the column defintion - for(id c in cqColumnDefinition) { - if([[c objectForKey:@"datacolumnindex"] isEqualToNumber:[aTableColumn identifier]]) { - columnDefinition = [NSDictionary dictionaryWithDictionary:c]; - columnTypeGroup = [columnDefinition objectForKey:@"typegrouping"]; - break; - } - } - - // Resolve the original table name for current column if AS was used - NSString *tableForColumn = [columnDefinition objectForKey:@"org_table"]; - - if(!tableForColumn || ![tableForColumn length]) { - [errorText setString:[NSString stringWithFormat:NSLocalizedString(@"Couldn't identify field origin unambiguously. The column '%@' contains data from more than one table.", @"Custom Query result editing error - could not identify a corresponding column"), [columnDefinition objectForKey:@"name"]]]; - NSBeep(); + // If the current cell should have been edited in a sheet, do nothing - field closing will have already + // updated the field. + if ([customQueryView shouldUseFieldEditorForRow:rowIndex column:[[aTableColumn identifier] integerValue]]) { return; } - // Resolve the original column name if AS was used - NSString *columnName = [columnDefinition objectForKey:@"org_name"]; - - // Check if the IDstring identifies the current field bijectively and get the WHERE clause - NSArray *editStatus = [self fieldEditStatusForRow:rowIndex andColumn:[aTableColumn identifier]]; - fieldIDQueryString = [editStatus objectAtIndex:1]; - NSInteger numberOfPossibleUpdateRows = [[editStatus objectAtIndex:0] integerValue]; - - if(numberOfPossibleUpdateRows == 1) { - - NSString *newObject = nil; - if ( [anObject isKindOfClass:[NSCalendarDate class]] ) { - newObject = [NSString stringWithFormat:@"'%@'", [mySQLConnection prepareString:[anObject description]]]; - } else if ( [anObject isKindOfClass:[NSNumber class]] ) { - newObject = [anObject stringValue]; - } else if ( [anObject isKindOfClass:[NSData class]] ) { - newObject = [NSString stringWithFormat:@"X'%@'", [mySQLConnection prepareBinaryData:anObject]]; - } else { - if ( [[anObject description] isEqualToString:@"CURRENT_TIMESTAMP"] ) { - newObject = @"CURRENT_TIMESTAMP"; - } else if ([anObject isEqualToString:[prefs stringForKey:SPNullValue]] - || (([columnTypeGroup isEqualToString:@"float"] || [columnTypeGroup isEqualToString:@"integer"]) - && [[anObject description] isEqualToString:@""])) - { - newObject = @"NULL"; - } else if ([columnTypeGroup isEqualToString:@"geometry"]) { - newObject = [(NSString*)anObject getGeomFromTextString]; - } else if ([columnTypeGroup isEqualToString:@"bit"]) { - newObject = [NSString stringWithFormat:@"b'%@'", ((![[anObject description] length] || [[anObject description] isEqualToString:@"0"]) ? @"0" : [anObject description])]; - } else if ([columnTypeGroup isEqualToString:@"date"] - && [[anObject description] isEqualToString:@"NOW()"]) { - newObject = @"NOW()"; - } else { - newObject = [NSString stringWithFormat:@"'%@'", [mySQLConnection prepareString:[anObject description]]]; - } - } - - [mySQLConnection queryString: - [NSString stringWithFormat:@"UPDATE %@.%@ SET %@.%@.%@ = %@ %@ LIMIT 1", - [[columnDefinition objectForKey:@"db"] backtickQuotedString], [[columnDefinition objectForKey:@"org_table"] backtickQuotedString], - [[columnDefinition objectForKey:@"db"] backtickQuotedString], [[columnDefinition objectForKey:@"org_table"] backtickQuotedString], [columnName backtickQuotedString], newObject, fieldIDQueryString]]; - - // Check for errors while UPDATE - if ([mySQLConnection queryErrored]) { - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), NSLocalizedString(@"Cancel", @"cancel button"), nil, [tableDocumentInstance parentWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"Couldn't write field.\nMySQL said: %@", @"message of panel when error while updating field to db"), [mySQLConnection getLastErrorMessage]]); - - return; - } - - // This shouldn't happen – for safety reasons - if ( ![mySQLConnection affectedRows] ) { -#ifndef SP_REFACTOR - if ( [prefs boolForKey:SPShowNoAffectedRowsError] ) { - SPBeginAlertSheet(NSLocalizedString(@"Warning", @"warning"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - NSLocalizedString(@"The row was not written to the MySQL database. You probably haven't changed anything.\nReload the table to be sure that the row exists and use a primary key for your table.\n(This error can be turned off in the preferences.)", @"message of panel when no rows have been affected after writing to the db")); - } else { - NSBeep(); - } -#endif - return; - } - - // On success reload table data by executing the last query if reloading is enabled -#ifndef SP_REFACTOR - if ([prefs boolForKey:SPReloadAfterEditingRow]) { - reloadingExistingResult = YES; - [self storeCurrentResultViewForRestoration]; - [self performQueries:[NSArray arrayWithObject:lastExecutedQuery] withCallback:NULL]; - } else { -#endif - // otherwise, just update the data in the data storage - SPDataStorageReplaceObjectAtRowAndColumn(resultData, rowIndex, [[aTableColumn identifier] intValue], anObject); -#ifndef SP_REFACTOR - } -#endif - } else { - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"Updating field content failed. Couldn't identify field origin unambiguously (%ld match%@). It's very likely that while editing this field of table `%@` was changed.", @"message of panel when error while updating field to db after enabling it"), - (numberOfPossibleUpdateRows<1)?0:numberOfPossibleUpdateRows, (numberOfPossibleUpdateRows>1)?@"es":@"", [columnDefinition objectForKey:@"org_table"]]); - - } + // Otherwise trigger a save + [self saveCellValue:anObject forTableColumn:aTableColumn row:rowIndex]; } } -/* +/** * Change the sort order by clicking at a column header */ - (void)tableView:(NSTableView*)tableView didClickTableColumn:(NSTableColumn *)tableColumn { - // Prevent sorting while a query is running if ([tableDocumentInstance isWorking]) return; if (!cqColumnDefinition || ![cqColumnDefinition count]) return; @@ -2099,7 +2077,7 @@ // Sets column order as tri-state descending, ascending, no sort, descending, ascending etc. order if the same // header is clicked several times - if (sortField && [[tableColumn identifier] isEqualToNumber:sortField]) { + if (sortField && [[tableColumn identifier] integerValue] == [sortField integerValue]) { if(isDesc) { [sortField release]; sortField = nil; @@ -2110,7 +2088,7 @@ } } else { isDesc = NO; - [[customQueryView onMainThread] setIndicatorImage:nil inTableColumn:[customQueryView tableColumnWithIdentifier:sortField]]; + [[customQueryView onMainThread] setIndicatorImage:nil inTableColumn:[customQueryView tableColumnWithIdentifier:[NSString stringWithFormat:@"%lld", (long long)[sortField integerValue]]]]; if (sortField) [sortField release]; sortField = [[NSNumber alloc] initWithInteger:[[tableColumn identifier] integerValue]]; } @@ -2225,7 +2203,6 @@ } - #pragma mark - #pragma mark TableView Drag & Drop datasource methods @@ -2310,7 +2287,6 @@ */ - (NSString *)tableView:(NSTableView *)aTableView toolTipForCell:(SPTextAndLinkCell *)aCell rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)row mouseLocation:(NSPoint)mouseLocation { - if([[aCell stringValue] length] < 2 || [tableDocumentInstance isWorking]) return nil; // Suppress tooltip if another toolip is already visible, mainly displayed by a Bundle command @@ -2334,7 +2310,7 @@ // cases. if (isWorking) { pthread_mutex_lock(&resultDataLock); - if (row < resultDataCount && [[aTableColumn identifier] unsignedIntegerValue] < [resultData columnCount]) { + if (row < resultDataCount && (NSUInteger)[[aTableColumn identifier] integerValue] < [resultData columnCount]) { theValue = [[SPDataStorageObjectAtRowAndColumn(resultData, row, [[aTableColumn identifier] integerValue]) copy] autorelease]; } pthread_mutex_unlock(&resultDataLock); @@ -2378,46 +2354,39 @@ return nil; } -/* +/** * Double-click action on a field */ - (BOOL)tableView:(NSTableView *)aTableView shouldEditTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex { - // Only allow editing if a task is not active if ([tableDocumentInstance isWorking]) return NO; // Check if the field can identified bijectively if ( aTableView == customQueryView ) { - - NSDictionary *columnDefinition; - BOOL isBlob = NO; - - // Retrieve the column defintion - for(id c in cqColumnDefinition) { - if([[c objectForKey:@"datacolumnindex"] isEqualToNumber:[aTableColumn identifier]]) { - columnDefinition = [NSDictionary dictionaryWithDictionary:c]; - break; - } - } + NSDictionary *columnDefinition = [cqColumnDefinition objectAtIndex:[[aTableColumn identifier] integerValue]]; // Check if current field is a blob - if([[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"textdata"] - || [[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"blobdata"]) - isBlob = YES; - else - isBlob = NO; - - if ([multipleLineEditingButton state] == NSOnState || isBlob) { + BOOL isBlob = ([[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"textdata"] + || [[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"blobdata"]); - if(fieldEditor) [fieldEditor release], fieldEditor = nil; + // Open the editing sheet if required + if ([customQueryView shouldUseFieldEditorForRow:rowIndex column:[[aTableColumn identifier] integerValue]]) + { + if (fieldEditor) [fieldEditor release], fieldEditor = nil; fieldEditor = [[SPFieldEditorController alloc] init]; // Remember edited row for reselecting and setting the scroll view after reload editedRow = rowIndex; editedScrollViewRect = [customQueryScrollView documentVisibleRect]; - NSArray *editStatus = [self fieldEditStatusForRow:rowIndex andColumn:[aTableColumn identifier]]; + NSInteger editedColumn = 0; + for (NSTableColumn* col in [customQueryView tableColumns]) { + if([[col identifier] isEqualToString:[aTableColumn identifier]]) break; + editedColumn++; + } + + NSArray *editStatus = [self fieldEditStatusForRow:rowIndex andColumn:[[aTableColumn identifier] integerValue]]; isFieldEditable = ([[editStatus objectAtIndex:0] integerValue] == 1) ? YES : NO; NSString *fieldType = nil; @@ -2458,17 +2427,17 @@ withWindow:[tableDocumentInstance parentWindow] sender:self contextInfo:[NSDictionary dictionaryWithObjectsAndKeys: - [NSNumber numberWithInteger:rowIndex], @"row", - [aTableColumn identifier], @"column", + [NSNumber numberWithInteger:rowIndex], @"rowIndex", + [NSNumber numberWithInteger:editedColumn], @"columnIndex", [NSNumber numberWithBool:isFieldEditable], @"isFieldEditable", nil]]; return NO; - } + return YES; - - } else { + } + else { return YES; } } @@ -2623,37 +2592,42 @@ // Return the width, while the delegate is empty to prevent column resize notifications [customQueryView setDelegate:nil]; [customQueryView performSelector:@selector(setDelegate:) withObject:self afterDelay:0.1]; + return targetWidth; } #pragma mark - #pragma mark TextView delegate methods -/* +/** * Traps enter key and performs query instead of inserting a line break if aTextView == textView * closes valueSheet if aTextView == valueTextField */ - (BOOL)textView:(NSTextView *)aTextView doCommandBySelector:(SEL)aSelector { - if ( aTextView == textView ) { - if ( [aTextView methodForSelector:aSelector] == [aTextView methodForSelector:@selector(insertNewline:)] && - [[[NSApp currentEvent] characters] isEqualToString:@"\003"] ) - { + if (aTextView == textView) { + if ([aTextView methodForSelector:aSelector] == [aTextView methodForSelector:@selector(insertNewline:)] && + [[[NSApp currentEvent] characters] isEqualToString:@"\003"]) { [self runAllQueries:self]; + return YES; - } else { + } + else { return NO; } - } else if ( aTextView == valueTextField ) { - if ( [aTextView methodForSelector:aSelector] == [aTextView methodForSelector:@selector(insertNewline:)] ) - { + } + else if (aTextView == valueTextField) { + if ([aTextView methodForSelector:aSelector] == [aTextView methodForSelector:@selector(insertNewline:)]) { [self closeSheet:self]; + return YES; - } else { + } + else { return NO; } } + return NO; } @@ -2663,18 +2637,17 @@ - (NSRange)textView:(NSTextView *)aTextView willChangeSelectionFromCharacterRange:(NSRange)oldSelectedCharRange toCharacterRange:(NSRange)newSelectedCharRange { // Check if snippet session is still valid - if(!newSelectedCharRange.length && [textView isSnippetMode]) [textView checkForCaretInsideSnippet]; + if (!newSelectedCharRange.length && [textView isSnippetMode]) [textView checkForCaretInsideSnippet]; return newSelectedCharRange; } -/* +/** * A notification posted when the selection changes within the text view; * used to control the run-currentrun-selection button state and action. */ - (void)textViewDidChangeSelection:(NSNotification *)aNotification { - // Ensure that the notification is from the custom query text view if ( [aNotification object] != textView ) return; @@ -2690,7 +2663,7 @@ currentQueryRange = NSMakeRange(0, 0); [textView setQueryRange:qRange]; - [textView setNeedsDisplay:YES]; + [textView setNeedsDisplayInRect:[textView bounds]]; // disable "Comment Current Query" menu item if no current query is selectable [commentCurrentQueryMenuItem setEnabled:(currentQueryRange.length) ? YES : NO]; @@ -2760,15 +2733,21 @@ else if ([notification object] == queryHistorySearchField) { [self filterQueryHistory:nil]; } +} +#ifndef SP_REFACTOR +- (NSUndoManager *)undoManagerForTextView:(NSTextView *)aTextView +{ + return [tableDocumentInstance undoManager]; } +#endif #pragma mark - #pragma mark SplitView delegate methods #ifndef SP_REFACTOR /* splitview delegate methods */ -/* +/** * Tells the splitView that it can collapse views */ - (BOOL)splitView:(NSSplitView *)sender canCollapseSubview:(NSView *)subview @@ -2776,21 +2755,23 @@ return YES; } -/* +/** * Defines max position of splitView */ - (CGFloat)splitView:(NSSplitView *)sender constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)offset { if (sender != queryInfoPaneSplitView) return (offset == 0) ? (proposedMax - 100) : (proposedMax - 73); + return proposedMax; } -/* +/** * Defines min position of splitView */ - (CGFloat)splitView:(NSSplitView *)sender constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)offset { if (sender != queryInfoPaneSplitView) return proposedMin + 100; + return proposedMin; } @@ -2807,18 +2788,17 @@ #pragma mark - #pragma mark MySQL Help -/* +/** * Set the MySQL version as X.Y for Help window title and online search */ - (void)setMySQLversion:(NSString *)theVersion { mySQLversion = [[theVersion substringToIndex:3] retain]; [textView setConnection:mySQLConnection withVersion:[[[mySQLversion componentsSeparatedByString:@"."] objectAtIndex:0] integerValue]]; - } #ifndef SP_REFACTOR -/* +/** * Return the Help window. */ - (NSWindow *)helpWebViewWindow @@ -2826,12 +2806,11 @@ return helpWebViewWindow; } -/* +/** * Show the data for "HELP 'searchString'". */ - (void)showHelpFor:(NSString *)searchString addToHistory:(BOOL)addToHistory calledByAutoHelp:(BOOL)autoHelp { - if(![searchString length]) return; NSString *helpString = [self getHTMLformattedMySQLHelpFor:searchString calledByAutoHelp:autoHelp]; @@ -2865,7 +2844,6 @@ // order out Help window if Help is available if(![helpString isEqualToString:SP_HELP_NOT_AVAILABLE]) [helpWebViewWindow orderFront:helpWebView]; - } // close Help window if no Help available @@ -2888,11 +2866,9 @@ // load HTML formatted help into the webview [[helpWebView mainFrame] loadHTMLString:helpString baseURL:nil]; - } - -/* +/** * Show the data for "HELP 'search word'" according to helpTarget */ - (IBAction)showHelpForSearchString:(id)sender @@ -2915,7 +2891,7 @@ } } -/* +/** * Show the Help for the selected text in the webview */ - (IBAction)showHelpForWebViewSelection:(id)sender @@ -2938,7 +2914,7 @@ } -/* +/** * Show the data for "HELP 'currentWord'" */ - (IBAction)showHelpForCurrentWord:(id)sender @@ -2947,7 +2923,7 @@ [self showHelpFor:searchString addToHistory:YES calledByAutoHelp:NO]; } -/* +/** * Find Next/Previous in current page */ - (IBAction)helpSearchFindNextInPage:(id)sender @@ -2956,6 +2932,7 @@ if(![helpWebView searchFor:[[helpSearchField stringValue] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] direction:YES caseSensitive:NO wrap:YES]) NSBeep(); } + - (IBAction)helpSearchFindPreviousInPage:(id)sender { if(helpTarget == SP_HELP_SEARCH_IN_PAGE) @@ -2963,7 +2940,7 @@ NSBeep(); } -/* +/** * Navigation for back/TOC/forward */ - (IBAction)helpSegmentDispatcher:(id)sender @@ -2980,13 +2957,14 @@ [helpWebView goForward]; break; } + // validate goback and goforward buttons according history [helpNavigator setEnabled:[[helpWebView backForwardList] backListCount] forSegment:SP_HELP_GOBACK_BUTTON]; [helpNavigator setEnabled:[[helpWebView backForwardList] forwardListCount] forSegment:SP_HELP_GOFORWARD_BUTTON]; } -/* +/** * Set helpTarget according user choice via mouse and keyboard short-cuts. */ - (IBAction)helpSelectHelpTargetMySQL:(id)sender @@ -2995,18 +2973,21 @@ [helpTargetSelector setSelectedSegment:SP_HELP_SEARCH_IN_MYSQL]; [self helpTargetValidation]; } + - (IBAction)helpSelectHelpTargetPage:(id)sender { helpTarget = SP_HELP_SEARCH_IN_PAGE; [helpTargetSelector setSelectedSegment:SP_HELP_SEARCH_IN_PAGE]; [self helpTargetValidation]; } + - (IBAction)helpSelectHelpTargetWeb:(id)sender { helpTarget = SP_HELP_SEARCH_IN_WEB; [helpTargetSelector setSelectedSegment:SP_HELP_SEARCH_IN_WEB]; [self helpTargetValidation]; } + - (IBAction)helpTargetDispatcher:(id)sender { helpTarget = [helpTargetSelector selectedSegment]; @@ -3031,7 +3012,7 @@ } #ifndef SP_REFACTOR -/* +/** * Show the data for "HELP 'currentWord' invoked by autohelp" */ - (void)showAutoHelpForCurrentWord:(id)sender @@ -3040,7 +3021,7 @@ [self showHelpFor:searchString addToHistory:YES calledByAutoHelp:YES]; } -/* +/** * Control the help search field behaviour. */ - (void)helpTargetValidation @@ -3073,7 +3054,7 @@ stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding]]]; } -/* +/** * Return the help string HTML formatted from executing "HELP 'searchString'". * If more than one help topic was found return a link list. */ @@ -3207,14 +3188,12 @@ [tableDetails release]; return [NSString stringWithFormat:helpHTMLTemplate, theHelp]; - } -////////////////////////////// -// WebView delegate methods // -////////////////////////////// +#pragma mark - +#pragma mark WebView delegate methods -/* +/** * Link detector: If user clicked at an http link open it in the default browser, * otherwise search for it in the MySQL help. Additionally handle back/forward events from * keyboard and context menu. @@ -3251,7 +3230,7 @@ } } -/* +/** * Manage contextual menu in helpWebView * Ignore "Reload", "Open Link", "Open Link in new Window", "Download link" etc. */ @@ -3363,6 +3342,7 @@ for(id historyMenuItem in [[SPQueryController sharedQueryController] historyMenuItemsForFileURL:[tableDocumentInstance fileURL]]) [historyMenu addItem:historyMenuItem]; } + /** * Called by the query favorites manager whenever the query favorites have been updated. */ @@ -3507,7 +3487,7 @@ [[SPQueryController sharedQueryController] addHistory:entryString forFileURL:[tableDocumentInstance fileURL]]; } -/* +/** * This method is called as part of Key Value Observing which is used to watch for prefernce changes which effect the interface. */ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context @@ -3530,7 +3510,6 @@ */ - (void)sheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo { - if ([contextInfo isEqualToString:@"runAllContinueStopSheet"]) { runAllContinueStopSheetReturnCode = returnCode; return; @@ -3666,15 +3645,15 @@ NSInteger column = -1; if(contextInfo) { - row = [[contextInfo objectForKey:@"row"] integerValue]; - column = [[contextInfo objectForKey:@"column"] integerValue]; + row = [[contextInfo objectForKey:@"rowIndex"] integerValue]; + column = [[contextInfo objectForKey:@"columnIndex"] integerValue]; } if (data && contextInfo) { BOOL isResultFieldEditable = ([contextInfo objectForKey:@"isFieldEditable"]) ? YES : NO; if(isResultFieldEditable) { - [self tableView:customQueryView setObjectValue:[[data copy] autorelease] forTableColumn:[customQueryView tableColumnWithIdentifier:[contextInfo objectForKey:@"column"]] row:row]; + [self saveCellValue:[[data copy] autorelease] forTableColumn:[[customQueryView tableColumns] objectAtIndex:column] row:row]; } } @@ -3793,14 +3772,13 @@ } } -/* +/** * If user selected a table cell which is a blob field and tried to edit it * cancel the fieldEditor, display the field editor sheet instead for editing * and re-enable the fieldEditor after editing. */ - (BOOL)control:(NSControl *)control textShouldBeginEditing:(NSText *)aFieldEditor { - if(![control isKindOfClass:[SPCopyTable class]]) return YES; NSUInteger row, column; @@ -3810,18 +3788,12 @@ row = [customQueryView editedRow]; column = [customQueryView editedColumn]; - // Retrieve the column defintion - NSNumber *colIdentifier = [NSArrayObjectAtIndex([customQueryView tableColumns], column) identifier]; - for(id c in cqColumnDefinition) { - if([[c objectForKey:@"datacolumnindex"] isEqualToNumber:colIdentifier]) { - columnDefinition = [NSDictionary dictionaryWithDictionary:c]; - break; - } - } + // Retrieve the column definition + columnDefinition = [NSDictionary dictionaryWithDictionary:[cqColumnDefinition objectAtIndex:[[[[customQueryView tableColumns] objectAtIndex:column] identifier] integerValue]]]; if(!columnDefinition) return NO; - NSArray *editStatus = [self fieldEditStatusForRow:row andColumn:colIdentifier]; + NSArray *editStatus = [self fieldEditStatusForRow:row andColumn:column]; NSInteger numberOfPossibleUpdateRows = [NSArrayObjectAtIndex(editStatus, 0) integerValue]; NSPoint pos = [[tableDocumentInstance parentWindow] convertBaseToScreen:[customQueryView convertPoint:[customQueryView frameOfCellAtColumn:column row:row].origin toView:nil]]; pos.y -= 20; @@ -3850,19 +3822,10 @@ shouldBeginEditing = NO; } - BOOL isBlob = NO; - - // Check if current field is a blob - if([[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"textdata"] - || [[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"blobdata"]) - isBlob = YES; - else - isBlob = NO; - isFieldEditable = shouldBeginEditing; - // Check if current edited field is a blob or should be displayed in field editor sheet - if (isBlob || [multipleLineEditingButton state] == NSOnState) + // Open the field editor sheet if required + if ([customQueryView shouldUseFieldEditorForRow:row column:column]) { [customQueryView setFieldEditorSelectedRange:[aFieldEditor selectedRange]]; @@ -3885,7 +3848,6 @@ [aFieldEditor setTextColor:[NSColor blackColor]]; return shouldBeginEditing; - } /** @@ -3894,7 +3856,6 @@ */ - (BOOL)control:(NSControl*)control textView:(NSTextView*)aTextView doCommandBySelector:(SEL)command { - if(control == queryHistorySearchField || control == queryFavoritesSearchField) { if(command == @selector(moveDown:) || command == @selector(moveUp:)) { [queryHistorySearchField abortEditing]; @@ -3930,22 +3891,10 @@ return TRUE; } - - } return NO; } -// - (void)menu:(NSMenu *)menu willHighlightItem:(NSMenuItem *)item -// { -// // Set the focus at the search field -// // TODO : but no way out; always selecting first/last menu item -// // because after setting focus to search field NSMenu selectedItemIndex is -1 -// if(item == queryHistorySearchMenuItem) { -// [queryHistorySearchField selectText:nil]; -// } -// -// } /** * Setup various interface controls. @@ -3993,6 +3942,71 @@ } } +#pragma mark - +#pragma mark Private API + +/** + * Retrieves the value from the underlying data storage at the supplied row and column indices. + * + * @param row The row index + * @param column The column index + * + * @return The value from the data storage + */ +- (id)_resultDataItemAtRow:(NSInteger)row columnIndex:(NSUInteger)column +{ + id value = nil; + + // While the table is being loaded, additional validation is required - data + // locks must be used to avoid crashes, and indexes higher than the available + // rows or columns may be requested. Return "..." to indicate loading in these + // cases. + if (isWorking) { + pthread_mutex_lock(&resultDataLock); + + if (row < resultDataCount && column < [resultData columnCount]) { + value = [[SPDataStorageObjectAtRowAndColumn(resultData, row, column) copy] autorelease]; + } + + pthread_mutex_unlock(&resultDataLock); + + if (!value) value = @"..."; + } + else { + value = SPDataStorageObjectAtRowAndColumn(resultData, row, column); + } + + return value; +} + +/** + * Converts the supplied value into it's displayable representation. + * + * @param value The value to convert + * @param preserveNULLs Whether or not NULLs should be preserved or converted to the + * user's NULL placeholder preference. + * + * @return The converted value + */ +- (id)_convertResultDataValueToDisplayableRepresentation:(id)value whilePreservingNULLs:(BOOL)preserveNULLs +{ + if ([value isKindOfClass:[NSData class]]) { + value = [value shortStringRepresentationUsingEncoding:[mySQLConnection stringEncoding]]; + } + + if ([value isNSNull] && !preserveNULLs) { + value = [prefs objectForKey:SPNullValue]; + } + + if ([value isKindOfClass:[MCPGeometryData class]]) { + value = [value wktString]; + } + + return value; +} + +#pragma mark - + /** * Dealloc. */ diff --git a/Source/SPDataCellFormatter.m b/Source/SPDataCellFormatter.m index 29b22cd7..70660223 100644 --- a/Source/SPDataCellFormatter.m +++ b/Source/SPDataCellFormatter.m @@ -34,7 +34,7 @@ - (NSString *)stringForObjectValue:(id)anObject { // Truncate the string for speed purposes if it's very long - improves table scrolling speed. - if ([(NSString *)anObject length] > 150) { + if ([anObject isKindOfClass:[NSString class]] && [(NSString *)anObject length] > 150) { return ([NSString stringWithFormat:@"%@...", [anObject substringToIndex:147]]); } @@ -53,9 +53,52 @@ return YES; } +/** + * When producing an attributed string, take the opportunity to convert to a single + * line for display, displaying placeholders for CR and LF characters. + */ - (NSAttributedString *)attributedStringForObjectValue:(id)anObject withDefaultAttributes:(NSDictionary *)attributes { - return [[[NSAttributedString alloc] initWithString:[self stringForObjectValue:anObject] attributes:attributes] autorelease]; + + // Start with a base string which has been shortened for fast display + NSString *baseString = [self stringForObjectValue:anObject]; + + // Look for any linebreaks within the string + NSRange linebreakRange = [baseString rangeOfCharacterFromSet:[NSCharacterSet newlineCharacterSet] options:NSLiteralSearch]; + + // If there's no linebreaks, return a non-mutable string + if (linebreakRange.location == NSNotFound) { + return [[[NSAttributedString alloc] initWithString:baseString attributes:attributes] autorelease]; + } + + NSMutableAttributedString *mutableString; + NSUInteger i, j, stringLength = [baseString length]; + unichar c; + + // Otherwise, prepare a mutable attributed string to alter, and walk along the string. + mutableString = [[[NSMutableAttributedString alloc] initWithString:baseString attributes:attributes] autorelease]; + for (i = linebreakRange.location, j = i; i < stringLength; i++, j++) { + c = [baseString characterAtIndex:i]; + switch (c) { + case '\n': + [mutableString replaceCharactersInRange:NSMakeRange(j, 1) withString:@"¶"]; + [mutableString addAttribute:NSForegroundColorAttributeName value:[NSColor lightGrayColor] range:NSMakeRange(j, 1)]; + break; + case '\r': + case 0x0085: + case 0x000b: + case 0x000c: + [mutableString replaceCharactersInRange:NSMakeRange(j, 1) withString:@"⁋"]; + [mutableString addAttribute:NSForegroundColorAttributeName value:[NSColor lightGrayColor] range:NSMakeRange(j, 1)]; + if (c == '\r' && i + 1 < stringLength && [baseString characterAtIndex:i+1] == '\n') { + [mutableString deleteCharactersInRange:NSMakeRange(j+1, 1)]; + i++; + } + break; + } + } + + return mutableString; } - (BOOL)isPartialStringValid:(NSString *)partialString newEditingString:(NSString **)newString errorDescription:(NSString **)error diff --git a/Source/SPDataImport.h b/Source/SPDataImport.h index b9810201..1b668be9 100644 --- a/Source/SPDataImport.h +++ b/Source/SPDataImport.h @@ -25,6 +25,12 @@ #import <MCPKit/MCPKit.h> +typedef enum { + SPFieldMapperInProgress = 1, + SPFieldMapperCompleted = 2, + SPFieldMapperCancelled = 3 +} SPFieldMapperSheetStatus; + @class SPFieldMapperController, SPFileHandle; @interface SPDataImport : NSObject @@ -89,7 +95,7 @@ NSString *lastFilename; NSString *csvImportHeaderString; NSString *csvImportTailString; - NSInteger fieldMapperSheetStatus; + SPFieldMapperSheetStatus fieldMapperSheetStatus; NSInteger numberOfImportDataColumns; BOOL fieldMappingArrayHasGlobalVariables; BOOL csvImportMethodHasTail; diff --git a/Source/SPDataImport.m b/Source/SPDataImport.m index d1b2b67a..606906f4 100644 --- a/Source/SPDataImport.m +++ b/Source/SPDataImport.m @@ -297,6 +297,9 @@ - (void)importFileSheetDidEnd:(id)sheet returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo { + // Ensure text inputs are completed, preventing dead character entry + [sheet makeFirstResponder:nil]; + // Save values to preferences [prefs setObject:[(NSOpenPanel*)sheet directory] forKey:@"openPath"]; [prefs setObject:[[importFormatPopup selectedItem] title] forKey:@"importFormatPopupValue"]; @@ -808,7 +811,12 @@ [csvParser setFieldTerminatorString:[importFieldsTerminatedField stringValue] convertDisplayStrings:YES]; [csvParser setLineTerminatorString:[importLinesTerminatedField stringValue] convertDisplayStrings:YES]; [csvParser setFieldQuoteString:[importFieldsEnclosedField stringValue] convertDisplayStrings:YES]; - [csvParser setEscapeString:[importFieldsEscapedField stringValue] convertDisplayStrings:YES]; + if ([[importFieldsEscapedField stringValue] isEqualToString:@"\\ or \""]) { + [csvParser setEscapeString:@"\\" convertDisplayStrings:NO]; + } else { + [csvParser setEscapeString:[importFieldsEscapedField stringValue] convertDisplayStrings:YES]; + [csvParser setEscapeStringsAreMatchedStrictly:YES]; + } [csvParser setNullReplacementString:[prefs objectForKey:SPNullValue]]; csvDataBuffer = [[NSMutableData alloc] init]; @@ -1245,7 +1253,7 @@ fieldMappingImportArray = [[NSArray alloc] initWithArray:importData]; numberOfImportDataColumns = [[importData objectAtIndex:0] count]; - fieldMapperSheetStatus = 1; + fieldMapperSheetStatus = SPFieldMapperInProgress; fieldMappingArrayHasGlobalVariables = NO; // Init the field mapper controller @@ -1264,9 +1272,15 @@ [[[fieldMapperController window] onMainThread] makeKeyWindow]; // Wait for field mapper sheet - while (fieldMapperSheetStatus == 1) + while (fieldMapperSheetStatus == SPFieldMapperInProgress) usleep(100000); + // If the mapping was cancelled, abort the import + if (fieldMapperSheetStatus == SPFieldMapperCancelled) { + if (fieldMapperController) [fieldMapperController release]; + return FALSE; + } + // Get mapping settings and preset some global variables fieldMapperOperator = [[NSArray arrayWithArray:[fieldMapperController fieldMapperOperator]] retain]; fieldMappingArray = [[NSArray arrayWithArray:[fieldMapperController fieldMappingArray]] retain]; @@ -1317,7 +1331,7 @@ if(fieldMapperController) [fieldMapperController release]; - if(fieldMapperSheetStatus == 2) + if(fieldMapperSheetStatus == SPFieldMapperCompleted) return YES; else return NO; @@ -1329,7 +1343,7 @@ - (void)fieldMapperDidEndSheet:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo { [sheet orderOut:self]; - fieldMapperSheetStatus = (returnCode) ? 2 : 3; + fieldMapperSheetStatus = (returnCode) ? SPFieldMapperCompleted : SPFieldMapperCancelled; } /** diff --git a/Source/SPDatabaseDocument.h b/Source/SPDatabaseDocument.h index 8f67ccc4..b56991f5 100644 --- a/Source/SPDatabaseDocument.h +++ b/Source/SPDatabaseDocument.h @@ -34,14 +34,18 @@ #ifndef SP_REFACTOR /* class forward decls */ SPProcessListController, SPServerVariablesController, SPUserManager, SPWindowController, #endif -SPTablesList, SPTableStructure, SPTableContent, SPTableData, SPServerSupport, SPCustomQuery; +SPDatabaseData, SPTablesList, SPTableStructure, SPTableContent, SPTableData, SPServerSupport, SPCustomQuery; #import "SPConnectionControllerDelegateProtocol.h" /** * The SPDatabaseDocument class controls the primary database view window. */ -@interface SPDatabaseDocument : NSObject <SPConnectionControllerDelegateProtocol> +@interface SPDatabaseDocument : NSObject <SPConnectionControllerDelegateProtocol +#ifdef SP_REFACTOR /* patch */ + , NSTextFieldDelegate +#endif +> { #ifdef SP_REFACTOR /* patch */ id delegate; @@ -163,9 +167,11 @@ SPTablesList, SPTableStructure, SPTableContent, SPTableData, SPServerSupport, SP NSString *selectedDatabase; NSString *mySQLVersion; + NSString *selectedDatabaseEncoding; #ifndef SP_REFACTOR /* ivars */ NSUserDefaults *prefs; NSMutableArray *nibObjectsToRelease; + NSUndoManager *undoManager; #endif NSMenu *selectEncodingMenu; @@ -230,13 +236,28 @@ SPTablesList, SPTableStructure, SPTableContent, SPTableData, SPServerSupport, SP BOOL isProcessing; #ifndef SP_REFACTOR /* ivars */ NSString *processID; + BOOL windowTitleStatusViewIsVisible; #endif } #ifdef SP_REFACTOR /* ivars */ +@property (assign) SPDatabaseData* databaseDataInstance; +@property (assign) SPTableData* tableDataInstance; +@property (assign) SPCustomQuery* customQueryInstance; +@property (assign) id databaseNameField; +@property (assign) id databaseEncodingButton; +@property (assign) id addDatabaseButton; + +@property (assign) id databaseRenameNameField; +@property (assign) id renameDatabaseButton; +@property (assign) id databaseRenameSheet; +#endif + +#ifdef SP_REFACTOR /* ivars */ @property (assign) id delegate; @property (readonly) NSMutableArray* allDatabases; @property (assign) NSProgressIndicator* queryProgressBar; +@property (assign) NSWindow* databaseSheet; #endif #ifndef SP_REFACTOR /* ivars */ @@ -251,8 +272,10 @@ SPTablesList, SPTableStructure, SPTableContent, SPTableData, SPServerSupport, SP #ifndef SP_REFACTOR /* method decls */ - (BOOL)isUntitled; +#endif - (BOOL)couldCommitCurrentViewActions; +#ifndef SP_REFACTOR /* method decls */ - (void)initQueryEditorWithString:(NSString *)query; // Connection callback and methods @@ -267,12 +290,14 @@ SPTablesList, SPTableStructure, SPTableContent, SPTableData, SPServerSupport, SP - (IBAction)chooseDatabase:(id)sender; #endif - (void)selectDatabase:(NSString *)aDatabase item:(NSString *)anItem; -#ifndef SP_REFACTOR /* method decls */ - (IBAction)addDatabase:(id)sender; - (IBAction)removeDatabase:(id)sender; +#ifndef SP_REFACTOR /* method decls */ - (IBAction)refreshTables:(id)sender; - (IBAction)copyDatabase:(id)sender; +#endif - (IBAction)renameDatabase:(id)sender; +#ifndef SP_REFACTOR /* method decls */ - (IBAction)showMySQLHelp:(id)sender; - (IBAction)showServerVariables:(id)sender; - (IBAction)showServerProcesses:(id)sender; @@ -301,6 +326,7 @@ SPTablesList, SPTableStructure, SPTableContent, SPTableData, SPServerSupport, SP // Encoding methods - (void)setConnectionEncoding:(NSString *)mysqlEncoding reloadingViews:(BOOL)reloadViews; - (NSString *)databaseEncoding; +- (void)detectDatabaseEncoding; - (IBAction)chooseEncoding:(id)sender; - (BOOL)supportsEncoding; - (void)updateEncodingMenuWithSelectedEncoding:(NSNumber *)encodingTag; @@ -368,6 +394,7 @@ SPTablesList, SPTableStructure, SPTableContent, SPTableData, SPServerSupport, SP - (NSURL *)fileURL; - (NSString *)displayName; #ifndef SP_REFACTOR /* method decls */ +- (NSUndoManager *)undoManager; // Notification center methods - (void)willPerformQuery:(NSNotification *)notification; @@ -387,6 +414,7 @@ SPTablesList, SPTableStructure, SPTableContent, SPTableData, SPServerSupport, SP - (void)setStatusIconToImageWithName:(NSString *)imagePath; - (void)setTitlebarStatus:(NSString *)status; - (void)clearStatusIcon; +- (void)updateTitlebarStatusVisibilityForcingHide:(BOOL)forceHide; // Toolbar methods - (void)updateWindowTitle:(id)sender; @@ -436,8 +464,6 @@ SPTablesList, SPTableStructure, SPTableContent, SPTableData, SPServerSupport, SP - (void)setTableSourceInstance:(SPTableStructure*)source; - (void)setTableContentInstance:(SPTableContent*)content; -@property (assign) SPTableData* tableDataInstance; -@property (assign) SPCustomQuery* customQueryInstance; #endif @end diff --git a/Source/SPDatabaseDocument.m b/Source/SPDatabaseDocument.m index 864061c4..bf9f96c7 100644 --- a/Source/SPDatabaseDocument.m +++ b/Source/SPDatabaseDocument.m @@ -25,6 +25,13 @@ // // More info at <http://code.google.com/p/sequel-pro/> +// Forward-declare for 10.7 compatibility +#if !defined(MAC_OS_X_VERSION_10_7) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7 +enum { + NSFullScreenWindowMask = 1 << 14 +}; +#endif + #import "SPDatabaseDocument.h" #import "SPConnectionController.h" @@ -41,6 +48,7 @@ #import "SPExportController.h" #endif #import "SPQueryController.h" +#import "SPQueryDocumentsController.h" #ifndef SP_REFACTOR /* headers */ #import "SPWindowController.h" #endif @@ -48,7 +56,9 @@ #ifndef SP_REFACTOR /* headers */ #import "SPSQLParser.h" #import "SPTableData.h" +#endif #import "SPDatabaseData.h" +#ifndef SP_REFACTOR /* headers */ #import "SPAppController.h" #import "SPExtendedTableInfo.h" #import "SPHistoryController.h" @@ -63,6 +73,7 @@ #import "SPDatabaseCopy.h" #import "SPTableCopy.h" #import "SPDatabaseRename.h" +#import "SPTableRelations.h" #endif #import "SPServerSupport.h" #ifndef SP_REFACTOR /* headers */ @@ -79,6 +90,7 @@ #import "SPAlertSheets.h" #import "NSNotificationAdditions.h" #import "SPCustomQuery.h" +#import "SPDatabaseRename.h" #endif // Constants @@ -88,12 +100,12 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; @interface SPDatabaseDocument () -#ifndef SP_REFACTOR /* method decls */ - (void)_addDatabase; +#ifndef SP_REFACTOR /* method decls */ - (void)_copyDatabase; +#endif - (void)_renameDatabase; - (void)_removeDatabase; -#endif - (void)_selectDatabaseAndItem:(NSDictionary *)selectionDetails; @end @@ -116,6 +128,14 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; @synthesize tableDataInstance; @synthesize customQueryInstance; @synthesize queryProgressBar; +@synthesize databaseSheet; +@synthesize databaseNameField; +@synthesize databaseEncodingButton; +@synthesize addDatabaseButton; +@synthesize databaseDataInstance; +@synthesize databaseRenameSheet; +@synthesize databaseRenameNameField; +@synthesize renameDatabaseButton; #endif - (id)init @@ -144,6 +164,7 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; triggersLoaded = NO; selectedDatabase = nil; + selectedDatabaseEncoding = [[NSString alloc] initWithString:@"latin1"]; mySQLConnection = nil; mySQLVersion = nil; allDatabases = nil; @@ -159,6 +180,7 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; [printWebView setFrameLoadDelegate:self]; prefs = [NSUserDefaults standardUserDefaults]; + undoManager = [[NSUndoManager alloc] init]; #endif queryEditorInitString = nil; @@ -184,6 +206,7 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; #ifndef SP_REFACTOR /* init ivars */ statusValues = nil; printThread = nil; + windowTitleStatusViewIsVisible = NO; nibObjectsToRelease = [[NSMutableArray alloc] init]; // As this object is not an NSWindowController subclass, top-level objects in loaded nibs aren't @@ -207,6 +230,7 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; // Set the connection controller's delegate [connectionController setDelegate:self]; + return connectionController; } @@ -325,6 +349,8 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; [taskProgressWindow setContentView:taskProgressLayer]; [contentViewSplitter setDelegate:self]; + + [self updateTitlebarStatusVisibilityForcingHide:NO]; #endif } @@ -409,14 +435,13 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; [databaseDataInstance setConnection:mySQLConnection]; -#ifndef SP_REFACTOR /* setServerSupport: */ // Pass the support class to the data instance [databaseDataInstance setServerSupport:serverSupport]; -#endif #ifdef SP_REFACTOR /* glue */ tablesListInstance = [[SPTablesList alloc] init]; [tablesListInstance setDatabaseDocument:self]; + [tablesListInstance awakeFromNib]; #endif // Set the connection on the tables list instance - this updates the table list while the connection @@ -429,6 +454,10 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; if ([encodingType intValue] != SPEncodingAutodetect) { [self setConnectionEncoding:[self mysqlEncodingFromEncodingTag:encodingType] reloadingViews:NO]; + } else { +#endif + [[self onMainThread] updateEncodingMenuWithSelectedEncoding:[self encodingTagFromMySQLEncoding:[mySQLConnection encoding]]]; +#ifndef SP_REFACTOR } #endif @@ -450,8 +479,15 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; [self updateWindowTitle:self]; // Connected Growl notification + NSString *serverDisplayName = nil; + if ([parentWindowController selectedTableDocument] == self) { + serverDisplayName = [parentWindow title]; + } else { + serverDisplayName = [parentTabViewItem label]; + } + [[SPGrowlController sharedGrowlController] notifyWithTitle:@"Connected" - description:[NSString stringWithFormat:NSLocalizedString(@"Connected to %@",@"description for connected growl notification"), [parentWindow title]] + description:[NSString stringWithFormat:NSLocalizedString(@"Connected to %@",@"description for connected growl notification"), serverDisplayName] document:self notificationName:@"Connected"]; @@ -522,7 +558,7 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; } } - (void)[self databaseEncoding]; + if ([self database]) [self detectDatabaseEncoding]; #endif #ifdef SP_REFACTOR /* glue */ if ( delegate && [delegate respondsToSelector:@selector(databaseDocumentDidConnect:)] ) @@ -679,7 +715,6 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; } } -#ifndef SP_REFACTOR /* operations on whole databases */ /** * opens the add-db sheet and creates the new db */ @@ -731,6 +766,7 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; } +#ifndef SP_REFACTOR /* operations on whole databases */ /** * opens the copy database sheet and copies the databsae */ @@ -739,7 +775,7 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; if (![tablesListInstance selectionShouldChangeInTableView:nil]) return; [databaseCopyNameField setStringValue:selectedDatabase]; - [copyDatabaseMessageField setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Duplicate database '%@' to:", @"duplicate database message"), selectedDatabase]]; + [copyDatabaseMessageField setStringValue:selectedDatabase]; [NSApp beginSheet:databaseCopySheet modalForWindow:parentWindow @@ -747,6 +783,7 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:@"copyDatabase"]; } +#endif /** * opens the rename database sheet and renames the databsae @@ -770,8 +807,10 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; */ - (IBAction)removeDatabase:(id)sender { +#ifndef SP_REFACTOR // No database selected, bail if ([chooseDatabaseButton indexOfSelectedItem] == 0) return; +#endif if (![tablesListInstance selectionShouldChangeInTableView:nil]) return; @@ -793,6 +832,7 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; [alert beginSheetModalForWindow:parentWindow modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:@"removeDatabase"]; } +#ifndef SP_REFACTOR /** * Refreshes the tables list by calling SPTablesList's updateTables. */ @@ -852,7 +892,6 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; return allSystemDatabases; } -#ifndef SP_REFACTOR /* sheetDidEnd: */ /** * Alert sheet method. Invoked when an alert sheet is dismissed. * @@ -863,11 +902,12 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; */ - (void)sheetDidEnd:(id)sheet returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo { - +#ifndef SP_REFACTOR if([contextInfo isEqualToString:@"saveDocPrefSheetStatus"]) { saveDocPrefSheetStatus = returnCode; return; } +#endif // Order out current sheet to suppress overlapping of sheets if ([sheet respondsToSelector:@selector(orderOut:)]) @@ -897,23 +937,27 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; [chooseDatabaseButton selectItemAtIndex:0]; } } +#ifndef SP_REFACTOR else if ([contextInfo isEqualToString:@"copyDatabase"]) { if (returnCode == NSOKButton) { [self _copyDatabase]; } } +#endif else if ([contextInfo isEqualToString:@"renameDatabase"]) { if (returnCode == NSOKButton) { [self _renameDatabase]; } } +#ifndef SP_REFACTOR // Close error status sheet for OPTIMIZE, CHECK, REPAIR etc. else if ([contextInfo isEqualToString:@"statusError"]) { if (statusValues) [statusValues release], statusValues = nil; } - +#endif } +#ifndef SP_REFACTOR /* sheetDidEnd: */ /** * Show Error sheet (can be called from inside of a endSheet selector) * via [self performSelector:@selector(showErrorSheetWithTitle:) withObject: afterDelay:] @@ -1500,11 +1544,20 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; } /** - * Detect and return the database encoding. - * Falls back to Latin1. + * Retrieve the current database encoding. This will return Latin-1 + * for unknown encodings. */ - (NSString *)databaseEncoding { + return selectedDatabaseEncoding; +} + +/** + * Detect and store the encoding of the currently selected database. + * Falls back to Latin-1 if the encoding cannot be retrieved. + */ +- (void)detectDatabaseEncoding +{ MCPResult *charSetResult; NSString *mysqlEncoding = nil; @@ -1521,14 +1574,16 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; mysqlEncoding = [[[mySQLConnection queryString:@"SHOW VARIABLES LIKE 'character_set'"] fetchRowAsDictionary] objectForKey:@"Value"]; } + [selectedDatabaseEncoding release]; + // Fallback or older version? -> set encoding to mysql default encoding latin1 if ( !mysqlEncoding ) { NSLog(@"Error: no character encoding found, mysql version is %@", [self mySQLVersion]); - mysqlEncoding = @"latin1"; + selectedDatabaseEncoding = [[NSString alloc] initWithString:@"latin1"]; _supportsEncoding = NO; + } else { + selectedDatabaseEncoding = [mysqlEncoding retain]; } - - return mysqlEncoding; } /** @@ -2208,6 +2263,7 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; { queryEditorInitString = [query retain]; } +#endif /** * Invoked when user hits the cancel button or close button in @@ -2227,6 +2283,7 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; [[sender window] orderOut:self]; } +#ifndef SP_REFACTOR /** * Displays the user account manager. */ @@ -3550,6 +3607,34 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; [titleImageView setImage:nil]; } +/** + * Update the title bar status area visibility. The status area is visible if the tab is + * frontmost in the window, and if the window is not fullscreen. + */ +- (void)updateTitlebarStatusVisibilityForcingHide:(BOOL)forceHide +{ + BOOL newIsVisible = !forceHide; + if (newIsVisible && [parentWindow styleMask] & NSFullScreenWindowMask) newIsVisible = NO; + if (newIsVisible && [parentWindowController selectedTableDocument] != self) newIsVisible = NO; + if (newIsVisible == windowTitleStatusViewIsVisible) return; + + if (newIsVisible) { + NSView *windowFrame = [[parentWindow contentView] superview]; + NSRect av = [titleAccessoryView frame]; + NSRect initialAccessoryViewFrame = NSMakeRect( + [windowFrame frame].size.width - av.size.width - 30, + [windowFrame frame].size.height - av.size.height, + av.size.width, + av.size.height); + [titleAccessoryView setFrame:initialAccessoryViewFrame]; + [windowFrame addSubview:titleAccessoryView]; + } else { + [titleAccessoryView removeFromSuperview]; + } + + windowTitleStatusViewIsVisible = newIsVisible; +} + #pragma mark - #pragma mark Toolbar Methods @@ -3905,9 +3990,7 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; */ - (void)willResignActiveTabInWindow { - - // Remove the icon accessory view from the title bar - [titleAccessoryView removeFromSuperview]; + [self updateTitlebarStatusVisibilityForcingHide:YES]; // Remove the task progress window [parentWindow removeChildWindow:taskProgressWindow]; @@ -3933,16 +4016,7 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; else [parentWindow setRepresentedURL:nil]; - // Add the icon accessory view to the title bar - NSView *windowFrame = [[parentWindow contentView] superview]; - NSRect av = [titleAccessoryView frame]; - NSRect initialAccessoryViewFrame = NSMakeRect( - [windowFrame frame].size.width - av.size.width - 30, - [windowFrame frame].size.height - av.size.height, - av.size.width, - av.size.height); - [titleAccessoryView setFrame:initialAccessoryViewFrame]; - [windowFrame addSubview:titleAccessoryView]; + [self updateTitlebarStatusVisibilityForcingHide:NO]; // Add the progress window to this window [self centerTaskWindow]; @@ -4077,6 +4151,15 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; } return [[[self fileURL] path] lastPathComponent]; } + +#ifndef SP_REFACTOR +- (NSUndoManager *)undoManager +{ + return undoManager; +} +#endif + + #ifndef SP_REFACTOR /* state saving and setting */ #pragma mark - #pragma mark State saving and setting @@ -5578,13 +5661,16 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; for (id retainedObject in nibObjectsToRelease) [retainedObject release]; [nibObjectsToRelease release]; + #endif [allDatabases release]; [allSystemDatabases release]; #ifndef SP_REFACTOR /* dealloc ivars */ + [undoManager release]; [printWebView release]; #endif + [selectedDatabaseEncoding release]; [taskProgressWindow close]; if (selectedTableName) [selectedTableName release]; @@ -5613,6 +5699,11 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; #endif if (runningActivitiesArray) [runningActivitiesArray release]; +#ifdef SP_REFACTOR + if ( tablesListInstance ) [tablesListInstance release]; + if ( customQueryInstance ) [customQueryInstance release]; +#endif + [super dealloc]; } @@ -5656,6 +5747,7 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; // Update DB list [self setDatabases:self]; } +#endif - (void)_renameDatabase { @@ -5682,6 +5774,20 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; // Update DB list [self setDatabases:self]; + +#ifdef SP_REFACTOR + if ( delegate && [delegate respondsToSelector:@selector(refreshDatabasePopup)] ) + [delegate performSelector:@selector(refreshDatabasePopup) withObject:nil]; + + if ( delegate && [delegate respondsToSelector:@selector(selectDatabaseInPopup:)] ) + { + if ( [allDatabases count] > 0 ) + { + NSString* db = [databaseRenameNameField stringValue]; + [delegate performSelector:@selector(selectDatabaseInPopup:) withObject:db]; + } + } +#endif } /** @@ -5735,7 +5841,21 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; [tablesListInstance setConnection:mySQLConnection]; [tableDumpInstance setConnection:mySQLConnection]; +#ifndef SP_REFACTOR [self updateWindowTitle:self]; +#endif +#ifdef SP_REFACTOR /* glue */ + if ( delegate && [delegate respondsToSelector:@selector(refreshDatabasePopup)] ) + [delegate performSelector:@selector(refreshDatabasePopup) withObject:nil]; + + if ( delegate && [delegate respondsToSelector:@selector(selectDatabaseInPopup:)] ) + { + if ( [allDatabases count] > 0 ) + { + [delegate performSelector:@selector(selectDatabaseInPopup:) withObject:selectedDatabase]; + } + } +#endif } /** @@ -5774,10 +5894,23 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; [tablesListInstance setConnection:mySQLConnection]; [tableDumpInstance setConnection:mySQLConnection]; +#ifndef SP_REFACTOR [self updateWindowTitle:self]; -} - #endif +#ifdef SP_REFACTOR /* glue */ + if ( delegate && [delegate respondsToSelector:@selector(refreshDatabasePopup)] ) + [delegate performSelector:@selector(refreshDatabasePopup) withObject:nil]; + + if ( delegate && [delegate respondsToSelector:@selector(selectDatabaseInPopup:)] ) + { + if ( [allDatabases count] > 0 ) + { + NSString* db = [allDatabases objectAtIndex:0]; + [delegate performSelector:@selector(selectDatabaseInPopup:) withObject:db]; + } + } +#endif +} /** * Select the specified database and, optionally, table. @@ -5811,7 +5944,7 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; // End the task first to ensure the database dropdown can be reselected [self endTask]; - if ( [mySQLConnection isConnected] ) { + if ([mySQLConnection isConnected]) { // Update the database list [[self onMainThread] setDatabases:self]; @@ -5840,6 +5973,10 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; [[[tablesListInstance valueForKey:@"tablesListView"] onMainThread] deselectAll:self]; [[tablesListInstance onMainThread] setTableListSelectability:NO]; } + + // Update the stored database encoding, used for views, "default" table encodings, and to allow + // or disallow use of the "View using encoding" menu + [self detectDatabaseEncoding]; #endif // Set the connection of SPTablesList and TablesDump to reload tables in db @@ -5858,10 +5995,12 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; // Set focus to table list filter field if visible // otherwise set focus to Table List view - if ( [[tablesListInstance tables] count] > 20 ) + if ([[tablesListInstance tables] count] > 20) { [[parentWindow onMainThread] makeFirstResponder:listFilterField]; - else + } + else { [[parentWindow onMainThread] makeFirstResponder:[tablesListInstance valueForKeyPath:@"tablesListView"]]; + } #endif } @@ -5919,8 +6058,9 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; #endif #ifdef SP_REFACTOR /* glue */ - if ( delegate && [delegate respondsToSelector:@selector(databaseDidChange:)] ) + if (delegate && [delegate respondsToSelector:@selector(databaseDidChange:)]) { [delegate performSelectorOnMainThread:@selector(databaseDidChange:) withObject:self waitUntilDone:NO]; + } #endif [taskPool drain]; diff --git a/Source/SPDotExporter.m b/Source/SPDotExporter.m index e0c1a294..cd00b61f 100644 --- a/Source/SPDotExporter.m +++ b/Source/SPDotExporter.m @@ -108,16 +108,16 @@ // Process the tables for (NSUInteger i = 0; i < [[self dotExportTables] count]; i++) { - // Check for cancellation flag if ([self isCancelled]) { [fkInfo release]; [pool release]; + return; } NSString *tableName = NSArrayObjectAtIndex([self dotExportTables], i); - NSString *tableLinkName = [self dotForceLowerTableNames]?[tableName lowercaseString]:tableName; + NSString *tableLinkName = [self dotForceLowerTableNames] ? [tableName lowercaseString] : tableName; NSDictionary *tableInfo = [[self dotTableData] informationForTable:tableName]; // Set the current table @@ -154,21 +154,24 @@ // Check if any relations are available for the table NSArray *tableConstraints = [tableInfo objectForKey:@"constraints"]; + if ([tableConstraints count]) { - for (NSDictionary* aConstraint in tableConstraints) { - + + for (NSDictionary* constraint in tableConstraints) + { // Check for cancellation flag if ([self isCancelled]) { [fkInfo release]; [pool release]; + return; } // Get the column references. Currently the columns themselves are an array, // while reference columns and tables are comma separated if there are more than // one. Only use the first of each for the time being. - NSArray *originColumns = [aConstraint objectForKey:@"columns"]; - NSArray *referenceColumns = [[aConstraint objectForKey:@"ref_columns"] componentsSeparatedByString:@","]; + NSArray *originColumns = [constraint objectForKey:@"columns"]; + NSArray *referenceColumns = [[constraint objectForKey:@"ref_columns"] componentsSeparatedByString:@","]; NSString *extra = @""; @@ -176,12 +179,13 @@ extra = @" [ arrowhead=crow, arrowtail=odiamond ]"; } - [fkInfo addObject:[NSString stringWithFormat:@"%@:%@ -> %@:%@ %@", tableLinkName, [originColumns objectAtIndex:0], [aConstraint objectForKey:@"ref_table"], [[referenceColumns objectAtIndex:0] lowercaseString], extra]]; + [fkInfo addObject:[NSString stringWithFormat:@"%@:%@ -> %@:%@ %@", tableLinkName, [originColumns objectAtIndex:0], [constraint objectForKey:@"ref_table"], [[referenceColumns objectAtIndex:0] lowercaseString], extra]]; } } // Update progress NSInteger progress = (i * ([self exportMaxProgress] / [[self dotExportTables] count])); + [self setExportProgressValue:progress]; [delegate performSelectorOnMainThread:@selector(dotExportProcessProgressUpdated:) withObject:self waitUntilDone:NO]; } @@ -193,7 +197,9 @@ // Get the relations for (id item in fkInfo) + { [metaString appendFormat:@"%@;\n", item]; + } [fkInfo release]; diff --git a/Source/SPExportController.h b/Source/SPExportController.h index 0130d508..7a4fcb3c 100644 --- a/Source/SPExportController.h +++ b/Source/SPExportController.h @@ -92,6 +92,7 @@ IBOutlet NSButton *exportSQLBLOBFieldsAsHexCheck; IBOutlet NSTextField *exportSQLInsertNValueTextField; IBOutlet NSPopUpButton *exportSQLInsertDividerPopUpButton; + IBOutlet NSButton *exportSQLIncludeAutoIncrementValueButton; // Excel IBOutlet NSMatrix *exportExcelSheetOrFilePerTableMatrix; @@ -112,6 +113,11 @@ // Dot IBOutlet NSButton *exportDotForceLowerTableNamesCheck; + + /** + * Whether the awakeFromNib routine has already been run + */ + BOOL mainNibLoaded; /** * Cancellation flag diff --git a/Source/SPExportController.m b/Source/SPExportController.m index 0ff78c1f..21ab667b 100644 --- a/Source/SPExportController.m +++ b/Source/SPExportController.m @@ -55,6 +55,7 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; - (void)_displayExportTypeOptions:(BOOL)display; - (void)_updateExportFormatInformation; - (void)_updateExportAdvancedOptionsLabel; +- (void)_setPreviousExportFilenameAndPath; - (void)_toggleExportButton:(id)uiStateDict; - (void)_toggleExportButtonOnBackgroundThread; @@ -83,7 +84,9 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; [self setExportCancelled:NO]; [self setExportToMultipleFiles:YES]; - + + mainNibLoaded = NO; + exportType = SPSQLExport; exportSource = SPTableExport; exportTableCount = 0; @@ -119,7 +122,12 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; * Upon awakening select the first toolbar item */ - (void)awakeFromNib -{ +{ + // As this controller also loads its own nib, it may call awakeFromNib multiple times; perform setup only once. + if (mainNibLoaded) return; + + mainNibLoaded = YES; + // Select the 'selected tables' option [exportInputPopUpButton selectItemAtIndex:SPTableExport]; @@ -135,17 +143,6 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; // Set the progress indicator's max value [exportProgressIndicator setMaxValue:(NSInteger)[exportProgressIndicator bounds].size.width]; - // If a directory has previously been selected, reselect it - if ([prefs objectForKey:SPExportLastDirectory]) { - [exportPathField setStringValue:[prefs objectForKey:SPExportLastDirectory]]; - } else { - - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSAllDomainsMask, YES); - - // If found the set the default path to the user's desktop, otherwise use their home directory - [exportPathField setStringValue:([paths count] > 0) ? [paths objectAtIndex:0] : NSHomeDirectory()]; - } - // Empty the tokenizing character set for the filename field [exportCustomFilenameTokenField setTokenizingCharacterSet:[NSCharacterSet characterSetWithCharactersInString:@""]]; @@ -170,12 +167,9 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; // Select the correct tab [exportTypeTabBar selectTabViewItemAtIndex:format]; - // Restore the export filename if it exists, and update the display - if ([prefs objectForKey:SPExportFilenameFormat]) { - [exportCustomFilenameTokenField setObjectValue:[NSKeyedUnarchiver unarchiveObjectWithData:[prefs objectForKey:SPExportFilenameFormat]]]; - } - [self updateDisplayedExportFilename]; + [self _setPreviousExportFilenameAndPath]; + [self updateDisplayedExportFilename]; [self refreshTableList:nil]; [exporters removeAllObjects]; @@ -582,6 +576,15 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; */ - (IBAction)toggleSQLIncludeStructure:(id)sender { + if (![sender state]) + { + [exportSQLIncludeDropSyntaxCheck setState:NSOffState]; + } + + [exportSQLIncludeDropSyntaxCheck setEnabled:[sender state]]; + [exportSQLIncludeAutoIncrementValueButton setEnabled:[sender state]]; + + [[exportTableList tableColumnWithIdentifier:SPTableViewDropColumnID] setHidden:(![sender state])]; [[exportTableList tableColumnWithIdentifier:SPTableViewStructureColumnID] setHidden:(![sender state])]; [self _toggleExportButtonOnBackgroundThread]; @@ -641,12 +644,17 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; // token - this suggests it's not a one-off filename if (![exportCustomFilenameTokenField stringValue]) { [prefs removeObjectForKey:SPExportFilenameFormat]; - } else { + } + else { BOOL saveFilename = NO; + NSArray *representedObjects = [exportCustomFilenameTokenField objectValue]; - for (id aToken in representedObjects) { + + for (id aToken in representedObjects) + { if ([aToken isKindOfClass:[SPExportFileNameTokenObject class]]) saveFilename = YES; } + if (saveFilename) [prefs setObject:[NSKeyedArchiver archivedDataWithRootObject:representedObjects] forKey:SPExportFilenameFormat]; } @@ -696,7 +704,7 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; - (BOOL)validateMenuItem:(NSMenuItem *)menuItem { if ([menuItem action] == @selector(exportCustomQueryResultAsFormat:)) { - return ([[customQueryInstance currentResult] count] > 1); + return (([[customQueryInstance currentResult] count] > 1) && (![tableDocumentInstance isProcessing])); } return YES; @@ -760,7 +768,7 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; // When exporting to SQL, only the selected tables option should be enabled if (isSQL) { - // Programmatically changing the selected item of a popup button does not fire it's action, so updated + // Programmatically changing the selected item of a popup button does not fire it's action, so update // the selected export source manually. exportSource = SPTableExport; @@ -786,13 +794,18 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; // When switching to Dot export, ensure the server's lower_case_table_names value is checked the first time // to set the export's link case sensitivity setting if (isDot && serverLowerCaseTableNameValue == NSNotFound) { + MCPResult *caseResult = [connection queryString:@"SHOW VARIABLES LIKE 'lower_case_table_names'"]; + [caseResult setReturnDataAsStrings:YES]; + if ([caseResult numOfRows] == 1) { serverLowerCaseTableNameValue = [[[caseResult fetchRowAsDictionary] objectForKey:@"Value"] integerValue]; - } else { + } + else { serverLowerCaseTableNameValue = 0; } + [exportDotForceLowerTableNamesCheck setState:(serverLowerCaseTableNameValue == 0)?NSOffState:NSOnState]; } @@ -872,10 +885,14 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; switch (exportType) { case SPCSVExport: if ([exportFilePerTableCheck state]) break; + NSUInteger numberOfTables = 0; - for (NSMutableArray *eachTable in tables) { + + for (NSMutableArray *eachTable in tables) + { if ([[eachTable objectAtIndex:2] boolValue]) numberOfTables++; } + if (numberOfTables <= 1) break; case SPXMLExport: case SPDotExport: @@ -902,15 +919,18 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; if ([exportProcessLowMemoryButton state]) { [optionsSummary addObject:NSLocalizedString(@"Low memory", @"Low memory export summary")]; - } else { + } + else { [optionsSummary addObject:NSLocalizedString(@"Standard memory", @"Standard memory export summary")]; } if ([exportOutputCompressionFormatPopupButton indexOfSelectedItem] == SPNoCompression) { [optionsSummary addObject:NSLocalizedString(@"no compression", @"No compression export summary - within a sentence")]; - } else if ([exportOutputCompressionFormatPopupButton indexOfSelectedItem] == SPGzipCompression) { + } + else if ([exportOutputCompressionFormatPopupButton indexOfSelectedItem] == SPGzipCompression) { [optionsSummary addObject:NSLocalizedString(@"Gzip compression", @"Gzip compression export summary - within a sentence")]; - } else if ([exportOutputCompressionFormatPopupButton indexOfSelectedItem] == SPBzip2Compression) { + } + else if ([exportOutputCompressionFormatPopupButton indexOfSelectedItem] == SPBzip2Compression) { [optionsSummary addObject:NSLocalizedString(@"bzip2 compression", @"bzip2 compression export summary - within a sentence")]; } @@ -918,6 +938,28 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; } /** + * Sets the previous export filename and path if available. + */ +- (void)_setPreviousExportFilenameAndPath +{ + // Restore the export filename if it exists, and update the display + if ([prefs objectForKey:SPExportFilenameFormat]) { + [exportCustomFilenameTokenField setObjectValue:[NSKeyedUnarchiver unarchiveObjectWithData:[prefs objectForKey:SPExportFilenameFormat]]]; + } + + // If a directory has previously been selected, reselect it + if ([prefs objectForKey:SPExportLastDirectory]) { + [exportPathField setStringValue:[prefs objectForKey:SPExportLastDirectory]]; + } + else { + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSAllDomainsMask, YES); + + // If found the set the default path to the user's desktop, otherwise use their home directory + [exportPathField setStringValue:([paths count] > 0) ? [paths objectAtIndex:0] : NSHomeDirectory()]; + } +} + +/** * Enables or disables the export button based on the state of various interface controls. * * @param uiStateDict A dictionary containing the state of various UI controls. diff --git a/Source/SPExportFilenameUtilities.m b/Source/SPExportFilenameUtilities.m index a4330e36..31625a4c 100644 --- a/Source/SPExportFilenameUtilities.m +++ b/Source/SPExportFilenameUtilities.m @@ -219,40 +219,50 @@ // Walk through the token field, appending token replacements or strings NSArray *representedFilenameParts = [exportCustomFilenameTokenField objectValue]; - for (id filenamePart in representedFilenameParts) { + + for (id filenamePart in representedFilenameParts) + { if ([filenamePart isKindOfClass:[SPExportFileNameTokenObject class]]) { NSString *tokenContent = [filenamePart tokenContent]; if ([tokenContent isEqualToString:NSLocalizedString(@"host", @"export filename host token")]) { [string appendString:[tableDocumentInstance host]]; - } else if ([tokenContent isEqualToString:NSLocalizedString(@"database", @"export filename database token")]) { + } + else if ([tokenContent isEqualToString:NSLocalizedString(@"database", @"export filename database token")]) { [string appendString:[tableDocumentInstance database]]; - } else if ([tokenContent isEqualToString:NSLocalizedString(@"table", @"table")]) { + } + else if ([tokenContent isEqualToString:NSLocalizedString(@"table", @"table")]) { [string appendString:(table) ? table : @""]; - } else if ([tokenContent isEqualToString:NSLocalizedString(@"date", @"export filename date token")]) { + } + else if ([tokenContent isEqualToString:NSLocalizedString(@"date", @"export filename date token")]) { [dateFormatter setDateStyle:NSDateFormatterShortStyle]; [dateFormatter setTimeStyle:NSDateFormatterNoStyle]; [string appendString:[dateFormatter stringFromDate:[NSDate date]]]; - } else if ([tokenContent isEqualToString:NSLocalizedString(@"year", @"export filename date token")]) { + } + else if ([tokenContent isEqualToString:NSLocalizedString(@"year", @"export filename date token")]) { [string appendString:[[NSDate date] descriptionWithCalendarFormat:@"%Y" timeZone:nil locale:nil]]; - } else if ([tokenContent isEqualToString:NSLocalizedString(@"month", @"export filename date token")]) { + } + else if ([tokenContent isEqualToString:NSLocalizedString(@"month", @"export filename date token")]) { [string appendString:[[NSDate date] descriptionWithCalendarFormat:@"%m" timeZone:nil locale:nil]]; - } else if ([tokenContent isEqualToString:NSLocalizedString(@"day", @"export filename date token")]) { + } + else if ([tokenContent isEqualToString:NSLocalizedString(@"day", @"export filename date token")]) { [string appendString:[[NSDate date] descriptionWithCalendarFormat:@"%d" timeZone:nil locale:nil]]; - } else if ([tokenContent isEqualToString:NSLocalizedString(@"time", @"export filename time token")]) { + } + else if ([tokenContent isEqualToString:NSLocalizedString(@"time", @"export filename time token")]) { [dateFormatter setDateStyle:NSDateFormatterNoStyle]; [dateFormatter setTimeStyle:NSDateFormatterShortStyle]; [string appendString:[dateFormatter stringFromDate:[NSDate date]]]; } - } else { + } + else { [string appendString:filenamePart]; } } diff --git a/Source/SPExtendedTableInfo.m b/Source/SPExtendedTableInfo.m index 13e60f92..a1e59110 100644 --- a/Source/SPExtendedTableInfo.m +++ b/Source/SPExtendedTableInfo.m @@ -383,7 +383,6 @@ */ - (NSDictionary *)tableInformationForPrinting { - // Update possible pending comment changes by set the focus to create table syntax view [[NSApp keyWindow] makeFirstResponder:tableCreateSyntaxTextView]; diff --git a/Source/SPFieldEditorController.m b/Source/SPFieldEditorController.m index 7bdd2eb9..7c8a960c 100644 --- a/Source/SPFieldEditorController.m +++ b/Source/SPFieldEditorController.m @@ -23,7 +23,9 @@ // More info at <http://code.google.com/p/sequel-pro/> #import "SPFieldEditorController.h" +#ifndef SP_REFACTOR #import "QLPreviewPanel.h" +#endif #import "SPDataCellFormatter.h" #import "RegexKitLite.h" #import "SPDataCellFormatter.h" @@ -51,7 +53,12 @@ */ - (id)init { - if ((self = [super initWithWindowNibName:@"FieldEditorSheet"])) { +#ifndef SP_REFACTOR + if ((self = [super initWithWindowNibName:@"FieldEditorSheet"])) +#else + if ((self = [super initWithWindowNibName:@"SQLFieldEditorSheet"])) +#endif + { // force the nib to be loaded (void) [self window]; counter = 0; @@ -86,6 +93,7 @@ [menuItem setEnabled:NO]; [menu addItem:menuItem]; [menuItem release]; +#ifndef SP_REFACTOR NSUInteger tag = 2; // Load default QL types @@ -112,7 +120,6 @@ [qlTypesItems addObject:type]; } } -#ifndef SP_REFACTOR // Load user-defined QL types if([prefs objectForKey:SPQuickLookTypes]) { for(id type in [prefs objectForKey:SPQuickLookTypes]) { @@ -125,10 +132,10 @@ [qlTypesItems addObject:type]; } } -#endif qlTypes = [[NSDictionary dictionaryWithObject:qlTypesItems forKey:SPQuickLookTypes] retain]; [qlTypesItems release]; +#endif fieldType = @""; fieldEncoding = @""; @@ -144,13 +151,18 @@ { [NSObject cancelPreviousPerformRequestsWithTarget:self]; +#ifndef SP_REFACTOR // On Mac OSX 10.6 QuickLook runs non-modal thus order out the panel // if still visible if([[NSClassFromString(@"QLPreviewPanel") sharedPreviewPanel] isVisible]) [[NSClassFromString(@"QLPreviewPanel") sharedPreviewPanel] orderOut:nil]; +#endif if ( sheetEditData ) [sheetEditData release]; +#ifndef SP_REFACTOR if ( qlTypes ) [qlTypes release]; +#endif + if ( tmpFileName ) [tmpFileName release]; if ( tmpDirPath ) [tmpDirPath release]; if ( esUndoManager ) [esUndoManager release]; if ( contextInfo ) [contextInfo release]; @@ -309,10 +321,10 @@ // Set window's min size since no segment and quicklook buttons are hidden if (_isBlob || _isBINARY || _isGeometry) { [usedSheet setFrameAutosaveName:@"SPFieldEditorBlobSheet"]; - [usedSheet setMinSize:NSMakeSize(560, 200)]; + [usedSheet setMinSize:NSMakeSize(650, 200)]; } else { [usedSheet setFrameAutosaveName:@"SPFieldEditorTextSheet"]; - [usedSheet setMinSize:NSMakeSize(340, 150)]; + [usedSheet setMinSize:NSMakeSize(390, 150)]; } [editTextView setEditable:_isEditable]; @@ -420,8 +432,10 @@ // After ordering out this sheet SPCopyTable remains the first responder thus set it hard. // This only works in conjunction with [NSTextView becomeFirstResponder] and [NSTextView resignFirstResponder] // which has to return YES. +#ifndef SP_REFACTOR if([[self window] firstResponder] == editTextView) [[NSApp mainWindow] makeFirstResponder:[[self window] firstResponder]]; +#endif } @@ -480,7 +494,9 @@ [hexTextView setHidden:YES]; [hexTextScrollView setHidden:YES]; [usedSheet makeFirstResponder:editTextView]; +#ifndef SP_REFACTOR [[NSApp mainWindow] makeFirstResponder:editTextView]; +#endif break; case 1: // image [editTextView setHidden:YES]; @@ -563,23 +579,31 @@ */ - (IBAction)closeEditSheet:(id)sender { - editSheetReturnCode = 0; // Validate the sheet data before saving them. // - for max text length (except for NULL value string) select the part which won't be saved // and suppress closing the sheet - if(sender == editSheetOkButton) { - if (maxTextLength > 0 && [[editTextView textStorage] length] > maxTextLength && ![[[editTextView textStorage] string] isEqualToString:[prefs objectForKey:SPNullValue]]) { - [editTextView setSelectedRange:NSMakeRange((NSUInteger)maxTextLength, [[editTextView textStorage] length] - (NSUInteger)maxTextLength)]; + if (sender == editSheetOkButton) { + + unsigned long long maxLength = maxTextLength; + + // For FLOAT fields ignore the decimal point in the text when comparing lengths + if ([[fieldType uppercaseString] isEqualToString:@"FLOAT"] && ([[[editTextView textStorage] string] rangeOfString:@"."].location != NSNotFound)) { + maxLength++; + } + + if (maxLength > 0 && [[editTextView textStorage] length] > maxLength && ![[[editTextView textStorage] string] isEqualToString:[prefs objectForKey:SPNullValue]]) { + [editTextView setSelectedRange:NSMakeRange((NSUInteger)maxLength, [[editTextView textStorage] length] - (NSUInteger)maxLength)]; [editTextView scrollRangeToVisible:NSMakeRange([editTextView selectedRange].location,0)]; [SPTooltip showWithObject:[NSString stringWithFormat:NSLocalizedString(@"Text is too long. Maximum text length is set to %llu.", @"Text is too long. Maximum text length is set to %llu."), maxTextLength]]; + return; } editSheetReturnCode = 1; } - else if(sender == bitSheetOkButton && _isEditable) { + else if (sender == bitSheetOkButton && _isEditable) { editSheetReturnCode = 1; } @@ -610,7 +634,6 @@ [callerInstance processFieldEditorResult:returnData contextInfo:contextInfo]; #endif } - } /** @@ -750,10 +773,12 @@ */ - (IBAction)quickLookFormatButton:(id)sender { +#ifndef SP_REFACTOR if(qlTypes != nil && [[qlTypes objectForKey:@"QuickLookTypes"] count] > (NSUInteger)[sender tag] - 2) { NSDictionary *type = [[qlTypes objectForKey:@"QuickLookTypes"] objectAtIndex:[sender tag] - 2]; [self invokeQuickLookOfType:[type objectForKey:@"Extension"] treatAsText:([[type objectForKey:@"treatAsText"] integerValue])]; } +#endif } /** @@ -770,7 +795,8 @@ // Create a temporary file name to store the data as file // since QuickLook only works on files. // Alternate the file name to suppress caching by using counter%2. - tmpFileName = [NSString stringWithFormat:@"%@SequelProQuickLook%d.%@", tmpDirPath, counter%2, type]; + if (tmpFileName) [tmpFileName release]; + tmpFileName = [[NSString alloc] initWithFormat:@"%@SequelProQuickLook%d.%@", tmpDirPath, counter%2, type]; // if data are binary if ( [sheetEditData isKindOfClass:[NSData class]] && !isText) { @@ -816,7 +842,7 @@ */ - (void)invokeQuickLookOfType:(NSString *)type treatAsText:(BOOL)isText { - +#ifndef SP_REFACTOR // Load QL via private framework (SDK 10.5) if([[NSBundle bundleWithPath:@"/System/Library/PrivateFrameworks/QuickLookUI.framework"] load]) { @@ -894,7 +920,7 @@ } else { [SPTooltip showWithObject:[NSString stringWithFormat:@"QuickLook is not available on that platform."]]; } - +#endif } /** @@ -902,6 +928,7 @@ */ - (void)beginPreviewPanelControl:(id)panel { +#ifndef SP_REFACTOR // This document is now responsible of the preview panel [panel setDelegate:self]; @@ -909,6 +936,7 @@ // Due to the unknown image format disable image sharing [panel setShowsAddToiPhotoButton:NO]; +#endif } @@ -1289,10 +1317,10 @@ */ - (BOOL)textView:(NSTextView *)textView shouldChangeTextInRange:(NSRange)r replacementString:(NSString *)replacementString { - - if(textView == editTextView && (maxTextLength > 0) - && ![ [[[editTextView textStorage] string] stringByAppendingString:replacementString] isEqualToString:[prefs objectForKey:SPNullValue]]) { - + if (textView == editTextView && + (maxTextLength > 0) && + ![[[[editTextView textStorage] string] stringByAppendingString:replacementString] isEqualToString:[prefs objectForKey:SPNullValue]]) + { NSInteger newLength; // Auxilary to ensure that eg textViewDidChangeSelection: @@ -1301,52 +1329,83 @@ // (OK button). editTextViewWasChanged = ([replacementString length] == 1); - // Pure attribute changes are ok. + // Pure attribute changes are ok if (!replacementString) return YES; // The exact change isn't known. Disallow the change to be safe. - if (r.location==NSNotFound) return NO; + if (r.location == NSNotFound) return NO; // Length checking while using the Input Manager (eg for Japanese) if ([textView hasMarkedText] && (maxTextLength > 0) && (r.location < maxTextLength)) { - + // User tries to insert a new char but max text length was already reached - return NO - if( !r.length && ([[textView textStorage] length] >= maxTextLength) ) { + if (!r.length && ([[textView textStorage] length] >= maxTextLength)) { [SPTooltip showWithObject:[NSString stringWithFormat:NSLocalizedString(@"Maximum text length is set to %llu.", @"Maximum text length is set to %llu."), maxTextLength]]; [textView unmarkText]; + return NO; } - // otherwise allow it if insertion point is valid for eg + // Otherwise allow it if insertion point is valid for eg // a VARCHAR(3) field filled with two Chinese chars and one inserts the // third char by typing its pronounciation "wo" - 2 Chinese chars plus "wo" would give // 4 which is larger than max length. // TODO this doesn't solve the problem of inserting more than one char. For now // that part which won't be saved will be hilited if user pressed the OK button. - else if (r.location < maxTextLength) + else if (r.location < maxTextLength) { return YES; + } } // Calculate the length of the text after the change. - newLength=[[[textView textStorage] string] length]+[replacementString length]-r.length; + newLength = [[[textView textStorage] string] length] + [replacementString length] - r.length; + NSUInteger textLength = [[[textView textStorage] string] length]; + + unsigned long long originalMaxTextLength = maxTextLength; + + // For FLOAT fields ignore the decimal point in the text when comparing lengths + if ([[fieldType uppercaseString] isEqualToString:@"FLOAT"] && + ([[[textView textStorage] string] rangeOfString:@"."].location != NSNotFound)) { + + if ((NSUInteger)newLength == (maxTextLength + 1)) { + maxTextLength++; + textLength--; + } + else if ((NSUInteger)newLength > maxTextLength) { + textLength--; + } + } + // If it's too long, disallow the change but try // to insert a text chunk partially to maxTextLength. if ((NSUInteger)newLength > maxTextLength) { - - if((maxTextLength-[[textView textStorage] length]+[textView selectedRange].length) <= [replacementString length]) { - if(maxTextLength-[[textView textStorage] length]+[textView selectedRange].length) - [SPTooltip showWithObject:[NSString stringWithFormat:NSLocalizedString(@"Maximum text length is set to %llu. Inserted text was truncated.", @"Maximum text length is set to %llu. Inserted text was truncated."), maxTextLength]]; - else - [SPTooltip showWithObject:[NSString stringWithFormat:NSLocalizedString(@"Maximum text length is set to %llu.", @"Maximum text length is set to %llu."), maxTextLength]]; - [textView insertText:[replacementString substringToIndex:(NSUInteger)maxTextLength-[[textView textStorage] length]+[textView selectedRange].length]]; + if ((maxTextLength - textLength + [textView selectedRange].length) <= [replacementString length]) { + + NSString *tooltip = nil; + + if (maxTextLength - textLength + [textView selectedRange].length) { + tooltip = [NSString stringWithFormat:NSLocalizedString(@"Maximum text length is set to %llu. Inserted text was truncated.", @"Maximum text length is set to %llu. Inserted text was truncated."), maxTextLength]; + } + else { + tooltip = [NSString stringWithFormat:NSLocalizedString(@"Maximum text length is set to %llu.", @"Maximum text length is set to %llu."), maxTextLength]; + } + + [SPTooltip showWithObject:tooltip]; + + [textView insertText:[replacementString substringToIndex:(NSUInteger)maxTextLength - textLength +[textView selectedRange].length]]; } + + maxTextLength = originalMaxTextLength; + return NO; } + + maxTextLength = originalMaxTextLength; - // Otherwise, allow it. + // Otherwise, allow it return YES; - } + return YES; } @@ -1355,7 +1414,6 @@ */ - (void)textViewDidChangeSelection:(NSNotification *)notification { - if([notification object] == editTextView) { // Do nothing if user really didn't changed text (e.g. for font size changing return) if(!editTextViewWasChanged && (editSheetWillBeInitialized diff --git a/Source/SPGeneralPreferencePane.h b/Source/SPGeneralPreferencePane.h index c573b84a..dda76ee9 100644 --- a/Source/SPGeneralPreferencePane.h +++ b/Source/SPGeneralPreferencePane.h @@ -42,5 +42,6 @@ - (IBAction)updateDefaultFavorite:(id)sender; - (void)updateDefaultFavoritePopup; +- (void)updateDefaultFavoritePopupSelection; @end diff --git a/Source/SPGeneralPreferencePane.m b/Source/SPGeneralPreferencePane.m index fc2b4014..4a263be4 100644 --- a/Source/SPGeneralPreferencePane.m +++ b/Source/SPGeneralPreferencePane.m @@ -96,12 +96,7 @@ static NSString *SPDatabaseImage = @"database-small"; } // Select the default favorite from prefs - if (![prefs boolForKey:SPSelectLastFavoriteUsed]) { - [defaultFavoritePopup selectItemWithTag:[prefs integerForKey:SPDefaultFavorite]]; - } - else { - [defaultFavoritePopup selectItemAtIndex:0]; - } + [self updateDefaultFavoritePopupSelection]; } #pragma mark - @@ -161,6 +156,13 @@ static NSString *SPDatabaseImage = @"database-small"; return items; } +- (void)updateDefaultFavoritePopupSelection +{ + NSUInteger index = [prefs integerForKey:SPDefaultFavorite]; + + [defaultFavoritePopup selectItemAtIndex:(![prefs boolForKey:SPSelectLastFavoriteUsed] && index > 0 && index < [[defaultFavoritePopup itemArray] count]) ? index + 2 : 0]; +} + #pragma mark - #pragma mark Preference pane protocol methods diff --git a/Source/SPGrowlController.h b/Source/SPGrowlController.h index d1cb7dc2..2075b143 100644 --- a/Source/SPGrowlController.h +++ b/Source/SPGrowlController.h @@ -32,7 +32,10 @@ @interface SPGrowlController : SPSingleton <GrowlApplicationBridgeDelegate> { NSString *timingNotificationName; + double timingNotificationStart; + + CGFloat longRunningQueryNotificationTime; } // Singleton controller diff --git a/Source/SPGrowlController.m b/Source/SPGrowlController.m index 3f98f41a..3fc27f64 100644 --- a/Source/SPGrowlController.m +++ b/Source/SPGrowlController.m @@ -60,8 +60,11 @@ static SPGrowlController *sharedGrowlController = nil; { if ((self = [super init])) { [GrowlApplicationBridge setGrowlDelegate:self]; + timingNotificationName = nil; timingNotificationStart = 0; + + longRunningQueryNotificationTime = [[NSUserDefaults standardUserDefaults] floatForKey:SPLongRunningQueryNotificationTime]; } return self; @@ -74,7 +77,6 @@ static SPGrowlController *sharedGrowlController = nil; */ - (void)notifyWithTitle:(NSString *)title description:(NSString *)description document:(SPDatabaseDocument *)document notificationName:(NSString *)name { - // Ensure that the delayed notification call is made on the main thread if (![NSThread isMainThread]) { [[self onMainThread] notifyWithTitle:title description:description document:document notificationName:name]; @@ -127,9 +129,10 @@ static SPGrowlController *sharedGrowlController = nil; // if it does, and the time exceeds the threshold, display the notification even for // frontmost windows to provide feedback for long-running tasks. if (timingNotificationName && [timingNotificationName isEqualToString:name]) { - if ([self milliTime] > (SPLongRunningNotificationTime * 1000) + timingNotificationStart) { + if ([self milliTime] > (longRunningQueryNotificationTime * 1000) + timingNotificationStart) { postNotification = YES; } + [timingNotificationName release], timingNotificationName = nil; } diff --git a/Source/SPIndexesController.h b/Source/SPIndexesController.h index 65cbb207..7c8fed07 100644 --- a/Source/SPIndexesController.h +++ b/Source/SPIndexesController.h @@ -58,6 +58,7 @@ IBOutlet NSPopUpButton *indexStorageTypePopUpButton; IBOutlet NSTextField *indexKeyBlockSizeTextField; + BOOL _mainNibLoaded; NSString *table; NSMutableArray *fields, *indexes, *indexedFields, *supportsLength, *requiresLength; diff --git a/Source/SPIndexesController.m b/Source/SPIndexesController.m index 989ec02c..8bdfb5bc 100644 --- a/Source/SPIndexesController.m +++ b/Source/SPIndexesController.m @@ -67,6 +67,7 @@ static const NSString *SPNewIndexKeyBlockSize = @"IndexKeyBlockSize"; { if ((self = [super initWithWindowNibName:@"IndexesView"])) { + _mainNibLoaded = NO; table = @""; fields = [[NSMutableArray alloc] init]; @@ -102,6 +103,11 @@ static const NSString *SPNewIndexKeyBlockSize = @"IndexKeyBlockSize"; */ - (void)awakeFromNib { + + // As this controller also loads its own nib, it may call awakeFromNib multiple times; perform setup only once. + if (_mainNibLoaded) return; + _mainNibLoaded = YES; + #ifndef SP_REFACTOR /* patch */ // Set the index tables view's vertical gridlines if required [indexesTableView setGridStyleMask:([prefs boolForKey:SPDisplayTableViewVerticalGridlines]) ? NSTableViewSolidVerticalGridLineMask : NSTableViewGridNone]; @@ -229,6 +235,10 @@ static const NSString *SPNewIndexKeyBlockSize = @"IndexKeyBlockSize"; [indexedFields removeAllObjects]; [indexedFields addObject:initialField]; + // Determine whether to show or hide the size column initially depending on whether the + // initial key has a required size + [indexSizeTableColumn setHidden:![requiresLength containsObject:[[initialField objectForKey:@"type"] uppercaseString]]]; + [indexedColumnsTableView reloadData]; [addIndexedColumnButton setEnabled:([indexedFields count] < [fields count])]; @@ -401,7 +411,15 @@ static const NSString *SPNewIndexKeyBlockSize = @"IndexKeyBlockSize"; [indexAdvancedOptionsViewButton setState:showAdvancedView]; [indexAdvancedOptionsView setHidden:(!showAdvancedView)]; - [indexSizeTableColumn setHidden:(!showAdvancedView)]; + // When hiding the advanced options, the size column would normally be hidden as well + // - unless any of the ndexes fields have a required key size. + BOOL hideSizesColumn = !showAdvancedView; + if (hideSizesColumn) { + for (NSDictionary *aField in indexedFields) { + if ([requiresLength containsObject:[[aField objectForKey:@"type"] uppercaseString]]) hideSizesColumn = NO; + } + } + [indexSizeTableColumn setHidden:hideSizesColumn]; [self _resizeWindowForAdvancedOptionsViewByHeightDelta:(showAdvancedView) ? ([indexAdvancedOptionsView frame].size.height + 10) : 0]; } @@ -421,7 +439,11 @@ static const NSString *SPNewIndexKeyBlockSize = @"IndexKeyBlockSize"; return [[indexes objectAtIndex:rowIndex] objectForKey:[tableColumn identifier]]; } else { - return [[indexedFields objectAtIndex:rowIndex] objectForKey:[tableColumn identifier]]; + id object = [[indexedFields objectAtIndex:rowIndex] objectForKey:[tableColumn identifier]]; + if ([[tableColumn identifier] isEqualToString:@"Size"] && object) { + object = [NSNumber numberWithLongLong:[object longLongValue]]; + } + return object; } } @@ -441,7 +463,16 @@ static const NSString *SPNewIndexKeyBlockSize = @"IndexKeyBlockSize"; } } else { - [[indexedFields objectAtIndex:rowIndex] setObject:object forKey:[tableColumn identifier]]; + + // Ensure conversion to string for Size column and its formatter + if ([object isKindOfClass:[NSNumber class]]) { + object = [NSString stringWithFormat:@"%llu", [object unsignedLongLongValue]]; + } + if (object) { + [[indexedFields objectAtIndex:rowIndex] setObject:object forKey:[tableColumn identifier]]; + } else { + [[indexedFields objectAtIndex:rowIndex] removeObjectForKey:[tableColumn identifier]]; + } } [self _reloadIndexedColumnsTableData]; @@ -975,6 +1006,10 @@ static const NSString *SPNewIndexKeyBlockSize = @"IndexKeyBlockSize"; if (indexedFields) [indexedFields release], indexedFields = nil; +#ifndef SP_REFACTOR + [prefs removeObserver:self forKeyPath:SPDisplayTableViewVerticalGridlines]; +#endif + [super dealloc]; } diff --git a/Source/SPKeychain.h b/Source/SPKeychain.h index c1984be1..31182fe1 100644 --- a/Source/SPKeychain.h +++ b/Source/SPKeychain.h @@ -30,6 +30,8 @@ - (NSString *)getPasswordForName:(NSString *)name account:(NSString *)account; - (void)deletePasswordForName:(NSString *)name account:(NSString *)account; - (BOOL)passwordExistsForName:(NSString *)name account:(NSString *)account; +- (void)updateItemWithName:(NSString *)name account:(NSString *)account toPassword:(NSString *)password; +- (void)updateItemWithName:(NSString *)name account:(NSString *)account toName:(NSString *)newName account:(NSString *)newAccount password:(NSString *)password; - (NSString *)nameForFavoriteName:(NSString *)theName id:(NSString *)theID; - (NSString *)accountForUser:(NSString *)theUser host:(NSString *)theHost database:(NSString *)theDatabase; - (NSString *)nameForSSHForFavoriteName:(NSString *)theName id:(NSString *)theID; diff --git a/Source/SPKeychain.m b/Source/SPKeychain.m index 931f14d7..aa38f7b5 100644 --- a/Source/SPKeychain.m +++ b/Source/SPKeychain.m @@ -232,6 +232,66 @@ } /** + * Change the password for a keychain item. This should be used instead of + * deleting and recreating the keychain item, as it allows preservation of + * access lists and works around Lion cacheing issues. + */ +- (void)updateItemWithName:(NSString *)name account:(NSString *)account toPassword:(NSString *)password +{ + [self updateItemWithName:name account:account toName:password account:name password:account]; +} + +/** + * Change the details for a keychain item. This should be used instead of + * deleting and recreating the keychain item, as it allows preservation of + * access lists and works around Lion cacheing issues. + */ +- (void)updateItemWithName:(NSString *)name account:(NSString *)account toName:(NSString *)newName account:(NSString *)newAccount password:(NSString *)password +{ + OSStatus status; + SecKeychainItemRef itemRef; + SecKeychainAttribute attributes[2]; + SecKeychainAttributeList attList; + + // Retrieve a reference to the keychain item + status = SecKeychainFindGenericPassword(NULL, // Default keychain + (UInt32)strlen([name UTF8String]), [name UTF8String], // Service name and length + (UInt32)strlen([account UTF8String]), [account UTF8String], // Account name and length + NULL, NULL, // No password retrieval required + &itemRef); // The item reference + + if (status != noErr) { + NSLog(@"Error (%i) while trying to find keychain item to edit for name: %@ account: %@", (int)status, name, account); + SPBeginAlertSheet(NSLocalizedString(@"Error retrieving Keychain item to edit", @"error finding keychain item to edit message"), + NSLocalizedString(@"OK", @"OK button"), + nil, nil, [NSApp mainWindow], self, nil, nil, + [NSString stringWithFormat:NSLocalizedString(@"An error occured while trying to retrieve the Keychain item you're trying to edit. Repairing your Keychain might resolve this, but if it doesn't please report it to the Sequel Pro team, supplying the error code %i.", @"error finding keychain item to edit informative message"), status]); + return; + } + + // Set up the attributes to modify + attributes[0].tag = kSecAccountItemAttr; + attributes[0].data = (unichar *)[newAccount UTF8String]; + attributes[0].length = (UInt32)strlen([newAccount UTF8String]); + attributes[1].tag = kSecServiceItemAttr; + attributes[1].data = (unichar *)[newName UTF8String]; + attributes[1].length = (UInt32)strlen([newName UTF8String]); + attList.count = 2; + attList.attr = attributes; + + // Amend the keychain item + status = SecKeychainItemModifyAttributesAndData(itemRef, &attList, (UInt32)strlen([password UTF8String]), [password UTF8String]); + + if (status != noErr) { + NSLog(@"Error (%i) while updating keychain item for name: %@ account: %@", (int)status, name, account); + SPBeginAlertSheet(NSLocalizedString(@"Error updating Keychain item", @"error updating keychain item message"), + NSLocalizedString(@"OK", @"OK button"), + nil, nil, [NSApp mainWindow], self, nil, nil, + [NSString stringWithFormat:NSLocalizedString(@"An error occured while trying to update the Keychain item. Repairing your Keychain might resolve this, but if it doesn't please report it to the Sequel Pro team, supplying the error code %i.", @"error updating keychain item informative message"), status]); + } +} + +/** * Retrieve the keychain item name for a supplied name and id. */ - (NSString *)nameForFavoriteName:(NSString *)theName id:(NSString *)theID diff --git a/Source/SPMenuAdditions.m b/Source/SPMenuAdditions.m index d653f71a..4ee5956f 100644 --- a/Source/SPMenuAdditions.m +++ b/Source/SPMenuAdditions.m @@ -24,7 +24,6 @@ #import "SPMenuAdditions.h" - @implementation NSMenu (SPMenuAdditions) // Add a 10.5-compatible removeAllItems @@ -32,7 +31,8 @@ { if ([self respondsToSelector:@selector(removeAllItems)]) { [(id)self removeAllItems]; - } else { + } + else { while ([self numberOfItems]) [self removeItemAtIndex:0]; } } diff --git a/Source/SPNarrowDownCompletion.m b/Source/SPNarrowDownCompletion.m index 04627e2d..9079954d 100644 --- a/Source/SPNarrowDownCompletion.m +++ b/Source/SPNarrowDownCompletion.m @@ -34,6 +34,7 @@ #import "SPQueryController.h" #import "RegexKitLite.h" #import "SPTextView.h" +#import "SPQueryDocumentsController.h" #pragma mark - #pragma mark attribute definition @@ -827,6 +828,9 @@ } else if((flags & NSAlternateKeyMask) || (flags & NSCommandKeyMask)) { + if (autocompletePlaceholderWasInserted) [self removeAutocompletionPlaceholderUsingFastMethod:YES]; + [theView setCompletionIsOpen:NO]; + [self close]; [NSApp sendEvent:event]; break; } @@ -1002,6 +1006,7 @@ if ([[theView textStorage] attribute:kSPAutoCompletePlaceholderName atIndex:scanPosition longestEffectiveRange:&attributeResultRange inRange:NSMakeRange(scanPosition, currentLength-scanPosition)]) { // A match was found - attributeResultRange contains the range of the attributed string + [theView shouldChangeTextInRange:attributeResultRange replacementString:@""]; [[theView textStorage] deleteCharactersInRange:attributeResultRange]; } else { @@ -1012,6 +1017,7 @@ // A match was found - retrieve the location NSUInteger matchStart = attributeResultRange.location+attributeResultRange.length; if ([[theView textStorage] attribute:kSPAutoCompletePlaceholderName atIndex:matchStart longestEffectiveRange:&attributeResultRange inRange:NSMakeRange(matchStart, currentLength - matchStart)]) { + [theView shouldChangeTextInRange:attributeResultRange replacementString:@""]; [[theView textStorage] deleteCharactersInRange:attributeResultRange]; } } diff --git a/Source/SPPrintController.m b/Source/SPPrintController.m index 199513f8..ad297fee 100644 --- a/Source/SPPrintController.m +++ b/Source/SPPrintController.m @@ -251,7 +251,7 @@ // Table content view else if ([tableTabView indexOfTabViewItem:[tableTabView selectedTabViewItem]] == 1) { - NSArray *data = [tableContentInstance currentDataResult]; + NSArray *data = [tableContentInstance currentDataResultWithNULLs:NO]; heading = NSLocalizedString(@"Table Content", @"table content print heading"); diff --git a/Source/SPProcessListController.m b/Source/SPProcessListController.m index b0242a35..4f0490cb 100644 --- a/Source/SPProcessListController.m +++ b/Source/SPProcessListController.m @@ -80,7 +80,7 @@ static NSString *SPTableViewIDColumnIdentifier = @"Id"; */ - (void)awakeFromNib { - [[self window] setTitle:[NSString stringWithFormat:@"%@ %@", [[(SPAppController*)[NSApp delegate] frontDocument] name], NSLocalizedString(@"Server Processes", @"server processes window title")]]; + [[self window] setTitle:[NSString stringWithFormat:NSLocalizedString(@"Server Processes on %@", @"server processes window title (var = hostname)"),[[(SPAppController*)[NSApp delegate] frontDocument] name]]]; [self setWindowFrameAutosaveName:@"ProcessList"]; @@ -700,8 +700,29 @@ static NSString *SPTableViewIDColumnIdentifier = @"Id"; for (i = 0; i < [processList numOfRows]; i++) { - [processes addObject:[processList fetchRowAsDictionary]]; + //MCPKit currently returns numbers as NSString, which will break sorting of numbers in this case. + NSMutableDictionary *rowsFixed = [[processList fetchRowAsDictionary] mutableCopy]; + + id idColumn = [rowsFixed objectForKey:@"Id"]; + //Id is a signed int(11) - this is a signed 32 bit int value + if(idColumn != nil && [idColumn isKindOfClass:[NSString class]]) { + int numRaw = [(NSString *)idColumn intValue]; + NSNumber *num = [NSNumber numberWithInt:numRaw]; + [rowsFixed setObject:num forKey:@"Id"]; + } + + id timeColumn = [rowsFixed objectForKey:@"Time"]; + //Time is a signed int(7) - this is the same 32 bit int value + if(timeColumn != nil && [timeColumn isKindOfClass:[NSString class]]) { + int numRaw = [(NSString *)timeColumn intValue]; + NSNumber *num = [NSNumber numberWithInt:numRaw]; + [rowsFixed setObject:num forKey:@"Time"]; + } + + [processes addObject:[[rowsFixed copy] autorelease]]; + [rowsFixed release]; } + } // Update the UI on the main thread diff --git a/Source/SPQueryConsoleDataSource.h b/Source/SPQueryConsoleDataSource.h new file mode 100644 index 00000000..229e3b9d --- /dev/null +++ b/Source/SPQueryConsoleDataSource.h @@ -0,0 +1,30 @@ +// +// $Id$ +// +// SPQueryConsoleDataSource.h +// sequel-pro +// +// Created by Stuart Connolly (stuconnolly.com) on August 30, 2011 +// Copyright (c) 2011 Stuart Connolly. All rights reserved. +// +// 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 <http://code.google.com/p/sequel-pro/> + +#import "SPQueryController.h" + +@interface SPQueryController (SPQueryConsoleDataSource) + +@end diff --git a/Source/SPQueryConsoleDataSource.m b/Source/SPQueryConsoleDataSource.m new file mode 100644 index 00000000..29a8dc7a --- /dev/null +++ b/Source/SPQueryConsoleDataSource.m @@ -0,0 +1,89 @@ +// +// $Id$ +// +// SPQueryConsoleDataSource.m +// sequel-pro +// +// Created by Stuart Connolly (stuconnolly.com) on August 30, 2011 +// Copyright (c) 2011 Stuart Connolly. All rights reserved. +// +// 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 <http://code.google.com/p/sequel-pro/> + +#import "SPQueryConsoleDataSource.h" +#import "SPConsoleMessage.h" + +static NSUInteger SPMessageTruncateCharacterLength = 256; + +@implementation SPQueryController (SPQueryConsoleDataSource) + +/** + * Table view delegate method. Returns the number of rows in the table veiw. + */ +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView +{ +#ifndef SP_REFACTOR + return [messagesVisibleSet count]; +#else + return 0; +#endif +} + +/** + * Table view delegate method. Returns the specific object for the request column and row. + */ +- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row +{ +#ifndef SP_REFACTOR + NSString *returnValue = nil; + + id object = [[messagesVisibleSet objectAtIndex:row] valueForKey:[tableColumn identifier]]; + + if ([[tableColumn identifier] isEqualToString:SPTableViewDateColumnID]) { + + returnValue = [dateFormatter stringFromDate:(NSDate *)object]; + } + else { + if ([(NSString *)object length] > SPMessageTruncateCharacterLength) { + object = [NSString stringWithFormat:@"%@...", [object substringToIndex:SPMessageTruncateCharacterLength]]; + } + + returnValue = object; + } + + NSMutableDictionary *stringAtributes = nil; + + if (consoleFont) { + stringAtributes = [NSMutableDictionary dictionaryWithObject:consoleFont forKey:NSFontAttributeName]; + } + + // If this is an error message give it a red colour + if ([(SPConsoleMessage *)[messagesVisibleSet objectAtIndex:row] isError]) { + if (stringAtributes) { + [stringAtributes setObject:[NSColor redColor] forKey:NSForegroundColorAttributeName]; + } + else { + stringAtributes = [NSMutableDictionary dictionaryWithObject:[NSColor redColor] forKey:NSForegroundColorAttributeName]; + } + } + + return [[[NSAttributedString alloc] initWithString:returnValue attributes:stringAtributes] autorelease]; +#else + return nil; +#endif +} + +@end diff --git a/Source/SPQueryController.h b/Source/SPQueryController.h index c80da762..58858ddd 100644 --- a/Source/SPQueryController.h +++ b/Source/SPQueryController.h @@ -23,9 +23,16 @@ // // More info at <http://code.google.com/p/sequel-pro/> +#ifndef SP_REFACTOR +static NSString *SPQueryConsoleWindowAutoSaveName = @"QueryConsole"; + +// Table view column identifier constants +static NSString *SPTableViewDateColumnID = @"messageDate"; +static NSString *SPTableViewConnectionColumnID = @"messageConnection"; +#endif + @interface SPQueryController : NSWindowController { - // QueryConsoleController IBOutlet NSView *saveLogView; IBOutlet NSTableView *consoleTableView; IBOutlet NSSearchField *consoleSearchField; @@ -61,11 +68,12 @@ pthread_mutex_t consoleLock; } +#ifndef SP_REFACTOR @property (readwrite, retain) NSFont *consoleFont; +#endif + (SPQueryController *)sharedQueryController; -// QueryConsoleController - (IBAction)copy:(id)sender; - (IBAction)clearConsole:(id)sender; - (IBAction)saveConsoleAs:(id)sender; @@ -84,31 +92,4 @@ - (NSUInteger)consoleMessageCount; -// Completion List Controller -- (NSArray*)functionList; -- (NSArray*)keywordList; -- (NSString*)argumentSnippetForFunction:(NSString*)func; - -// DocumentsController -- (NSURL *)registerDocumentWithFileURL:(NSURL *)fileURL andContextInfo:(NSMutableDictionary *)contextInfo; -- (void)removeRegisteredDocumentWithFileURL:(NSURL *)fileURL; - -- (void)addFavorite:(NSDictionary *)favorite forFileURL:(NSURL *)fileURL; -- (void)replaceFavoritesByArray:(NSArray *)favoritesArray forFileURL:(NSURL *)fileURL; -- (void)removeFavoriteAtIndex:(NSUInteger)index forFileURL:(NSURL *)fileURL; -- (void)insertFavorite:(NSDictionary *)favorite atIndex:(NSUInteger)index forFileURL:(NSURL *)fileURL; - -- (void)addHistory:(NSString *)history forFileURL:(NSURL *)fileURL; -- (void)replaceHistoryByArray:(NSArray *)historyArray forFileURL:(NSURL *)fileURL; - -- (void)replaceContentFilterByArray:(NSArray *)contentFilterArray ofType:(NSString *)filterType forFileURL:(NSURL *)fileURL; - -- (NSMutableArray *)favoritesForFileURL:(NSURL *)fileURL; -- (NSMutableArray *)historyForFileURL:(NSURL *)fileURL; -- (NSArray *)historyMenuItemsForFileURL:(NSURL *)fileURL; -- (NSUInteger)numberOfHistoryItemsForFileURL:(NSURL *)fileURL; -- (NSMutableDictionary *)contentFilterForFileURL:(NSURL *)fileURL; - -- (NSArray *)queryFavoritesForFileURL:(NSURL *)fileURL andTabTrigger:(NSString *)tabTrigger includeGlobals:(BOOL)includeGlobals; - @end diff --git a/Source/SPQueryController.m b/Source/SPQueryController.m index 3a003f82..a3c46160 100644 --- a/Source/SPQueryController.m +++ b/Source/SPQueryController.m @@ -26,16 +26,11 @@ #import "SPQueryController.h" #import "SPConsoleMessage.h" #import "SPCustomQuery.h" +#import "SPQueryControllerInitializer.h" #import "pthread.h" -#define MESSAGE_TRUNCATE_CHARACTER_LENGTH 256 - -// Table view column identifier constants -static NSString *SPTableViewDateColumnID = @"messageDate"; -static NSString *SPTableViewConnectionColumnID = @"messageConnection"; - -@interface SPQueryController (PrivateAPI) +@interface SPQueryController () - (void)_updateFilterState; - (BOOL)_messageMatchesCurrentFilters:(NSString *)message; @@ -48,7 +43,9 @@ static SPQueryController *sharedQueryController = nil; @implementation SPQueryController +#ifndef SP_REFACTOR @synthesize consoleFont; +#endif /** * Returns the shared query console. @@ -69,11 +66,16 @@ static SPQueryController *sharedQueryController = nil; @synchronized(self) { return [[self sharedQueryController] retain]; } + +#ifdef SP_REFACTOR + return nil; // only here to stop clang's 'can reach end of non-void function' +#endif } - (id)init { if ((self = [super initWithWindowNibName:@"Console"])) { +#ifndef SP_REFACTOR messagesFullSet = [[NSMutableArray alloc] init]; messagesFilteredSet = [[NSMutableArray alloc] init]; @@ -92,47 +94,20 @@ static SPQueryController *sharedQueryController = nil; favoritesContainer = [[NSMutableDictionary alloc] init]; historyContainer = [[NSMutableDictionary alloc] init]; contentFilterContainer = [[NSMutableDictionary alloc] init]; +#endif completionKeywordList = nil; completionFunctionList = nil; functionArgumentSnippets = nil; +#ifndef SP_REFACTOR pthread_mutex_init(&consoleLock, NULL); +#endif - NSError *readError = nil; - NSString *convError = nil; - NSPropertyListFormat format; - NSDictionary *completionPlist; - NSData *completionTokensData = [NSData dataWithContentsOfFile:[NSBundle pathForResource:@"CompletionTokens.plist" ofType:nil inDirectory:[[NSBundle mainBundle] bundlePath]] - options:NSMappedRead error:&readError]; - - - completionPlist = [NSDictionary dictionaryWithDictionary:[NSPropertyListSerialization propertyListFromData:completionTokensData - mutabilityOption:NSPropertyListMutableContainersAndLeaves format:&format errorDescription:&convError]]; - - if(completionPlist == nil || readError != nil || convError != nil) { - NSLog(@"Error while reading “CompletionTokens.plist”:\n%@\n%@", [readError localizedDescription], convError); - NSBeep(); - } else { - if([completionPlist objectForKey:@"core_keywords"]) { - completionKeywordList = [[NSArray arrayWithArray:[completionPlist objectForKey:@"core_keywords"]] retain]; - } else { - NSLog(@"No “core_keywords” array found."); - NSBeep(); - } - if([completionPlist objectForKey:@"core_builtin_functions"]) { - completionFunctionList = [[NSArray arrayWithArray:[completionPlist objectForKey:@"core_builtin_functions"]] retain]; - } else { - NSLog(@"No “core_builtin_functions” array found."); - NSBeep(); - } - if([completionPlist objectForKey:@"function_argument_snippets"]) { - functionArgumentSnippets = [[NSDictionary dictionaryWithDictionary:[completionPlist objectForKey:@"function_argument_snippets"]] retain]; - } else { - NSLog(@"No “function_argument_snippets” dictionary found."); - NSBeep(); - } + NSError *error = [self loadCompletionLists]; + + if (error) { + NSLog(@"Error loading completion tokens data: %@", [error localizedDescription]); } - } return self; @@ -152,73 +127,6 @@ static SPQueryController *sharedQueryController = nil; - (void)release { } -/** - * Set the window's auto save name and initialise display - */ -- (void)awakeFromNib -{ -#ifndef SP_REFACTOR /* init ivars */ - prefs = [NSUserDefaults standardUserDefaults]; -#endif - - [self setWindowFrameAutosaveName:@"QueryConsole"]; - - // Show/hide table columns - [[consoleTableView tableColumnWithIdentifier:SPTableViewDateColumnID] setHidden: -#ifndef SP_REFACTOR - ![prefs boolForKey:SPConsoleShowTimestamps] -#else - YES -#endif - ]; - [[consoleTableView tableColumnWithIdentifier:SPTableViewConnectionColumnID] setHidden: -#ifndef SP_REFACTOR - ![prefs boolForKey:SPConsoleShowConnections] -#else - YES -#endif - ]; - -#ifndef SP_REFACTOR - showSelectStatementsAreDisabled = ![prefs boolForKey:SPConsoleShowSelectsAndShows]; - showHelpStatementsAreDisabled = ![prefs boolForKey:SPConsoleShowHelps]; -#else - showSelectStatementsAreDisabled = YES; - showHelpStatementsAreDisabled = YES; -#endif - - [self _updateFilterState]; - -#ifndef SP_REFACTOR - [loggingDisabledTextField setStringValue:([prefs boolForKey:SPConsoleEnableLogging]) ? @"" : NSLocalizedString(@"Query logging is currently disabled", @"query logging disabled label")]; -#endif - - // Setup data formatter - dateFormatter = [[NSDateFormatter alloc] init]; - - [dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; - - [dateFormatter setDateStyle:NSDateFormatterNoStyle]; - [dateFormatter setTimeStyle:NSDateFormatterMediumStyle]; - -#ifndef SP_REFACTOR - // Set the process table view's vertical gridlines if required - [consoleTableView setGridStyleMask:([prefs boolForKey:SPDisplayTableViewVerticalGridlines]) ? NSTableViewSolidVerticalGridLineMask : NSTableViewGridNone]; -#endif - - // Set the strutcture and index view's font -#ifndef SP_REFACTOR - BOOL useMonospacedFont = [prefs boolForKey:SPUseMonospacedFonts]; -#else - BOOL useMonospacedFont = YES; -#endif - - for (NSTableColumn *column in [consoleTableView tableColumns]) - { - [[column dataCell] setFont:(useMonospacedFont) ? [NSFont fontWithName:SPDefaultMonospacedFontName size:[NSFont smallSystemFontSize]] : [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; - } -} - #pragma mark - #pragma mark QueryConsoleController @@ -227,6 +135,7 @@ static SPQueryController *sharedQueryController = nil; */ - (void)copy:(id)sender { +#ifndef SP_REFACTOR NSResponder *firstResponder = [[self window] firstResponder]; if ((firstResponder == consoleTableView) && ([consoleTableView numberOfSelectedRows] > 0)) { @@ -267,6 +176,7 @@ static SPQueryController *sharedQueryController = nil; [pasteBoard declareTypes:[NSArray arrayWithObjects:NSStringPboardType, nil] owner:nil]; [pasteBoard setString:string forType:NSStringPboardType]; } +#endif } /** @@ -274,10 +184,12 @@ static SPQueryController *sharedQueryController = nil; */ - (IBAction)clearConsole:(id)sender { +#ifndef SP_REFACTOR [messagesFullSet removeAllObjects]; [messagesFilteredSet removeAllObjects]; [consoleTableView reloadData]; +#endif } /** @@ -285,6 +197,7 @@ static SPQueryController *sharedQueryController = nil; */ - (IBAction)saveConsoleAs:(id)sender { +#ifndef SP_REFACTOR NSSavePanel *panel = [NSSavePanel savePanel]; [panel setRequiredFileType:SPFileExtensionSQL]; @@ -295,7 +208,8 @@ static SPQueryController *sharedQueryController = nil; [panel setAccessoryView:saveLogView]; - [panel beginSheetForDirectory:nil file:NSLocalizedString(@"ConsoleLog",@"Console : Save as : Initial filename") modalForWindow:[self window] modalDelegate:self didEndSelector:@selector(savePanelDidEnd:returnCode:contextInfo:) contextInfo:NULL]; + [panel beginSheetForDirectory:nil file:NSLocalizedString(@"ConsoleLog", @"Console : Save as : Initial filename") modalForWindow:[self window] modalDelegate:self didEndSelector:@selector(savePanelDidEnd:returnCode:contextInfo:) contextInfo:NULL]; +#endif } /** @@ -303,7 +217,9 @@ static SPQueryController *sharedQueryController = nil; */ - (IBAction)toggleShowTimeStamps:(id)sender { +#ifndef SP_REFACTOR [[consoleTableView tableColumnWithIdentifier:SPTableViewDateColumnID] setHidden:([sender state])]; +#endif } /** @@ -311,7 +227,9 @@ static SPQueryController *sharedQueryController = nil; */ - (IBAction)toggleShowConnections:(id)sender { +#ifndef SP_REFACTOR [[consoleTableView tableColumnWithIdentifier:SPTableViewConnectionColumnID] setHidden:([sender state])]; +#endif } /** @@ -319,10 +237,12 @@ static SPQueryController *sharedQueryController = nil; */ - (IBAction)toggleShowSelectShowStatements:(id)sender { +#ifndef SP_REFACTOR // Store the state of the toggle for later quick reference showSelectStatementsAreDisabled = [sender state]; [self _updateFilterState]; +#endif } /** @@ -330,10 +250,12 @@ static SPQueryController *sharedQueryController = nil; */ - (IBAction)toggleShowHelpStatements:(id)sender { +#ifndef SP_REFACTOR // Store the state of the toggle for later quick reference showHelpStatementsAreDisabled = [sender state]; [self _updateFilterState]; +#endif } /** @@ -341,7 +263,9 @@ static SPQueryController *sharedQueryController = nil; */ - (void)showMessageInConsole:(NSString *)message connection:(NSString *)connection { +#ifndef SP_REFACTOR [self _addMessageToConsole:message connection:connection isError:NO]; +#endif } /** @@ -349,7 +273,9 @@ static SPQueryController *sharedQueryController = nil; */ - (void)showErrorInConsole:(NSString *)error connection:(NSString *)connection { +#ifndef SP_REFACTOR [self _addMessageToConsole:error connection:connection isError:YES]; +#endif } /** @@ -357,7 +283,11 @@ static SPQueryController *sharedQueryController = nil; */ - (NSUInteger)consoleMessageCount { +#ifndef SP_REFACTOR return [messagesFullSet count]; +#else + return 0; +#endif } /** @@ -365,60 +295,11 @@ static SPQueryController *sharedQueryController = nil; */ - (void)savePanelDidEnd:(NSSavePanel *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo { +#ifndef SP_REFACTOR if (returnCode == NSOKButton) { [[self _getConsoleStringWithTimeStamps:[includeTimeStampsButton integerValue] connections:[includeConnectionButton integerValue]] writeToFile:[sheet filename] atomically:YES encoding:NSUTF8StringEncoding error:NULL]; } -} - -#pragma mark - -#pragma mark Tableview delegate methods - -/** - * Table view delegate method. Returns the number of rows in the table veiw. - */ -- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView -{ - return [messagesVisibleSet count]; -} - -/** - * Table view delegate method. Returns the specific object for the request column and row. - */ -- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row -{ - NSString *returnValue = nil; - - id object = [[messagesVisibleSet objectAtIndex:row] valueForKey:[tableColumn identifier]]; - - if ([[tableColumn identifier] isEqualToString:SPTableViewDateColumnID]) { - - returnValue = [dateFormatter stringFromDate:(NSDate *)object]; - } - else { - if ([(NSString *)object length] > MESSAGE_TRUNCATE_CHARACTER_LENGTH) { - object = [NSString stringWithFormat:@"%@...", [object substringToIndex:MESSAGE_TRUNCATE_CHARACTER_LENGTH]]; - } - - returnValue = object; - } - - NSMutableDictionary *stringAtributes = nil; - - if (consoleFont) { - stringAtributes = [NSMutableDictionary dictionaryWithObject:consoleFont forKey:NSFontAttributeName]; - } - - // If this is an error message give it a red colour - if ([(SPConsoleMessage *)[messagesVisibleSet objectAtIndex:row] isError]) { - if (stringAtributes) { - [stringAtributes setObject:[NSColor redColor] forKey:NSForegroundColorAttributeName]; - } - else { - stringAtributes = [NSMutableDictionary dictionaryWithObject:[NSColor redColor] forKey:NSForegroundColorAttributeName]; - } - } - - return [[[NSAttributedString alloc] initWithString:returnValue attributes:stringAtributes] autorelease]; +#endif } #pragma mark - @@ -429,16 +310,17 @@ static SPQueryController *sharedQueryController = nil; */ - (void)controlTextDidChange:(NSNotification *)notification { - id object = [notification object]; - - if ([object isEqualTo:consoleSearchField]) { +#ifndef SP_REFACTOR + if ([[notification object] isEqualTo:consoleSearchField]) { // Store the state of the text filter and the current filter string for later quick reference - [activeFilterString setString:[[object stringValue] lowercaseString]]; - filterIsActive = [activeFilterString length]?YES:NO; + [activeFilterString setString:[[[notification object] stringValue] lowercaseString]]; + + filterIsActive = [activeFilterString length] > 0; [self _updateFilterState]; } +#endif } /** @@ -446,9 +328,10 @@ static SPQueryController *sharedQueryController = nil; */ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { +#ifndef SP_REFACTOR // Show/hide logging disabled label if ([keyPath isEqualToString:SPConsoleEnableLogging]) { - [loggingDisabledTextField setStringValue:([[change objectForKey:NSKeyValueChangeNewKey] boolValue]) ? @"" : @"Query logging is currently disabled"]; + [loggingDisabledTextField setStringValue:([[change objectForKey:NSKeyValueChangeNewKey] boolValue]) ? @"" : NSLocalizedString(@"Query logging is currently disabled", @"query logging currently disabled label")]; } // Display table veiew vertical gridlines preference changed else if ([keyPath isEqualToString:SPDisplayTableViewVerticalGridlines]) { @@ -466,6 +349,7 @@ static SPQueryController *sharedQueryController = nil; [consoleTableView reloadData]; } +#endif } /** @@ -473,6 +357,7 @@ static SPQueryController *sharedQueryController = nil; */ - (BOOL)validateMenuItem:(NSMenuItem *)menuItem { +#ifndef SP_REFACTOR if ([menuItem action] == @selector(copy:)) { return ([consoleTableView numberOfSelectedRows] > 0); } @@ -481,19 +366,27 @@ static SPQueryController *sharedQueryController = nil; if ([menuItem action] == @selector(clearConsole:)) { return ([self consoleMessageCount] > 0); } +#endif return [[self window] validateMenuItem:menuItem]; } -- (BOOL) allowConsoleUpdate +- (BOOL)allowConsoleUpdate { +#ifndef SP_REFACTOR return allowConsoleUpdate; +#else + return NO; +#endif } -- (void) setAllowConsoleUpdate:(BOOL)allowUpdate +- (void)setAllowConsoleUpdate:(BOOL)allowUpdate { +#ifndef SP_REFACTOR allowConsoleUpdate = allowUpdate; + if (allowUpdate && [[self window] isVisible]) [self updateEntries]; +#endif } /** @@ -501,8 +394,10 @@ static SPQueryController *sharedQueryController = nil; */ - (void)updateEntries { +#ifndef SP_REFACTOR [consoleTableView reloadData]; [consoleTableView scrollRowToVisible:([messagesVisibleSet count] - 1)]; +#endif } /** @@ -510,438 +405,77 @@ static SPQueryController *sharedQueryController = nil; */ - (NSString *)windowFrameAutosaveName { - return @"QueryConsole"; -} - -#pragma mark - -#pragma mark Completion List Controller - -/** - * Return an array of all pre-defined SQL functions for completion. - */ -- (NSArray*)functionList -{ - if(completionFunctionList != nil && [completionFunctionList count]) - return completionFunctionList; - return [NSArray array]; -} - -/** - * Return an array of all pre-defined SQL keywords for completion. - */ -- (NSArray*)keywordList -{ - if(completionKeywordList != nil && [completionKeywordList count]) - return completionKeywordList; - return [NSArray array]; -} - -/** - * Return the parameter list as snippet of the passed SQL functions for completion. - * - * @param func The name of the function whose parameter list is asked for - */ -- (NSString*)argumentSnippetForFunction:(NSString*)func -{ - if(functionArgumentSnippets && [functionArgumentSnippets objectForKey:[func uppercaseString]]) - return [functionArgumentSnippets objectForKey:[func uppercaseString]]; - return @""; -} - -#pragma mark - -#pragma mark DocumentsController - -- (NSURL *)registerDocumentWithFileURL:(NSURL *)fileURL andContextInfo:(NSMutableDictionary *)contextInfo -{ - // Register a new untiled document and return its URL - if(fileURL == nil) { - NSURL *new = [NSURL URLWithString:[[NSString stringWithFormat:NSLocalizedString(@"Untitled %ld",@"Title of a new Sequel Pro Document"), (unsigned long)untitledDocumentCounter] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; - untitledDocumentCounter++; - - if(![favoritesContainer objectForKey:[new absoluteString]]) { - NSMutableArray *arr = [[NSMutableArray alloc] init]; - [favoritesContainer setObject:arr forKey:[new absoluteString]]; - [arr release]; - } - - // Set the global history coming from the Prefs as default if available - if(![historyContainer objectForKey:[new absoluteString]]) { -#ifndef SP_REFACTOR - if([prefs objectForKey:SPQueryHistory]) { - NSMutableArray *arr = [[NSMutableArray alloc] init]; - [arr addObjectsFromArray:[prefs objectForKey:SPQueryHistory]]; - [historyContainer setObject:arr forKey:[new absoluteString]]; - [arr release]; - } else { -#endif - NSMutableArray *arr = [[NSMutableArray alloc] init]; - [historyContainer setObject:[NSMutableArray array] forKey:[new absoluteString]]; - [arr release]; -#ifndef SP_REFACTOR - } -#endif - } - - // Set the doc-based content filters - if(![contentFilterContainer objectForKey:[new absoluteString]]) { - [contentFilterContainer setObject:[NSMutableDictionary dictionary] forKey:[new absoluteString]]; - } - - return new; - - } - - // Register a spf file to manage all query favorites and query history items - // file path based (incl. Untitled docs) in a dictionary whereby the key represents the file URL as string. - if(![favoritesContainer objectForKey:[fileURL absoluteString]]) { - if(contextInfo != nil && [contextInfo objectForKey:SPQueryFavorites] && [[contextInfo objectForKey:SPQueryFavorites] count]) { - NSMutableArray *arr = [[NSMutableArray alloc] init]; - [arr addObjectsFromArray:[contextInfo objectForKey:SPQueryFavorites]]; - [favoritesContainer setObject:arr forKey:[fileURL absoluteString]]; - [arr release]; - } else { - NSMutableArray *arr = [[NSMutableArray alloc] init]; - [favoritesContainer setObject:arr forKey:[fileURL absoluteString]]; - [arr release]; - } - } - - if(![historyContainer objectForKey:[fileURL absoluteString]]) { - if(contextInfo != nil && [contextInfo objectForKey:SPQueryHistory] && [[contextInfo objectForKey:SPQueryHistory] count]) { - NSMutableArray *arr = [[NSMutableArray alloc] init]; - [arr addObjectsFromArray:[contextInfo objectForKey:SPQueryHistory]]; - [historyContainer setObject:arr forKey:[fileURL absoluteString]]; - [arr release]; - } else { - NSMutableArray *arr = [[NSMutableArray alloc] init]; - [historyContainer setObject:arr forKey:[fileURL absoluteString]]; - [arr release]; - } - } - - if(![contentFilterContainer objectForKey:[fileURL absoluteString]]) { - if(contextInfo != nil && [contextInfo objectForKey:SPContentFilters]) { - [contentFilterContainer setObject:[contextInfo objectForKey:SPContentFilters] forKey:[fileURL absoluteString]]; - } else { - NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; - [contentFilterContainer setObject:dict forKey:[fileURL absoluteString]]; - [dict release]; - } - } - - return fileURL; -} - -- (void)removeRegisteredDocumentWithFileURL:(NSURL *)fileURL -{ - // Check for multiple instance of the same document. - // Remove it if only one instance was registerd. - NSArray *allDocs = [[NSApp delegate] orderedDocuments]; - NSMutableArray *allURLs = [NSMutableArray array]; - for(id doc in allDocs) { - if (![doc fileURL]) continue; - if([allURLs containsObject:[doc fileURL]]) - return; - else - [allURLs addObject:[doc fileURL]]; - } - - if([favoritesContainer objectForKey:[fileURL absoluteString]]) - [favoritesContainer removeObjectForKey:[fileURL absoluteString]]; - if([historyContainer objectForKey:[fileURL absoluteString]]) - [historyContainer removeObjectForKey:[fileURL absoluteString]]; - if([contentFilterContainer objectForKey:[fileURL absoluteString]]) - [contentFilterContainer removeObjectForKey:[fileURL absoluteString]]; -} - -- (void)replaceContentFilterByArray:(NSArray *)contentFilterArray ofType:(NSString *)filterType forFileURL:(NSURL *)fileURL -{ - if([contentFilterContainer objectForKey:[fileURL absoluteString]]) { - NSMutableDictionary *c = [[NSMutableDictionary alloc] init]; - [c setDictionary:[contentFilterContainer objectForKey:[fileURL absoluteString]]]; - [c setObject:contentFilterArray forKey:filterType]; - [contentFilterContainer setObject:c forKey:[fileURL absoluteString]]; - [c release]; - } -} - -- (void)replaceFavoritesByArray:(NSArray *)favoritesArray forFileURL:(NSURL *)fileURL -{ - if([favoritesContainer objectForKey:[fileURL absoluteString]]) - [favoritesContainer setObject:favoritesArray forKey:[fileURL absoluteString]]; -} - -- (void)replaceHistoryByArray:(NSArray *)historyArray forFileURL:(NSURL *)fileURL -{ - if([historyContainer objectForKey:[fileURL absoluteString]]) - [historyContainer setObject:historyArray forKey:[fileURL absoluteString]]; - - // Inform all opened documents to update the history list - for(id doc in [[NSApp delegate] orderedDocuments]) - if([[doc valueForKeyPath:@"customQueryInstance"] respondsToSelector:@selector(historyItemsHaveBeenUpdated:)]) - [[doc valueForKeyPath:@"customQueryInstance"] performSelectorOnMainThread:@selector(historyItemsHaveBeenUpdated:) withObject:self waitUntilDone:NO]; - - -#ifndef SP_REFACTOR - // User did choose to clear the global history list - if(![fileURL isFileURL] && ![historyArray count]) - [prefs setObject:historyArray forKey:SPQueryHistory]; -#endif -} - -- (void)addFavorite:(NSDictionary *)favorite forFileURL:(NSURL *)fileURL -{ - if([favoritesContainer objectForKey:[fileURL absoluteString]]) - [[favoritesContainer objectForKey:[fileURL absoluteString]] addObject:favorite]; -} - -- (void)addHistory:(NSString *)history forFileURL:(NSURL *)fileURL -{ -#ifndef SP_REFACTOR - NSUInteger maxHistoryItems = [[prefs objectForKey:SPCustomQueryMaxHistoryItems] integerValue]; -#else - NSUInteger maxHistoryItems = 20; -#endif - - // Save each history item due to its document source - if([historyContainer objectForKey:[fileURL absoluteString]]) { - - // Remove all duplicates by using a NSPopUpButton - NSPopUpButton *uniquifier = [[NSPopUpButton alloc] initWithFrame:NSMakeRect(0,0,0,0) pullsDown:YES]; - [uniquifier addItemsWithTitles:[historyContainer objectForKey:[fileURL absoluteString]]]; - [uniquifier insertItemWithTitle:history atIndex:0]; - - while ( (NSUInteger)[uniquifier numberOfItems] > maxHistoryItems ) - [uniquifier removeItemAtIndex:[uniquifier numberOfItems]-1]; - - [self replaceHistoryByArray:[uniquifier itemTitles] forFileURL:fileURL]; - [uniquifier release]; - } - - // Save history items coming from each Untitled document in the global Preferences successively - // regardingless of the source document. - if(![fileURL isFileURL]) { -#ifndef SP_REFACTOR - - // Remove all duplicates by using a NSPopUpButton - NSPopUpButton *uniquifier = [[NSPopUpButton alloc] initWithFrame:NSMakeRect(0,0,0,0) pullsDown:YES]; - [uniquifier addItemsWithTitles:[prefs objectForKey:SPQueryHistory]]; - [uniquifier insertItemWithTitle:history atIndex:0]; - - while ( (NSUInteger)[uniquifier numberOfItems] > maxHistoryItems ) - [uniquifier removeItemAtIndex:[uniquifier numberOfItems]-1]; - - [prefs setObject:[uniquifier itemTitles] forKey:SPQueryHistory]; - [uniquifier release]; -#endif - } -} - -- (NSMutableArray *)favoritesForFileURL:(NSURL *)fileURL -{ - if([favoritesContainer objectForKey:[fileURL absoluteString]]) - return [favoritesContainer objectForKey:[fileURL absoluteString]]; - - return [NSMutableArray array]; -} - -- (NSMutableArray *)historyForFileURL:(NSURL *)fileURL -{ - if([historyContainer objectForKey:[fileURL absoluteString]]) - return [historyContainer objectForKey:[fileURL absoluteString]]; - - return [NSMutableArray array]; -} - -- (NSArray *)historyMenuItemsForFileURL:(NSURL *)fileURL -{ - if([historyContainer objectForKey:[fileURL absoluteString]]) { - NSMutableArray *returnArray = [NSMutableArray arrayWithCapacity:[[historyContainer objectForKey:[fileURL absoluteString]] count]]; - NSMenuItem *historyMenuItem; - for(NSString* history in [historyContainer objectForKey:[fileURL absoluteString]]) { - historyMenuItem = [[[NSMenuItem alloc] initWithTitle:([history length] > 64) ? [NSString stringWithFormat:@"%@…", [history substringToIndex:63]] : history - action:NULL - keyEquivalent:@""] autorelease]; - [historyMenuItem setToolTip:([history length] > 256) ? [NSString stringWithFormat:@"%@…", [history substringToIndex:255]] : history]; - [returnArray addObject:historyMenuItem]; - } - - return returnArray; - } - - return [NSArray array]; -} - -/** - * Return the number of history items for the passed file URL - * - * @param fileURL The NSURL of the current active SPDatabaseDocument - * - */ -- (NSUInteger)numberOfHistoryItemsForFileURL:(NSURL *)fileURL -{ - if([historyContainer objectForKey:[fileURL absoluteString]]) - return [[historyContainer objectForKey:[fileURL absoluteString]] count]; - else - return 0; -} - -/** - * Return a mutable dictionary of all content filters for the passed file URL. - * If no content filters were found it returns an empty mutable dictionary. - * - * @param fileURL The NSURL of the current active SPDatabaseDocument - * - */ -- (NSMutableDictionary *)contentFilterForFileURL:(NSURL *)fileURL -{ - if([contentFilterContainer objectForKey:[fileURL absoluteString]]) - return [contentFilterContainer objectForKey:[fileURL absoluteString]]; - - return [NSMutableDictionary dictionary]; -} - -- (NSArray *)queryFavoritesForFileURL:(NSURL *)fileURL andTabTrigger:(NSString *)tabTrigger includeGlobals:(BOOL)includeGlobals -{ - if(![tabTrigger length]) return [NSArray array]; - - NSMutableArray *result = [[NSMutableArray alloc] init]; - for(id fav in [self favoritesForFileURL:fileURL]) { - if([fav objectForKey:@"tabtrigger"] && [[fav objectForKey:@"tabtrigger"] isEqualToString:tabTrigger]) - [result addObject:fav]; - } - -#ifndef SP_REFACTOR - if(includeGlobals && [prefs objectForKey:SPQueryFavorites]) { - for(id fav in [prefs objectForKey:SPQueryFavorites]) { - if([fav objectForKey:@"tabtrigger"] && [[fav objectForKey:@"tabtrigger"] isEqualToString:tabTrigger]) { - [result addObject:fav]; - break; - } - } - } -#endif - - return [result autorelease]; -} - -/** - * Remove a Query Favorite the passed file URL - * - * @param index The index of the to be removed favorite - * - * @param fileURL The NSURL of the current active SPDatabaseDocument - * - */ -- (void)removeFavoriteAtIndex:(NSUInteger)index forFileURL:(NSURL *)fileURL -{ - [[favoritesContainer objectForKey:[fileURL absoluteString]] removeObjectAtIndex:index]; + return SPQueryConsoleWindowAutoSaveName; } -- (void)insertFavorite:(NSDictionary *)favorite atIndex:(NSUInteger)index forFileURL:(NSURL *)fileURL -{ - [[favoritesContainer objectForKey:[fileURL absoluteString]] insertObject:favorite atIndex:index]; -} - -#pragma mark - - -/** - * Dealloc. - */ -- (void)dealloc -{ - messagesVisibleSet = nil; - [NSObject cancelPreviousPerformRequestsWithTarget:self]; - - [dateFormatter release], dateFormatter = nil; - - [messagesFullSet release], messagesFullSet = nil; - [messagesFilteredSet release], messagesFilteredSet = nil; - [activeFilterString release], activeFilterString = nil; - - [favoritesContainer release], favoritesContainer = nil; - [historyContainer release], historyContainer = nil; - [contentFilterContainer release], contentFilterContainer = nil; - - if(completionKeywordList) [completionKeywordList release]; - if(completionFunctionList) [completionFunctionList release]; - if(functionArgumentSnippets) [functionArgumentSnippets release]; - - pthread_mutex_destroy(&consoleLock); - - [super dealloc]; -} - -@end - -@implementation SPQueryController (PrivateAPI) - /** * Updates the filtered result set based on any filter string and whether or not * all SELECT nd SHOW statements should be shown within the console. */ - (void)_updateFilterState { - +#ifndef SP_REFACTOR // Display start progress spinner [progressIndicator setHidden:NO]; [progressIndicator startAnimation:self]; - + // Don't allow clearing the console while filtering its content [saveConsoleButton setEnabled:NO]; [clearConsoleButton setEnabled:NO]; - + [messagesFilteredSet removeAllObjects]; - + // If filtering is disabled and all show/selects are shown, empty the filtered // result set and set the full set to visible. if (!filterIsActive && !showSelectStatementsAreDisabled && !showHelpStatementsAreDisabled) { messagesVisibleSet = messagesFullSet; - + [consoleTableView reloadData]; [consoleTableView scrollRowToVisible:([messagesVisibleSet count] - 1)]; - + [saveConsoleButton setEnabled:YES]; [clearConsoleButton setEnabled:YES]; - + [saveConsoleButton setTitle:NSLocalizedString(@"Save As...", @"save as button title")]; - + // Hide progress spinner [progressIndicator setHidden:YES]; [progressIndicator stopAnimation:self]; + return; } - + // Cache frequently used selector, avoiding dynamic binding overhead IMP messageMatchesFilters = [self methodForSelector:@selector(_messageMatchesCurrentFilters:)]; - + // Loop through all the messages in the full set to determine which should be // added to the filtered set. for (SPConsoleMessage *message in messagesFullSet) { - + // Add a reference to the message to the filtered set if filters are active and the // current message matches them if ((messageMatchesFilters)(self, @selector(_messageMatchesCurrentFilters:), [message message])) { [messagesFilteredSet addObject:message]; } } - + // Ensure that the filtered set is marked as the currently visible set. messagesVisibleSet = messagesFilteredSet; - + [consoleTableView reloadData]; [consoleTableView scrollRowToVisible:([messagesVisibleSet count] - 1)]; - + if ([messagesVisibleSet count] > 0) { [saveConsoleButton setEnabled:YES]; [clearConsoleButton setEnabled:YES]; } - + [saveConsoleButton setTitle:NSLocalizedString(@"Save View As...", @"save view as button title")]; - + // Hide progress spinner [progressIndicator setHidden:YES]; [progressIndicator stopAnimation:self]; +#endif } /** @@ -951,30 +485,27 @@ static SPQueryController *sharedQueryController = nil; - (BOOL)_messageMatchesCurrentFilters:(NSString *)message { BOOL messageMatchesCurrentFilters = YES; - + +#ifndef SP_REFACTOR // Check whether to hide the message based on the current filter text, if any - if (filterIsActive - && [message rangeOfString:activeFilterString options:NSCaseInsensitiveSearch].location == NSNotFound) - { + if (filterIsActive && [message rangeOfString:activeFilterString options:NSCaseInsensitiveSearch].location == NSNotFound) { messageMatchesCurrentFilters = NO; } - + // If hiding SELECTs and SHOWs is toggled to on, check whether the message is a SELECT or SHOW - if (messageMatchesCurrentFilters - && showSelectStatementsAreDisabled - && ([[message uppercaseString] hasPrefix:@"SELECT"] || [[message uppercaseString] hasPrefix:@"SHOW"])) + if (messageMatchesCurrentFilters && + showSelectStatementsAreDisabled && + ([[message uppercaseString] hasPrefix:@"SELECT"] || [[message uppercaseString] hasPrefix:@"SHOW"])) { messageMatchesCurrentFilters = NO; } - + // If hiding HELP is toggled to on, check whether the message is a HELP - if (messageMatchesCurrentFilters - && showHelpStatementsAreDisabled - && ([[message uppercaseString] hasPrefix:@"HELP"])) - { + if (messageMatchesCurrentFilters && showHelpStatementsAreDisabled && ([[message uppercaseString] hasPrefix:@"HELP"])) { messageMatchesCurrentFilters = NO; } - +#endif + return messageMatchesCurrentFilters; } @@ -985,28 +516,35 @@ static SPQueryController *sharedQueryController = nil; - (NSString *)_getConsoleStringWithTimeStamps:(BOOL)timeStamps connections:(BOOL)connections { NSMutableString *consoleString = [NSMutableString string]; - + +#ifndef SP_REFACTOR + NSArray *messageCopy = [messagesVisibleSet copy]; + for (SPConsoleMessage *message in messagesVisibleSet) { // As we are going to save the messages as an SQL file we need to comment // the timestamps and connections if included. if (timeStamps || connections) [consoleString appendString:@"/* "]; - + // If the timestamp column is not hidden we need to include them in the copy - if (timeStamps) + if (timeStamps) { [consoleString appendFormat:@"%@ ", [dateFormatter stringFromDate:[message messageDate]]]; - + } + // If the connection column is not hidden we need to include them in the copy - if (connections) + if (connections) { [consoleString appendFormat:@"%@ ", [message messageConnection]]; - + } + // Close the comment if (timeStamps || connections) [consoleString appendString:@"*/ "]; - + [consoleString appendFormat:@"%@\n", [message message]]; - } - + + [messageCopy release]; +#endif + return consoleString; } @@ -1015,21 +553,20 @@ static SPQueryController *sharedQueryController = nil; */ - (void)_addMessageToConsole:(NSString *)message connection:(NSString *)connection isError:(BOOL)error { +#ifndef SP_REFACTOR NSString *messageTemp = [[message stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] stringByReplacingOccurrencesOfString:@"\n" withString:@" "]; - + // Only append a semi-colon (;) if the supplied message is not an error - if (!error) { - messageTemp = [messageTemp stringByAppendingString:@";"]; - } - + if (!error) messageTemp = [messageTemp stringByAppendingString:@";"]; + SPConsoleMessage *consoleMessage = [SPConsoleMessage consoleMessageWithMessage:messageTemp date:[NSDate date] connection:connection]; - + [consoleMessage setIsError:error]; - + pthread_mutex_lock(&consoleLock); [messagesFullSet addObject:consoleMessage]; - + // If filtering is active, determine whether to add a reference to the filtered set if ((showSelectStatementsAreDisabled || showHelpStatementsAreDisabled || filterIsActive) && [self _messageMatchesCurrentFilters:[consoleMessage message]]) @@ -1038,7 +575,7 @@ static SPQueryController *sharedQueryController = nil; [saveConsoleButton setEnabled:YES]; [clearConsoleButton setEnabled:YES]; } - + // Reload the table and scroll to the new message if it's visible (for speed) if (allowConsoleUpdate && [[self window] isVisible]) { [consoleTableView noteNumberOfRowsChanged]; @@ -1047,6 +584,42 @@ static SPQueryController *sharedQueryController = nil; } pthread_mutex_unlock(&consoleLock); +#endif +} + +#pragma mark - + +/** + * Dealloc. + */ +- (void)dealloc +{ +#ifndef SP_REFACTOR + messagesVisibleSet = nil; +#endif + [NSObject cancelPreviousPerformRequestsWithTarget:self]; + +#ifndef SP_REFACTOR + [dateFormatter release], dateFormatter = nil; + + [messagesFullSet release], messagesFullSet = nil; + [messagesFilteredSet release], messagesFilteredSet = nil; + [activeFilterString release], activeFilterString = nil; + + [favoritesContainer release], favoritesContainer = nil; + [historyContainer release], historyContainer = nil; + [contentFilterContainer release], contentFilterContainer = nil; +#endif + + if (completionKeywordList) [completionKeywordList release], completionKeywordList = nil; + if (completionFunctionList) [completionFunctionList release], completionFunctionList = nil; + if (functionArgumentSnippets) [functionArgumentSnippets release], functionArgumentSnippets = nil; + +#ifndef SP_REFACTOR + pthread_mutex_destroy(&consoleLock); +#endif + + [super dealloc]; } @end diff --git a/Source/SPQueryControllerInitializer.h b/Source/SPQueryControllerInitializer.h new file mode 100644 index 00000000..2bee90af --- /dev/null +++ b/Source/SPQueryControllerInitializer.h @@ -0,0 +1,32 @@ +// +// $Id$ +// +// SPQueryControllerInitializer.h +// sequel-pro +// +// Created by Stuart Connolly (stuconnolly.com) on September 1, 2011 +// Copyright (c) 2011 Stuart Connolly. All rights reserved. +// +// 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 <http://code.google.com/p/sequel-pro/> + +#import "SPQueryController.h" + +@interface SPQueryController (SPQueryControllerInitializer) + +- (NSError *)loadCompletionLists; + +@end diff --git a/Source/SPQueryControllerInitializer.m b/Source/SPQueryControllerInitializer.m new file mode 100644 index 00000000..2d43898e --- /dev/null +++ b/Source/SPQueryControllerInitializer.m @@ -0,0 +1,135 @@ +// +// $Id$ +// +// SPQueryControllerInitializer.m +// sequel-pro +// +// Created by Stuart Connolly (stuconnolly.com) on September 1, 2011 +// Copyright (c) 2011 Stuart Connolly. All rights reserved. +// +// 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 <http://code.google.com/p/sequel-pro/> + +#import "SPQueryControllerInitializer.h" + +static NSString *SPCompletionTokensFilename = @"CompletionTokens.plist"; + +static NSString *SPCompletionTokensKeywordsKey = @"core_keywords"; +static NSString *SPCompletionTokensFunctionsKey = @"core_builtin_functions"; +static NSString *SPCompletionTokensSnippetsKey = @"function_argument_snippets"; + +@interface SPQueryController () + +- (void)_updateFilterState; + +@end + +@implementation SPQueryController (SPQueryControllerInitializer) + +/** + * Set the window's auto save name and initialise display + */ +- (void)awakeFromNib +{ +#ifndef SP_REFACTOR /* init ivars */ + prefs = [NSUserDefaults standardUserDefaults]; + + [self setWindowFrameAutosaveName:SPQueryConsoleWindowAutoSaveName]; + + // Show/hide table columns + [[consoleTableView tableColumnWithIdentifier:SPTableViewDateColumnID] setHidden:![prefs boolForKey:SPConsoleShowTimestamps]]; + [[consoleTableView tableColumnWithIdentifier:SPTableViewConnectionColumnID] setHidden:![prefs boolForKey:SPConsoleShowConnections]]; + + showSelectStatementsAreDisabled = ![prefs boolForKey:SPConsoleShowSelectsAndShows]; + showHelpStatementsAreDisabled = ![prefs boolForKey:SPConsoleShowHelps]; + + [self _updateFilterState]; + + [loggingDisabledTextField setStringValue:([prefs boolForKey:SPConsoleEnableLogging]) ? @"" : NSLocalizedString(@"Query logging is currently disabled", @"query logging disabled label")]; + + // Setup data formatter + dateFormatter = [[NSDateFormatter alloc] init]; + + [dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; + + [dateFormatter setDateStyle:NSDateFormatterNoStyle]; + [dateFormatter setTimeStyle:NSDateFormatterMediumStyle]; + + // Set the process table view's vertical gridlines if required + [consoleTableView setGridStyleMask:([prefs boolForKey:SPDisplayTableViewVerticalGridlines]) ? NSTableViewSolidVerticalGridLineMask : NSTableViewGridNone]; + + // Set the strutcture and index view's font + BOOL useMonospacedFont = [prefs boolForKey:SPUseMonospacedFonts]; + + for (NSTableColumn *column in [consoleTableView tableColumns]) + { + [[column dataCell] setFont:(useMonospacedFont) ? [NSFont fontWithName:SPDefaultMonospacedFontName size:[NSFont smallSystemFontSize]] : [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; + } +#endif +} + +/** + * Loads the query controller's completion tokens data. + */ +- (NSError *)loadCompletionLists +{ + NSError *readError = nil; + NSString *convError = nil; + NSString *errorDescription = nil; + + NSPropertyListFormat format; + NSData *completionTokensData = [NSData dataWithContentsOfFile: + [NSBundle pathForResource:SPCompletionTokensFilename + ofType:nil + inDirectory:[[NSBundle mainBundle] bundlePath]] + options:NSMappedRead error:&readError]; + + NSDictionary *completionPlist = [NSDictionary dictionaryWithDictionary: + [NSPropertyListSerialization propertyListFromData:completionTokensData + mutabilityOption:NSPropertyListMutableContainersAndLeaves + format:&format + errorDescription:&convError]]; + + if (completionPlist == nil || readError != nil || convError != nil) { + errorDescription = [NSString stringWithFormat:@"Error reading '%@': %@, %@", SPCompletionTokensFilename, [readError localizedDescription], convError]; + } + else { + if ([completionPlist objectForKey:SPCompletionTokensKeywordsKey]) { + completionKeywordList = [[NSArray arrayWithArray:[completionPlist objectForKey:SPCompletionTokensKeywordsKey]] retain]; + } + else { + errorDescription = [NSString stringWithFormat:@"No '%@' array found.", SPCompletionTokensKeywordsKey]; + } + + if ([completionPlist objectForKey:SPCompletionTokensFunctionsKey]) { + completionFunctionList = [[NSArray arrayWithArray:[completionPlist objectForKey:SPCompletionTokensFunctionsKey]] retain]; + } + else { + errorDescription = [NSString stringWithFormat:@"No '%@' array found.", SPCompletionTokensFunctionsKey]; + } + + if ([completionPlist objectForKey:SPCompletionTokensSnippetsKey]) { + functionArgumentSnippets = [[NSDictionary dictionaryWithDictionary:[completionPlist objectForKey:SPCompletionTokensSnippetsKey]] retain]; + } + else { + errorDescription = [NSString stringWithFormat:@"No '%@' dictionary found.", SPCompletionTokensSnippetsKey]; + } + } + + return errorDescription ? [NSError errorWithDomain:NSCocoaErrorDomain code:1 userInfo:[NSDictionary dictionaryWithObject:errorDescription forKey:NSLocalizedDescriptionKey]] : nil; +} + +@end diff --git a/Source/SPQueryDocumentsController.h b/Source/SPQueryDocumentsController.h new file mode 100644 index 00000000..2d109b27 --- /dev/null +++ b/Source/SPQueryDocumentsController.h @@ -0,0 +1,56 @@ +// +// $Id$ +// +// SPQueryDocumentsController.h +// sequel-pro +// +// Created by Stuart Connolly (stuconnolly.com) on August 30, 2011 +// Copyright (c) 2011 Stuart Connolly. All rights reserved. +// +// 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 <http://code.google.com/p/sequel-pro/> + +#import "SPQueryController.h" + +@interface SPQueryController (SPQueryDocumentsController) + +- (NSURL *)registerDocumentWithFileURL:(NSURL *)fileURL andContextInfo:(NSMutableDictionary *)contextInfo; +- (void)removeRegisteredDocumentWithFileURL:(NSURL *)fileURL; + +- (void)addFavorite:(NSDictionary *)favorite forFileURL:(NSURL *)fileURL; +- (void)replaceFavoritesByArray:(NSArray *)favoritesArray forFileURL:(NSURL *)fileURL; +- (void)removeFavoriteAtIndex:(NSUInteger)index forFileURL:(NSURL *)fileURL; +- (void)insertFavorite:(NSDictionary *)favorite atIndex:(NSUInteger)index forFileURL:(NSURL *)fileURL; + +- (void)addHistory:(NSString *)history forFileURL:(NSURL *)fileURL; +- (void)replaceHistoryByArray:(NSArray *)historyArray forFileURL:(NSURL *)fileURL; + +- (void)replaceContentFilterByArray:(NSArray *)contentFilterArray ofType:(NSString *)filterType forFileURL:(NSURL *)fileURL; + +- (NSMutableArray *)favoritesForFileURL:(NSURL *)fileURL; +- (NSMutableArray *)historyForFileURL:(NSURL *)fileURL; +- (NSArray *)historyMenuItemsForFileURL:(NSURL *)fileURL; +- (NSUInteger)numberOfHistoryItemsForFileURL:(NSURL *)fileURL; +- (NSMutableDictionary *)contentFilterForFileURL:(NSURL *)fileURL; + +- (NSArray *)queryFavoritesForFileURL:(NSURL *)fileURL andTabTrigger:(NSString *)tabTrigger includeGlobals:(BOOL)includeGlobals; + +// Completion list controller +- (NSArray*)functionList; +- (NSArray*)keywordList; +- (NSString*)argumentSnippetForFunction:(NSString*)func; + +@end diff --git a/Source/SPQueryDocumentsController.m b/Source/SPQueryDocumentsController.m new file mode 100644 index 00000000..b4c3df22 --- /dev/null +++ b/Source/SPQueryDocumentsController.m @@ -0,0 +1,407 @@ +// +// $Id$ +// +// SPQueryDocumentsController.m +// sequel-pro +// +// Created by Stuart Connolly (stuconnolly.com) on August 30, 2011 +// Copyright (c) 2011 Stuart Connolly. All rights reserved. +// +// 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 <http://code.google.com/p/sequel-pro/> + +#import "SPQueryDocumentsController.h" + +@implementation SPQueryController (SPQueryDocumentsController) + +- (NSURL *)registerDocumentWithFileURL:(NSURL *)fileURL andContextInfo:(NSMutableDictionary *)contextInfo +{ +#ifndef SP_REFACTOR + // Register a new untiled document and return its URL + if (fileURL == nil) { + NSURL *new = [NSURL URLWithString:[[NSString stringWithFormat:NSLocalizedString(@"Untitled %ld",@"Title of a new Sequel Pro Document"), (unsigned long)untitledDocumentCounter] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; + untitledDocumentCounter++; + + if (![favoritesContainer objectForKey:[new absoluteString]]) { + NSMutableArray *arr = [[NSMutableArray alloc] init]; + [favoritesContainer setObject:arr forKey:[new absoluteString]]; + [arr release]; + } + + // Set the global history coming from the Prefs as default if available + if (![historyContainer objectForKey:[new absoluteString]]) { + if ([prefs objectForKey:SPQueryHistory]) { + NSMutableArray *arr = [[NSMutableArray alloc] init]; + [arr addObjectsFromArray:[prefs objectForKey:SPQueryHistory]]; + [historyContainer setObject:arr forKey:[new absoluteString]]; + [arr release]; + } + else { + NSMutableArray *arr = [[NSMutableArray alloc] init]; + [historyContainer setObject:[NSMutableArray array] forKey:[new absoluteString]]; + [arr release]; + } + } + + // Set the doc-based content filters + if (![contentFilterContainer objectForKey:[new absoluteString]]) { + [contentFilterContainer setObject:[NSMutableDictionary dictionary] forKey:[new absoluteString]]; + } + + return new; + } + + // Register a spf file to manage all query favorites and query history items + // file path based (incl. Untitled docs) in a dictionary whereby the key represents the file URL as string. + if (![favoritesContainer objectForKey:[fileURL absoluteString]]) { + if (contextInfo != nil && [contextInfo objectForKey:SPQueryFavorites] && [[contextInfo objectForKey:SPQueryFavorites] count]) { + NSMutableArray *arr = [[NSMutableArray alloc] init]; + [arr addObjectsFromArray:[contextInfo objectForKey:SPQueryFavorites]]; + [favoritesContainer setObject:arr forKey:[fileURL absoluteString]]; + [arr release]; + } + else { + NSMutableArray *arr = [[NSMutableArray alloc] init]; + [favoritesContainer setObject:arr forKey:[fileURL absoluteString]]; + [arr release]; + } + } + + if (![historyContainer objectForKey:[fileURL absoluteString]]) { + if (contextInfo != nil && [contextInfo objectForKey:SPQueryHistory] && [[contextInfo objectForKey:SPQueryHistory] count]) { + NSMutableArray *arr = [[NSMutableArray alloc] init]; + [arr addObjectsFromArray:[contextInfo objectForKey:SPQueryHistory]]; + [historyContainer setObject:arr forKey:[fileURL absoluteString]]; + [arr release]; + } + else { + NSMutableArray *arr = [[NSMutableArray alloc] init]; + [historyContainer setObject:arr forKey:[fileURL absoluteString]]; + [arr release]; + } + } + + if (![contentFilterContainer objectForKey:[fileURL absoluteString]]) { + if (contextInfo != nil && [contextInfo objectForKey:SPContentFilters]) { + [contentFilterContainer setObject:[contextInfo objectForKey:SPContentFilters] forKey:[fileURL absoluteString]]; + } + else { + NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; + [contentFilterContainer setObject:dict forKey:[fileURL absoluteString]]; + [dict release]; + } + } + + return fileURL; +#else + return nil; +#endif +} + +- (void)removeRegisteredDocumentWithFileURL:(NSURL *)fileURL +{ +#ifndef SP_REFACTOR + // Check for multiple instance of the same document. + // Remove it if only one instance was registerd. + NSArray *allDocs = [[NSApp delegate] orderedDocuments]; + NSMutableArray *allURLs = [NSMutableArray array]; + + for (id doc in allDocs) + { + if (![doc fileURL]) continue; + + if ([allURLs containsObject:[doc fileURL]]) { + return; + } + else { + [allURLs addObject:[doc fileURL]]; + } + } + + if ([favoritesContainer objectForKey:[fileURL absoluteString]]) { + [favoritesContainer removeObjectForKey:[fileURL absoluteString]]; + } + + if ([historyContainer objectForKey:[fileURL absoluteString]]) { + [historyContainer removeObjectForKey:[fileURL absoluteString]]; + } + + if ([contentFilterContainer objectForKey:[fileURL absoluteString]]) { + [contentFilterContainer removeObjectForKey:[fileURL absoluteString]]; + } +#endif +} + +- (void)replaceContentFilterByArray:(NSArray *)contentFilterArray ofType:(NSString *)filterType forFileURL:(NSURL *)fileURL +{ +#ifndef SP_REFACTOR + if ([contentFilterContainer objectForKey:[fileURL absoluteString]]) { + NSMutableDictionary *c = [[NSMutableDictionary alloc] init]; + [c setDictionary:[contentFilterContainer objectForKey:[fileURL absoluteString]]]; + [c setObject:contentFilterArray forKey:filterType]; + [contentFilterContainer setObject:c forKey:[fileURL absoluteString]]; + [c release]; + } +#endif +} + +- (void)replaceFavoritesByArray:(NSArray *)favoritesArray forFileURL:(NSURL *)fileURL +{ +#ifndef SP_REFACTOR + if ([favoritesContainer objectForKey:[fileURL absoluteString]]) { + [favoritesContainer setObject:favoritesArray forKey:[fileURL absoluteString]]; + } +#endif +} + +/** + * Remove a Query Favorite the passed file URL + * + * @param index The index of the to be removed favorite + * + * @param fileURL The NSURL of the current active SPDatabaseDocument + */ +- (void)removeFavoriteAtIndex:(NSUInteger)index forFileURL:(NSURL *)fileURL +{ +#ifndef SP_REFACTOR + [[favoritesContainer objectForKey:[fileURL absoluteString]] removeObjectAtIndex:index]; +#endif +} + +- (void)insertFavorite:(NSDictionary *)favorite atIndex:(NSUInteger)index forFileURL:(NSURL *)fileURL +{ +#ifndef SP_REFACTOR + [[favoritesContainer objectForKey:[fileURL absoluteString]] insertObject:favorite atIndex:index]; +#endif +} + +- (void)replaceHistoryByArray:(NSArray *)historyArray forFileURL:(NSURL *)fileURL +{ +#ifndef SP_REFACTOR + if ([historyContainer objectForKey:[fileURL absoluteString]]) { + [historyContainer setObject:historyArray forKey:[fileURL absoluteString]]; + } + + // Inform all opened documents to update the history list + for (id doc in [[NSApp delegate] orderedDocuments]) + { + if([[doc valueForKeyPath:@"customQueryInstance"] respondsToSelector:@selector(historyItemsHaveBeenUpdated:)]) { + [[doc valueForKeyPath:@"customQueryInstance"] performSelectorOnMainThread:@selector(historyItemsHaveBeenUpdated:) withObject:self waitUntilDone:NO]; + } + } + + // User did choose to clear the global history list + if (![fileURL isFileURL] && ![historyArray count]) { + [prefs setObject:historyArray forKey:SPQueryHistory]; + } +#endif +} + +- (void)addFavorite:(NSDictionary *)favorite forFileURL:(NSURL *)fileURL +{ +#ifndef SP_REFACTOR + if ([favoritesContainer objectForKey:[fileURL absoluteString]]) { + [[favoritesContainer objectForKey:[fileURL absoluteString]] addObject:favorite]; + } +#endif +} + +- (void)addHistory:(NSString *)history forFileURL:(NSURL *)fileURL +{ +#ifndef SP_REFACTOR + NSUInteger maxHistoryItems = [[prefs objectForKey:SPCustomQueryMaxHistoryItems] integerValue]; + + // Save each history item due to its document source + if ([historyContainer objectForKey:[fileURL absoluteString]]) { + + // Remove all duplicates by using a NSPopUpButton + NSPopUpButton *uniquifier = [[NSPopUpButton alloc] initWithFrame:NSMakeRect(0,0,0,0) pullsDown:YES]; + + [uniquifier addItemsWithTitles:[historyContainer objectForKey:[fileURL absoluteString]]]; + [uniquifier insertItemWithTitle:history atIndex:0]; + + while ((NSUInteger)[uniquifier numberOfItems] > maxHistoryItems) + { + [uniquifier removeItemAtIndex:[uniquifier numberOfItems]-1]; + } + + [self replaceHistoryByArray:[uniquifier itemTitles] forFileURL:fileURL]; + [uniquifier release]; + } + + // Save history items coming from each Untitled document in the global Preferences successively + // regardingless of the source document. + if (![fileURL isFileURL]) { + + // Remove all duplicates by using a NSPopUpButton + NSPopUpButton *uniquifier = [[NSPopUpButton alloc] initWithFrame:NSMakeRect(0,0,0,0) pullsDown:YES]; + [uniquifier addItemsWithTitles:[prefs objectForKey:SPQueryHistory]]; + [uniquifier insertItemWithTitle:history atIndex:0]; + + while ((NSUInteger)[uniquifier numberOfItems] > maxHistoryItems) + { + [uniquifier removeItemAtIndex:[uniquifier numberOfItems] - 1]; + } + + [prefs setObject:[uniquifier itemTitles] forKey:SPQueryHistory]; + [uniquifier release]; + } +#endif +} + +- (NSMutableArray *)favoritesForFileURL:(NSURL *)fileURL +{ +#ifndef SP_REFACTOR + if ([favoritesContainer objectForKey:[fileURL absoluteString]]) { + return [favoritesContainer objectForKey:[fileURL absoluteString]]; + } +#endif + + return [NSMutableArray array]; +} + +- (NSMutableArray *)historyForFileURL:(NSURL *)fileURL +{ +#ifndef SP_REFACTOR + if ([historyContainer objectForKey:[fileURL absoluteString]]) { + return [historyContainer objectForKey:[fileURL absoluteString]]; + } +#endif + + return [NSMutableArray array]; +} + +- (NSArray *)historyMenuItemsForFileURL:(NSURL *)fileURL +{ +#ifndef SP_REFACTOR + if ([historyContainer objectForKey:[fileURL absoluteString]]) { + NSMutableArray *returnArray = [NSMutableArray arrayWithCapacity:[[historyContainer objectForKey:[fileURL absoluteString]] count]]; + NSMenuItem *historyMenuItem; + + for (NSString* history in [historyContainer objectForKey:[fileURL absoluteString]]) + { + historyMenuItem = [[[NSMenuItem alloc] initWithTitle:([history length] > 64) ? [NSString stringWithFormat:@"%@…", [history substringToIndex:63]] : history + action:NULL + keyEquivalent:@""] autorelease]; + + [historyMenuItem setToolTip:([history length] > 256) ? [NSString stringWithFormat:@"%@…", [history substringToIndex:255]] : history]; + [returnArray addObject:historyMenuItem]; + } + + return returnArray; + } +#endif + + return [NSArray array]; +} + +/** + * Return the number of history items for the passed file URL + * + * @param fileURL The NSURL of the current active SPDatabaseDocument + * + */ +- (NSUInteger)numberOfHistoryItemsForFileURL:(NSURL *)fileURL +{ +#ifndef SP_REFACTOR + if ([historyContainer objectForKey:[fileURL absoluteString]]) { + return [[historyContainer objectForKey:[fileURL absoluteString]] count]; + } + else { + return 0; + } +#endif + + return 0; +} + +/** + * Return a mutable dictionary of all content filters for the passed file URL. + * If no content filters were found it returns an empty mutable dictionary. + * + * @param fileURL The NSURL of the current active SPDatabaseDocument + * + */ +- (NSMutableDictionary *)contentFilterForFileURL:(NSURL *)fileURL +{ +#ifndef SP_REFACTOR + if ([contentFilterContainer objectForKey:[fileURL absoluteString]]) { + return [contentFilterContainer objectForKey:[fileURL absoluteString]]; + } +#endif + + return [NSMutableDictionary dictionary]; +} + +- (NSArray *)queryFavoritesForFileURL:(NSURL *)fileURL andTabTrigger:(NSString *)tabTrigger includeGlobals:(BOOL)includeGlobals +{ + if (![tabTrigger length]) return [NSArray array]; + + NSMutableArray *result = [[NSMutableArray alloc] init]; + + for (id fav in [self favoritesForFileURL:fileURL]) + { + if ([fav objectForKey:@"tabtrigger"] && [[fav objectForKey:@"tabtrigger"] isEqualToString:tabTrigger]) { + [result addObject:fav]; + } + } + +#ifndef SP_REFACTOR + if (includeGlobals && [prefs objectForKey:SPQueryFavorites]) { + + for (id fav in [prefs objectForKey:SPQueryFavorites]) + { + if ([fav objectForKey:@"tabtrigger"] && [[fav objectForKey:@"tabtrigger"] isEqualToString:tabTrigger]) { + [result addObject:fav]; + break; + } + } + } +#endif + + return [result autorelease]; +} + +#pragma mark - +#pragma mark Completion list controller + +/** + * Return an array of all pre-defined SQL functions for completion. + */ +- (NSArray*)functionList +{ + return (completionFunctionList != nil && [completionFunctionList count]) ? completionFunctionList : [NSArray array]; +} + +/** + * Return an array of all pre-defined SQL keywords for completion. + */ +- (NSArray*)keywordList +{ + return (completionKeywordList != nil && [completionKeywordList count]) ? completionKeywordList : [NSArray array]; +} + +/** + * Return the parameter list as snippet of the passed SQL functions for completion. + * + * @param func The name of the function whose parameter list is asked for + */ +- (NSString*)argumentSnippetForFunction:(NSString*)func +{ + return (functionArgumentSnippets && [functionArgumentSnippets objectForKey:[func uppercaseString]]) ? [functionArgumentSnippets objectForKey:[func uppercaseString]] : @""; +} + +@end diff --git a/Source/SPQueryFavoriteManager.m b/Source/SPQueryFavoriteManager.m index 7fe5797f..90575e33 100644 --- a/Source/SPQueryFavoriteManager.m +++ b/Source/SPQueryFavoriteManager.m @@ -27,6 +27,7 @@ #import "ImageAndTextCell.h" #import "SPEncodingPopupAccessory.h" #import "SPQueryController.h" +#import "SPQueryDocumentsController.h" #import "SPConnectionController.h" #import "RegexKitLite.h" #import "SPTextView.h" diff --git a/Source/SPSSHTunnel.m b/Source/SPSSHTunnel.m index d0464d59..0c25c181 100644 --- a/Source/SPSSHTunnel.m +++ b/Source/SPSSHTunnel.m @@ -431,7 +431,9 @@ [debugMessages addObject:[NSString stringWithString:message]]; [debugMessagesLock unlock]; - if ([message rangeOfString:@"Entering interactive session."].location != NSNotFound) { + if ([message rangeOfString:@"Entering interactive session."].location != NSNotFound + || [message rangeOfString:@"mux_client_request_session: master session id: "].location != NSNotFound) + { connectionState = PROXY_STATE_CONNECTED; if (delegate) [delegate performSelectorOnMainThread:stateChangeSelector withObject:self waitUntilDone:NO]; } @@ -644,7 +646,7 @@ if (requestedResponse) { NSString *thePassword = [NSString stringWithString:[sshPasswordField stringValue]]; [sshPasswordField setStringValue:@""]; - if ([delegate respondsToSelector:@selector(setUndoManager:)] && [delegate undoManager]) { + if ([delegate respondsToSelector:@selector(undoManager)] && [delegate undoManager]) { [[delegate undoManager] removeAllActionsWithTarget:sshPasswordField]; } else if ([[parentWindow windowController] document] && [[[parentWindow windowController] document] undoManager]) { [[[[parentWindow windowController] document] undoManager] removeAllActionsWithTarget:sshPasswordField]; diff --git a/Source/SPServerSupport.h b/Source/SPServerSupport.h index bf18a8e8..fd4535a2 100644 --- a/Source/SPServerSupport.h +++ b/Source/SPServerSupport.h @@ -71,6 +71,7 @@ BOOL supportsBlackholeStorageEngine; BOOL supportsArchiveStorageEngine; BOOL supportsCSVStorageEngine; + BOOL supportsQuotingEngineTypeInCreateSyntax; // Triggers BOOL supportsTriggers; @@ -213,6 +214,12 @@ */ @property (readonly) BOOL supportsIndexKeyBlockSize; +/** + * @property supportsQuotingEngineTypeInCreateSyntax Indicates whether the server supports quoting the engine + * type in the create syntax. + */ +@property (readonly) BOOL supportsQuotingEngineTypeInCreateSyntax; + - (id)initWithMajorVersion:(NSInteger)majorVersion minor:(NSInteger)minorVersion release:(NSInteger)releaseVersion; - (void)evaluate; diff --git a/Source/SPServerSupport.m b/Source/SPServerSupport.m index 15c3555f..3d38ff79 100644 --- a/Source/SPServerSupport.m +++ b/Source/SPServerSupport.m @@ -62,6 +62,7 @@ @synthesize supportsCSVStorageEngine; @synthesize supportsTriggers; @synthesize supportsIndexKeyBlockSize; +@synthesize supportsQuotingEngineTypeInCreateSyntax; @synthesize serverMajorVersion; @synthesize serverMinorVersion; @synthesize serverReleaseVersion; @@ -81,7 +82,7 @@ */ - (id)initWithMajorVersion:(NSInteger)majorVersion minor:(NSInteger)minorVersion release:(NSInteger)releaseVersion { - if ((self == [super init])) { + if ((self = [super init])) { serverMajorVersion = majorVersion; serverMinorVersion = minorVersion; @@ -153,7 +154,7 @@ supportsShowPrivileges = [self isEqualToOrGreaterThanMajorVersion:4 minor:1 release:0]; // MySQL 4.0.18+ and 4.1.2+ changed the TYPE option to ENGINE, but 4.x supports both - engineTypeQueryName = [self isEqualToOrGreaterThanMajorVersion:5 minor:0 release:0]?@"ENGINE":@"TYPE"; + engineTypeQueryName = [self isEqualToOrGreaterThanMajorVersion:5 minor:0 release:0] ? @"ENGINE" : @"TYPE"; // Before MySQL 4.1 the MEMORY engine was known as HEAP and the ISAM engine was available supportsPre41StorageEngines = (![self isEqualToOrGreaterThanMajorVersion:4 minor:1 release:0]); @@ -171,7 +172,10 @@ supportsTriggers = [self isEqualToOrGreaterThanMajorVersion:5 minor:0 release:2]; // Support for specifying an index's key block size wasn't added until MySQL 5.1.10 - supportsIndexKeyBlockSize = [self isEqualToOrGreaterThanMajorVersion:5 minor:1 release:10]; + supportsIndexKeyBlockSize = [self isEqualToOrGreaterThanMajorVersion:5 minor:1 release:10]; + + // MySQL 4.0 doesn't seem to like having the ENGINE/TYPE quoted in a table's create syntax + supportsQuotingEngineTypeInCreateSyntax = [self isEqualToOrGreaterThanMajorVersion:4 minor:1 release:0]; } /** @@ -234,29 +238,30 @@ */ - (void)_invalidate { - isMySQL3 = NO; - isMySQL4 = NO; - isMySQL5 = NO; - isMySQL6 = NO; - - supportsInformationSchema = NO; - supportsSpatialExtensions = NO; - supportsShowCharacterSet = NO; - supportsCharacterSetDatabaseVar = NO; - supportsPost41CharacterSetHandling = NO; - supportsCreateUser = NO; - supportsDropUser = NO; - supportsFullDropUser = NO; - supportsUserMaxVars = NO; - supportsShowPrivileges = NO; - engineTypeQueryName = @"ENGINE"; - supportsInformationSchemaEngines = NO; - supportsPre41StorageEngines = NO; - supportsBlackholeStorageEngine = NO; - supportsArchiveStorageEngine = NO; - supportsCSVStorageEngine = NO; - supportsTriggers = NO; - supportsIndexKeyBlockSize = NO; + isMySQL3 = NO; + isMySQL4 = NO; + isMySQL5 = NO; + isMySQL6 = NO; + + supportsInformationSchema = NO; + supportsSpatialExtensions = NO; + supportsShowCharacterSet = NO; + supportsCharacterSetDatabaseVar = NO; + supportsPost41CharacterSetHandling = NO; + supportsCreateUser = NO; + supportsDropUser = NO; + supportsFullDropUser = NO; + supportsUserMaxVars = NO; + supportsShowPrivileges = NO; + engineTypeQueryName = @"ENGINE"; + supportsInformationSchemaEngines = NO; + supportsPre41StorageEngines = NO; + supportsBlackholeStorageEngine = NO; + supportsArchiveStorageEngine = NO; + supportsCSVStorageEngine = NO; + supportsTriggers = NO; + supportsIndexKeyBlockSize = NO; + supportsQuotingEngineTypeInCreateSyntax = NO; } /** diff --git a/Source/SPTableContent.h b/Source/SPTableContent.h index 9045772e..cc965e19 100644 --- a/Source/SPTableContent.h +++ b/Source/SPTableContent.h @@ -28,12 +28,11 @@ #import <MCPKit/MCPKit.h> @class SPDatabaseDocument, SPCopyTable, SPTextAndLinkCell, SPHistoryController, SPTableInfo, SPDataStorage, SPTextView, SPFieldEditorController; - -@class SPTableData, SPDatabaseDocument, SPTablesList; +@class SPTableData, SPDatabaseDocument, SPTablesList, SPTableStructure, SPTableList, SPContentFilterManager; @interface SPTableContent : NSObject #ifdef SP_REFACTOR -<NSTableViewDelegate, NSTableViewDataSource> +<NSTableViewDelegate, NSTableViewDataSource, NSComboBoxDataSource, NSComboBoxDelegate> #endif { IBOutlet SPDatabaseDocument *tableDocumentInstance; @@ -41,8 +40,10 @@ IBOutlet SPTableData* tableDataInstance; IBOutlet id tableSourceInstance; +#ifndef SP_REFACTOR IBOutlet SPTableInfo *tableInfoInstance; IBOutlet SPHistoryController *spHistoryControllerInstance; +#endif IBOutlet SPCopyTable *tableContentView; IBOutlet NSPopUpButton *fieldField; @@ -53,21 +54,28 @@ IBOutlet id copyButton; IBOutlet id removeButton; IBOutlet id reloadButton; +#ifndef SP_REFACTOR IBOutlet id multipleLineEditingButton; IBOutlet id countText; IBOutlet id limitRowsField; IBOutlet id limitRowsButton; IBOutlet id limitRowsStepper; +#endif IBOutlet id firstBetweenField; IBOutlet id secondBetweenField; IBOutlet id betweenTextField; IBOutlet NSButton *paginationPreviousButton; +#ifndef SP_REFACTOR IBOutlet NSButton *paginationButton; +#endif IBOutlet NSButton *paginationNextButton; +#ifndef SP_REFACTOR IBOutlet NSView *contentViewPane; IBOutlet NSView *paginationView; +#endif IBOutlet NSTextField *paginationPageField; +#ifndef SP_REFACTOR IBOutlet NSStepper *paginationPageStepper; IBOutlet SPCopyTable *filterTableView; @@ -81,13 +89,15 @@ IBOutlet NSMenuItem *filterTableGearLookAllFields; IBOutlet NSPanel *filterTableSetDefaultOperatorSheet; IBOutlet NSComboBox* filterTableSetDefaultOperatorValue; - +#endif MCPConnection *mySQLConnection; BOOL _mainNibLoaded; BOOL isWorking; pthread_mutex_t tableValuesLock; +#ifndef SP_REFACTOR NSMutableArray *nibObjectsToRelease; +#endif NSString *selectedTable, *usedQuery; SPDataStorage *tableValues; @@ -103,9 +113,10 @@ NSMutableDictionary *contentFilters; NSMutableDictionary *numberOfDefaultFilters; NSUInteger lastSelectedContentFilterIndex; - id contentFilterManager; + SPContentFilterManager *contentFilterManager; NSUInteger contentPage; +#ifndef SP_REFACTOR NSMutableDictionary *filterTableData; BOOL filterTableNegate; BOOL filterTableDistinct; @@ -114,6 +125,7 @@ NSString *lastEditedFilterTableValue; NSInteger activeFilter; // 0 = default filter; 1 = filter table; 2 = sequelpro url scheme NSString *schemeFilter; +#endif BOOL sortColumnToRestoreIsAsc; BOOL tableRowsSelectable; @@ -123,7 +135,9 @@ NSRect selectionViewportToRestore; NSString *filterFieldToRestore, *filterComparisonToRestore, *filterValueToRestore, *firstBetweenValueToRestore, *secondBetweenValueToRestore; +#ifndef SP_REFACTOR NSInteger paginationViewHeight; +#endif NSTimer *tableLoadTimer; NSUInteger tableLoadInterfaceUpdateInterval, tableLoadTimerTicksSinceLastUpdate, tableLoadLastRowCount, tableLoadTargetRowCount; @@ -141,10 +155,8 @@ SPFieldEditorController *fieldEditor; NSRange fieldEditorSelectedRange; - } - - (void)setFieldEditorSelectedRange:(NSRange)aRange; - (NSRange)fieldEditorSelectedRange; @@ -169,7 +181,9 @@ // Pagination - (IBAction) navigatePaginationFromButton:(id)sender; +#ifndef SP_REFACTOR - (IBAction) togglePagination:(id)sender; +#endif - (void) setPaginationViewVisibility:(BOOL)makeVisible; - (void) updatePaginationState; @@ -188,10 +202,11 @@ - (IBAction)swapFilterTable:(id)sender; - (IBAction)toggleLookAllFieldsMode:(id)sender; - (IBAction)closeSheet:(id)sender; +- (IBAction)showDefaultOperaterHelp:(id)sender; -// Getter methods +// Data accessors - (NSArray *)currentResult; -- (NSArray *)currentDataResult; +- (NSArray *)currentDataResultWithNULLs:(BOOL)includeNULLs; // Task interaction - (void) startDocumentTaskForTab:(NSNotification *)aNotification; @@ -217,6 +232,7 @@ - (void)sortTableTaskWithColumn:(NSTableColumn *)tableColumn; - (void)showErrorSheetWith:(id)error; - (void)processFieldEditorResult:(id)data contextInfo:(NSDictionary*)contextInfo; +- (void)saveViewCellValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSUInteger)rowIndex; // Retrieving and setting table state - (NSString *) sortColumnName; @@ -241,15 +257,31 @@ - (void)openContentFilterManager; - (void)makeContentFilterHaveFocus; -- (NSArray*)fieldEditStatusForRow:(NSInteger)rowIndex andColumn:(NSNumber *)columnIndex; +- (NSArray*)fieldEditStatusForRow:(NSInteger)rowIndex andColumn:(NSInteger)columnIndex; - (void)updateFilterTableClause:(id)currentValue; - (NSString*)escapeFilterTableDefaultOperator:(NSString*)anOperator; #ifdef SP_REFACTOR /* glue */ -- (void)setDatabaseDocument:(SPDatabaseDocument*)doc; -- (void)setTableListInstance:(SPTablesList*)list; -- (void)setConnection:(MCPConnection *)theConnection; +@property (assign) id filterButton; +@property (assign) id fieldField; +@property (assign) id compareField; +@property (assign) id betweenTextField; +@property (assign) id firstBetweenField; +@property (assign) id secondBetweenField; +@property (assign) id argumentField; +@property (assign) NSButton* addButton; +@property (assign) NSButton* copyButton; +@property (assign) NSButton* removeButton; +@property (assign) NSButton* reloadButton; +@property (assign) NSButton* paginationNextButton; +@property (assign) NSButton* paginationPreviousButton; +@property (assign) NSTextField* paginationPageField; +@property (assign) SPDatabaseDocument* tableDocumentInstance; +@property (assign) SPTablesList* tablesListInstance; +@property (assign) SPCopyTable* tableContentView; +@property (assign) SPTableData* tableDataInstance; +@property (assign) SPTableStructure* tableSourceInstance; #endif @end diff --git a/Source/SPTableContent.m b/Source/SPTableContent.m index df4eccf7..14f07866 100644 --- a/Source/SPTableContent.m +++ b/Source/SPTableContent.m @@ -35,8 +35,11 @@ #import "SPDataCellFormatter.h" #import "SPTableData.h" #import "SPQueryController.h" +#import "SPQueryDocumentsController.h" #import "SPTextAndLinkCell.h" +#ifndef SP_REFACTOR #import "QLPreviewPanel.h" +#endif #import "SPFieldEditorController.h" #import "SPTooltip.h" #import "RegexKitLite.h" @@ -59,31 +62,59 @@ @implementation SPTableContent +#ifdef SP_REFACTOR +@synthesize addButton; +@synthesize argumentField; +@synthesize betweenTextField; +@synthesize compareField; +@synthesize copyButton; +@synthesize fieldField; +@synthesize filterButton; +@synthesize firstBetweenField; +@synthesize paginationNextButton; +@synthesize paginationPageField; +@synthesize paginationPreviousButton; +@synthesize reloadButton; +@synthesize removeButton; +@synthesize secondBetweenField; +@synthesize tableContentView; +@synthesize tableDataInstance; +@synthesize tableDocumentInstance; +@synthesize tableSourceInstance; +@synthesize tablesListInstance; +#endif + /** * Standard init method. Initialize various ivars. */ - (id)init { - if ((self == [super init])) { + if ((self = [super init])) { _mainNibLoaded = NO; isWorking = NO; pthread_mutex_init(&tableValuesLock, NULL); +#ifndef SP_REFACTOR nibObjectsToRelease = [[NSMutableArray alloc] init]; +#endif tableValues = [[SPDataStorage alloc] init]; dataColumns = [[NSMutableArray alloc] init]; oldRow = [[NSMutableArray alloc] init]; +#ifndef SP_REFACTOR filterTableData = [[NSMutableDictionary alloc] initWithCapacity:1]; +#endif tableRowsCount = 0; previousTableRowsCount = 0; +#ifndef SP_REFACTOR filterTableNegate = NO; filterTableDistinct = NO; filterTableIsSwapped = NO; lastEditedFilterTableValue = nil; activeFilter = 0; schemeFilter = nil; +#endif selectedTable = nil; sortCol = nil; @@ -133,10 +164,12 @@ [contentFilters setDictionary:[NSPropertyListSerialization propertyListFromData:defaultFilterData mutabilityOption:NSPropertyListMutableContainersAndLeaves format:&format errorDescription:&convError]]; - if(contentFilters == nil || readError != nil || convError != nil) { + + if (contentFilters == nil || readError != nil || convError != nil) { NSLog(@"Error while reading 'ContentFilters.plist':\n%@\n%@", [readError localizedDescription], convError); NSBeep(); - } else { + } + else { [numberOfDefaultFilters setObject:[NSNumber numberWithInteger:[[contentFilters objectForKey:@"number"] count]] forKey:@"number"]; [numberOfDefaultFilters setObject:[NSNumber numberWithInteger:[[contentFilters objectForKey:@"date"] count]] forKey:@"date"]; [numberOfDefaultFilters setObject:[NSNumber numberWithInteger:[[contentFilters objectForKey:@"string"] count]] forKey:@"string"]; @@ -146,7 +179,6 @@ kCellEditorErrorNoMatch = NSLocalizedString(@"Field is not editable. No matching record found.\nReload table, check the encoding, or try to add\na primary key field or more fields\nin the view declaration of '%@' to identify\nfield origin unambiguously.", @"Table Content result editing error - could not identify original row"); kCellEditorErrorNoMultiTabDb = NSLocalizedString(@"Field is not editable. Field has no or multiple table or database origin(s).",@"field is not editable due to no table/database"); kCellEditorErrorTooManyMatches = NSLocalizedString(@"Field is not editable. Couldn't identify field origin unambiguously (%ld match%@).", @"Query result editing error - could not match row being edited uniquely"); - } return self; @@ -163,7 +195,6 @@ #ifndef SP_REFACTOR /* ui manipulation */ // Set the table content view's vertical gridlines if required [tableContentView setGridStyleMask:([prefs boolForKey:SPDisplayTableViewVerticalGridlines]) ? NSTableViewSolidVerticalGridLineMask : NSTableViewGridNone]; -#endif // Set the double-click action in blank areas of the table to create new rows [tableContentView setEmptyDoubleClickAction:@selector(addRow:)]; @@ -187,17 +218,20 @@ paginationViewFrame.size.height = 0; [paginationView setFrame:paginationViewFrame]; [contentViewPane addSubview:paginationView]; +#endif [tableContentView setFieldEditorSelectedRange:NSMakeRange(0,0)]; +#ifndef SP_REFACTOR // Init Filter Table GUI [filterTableDistinctMenuItem setState:(filterTableDistinct) ? NSOnState : NSOffState]; [filterTableNegateCheckbox setState:(filterTableNegate) ? NSOnState : NSOffState]; [filterTableLiveSearchCheckbox setState:NSOffState]; +#endif #ifndef SP_REFACTOR /* patch */ filterTableDefaultOperator = [[self escapeFilterTableDefaultOperator:[prefs objectForKey:SPFilterTableDefaultOperator]] retain]; #else - filterTableDefaultOperator = [[self escapeFilterTableDefaultOperator:nil] retain]; +// filterTableDefaultOperator = [[self escapeFilterTableDefaultOperator:nil] retain]; #endif // Add observers for document task activity @@ -219,13 +253,11 @@ * reloading table data into the data array and redrawing the table. * * @param aTable The to be loaded table name - * */ - (void)loadTable:(NSString *)aTable { // Abort the reload if the user is still editing a row - if ( isEditingRow ) - return; + if (isEditingRow) return; // If no table has been supplied, clear the table interface and return if (!aTable || [aTable isEqualToString:@""]) { @@ -306,15 +338,16 @@ - (void) setTableDetails:(NSDictionary *)tableDetails { NSString *newTableName; - NSInteger i; - NSNumber + NSInteger i, sortColumnNumberToRestore = NSNotFound; #ifndef SP_REFACTOR - *colWidth, + NSNumber *colWidth; #endif - *sortColumnNumberToRestore = nil; NSArray *columnNames; NSDictionary *columnDefinition; - NSTableColumn *theCol, *filterCol; + NSTableColumn *theCol; +#ifndef SP_REFACTOR + NSTableColumn *filterCol; +#endif BOOL enableInteraction = #ifndef SP_REFACTOR /* checking toolbar state */ ![[tableDocumentInstance selectedToolbarItemIdentifier] isEqualToString:SPMainToolbarTableContent] || @@ -331,8 +364,10 @@ newTableName = [tableDetails objectForKey:@"name"]; } +#ifndef SP_REFACTOR // Ensure the pagination view hides itself if visible, after a tiny delay for smoothness [self performSelector:@selector(setPaginationViewVisibility:) withObject:nil afterDelay:0.1]; +#endif // Reset table key store for use in argumentForRow: if (keys) [keys release], keys = nil; @@ -379,7 +414,9 @@ [tableContentView reloadData]; isFiltered = NO; isLimited = NO; +#ifndef SP_REFACTOR [countText setStringValue:@""]; +#endif // Reset sort column if (sortCol) [sortCol release]; sortCol = nil; @@ -408,8 +445,10 @@ // Disable pagination [paginationPreviousButton setEnabled:NO]; +#ifndef SP_REFACTOR [paginationButton setEnabled:NO]; [paginationButton setTitle:@""]; +#endif [paginationNextButton setEnabled:NO]; // Disable table action buttons @@ -420,6 +459,7 @@ // Clear restoration settings [self clearDetailsToRestore]; +#ifndef SP_REFACTOR // Clear filter table while ([[filterTableView tableColumns] count]) { [filterTableView removeTableColumn:NSArrayObjectAtIndex([filterTableView tableColumns], 0)]; @@ -428,6 +468,7 @@ [filterTableData removeAllObjects]; [filterTableWhereClause setString:@""]; activeFilter = 0; +#endif return; } @@ -437,6 +478,7 @@ while ([[tableContentView tableColumns] count]) { [tableContentView removeTableColumn:NSArrayObjectAtIndex([tableContentView tableColumns], 0)]; } +#ifndef SP_REFACTOR // Remove existing columns from the filter table [filterTableView abortEditing]; while ([[filterTableView tableColumns] count]) { @@ -446,6 +488,7 @@ [filterTableData removeAllObjects]; [filterTableWhereClause setString:@""]; activeFilter = 0; +#endif // Retrieve the field names and types for this table from the data cache. This is used when requesting all data as part // of the fieldListForQuery method, and also to decide whether or not to preserve the current filter/sort settings. @@ -496,6 +539,7 @@ ]]; [theCol setEditable:YES]; +#ifndef SP_REFACTOR // Set up column for filterTable filterCol = [[NSTableColumn alloc] initWithIdentifier:[columnDefinition objectForKey:@"datacolumnindex"]]; [[filterCol headerCell] setStringValue:[columnDefinition objectForKey:@"name"]]; @@ -511,6 +555,7 @@ [columnDefinition objectForKey:@"typegrouping"], @"typegrouping", [NSMutableArray arrayWithObjects:@"", @"", @"", @"", @"", @"", @"", @"", @"", @"", nil], @"filter", nil] forKey:[columnDefinition objectForKey:@"datacolumnindex"]]; +#endif // Set up the data cell depending on the column type id dataCell; @@ -574,22 +619,24 @@ // Set the column to be reselected for sorting if appropriate if (sortColumnToRestore && [sortColumnToRestore isEqualToString:[columnDefinition objectForKey:@"name"]]) - sortColumnNumberToRestore = [columnDefinition objectForKey:@"datacolumnindex"]; + sortColumnNumberToRestore = [[columnDefinition objectForKey:@"datacolumnindex"] integerValue]; // Add the column to the table [tableContentView addTableColumn:theCol]; [theCol release]; } +#ifndef SP_REFACTOR [filterTableView setDelegate:self]; [filterTableView setDataSource:self]; [filterTableView reloadData]; +#endif // If the table has been reloaded and the previously selected sort column is still present, reselect it. - if (sortColumnNumberToRestore) { - theCol = [tableContentView tableColumnWithIdentifier:sortColumnNumberToRestore]; + if (sortColumnNumberToRestore != NSNotFound) { + theCol = [tableContentView tableColumnWithIdentifier:[NSString stringWithFormat:@"%lld", (long long)sortColumnNumberToRestore]]; if (sortCol) [sortCol release]; - sortCol = [sortColumnNumberToRestore copy]; + sortCol = [[NSNumber alloc] initWithInteger:sortColumnNumberToRestore]; [tableContentView setHighlightedTableColumn:theCol]; isDesc = !sortColumnToRestoreIsAsc; if ( isDesc ) { @@ -660,7 +707,9 @@ if (!previousTableRowsCount) { [self clearTableValues]; } +#ifndef SP_REFACTOR [filterTableView reloadData]; +#endif } @@ -699,7 +748,9 @@ MCPStreamingResult *streamingResult; NSInteger rowsToLoad = [[tableDataInstance statusValueForKey:@"Rows"] integerValue]; +#ifndef SP_REFACTOR [countText setStringValue:NSLocalizedString(@"Loading table data...", @"Loading table data string")]; +#endif // Notify any listeners that a query has started #ifndef SP_REFACTOR @@ -709,7 +760,12 @@ #endif // Start construction of the query string - queryString = [NSMutableString stringWithFormat:@"SELECT %@%@ FROM %@", (activeFilter == 1 && [self tableFilterString] && filterTableDistinct) ? @"DISTINCT " : @"", [self fieldListForQuery], [selectedTable backtickQuotedString]]; + queryString = [NSMutableString stringWithFormat:@"SELECT %@%@ FROM %@", +#ifndef SP_REFACTOR + (activeFilter == 1 && [self tableFilterString] && filterTableDistinct) ? @"DISTINCT " : +#endif + @"", + [self fieldListForQuery], [selectedTable backtickQuotedString]]; // Add a filter string if appropriate filterString = [self tableFilterString]; @@ -734,7 +790,7 @@ if (contentPage <= 0) contentPage = 1; else if (contentPage > 1 && (NSInteger)(contentPage - 1) * [prefs integerForKey:SPLimitResultsValue] >= maxNumRows) - contentPage = ceil((CGFloat)maxNumRows / [prefs floatForKey:SPLimitResultsValue]); + contentPage = ceilf((CGFloat)maxNumRows / [prefs floatForKey:SPLimitResultsValue]); // If the result set is from a late page, take a copy of the string to allow resetting limit // if no results are found @@ -824,7 +880,9 @@ #endif if ([mySQLConnection queryErrored] && ![mySQLConnection queryCancelled]) { +#ifndef SP_REFACTOR if(activeFilter == 0) { +#endif if(filterString) SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, [NSString stringWithFormat:NSLocalizedString(@"The table data couldn't be loaded presumably due to used filter clause. \n\nMySQL said: %@", @"message of panel when loading of table failed and presumably due to used filter argument"), [mySQLConnection getLastErrorMessage]]); @@ -832,14 +890,20 @@ SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, [NSString stringWithFormat:NSLocalizedString(@"The table data couldn't be loaded.\n\nMySQL said: %@", @"message of panel when loading of table failed"), [mySQLConnection getLastErrorMessage]]); } +#ifndef SP_REFACTOR // Filter task came from filter table else if(activeFilter == 1){ [filterTableWindow setTitle:[NSString stringWithFormat:@"%@ – %@", NSLocalizedString(@"Filter", @"filter label"), NSLocalizedString(@"WHERE clause not valid", @"WHERE clause not valid")]]; } - } else { + } +#endif + else + { +#ifndef SP_REFACTOR // Trigger a full reload if required if (fullTableReloadRequired) [self reloadTable:self]; [[filterTableWindow onMainThread] setTitle:NSLocalizedString(@"Filter", @"filter label")]; +#endif } } @@ -862,7 +926,11 @@ [[self onMainThread] initTableLoadTimer]; NSAutoreleasePool *dataLoadingPool; +#ifndef SP_REFACTOR NSProgressIndicator *dataLoadingIndicator = [tableDocumentInstance valueForKey:@"queryProgressBar"]; +#else + NSProgressIndicator *dataLoadingIndicator = [tableDocumentInstance queryProgressBar]; +#endif BOOL prefsLoadBlobsAsNeeded = #ifndef SP_REFACTOR [prefs boolForKey:SPLoadBlobsAsNeeded] @@ -945,6 +1013,7 @@ - (NSString *)tableFilterString { +#ifndef SP_REFACTOR // If filter command was passed by sequelpro url scheme if(activeFilter == 2) { if(schemeFilter) @@ -963,6 +1032,7 @@ return nil; } +#endif // If the clause has the placeholder $BINARY that placeholder will be replaced // by BINARY if the user pressed ⇧ while invoking 'Filter' otherwise it will @@ -1202,7 +1272,9 @@ [countString appendFormat:NSLocalizedString(@"%@ %@ selected", @"text showing how many rows are selected"), [numberFormatter stringFromNumber:[NSNumber numberWithInteger:[tableContentView numberOfSelectedRows]]], rowString]; } +#ifndef SP_REFACTOR [[countText onMainThread] setStringValue:countString]; +#endif } /** @@ -1337,6 +1409,8 @@ */ - (IBAction)filterTable:(id)sender { +#ifndef SP_REFACTOR + if(sender == filterTableFilterButton) activeFilter = 1; else if([sender isKindOfClass:[NSString class]] && [(NSString *)sender length]) { @@ -1346,12 +1420,15 @@ } else activeFilter = 0; +#endif NSString *taskString; if ([tableDocumentInstance isWorking]) return; if (![self saveRowOnDeselect]) return; +#ifndef SP_REFACTOR [self setPaginationViewVisibility:FALSE]; +#endif // Select the correct pagination value if (![prefs boolForKey:SPLimitResults] || [paginationPageField integerValue] <= 0) @@ -1384,8 +1461,10 @@ // Check whether a save of the current row is required. if (![[self onMainThread] saveRowOnDeselect]) return; +#ifndef SP_REFACTOR // Update history [spHistoryControllerInstance updateHistoryEntries]; +#endif // Reset and reload data using the new filter settings previousTableRowsCount = 0; @@ -1489,17 +1568,20 @@ * When the Pagination button is pressed, show or hide the pagination * layer depending on the current state. */ +#ifndef SP_REFACTOR - (IBAction) togglePagination:(id)sender { if ([sender state] == NSOnState) [self setPaginationViewVisibility:YES]; else [self setPaginationViewVisibility:NO]; } +#endif /** * Show or hide the pagination layer, also changing the first responder as appropriate. */ - (void) setPaginationViewVisibility:(BOOL)makeVisible { +#ifndef SP_REFACTOR NSRect paginationViewFrame = [paginationView frame]; if (makeVisible) { @@ -1524,6 +1606,7 @@ } [[paginationView animator] setFrame:paginationViewFrame]; +#endif } /** @@ -1553,14 +1636,18 @@ else [paginationNextButton setEnabled:NO]; +#ifndef SP_REFACTOR // As long as a table is selected (which it will be if this is called), enable pagination detail button [paginationButton setEnabled:enabledMode]; +#endif // Set the values and maximums for the text field and associated pager [paginationPageField setStringValue:[numberFormatter stringFromNumber:[NSNumber numberWithUnsignedInteger:contentPage]]]; [[paginationPageField formatter] setMaximum:[NSNumber numberWithUnsignedInteger:maxPage]]; +#ifndef SP_REFACTOR [paginationPageStepper setIntegerValue:contentPage]; [paginationPageStepper setMaxValue:maxPage]; +#endif } #pragma mark - @@ -1575,79 +1662,78 @@ NSArray *dataRow; NSDictionary *theRow; id field; + NSMutableArray *argumentParts = [NSMutableArray array]; - //Look for all columns which are coming from "tableForColumn" - NSMutableArray *columnsForFieldTableName = [NSMutableArray array]; + // Check the table/view columns and select only those coming from the supplied database and table + NSMutableArray *columnsInSpecifiedTable = [NSMutableArray array]; for(field in cqColumnDefinition) { - if([[field objectForKey:@"org_table"] isEqualToString:tableForColumn]) - [columnsForFieldTableName addObject:field]; + if([[field objectForKey:@"db"] isEqualToString:database] && [[field objectForKey:@"org_table"] isEqualToString:tableForColumn]) + [columnsInSpecifiedTable addObject:field]; } - // Try to identify the field bijectively - NSMutableString *fieldIDQueryStr = [NSMutableString string]; - [fieldIDQueryStr setString:@"WHERE ("]; - // --- Build WHERE clause --- dataRow = [tableValues rowContentsAtIndex:rowIndex]; - // Get the primary key if there is one + // Get the primary key if there is one, using any columns present within it MCPResult *theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW COLUMNS FROM %@.%@", [database backtickQuotedString], [tableForColumn backtickQuotedString]]]; [theResult setReturnDataAsStrings:YES]; if ([theResult numOfRows]) [theResult dataSeek:0]; + NSMutableArray *primaryColumnsInSpecifiedTable = [NSMutableArray array]; NSUInteger i; for ( i = 0 ; i < [theResult numOfRows] ; i++ ) { theRow = [theResult fetchRowAsDictionary]; if ( [[theRow objectForKey:@"Key"] isEqualToString:@"PRI"] ) { - for(field in columnsForFieldTableName) { - id aValue = [dataRow objectAtIndex:[[field objectForKey:@"datacolumnindex"] integerValue]]; + for (field in columnsInSpecifiedTable) { if([[field objectForKey:@"org_name"] isEqualToString:[theRow objectForKey:@"Field"]]) { - [fieldIDQueryStr appendFormat:@"%@.%@.%@ = %@)", - [database backtickQuotedString], - [tableForColumn backtickQuotedString], - [[theRow objectForKey:@"Field"] backtickQuotedString], - [aValue description]]; - return fieldIDQueryStr; + [primaryColumnsInSpecifiedTable addObject:field]; } } } } - // If there is no primary key, all found fields belonging to the same table are used in the argument - for(field in columnsForFieldTableName) { + // Determine whether to use the primary keys list or fall back to all fields when building the query string + NSMutableArray *columnsToQuery = [primaryColumnsInSpecifiedTable count] ? primaryColumnsInSpecifiedTable : columnsInSpecifiedTable; + + // Build up the argument + for (field in columnsToQuery) { id aValue = [dataRow objectAtIndex:[[field objectForKey:@"datacolumnindex"] integerValue]]; if ([aValue isKindOfClass:[NSNull class]] || [aValue isNSNull]) { - [fieldIDQueryStr appendFormat:@"%@ IS NULL AND ", [[field objectForKey:@"org_name"] backtickQuotedString]]; + [argumentParts addObject:[NSString stringWithFormat:@"%@ IS NULL", [[field objectForKey:@"org_name"] backtickQuotedString]]]; } else { - if ([[field objectForKey:@"typegrouping"] isEqualToString:@"textdata"]) { - if(includeBlobs) { - [fieldIDQueryStr appendFormat:@"%@='%@' AND ", [[field objectForKey:@"org_name"] backtickQuotedString], [mySQLConnection prepareString:aValue]]; - } - } - else if ([[field objectForKey:@"typegrouping"] isEqualToString:@"blobdata"] || [[field objectForKey:@"type"] isEqualToString:@"BINARY"] || [[field objectForKey:@"type"] isEqualToString:@"VARBINARY"]) { - if(includeBlobs) { - [fieldIDQueryStr appendFormat:@"%@=X'%@' AND ", [[field objectForKey:@"org_name"] backtickQuotedString], [mySQLConnection prepareBinaryData:aValue]]; - } + NSString *fieldTypeGrouping = [field objectForKey:@"typegrouping"]; + + // Skip blob-type fields if requested + if (!includeBlobs + && ([fieldTypeGrouping isEqualToString:@"textdata"] + || [fieldTypeGrouping isEqualToString:@"blobdata"] + || [[field objectForKey:@"type"] isEqualToString:@"BINARY"] + || [[field objectForKey:@"type"] isEqualToString:@"VARBINARY"])) + { + continue; } - else if ([[field objectForKey:@"typegrouping"] isEqualToString:@"bit"]) { - [fieldIDQueryStr appendFormat:@"%@=b'%@' AND ", [[field objectForKey:@"org_name"] backtickQuotedString], [aValue description]]; + + // If the field is of type BIT then it needs a binary prefix + if ([fieldTypeGrouping isEqualToString:@"bit"]) { + [argumentParts addObject:[NSString stringWithFormat:@"%@=b'%@'", [[field objectForKey:@"org_name"] backtickQuotedString], [aValue description]]]; } - else if ([[field objectForKey:@"typegrouping"] isEqualToString:@"integer"]) { - [fieldIDQueryStr appendFormat:@"%@=%@ AND ", [[field objectForKey:@"org_name"] backtickQuotedString], [aValue description]]; + else if ([fieldTypeGrouping isEqualToString:@"geometry"]) { + [argumentParts addObject:[NSString stringWithFormat:@"%@=X'%@'", [[field objectForKey:@"org_name"] backtickQuotedString], [mySQLConnection prepareBinaryData:[aValue data]]]]; } - else if ([[field objectForKey:@"typegrouping"] isEqualToString:@"geometry"]) { - [fieldIDQueryStr appendFormat:@"%@=X'%@' AND ", [[field objectForKey:@"org_name"] backtickQuotedString], [mySQLConnection prepareBinaryData:[aValue data]]]; + // BLOB/TEXT data + else if ([aValue isKindOfClass:[NSData class]]) { + [argumentParts addObject:[NSString stringWithFormat:@"%@=X'%@'", [[field objectForKey:@"org_name"] backtickQuotedString], [mySQLConnection prepareBinaryData:aValue]]]; } else { - [fieldIDQueryStr appendFormat:@"%@='%@' AND ", [[field objectForKey:@"org_name"] backtickQuotedString], [mySQLConnection prepareString:aValue]]; + [argumentParts addObject:[NSString stringWithFormat:@"%@='%@'", [[field objectForKey:@"org_name"] backtickQuotedString], [mySQLConnection prepareString:aValue]]]; } } } - // Remove last " AND " - if([fieldIDQueryStr length]>12) - [fieldIDQueryStr replaceCharactersInRange:NSMakeRange([fieldIDQueryStr length]-5,5) withString:@")"]; - return fieldIDQueryStr; + // Check for empty strings + if (![argumentParts count]) return nil; + + return [NSString stringWithFormat:@"WHERE (%@)", [argumentParts componentsJoinedByString:@" AND "]]; } /** @@ -1692,7 +1778,9 @@ isEditingRow = YES; isEditingNewRow = YES; currentlyEditingRow = [tableContentView selectedRow]; +#ifndef SP_REFACTOR if ( [multipleLineEditingButton state] == NSOffState ) +#endif [tableContentView editColumn:0 row:[tableContentView numberOfRows]-1 withEvent:nil select:YES]; } @@ -1706,42 +1794,50 @@ NSDictionary *row; NSArray *dbDataRow = nil; NSUInteger i; - + // Check whether a save of the current row is required. - if ( ![self saveRowOnDeselect] ) return; + if (![self saveRowOnDeselect]) return; - if ( [tableContentView numberOfSelectedRows] < 1 ) - return; - if ( [tableContentView numberOfSelectedRows] > 1 ) { + if (![tableContentView numberOfSelectedRows]) return; + + if ([tableContentView numberOfSelectedRows] > 1) { SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, NSLocalizedString(@"You can only copy single rows.", @"message of panel when trying to copy multiple rows")); return; } - - //copy row + + // Row contents tempRow = [tableValues rowContentsAtIndex:[tableContentView selectedRow]]; #ifndef SP_REFACTOR - //if we don't show blobs, read data for this duplicate column from db + // If we don't show blobs, read data for this duplicate column from db if ([prefs boolForKey:SPLoadBlobsAsNeeded]) { + // Abort if there are no indices on this table - argumentForRow will display an error. - if (![[self argumentForRow:[tableContentView selectedRow]] length]){ + if (![[self argumentForRow:[tableContentView selectedRow]] length]) { return; } - //if we have indexes, use argumentForRow + + // If we have indexes, use argumentForRow queryResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SELECT * FROM %@ WHERE %@", [selectedTable backtickQuotedString], [self argumentForRow:[tableContentView selectedRow]]]]; dbDataRow = [queryResult fetchRowAsArray]; } #endif - //set autoincrement fields to NULL + // Set autoincrement fields to NULL queryResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW COLUMNS FROM %@", [selectedTable backtickQuotedString]]]; + [queryResult setReturnDataAsStrings:YES]; + if ([queryResult numOfRows]) [queryResult dataSeek:0]; - for ( i = 0 ; i < [queryResult numOfRows] ; i++ ) { + + for (i = 0; i < [queryResult numOfRows]; i++) + { row = [queryResult fetchRowAsDictionary]; - if ( [[row objectForKey:@"Extra"] isEqualToString:@"auto_increment"] ) { + + if ([[row objectForKey:@"Extra"] isEqualToString:@"auto_increment"]) { [tempRow replaceObjectAtIndex:i withObject:[NSNull null]]; - } else if ( [tableDataInstance columnIsBlobOrText:[row objectForKey:@"Field"]] && + } + else if ([tableDataInstance columnIsBlobOrText:[row objectForKey:@"Field"]] && #ifndef SP_REFACTOR [prefs boolForKey:SPLoadBlobsAsNeeded] #else @@ -1752,18 +1848,25 @@ } } - //insert the copied row - [tableValues insertRowContents:tempRow atIndex:[tableContentView selectedRow]+1]; + // Insert the copied row + [tableValues insertRowContents:tempRow atIndex:[tableContentView selectedRow] + 1]; tableRowsCount++; - //select row and go in edit mode + // Select row and go in edit mode [tableContentView reloadData]; - [tableContentView selectRowIndexes:[NSIndexSet indexSetWithIndex:[tableContentView selectedRow]+1] byExtendingSelection:NO]; + [tableContentView selectRowIndexes:[NSIndexSet indexSetWithIndex:[tableContentView selectedRow] + 1] byExtendingSelection:NO]; + isEditingRow = YES; isEditingNewRow = YES; + currentlyEditingRow = [tableContentView selectedRow]; - if ( [multipleLineEditingButton state] == NSOffState ) +#ifndef SP_REFACTOR + if ([multipleLineEditingButton state]) { +#endif [tableContentView editColumn:0 row:[tableContentView selectedRow] withEvent:nil select:YES]; +#ifndef SP_REFACTOR + } +#endif } /** @@ -1771,16 +1874,10 @@ */ - (IBAction)removeRow:(id)sender { - // Check whether a save of the current row is required. - // if (![self saveRowOnDeselect]) - // return; - // cancel editing (maybe this is not the ideal method -- see xcode docs for that method) [[tableDocumentInstance parentWindow] endEditingFor:nil]; - - if (![tableContentView numberOfSelectedRows]) - return; + if (![tableContentView numberOfSelectedRows]) return; NSAlert *alert = [NSAlert alertWithMessageText:@"" defaultButton:NSLocalizedString(@"Delete", @"delete button") @@ -1802,7 +1899,7 @@ NSString *contextInfo = @"removerow"; - if (([tableContentView numberOfSelectedRows] == [tableContentView numberOfRows]) && !isFiltered && !isLimited && !isInterruptedLoad && !isEditingNewRow) { + if (([tableContentView numberOfSelectedRows] == [tableContentView numberOfRows]) && [tableContentView numberOfSelectedRows] > 50 && !isFiltered && !isLimited && !isInterruptedLoad && !isEditingNewRow) { contextInfo = @"removeallrows"; @@ -2022,7 +2119,8 @@ } errors = (affectedRows > 0) ? [selectedRows count] - affectedRows : [selectedRows count]; - } else { + } + else { // if table has more than one PRIMARY KEY // delete the row by using all PRIMARY KEYs in an OR clause NSMutableString *deleteQuery = [NSMutableString string]; @@ -2071,37 +2169,50 @@ [[SPQueryController sharedQueryController] setAllowConsoleUpdate:consoleUpdateStatus]; if (errors) { - NSArray *message; - //TODO: The following three messages are NOT localisable! + NSMutableString *messageText = [NSMutableString stringWithCapacity:50]; + NSString *messageTitle = NSLocalizedString(@"Unexpected number of rows removed!", @"Table Content : Remove Row : Result : n Error title"); + if (errors < 0) { - message = [NSArray arrayWithObjects:NSLocalizedString(@"Warning", @"warning"), - [NSString stringWithFormat:NSLocalizedString(@"%ld row%@ more %@ deleted! Please check the Console and inform the Sequel Pro team!", @"message of panel when more rows were deleted"), (long)(errors*-1), ((errors*-1)>1)?@"s":@"", (errors>1)?@"were":@"was"], - nil]; + long numErrors = (long)(errors *- 1); + if(numErrors == 1) + [messageText appendString:NSLocalizedString(@"One additional row was removed!",@"Table Content : Remove Row : Result : Too Many : Part 1 : n+1 rows instead of n selected were deleted.")]; + else + [messageText appendFormat:NSLocalizedString(@"%ld additional rows were removed!",@"Table Content : Remove Row : Result : Too Many : Part 1 : n+y (y!=1) rows instead of n selected were deleted."),numErrors]; + + [messageText appendString:NSLocalizedString(@" Please check the Console and inform the Sequel Pro team!",@"Table Content : Remove Row : Result : Too Many : Part 2 : Generic text")]; + } else { + //part 1 number of rows not deleted + if(errors == 1) + [messageText appendString:NSLocalizedString(@"One row was not removed.",@"Table Content : Remove Row : Result : Too Few : Part 1 : Only n-1 of n selected rows were deleted.")]; + else + [messageText appendFormat:NSLocalizedString(@"%ld rows were not removed.",@"Table Content : Remove Row : Result : Too Few : Part 1 : n-x (x!=1) of n selected rows were deleted."),errors]; + //part 2 generic help text + [messageText appendString:NSLocalizedString(@" Reload the table to be sure that the contents have not changed in the meantime.",@"Table Content : Remove Row : Result : Too Few : Part 2 : Generic help message")]; + //part 3 primary keys if (primaryKeyFieldNames == nil) - message = [NSArray arrayWithObjects:NSLocalizedString(@"Warning", @"warning"), - [NSString stringWithFormat:NSLocalizedString(@"%ld row%@ ha%@ not been deleted. Reload the table to be sure that the rows exist and use a primary key for your table.", @"message of panel when not all selected fields have been deleted"), (long)errors, (errors>1)?@"s":@"", (errors>1)?@"ve":@"s"], - nil]; + [messageText appendString:NSLocalizedString(@" You should also add a primary key to this table!",@"Table Content : Remove Row : Result : Too Few : Part 3 : no primary key in table generic message")]; else - message = [NSArray arrayWithObjects:NSLocalizedString(@"Warning", @"warning"), - [NSString stringWithFormat:NSLocalizedString(@"%ld row%@ ha%@ not been deleted. Reload the table to be sure that the rows exist and check the Console for possible errors inside the primary key%@ for your table.", @"message of panel when not all selected fields have been deleted by using primary keys"), (long)errors, (errors>1)?@"s":@"", (errors>1)?@"ve":@"s", (errors>1)?@"s":@""], - nil]; + [messageText appendString:NSLocalizedString(@" Check the Console for possible errors inside the primary key(s) of this table!",@"Table Content : Remove Row : Result : Too Few : Part 3 : Row not deleted when using primary key for DELETE statement.")]; } [self performSelector:@selector(showErrorSheetWith:) - withObject:message + withObject:[NSArray arrayWithObjects:messageTitle,messageText,nil] afterDelay:0.3]; } // Refresh table content - if ( errors || reloadAfterRemovingRow ) { + if (errors || reloadAfterRemovingRow) { previousTableRowsCount = tableRowsCount; [self loadTableValues]; - } else { - for ( i = tableRowsCount - 1 ; i >= 0 ; i-- ) { + } + else { + for ( i = tableRowsCount - 1; i >= 0; i--) + { if ([selectedRows containsIndex:i]) [tableValues removeRowAtIndex:i]; } + tableRowsCount = [tableValues count]; [tableContentView reloadData]; @@ -2109,92 +2220,110 @@ maxNumRows -= affectedRows; [self updateCountText]; } + [tableContentView deselectAll:self]; - } else { - // The user clicked cancel in the "sure you wanna delete" message - // restore editing or whatever - } - + } } } -// Accessors +#pragma mark - +#pragma mark Data accessors /** * Returns the current result (as shown in table content view) as array, the first object containing the field * names as array, the following objects containing the rows as array. */ -- (NSArray *)currentDataResult +- (NSArray *)currentDataResultWithNULLs:(BOOL)includeNULLs { + NSInteger i; NSArray *tableColumns; NSMutableArray *currentResult = [NSMutableArray array]; NSMutableArray *tempRow = [NSMutableArray array]; - NSInteger i; // Load table if not already done - if ( ![tableDocumentInstance contentLoaded] ) { + if (![tableDocumentInstance contentLoaded]) { [self loadTable:[tableDocumentInstance table]]; } tableColumns = [tableContentView tableColumns]; // Set field names as first line - for (NSTableColumn *aTableColumn in tableColumns) { + for (NSTableColumn *aTableColumn in tableColumns) + { [tempRow addObject:[[aTableColumn headerCell] stringValue]]; } + [currentResult addObject:[NSArray arrayWithArray:tempRow]]; // Add rows - for ( i = 0 ; i < [self numberOfRowsInTableView:tableContentView] ; i++) { + for (i = 0; i < [self numberOfRowsInTableView:tableContentView]; i++) + { [tempRow removeAllObjects]; - for (NSTableColumn *aTableColumn in tableColumns) { + + for (NSTableColumn *aTableColumn in tableColumns) + { id o = SPDataStorageObjectAtRowAndColumn(tableValues, i, [[aTableColumn identifier] integerValue]); - if ([o isNSNull]) - [tempRow addObject:@"NULL"]; - else if ([o isSPNotLoaded]) + + if ([o isNSNull]) { + [tempRow addObject:(includeNULLs) ? [NSNull null] : [prefs objectForKey:SPNullValue]]; + } + else if ([o isSPNotLoaded]) { [tempRow addObject:NSLocalizedString(@"(not loaded)", @"value shown for hidden blob and text fields")]; - else if([o isKindOfClass:[NSString class]]) + } + else if([o isKindOfClass:[NSString class]]) { [tempRow addObject:[o description]]; + } else if([o isKindOfClass:[MCPGeometryData class]]) { SPGeometryDataView *v = [[SPGeometryDataView alloc] initWithCoordinates:[o coordinates]]; NSImage *image = [v thumbnailImage]; NSString *imageStr = @""; + if(image) { NSString *maxSizeValue = @"WIDTH"; NSInteger imageWidth = [image size].width; NSInteger imageHeight = [image size].height; + if(imageHeight > imageWidth) { maxSizeValue = @"HEIGHT"; imageWidth = imageHeight; } + if (imageWidth > 100) imageWidth = 100; + imageStr = [NSString stringWithFormat: @"<BR><IMG %@='%ld' SRC=\"data:image/auto;base64,%@\">", maxSizeValue, (long)imageWidth, [[image TIFFRepresentationUsingCompression:NSTIFFCompressionJPEG factor:0.01f] base64EncodingWithLineLength:0]]; } + [v release]; [tempRow addObject:[NSString stringWithFormat:@"%@%@", [o wktString], imageStr]]; } else { NSImage *image = [[NSImage alloc] initWithData:o]; + if (image) { NSInteger imageWidth = [image size].width; + if (imageWidth > 100) imageWidth = 100; [tempRow addObject:[NSString stringWithFormat: @"<IMG WIDTH='%ld' SRC=\"data:image/auto;base64,%@\">", (long)imageWidth, [[image TIFFRepresentationUsingCompression:NSTIFFCompressionJPEG factor:0.01f] base64EncodingWithLineLength:0]]]; - } else { + } + else { [tempRow addObject:@"<BLOB>"]; } + if(image) [image release]; } } + [currentResult addObject:[NSArray arrayWithArray:tempRow]]; } + return currentResult; } @@ -2234,6 +2363,8 @@ return currentResult; } +#pragma mark - + // Additional methods /** @@ -2281,9 +2412,11 @@ return; } +#ifndef SP_REFACTOR // Save existing scroll position and details and mark that state is being modified [spHistoryControllerInstance updateHistoryEntries]; [spHistoryControllerInstance setModifyingState:YES]; +#endif NSString *targetFilterValue = [tableValues cellDataAtRow:[theArrowCell getClickedRow] column:dataColumnIndex]; @@ -2314,16 +2447,20 @@ } } +#ifndef SP_REFACTOR // End state and ensure a new history entry [spHistoryControllerInstance setModifyingState:NO]; [spHistoryControllerInstance updateHistoryEntries]; +#endif // End the task [tableDocumentInstance endTask]; +#ifndef SP_REFACTOR // If the same table is the target, trigger a filter task on the main thread if (tableFilterRequired) [self performSelectorOnMainThread:@selector(filterTable:) withObject:self waitUntilDone:NO]; +#endif // Empty the loading pool and exit the thread [linkPool drain]; @@ -2463,12 +2600,14 @@ i++; } +#ifndef SP_REFACTOR [menu addItem:[NSMenuItem separatorItem]]; NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Edit Filters…", @"edit filter") action:NULL keyEquivalent:@""]; [item setToolTip:NSLocalizedString(@"Edit user-defined Filters…", @"edit user-defined filter")]; [item setTag:i]; [menu addItem:item]; [item release]; +#endif // Attempt to reselect the previously selected title, falling back to the first // item on failure, as long as there is no filter selection to be restored. @@ -2504,7 +2643,6 @@ contextInfo:nil]; } - /** * Tries to write a new row to the table. * Returns YES if row is written to table, otherwise NO; also returns YES if no row @@ -2512,7 +2650,6 @@ */ - (BOOL)saveRowToTable { - // Only handle tables - views should be handled per-cell. if ([tablesListInstance tableType] == SPTableTypeView) return NO; @@ -2532,7 +2669,9 @@ NSUInteger i; NSDictionary *fieldDefinition; id rowObject; - for (i = 0; i < [dataColumns count]; i++) { + + for (i = 0; i < [dataColumns count]; i++) + { rowObject = [tableValues cellDataAtRow:currentlyEditingRow column:i]; fieldDefinition = NSArrayObjectAtIndex(dataColumns, i); @@ -2664,7 +2803,8 @@ [[tableDocumentInstance parentWindow] endEditingFor:nil]; previousTableRowsCount = tableRowsCount; [self loadTableValues]; - } else { + } + else { #endif // Set the insertId for fields with auto_increment for ( i = 0; i < [dataColumns count] ; i++ ) { @@ -2682,7 +2822,7 @@ // Reload table if set to - otherwise no action required. #ifndef SP_REFACTOR - if ( [prefs boolForKey:SPReloadAfterEditingRow] ) { + if ([prefs boolForKey:SPReloadAfterEditingRow]) { [[tableDocumentInstance parentWindow] endEditingFor:nil]; previousTableRowsCount = tableRowsCount; [self loadTableValues]; @@ -2694,7 +2834,8 @@ return YES; // Report errors which have occurred - } else { + } + else { SPBeginAlertSheet(NSLocalizedString(@"Couldn't write row", @"Couldn't write row error"), NSLocalizedString(@"Edit row", @"Edit row button"), NSLocalizedString(@"Discard changes", @"discard changes button"), nil, [tableDocumentInstance parentWindow], self, @selector(addRowErrorSheetDidEnd:returnCode:contextInfo:), nil, [NSString stringWithFormat:NSLocalizedString(@"MySQL said:\n\n%@", @"message of panel when error while adding row to db"), [mySQLConnection getLastErrorMessage]]); return NO; @@ -2729,8 +2870,7 @@ */ - (BOOL)saveRowOnDeselect { - - if([tablesListInstance tableType] == SPTableTypeView) { + if ([tablesListInstance tableType] == SPTableTypeView) { isSavingRow = NO; return YES; } @@ -2743,6 +2883,7 @@ // If no rows are currently being edited, or a save is in progress, return success at once. if (!isEditingRow || isSavingRow) return YES; + isSavingRow = YES; // Attempt to save the row, and return YES if the save succeeded. @@ -2753,6 +2894,7 @@ // Saving failed - return failure. isSavingRow = NO; + return NO; } @@ -2949,17 +3091,11 @@ * -2 for other errors * and the used WHERE clause to identify */ -- (NSArray*)fieldEditStatusForRow:(NSInteger)rowIndex andColumn:(NSNumber *)columnIndex +- (NSArray*)fieldEditStatusForRow:(NSInteger)rowIndex andColumn:(NSInteger)columnIndex { - NSDictionary *columnDefinition = nil; // Retrieve the column defintion - for(id c in cqColumnDefinition) { - if([[c objectForKey:@"datacolumnindex"] isEqualToNumber:columnIndex]) { - columnDefinition = [NSDictionary dictionaryWithDictionary:c]; - break; - } - } + NSDictionary *columnDefinition = [NSDictionary dictionaryWithDictionary:[cqColumnDefinition objectAtIndex:[[[[tableContentView tableColumns] objectAtIndex:columnIndex] identifier] integerValue]]]; if(!columnDefinition) return [NSArray arrayWithObjects:[NSNumber numberWithInteger:-2], @"", nil]; @@ -3037,6 +3173,7 @@ */ - (void)sheetDidEnd:(id)sheet returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo { +#ifndef SP_REFACTOR [sheet orderOut:self]; if([contextInfo isEqualToString:@"setdefaultoperator"]) { @@ -3044,18 +3181,15 @@ if(filterTableDefaultOperator) [filterTableDefaultOperator release]; NSString *newOperator = [filterTableSetDefaultOperatorValue stringValue]; filterTableDefaultOperator = [[self escapeFilterTableDefaultOperator:newOperator] retain]; -#ifndef SP_REFACTOR [prefs setObject:newOperator forKey:SPFilterTableDefaultOperator]; -#endif + if(![newOperator isMatchedByRegex:@"(?i)like\\s+['\"]%@%['\"]\\s*"]) { -#ifndef SP_REFACTOR if(![prefs objectForKey:SPFilterTableDefaultOperatorLastItems]) [prefs setObject:[NSMutableArray array] forKey:SPFilterTableDefaultOperatorLastItems]; -#endif + NSMutableArray *lastItems = [NSMutableArray array]; -#ifndef SP_REFACTOR [lastItems setArray:[prefs objectForKey:SPFilterTableDefaultOperatorLastItems]]; -#endif + if([lastItems containsObject:newOperator]) [lastItems removeObject:newOperator]; if([lastItems count] > 0) @@ -3066,13 +3200,13 @@ if([lastItems count] > 15) while([lastItems count] > 15) [filterTableSetDefaultOperatorValue removeItemAtIndex:[lastItems count]-1]; -#ifndef SP_REFACTOR + [prefs setObject:lastItems forKey:SPFilterTableDefaultOperatorLastItems]; -#endif } [self updateFilterTableClause:nil]; } } +#endif } /** @@ -3092,15 +3226,14 @@ NSInteger row = -1; NSInteger column = -1; - NSInteger editedColumn = -1; if(contextInfo) { - row = [[contextInfo objectForKey:@"row"] integerValue]; - column = [[contextInfo objectForKey:@"column"] integerValue]; - editedColumn = [[contextInfo objectForKey:@"editedColumn"] integerValue]; + row = [[contextInfo objectForKey:@"rowIndex"] integerValue]; + column = [[contextInfo objectForKey:@"columnIndex"] integerValue]; } if (data && contextInfo) { + NSTableColumn *theTableColumn = [[tableContentView tableColumns] objectAtIndex:column]; BOOL isFieldEditable = ([contextInfo objectForKey:@"isFieldEditable"]) ? YES : NO; if (!isEditingRow && [tablesListInstance tableType] != SPTableTypeView) { [oldRow setArray:[tableValues rowContentsAtIndex:row]]; @@ -3109,18 +3242,22 @@ } if ([data isKindOfClass:[NSString class]] - && [data isEqualToString:[prefs objectForKey:SPNullValue]] && [[NSArrayObjectAtIndex(dataColumns, column) objectForKey:@"null"] boolValue]) + && [data isEqualToString:[prefs objectForKey:SPNullValue]] && [[NSArrayObjectAtIndex(dataColumns, [[theTableColumn identifier] integerValue]) objectForKey:@"null"] boolValue]) { data = [[NSNull null] retain]; } if(isFieldEditable) { - if([tablesListInstance tableType] == SPTableTypeView) { + if ([tablesListInstance tableType] == SPTableTypeView) { + // since in a view we're editing a field rather than a row isEditingRow = NO; + // update the field and refresh the table - [self tableView:tableContentView setObjectValue:[[data copy] autorelease] forTableColumn:[tableContentView tableColumnWithIdentifier:[contextInfo objectForKey:@"column"]] row:row]; + [self saveViewCellValue:[[data copy] autorelease] forTableColumn:theTableColumn row:row]; + + // Otherwise, in tables, save back to the row store } else { - [tableValues replaceObjectInRow:row column:column withObject:[[data copy] autorelease]]; + [tableValues replaceObjectInRow:row column:[[theTableColumn identifier] integerValue] withObject:[[data copy] autorelease]]; } } } @@ -3132,8 +3269,130 @@ [[tableContentView window] makeFirstResponder:tableContentView]; - if(row > -1 && editedColumn > -1) - [tableContentView editColumn:editedColumn row:row withEvent:nil select:YES]; + if(row > -1 && column > -1) + [tableContentView editColumn:column row:row withEvent:nil select:YES]; +} + +- (void)saveViewCellValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSUInteger)rowIndex +{ + + // Field editing + NSDictionary *columnDefinition = [cqColumnDefinition objectAtIndex:[[aTableColumn identifier] integerValue]]; + + // Resolve the original table name for current column if AS was used + NSString *tableForColumn = [columnDefinition objectForKey:@"org_table"]; + + if (!tableForColumn || ![tableForColumn length]) { + NSPoint pos = [NSEvent mouseLocation]; + pos.y -= 20; + [SPTooltip showWithObject:NSLocalizedString(@"Field is not editable. Field has no or multiple table or database origin(s).",@"field is not editable due to no table/database") + atLocation:pos + ofType:@"text"]; + NSBeep(); + return; + } + + // Resolve the original column name if AS was used + NSString *columnName = [columnDefinition objectForKey:@"org_name"]; + + [tableDocumentInstance startTaskWithDescription:NSLocalizedString(@"Updating field data...", @"updating field task description")]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:tableDocumentInstance]; + + [self storeCurrentDetailsForRestoration]; + + // Check if the IDstring identifies the current field bijectively and get the WHERE clause + NSArray *editStatus = [self fieldEditStatusForRow:rowIndex andColumn:[[aTableColumn identifier] integerValue]]; + NSString *fieldIDQueryStr = [editStatus objectAtIndex:1]; + NSInteger numberOfPossibleUpdateRows = [[editStatus objectAtIndex:0] integerValue]; + + if(numberOfPossibleUpdateRows == 1) { + + NSString *newObject = nil; + if ( [anObject isKindOfClass:[NSCalendarDate class]] ) { + newObject = [NSString stringWithFormat:@"'%@'", [mySQLConnection prepareString:[anObject description]]]; + } else if ( [anObject isKindOfClass:[NSNumber class]] ) { + newObject = [anObject stringValue]; + } else if ( [anObject isKindOfClass:[NSData class]] ) { + newObject = [NSString stringWithFormat:@"X'%@'", [mySQLConnection prepareBinaryData:anObject]]; + } else { + if ( [[anObject description] isEqualToString:@"CURRENT_TIMESTAMP"] ) { + newObject = @"CURRENT_TIMESTAMP"; + } else if([anObject isEqualToString:[prefs stringForKey:SPNullValue]]) { + newObject = @"NULL"; + } else if ([[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"geometry"]) { + newObject = [(NSString*)anObject getGeomFromTextString]; + } else if ([[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"bit"]) { + newObject = [NSString stringWithFormat:@"b'%@'", ((![[anObject description] length] || [[anObject description] isEqualToString:@"0"]) ? @"0" : [anObject description])]; + } else if ([[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"date"] + && [[anObject description] isEqualToString:@"NOW()"]) { + newObject = @"NOW()"; + } else { + newObject = [NSString stringWithFormat:@"'%@'", [mySQLConnection prepareString:[anObject description]]]; + } + } + + [mySQLConnection queryString: + [NSString stringWithFormat:@"UPDATE %@.%@ SET %@.%@.%@ = %@ %@ LIMIT 1", + [[columnDefinition objectForKey:@"db"] backtickQuotedString], [tableForColumn backtickQuotedString], + [[columnDefinition objectForKey:@"db"] backtickQuotedString], [tableForColumn backtickQuotedString], [columnName backtickQuotedString], newObject, fieldIDQueryStr]]; + + + // Check for errors while UPDATE + if ([mySQLConnection queryErrored]) { + SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), NSLocalizedString(@"Cancel", @"cancel button"), nil, [tableDocumentInstance parentWindow], self, nil, nil, + [NSString stringWithFormat:NSLocalizedString(@"Couldn't write field.\nMySQL said: %@", @"message of panel when error while updating field to db"), [mySQLConnection getLastErrorMessage]]); + + [tableDocumentInstance endTask]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; + return; + } + + + // This shouldn't happen – for safety reasons + if ( ![mySQLConnection affectedRows] ) { +#ifndef SP_REFACTOR + if ( [prefs boolForKey:SPShowNoAffectedRowsError] ) { + SPBeginAlertSheet(NSLocalizedString(@"Warning", @"warning"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, + NSLocalizedString(@"The row was not written to the MySQL database. You probably haven't changed anything.\nReload the table to be sure that the row exists and use a primary key for your table.\n(This error can be turned off in the preferences.)", @"message of panel when no rows have been affected after writing to the db")); + } else { + NSBeep(); + } +#endif + [tableDocumentInstance endTask]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; + return; + } + + } else { + SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, + [NSString stringWithFormat:NSLocalizedString(@"Updating field content failed. Couldn't identify field origin unambiguously (%ld match%@). It's very likely that while editing this field the table `%@` was changed by an other user.", @"message of panel when error while updating field to db after enabling it"), + (long)numberOfPossibleUpdateRows, (numberOfPossibleUpdateRows>1)?@"es":@"", tableForColumn]); + + [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; + [tableDocumentInstance endTask]; + return; + + } + + // Reload table after each editing due to complex declarations + if (isFirstChangeInView) { + + // Set up the table details for the new table, and trigger an interface update + // if the view was modified for the very first time + NSDictionary *tableDetails = [NSDictionary dictionaryWithObjectsAndKeys: + selectedTable, @"name", + [tableDataInstance columns], @"columns", + [tableDataInstance columnNames], @"columnNames", + [tableDataInstance getConstraints], @"constraints", + nil]; + [self performSelectorOnMainThread:@selector(setTableDetails:) withObject:tableDetails waitUntilDone:YES]; + isFirstChangeInView = NO; + } + + [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; + [tableDocumentInstance endTask]; + + [self loadTableValues]; } #pragma mark - @@ -3144,6 +3403,7 @@ */ - (IBAction)tableFilterClear:(id)sender { +#ifndef SP_REFACTOR [filterTableView abortEditing]; @@ -3162,6 +3422,7 @@ } +#endif } /** @@ -3169,19 +3430,19 @@ */ - (IBAction)showFilterTable:(id)sender { +#ifndef SP_REFACTOR [filterTableWindow makeKeyAndOrderFront:nil]; [filterTableWhereClause setContinuousSpellCheckingEnabled:NO]; [filterTableWhereClause setAutoindent:NO]; [filterTableWhereClause setAutoindentIgnoresEnter:NO]; -#ifndef SP_REFACTOR [filterTableWhereClause setAutopair:[prefs boolForKey:SPCustomQueryAutoPairCharacters]]; [filterTableWhereClause setAutohelp:NO]; [filterTableWhereClause setAutouppercaseKeywords:[prefs boolForKey:SPCustomQueryAutoUppercaseKeywords]]; -#endif [filterTableWhereClause setCompletionWasReinvokedAutomatically:NO]; [filterTableWhereClause insertText:@""]; [filterTableWhereClause didChangeText]; [[filterTableView window] makeFirstResponder:filterTableView]; +#endif } /** @@ -3189,11 +3450,13 @@ */ - (IBAction)toggleNegateClause:(id)sender { +#ifndef SP_REFACTOR filterTableNegate = !filterTableNegate; // If live search is set perform filtering if([filterTableLiveSearchCheckbox state] == NSOnState) [self filterTable:filterTableFilterButton]; +#endif } @@ -3202,6 +3465,7 @@ */ - (IBAction)toggleDistinctSelect:(id)sender { +#ifndef SP_REFACTOR filterTableDistinct = !filterTableDistinct; [filterTableDistinctMenuItem setState:(filterTableDistinct) ? NSOnState : NSOffState]; @@ -3209,6 +3473,7 @@ // If live search is set perform filtering if([filterTableLiveSearchCheckbox state] == NSOnState) [self filterTable:filterTableFilterButton]; +#endif } @@ -3217,11 +3482,11 @@ */ - (IBAction)setDefaultOperator:(id)sender { +#ifndef SP_REFACTOR [filterTableWindow makeFirstResponder:filterTableView]; // Load history -#ifndef SP_REFACTOR if([prefs objectForKey:SPFilterTableDefaultOperatorLastItems]) { NSMutableArray *lastItems = [NSMutableArray array]; NSString *defaultItem = @"LIKE '%@%'"; @@ -3234,13 +3499,13 @@ } [filterTableSetDefaultOperatorValue setStringValue:[prefs objectForKey:SPFilterTableDefaultOperator]]; -#endif [NSApp beginSheet:filterTableSetDefaultOperatorSheet modalForWindow:filterTableWindow modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:@"setdefaultoperator"]; +#endif } @@ -3256,9 +3521,11 @@ { [self updateFilterTableClause:sender]; +#ifndef SP_REFACTOR // If live search is set perform filtering if([filterTableLiveSearchCheckbox state] == NSOnState) [self filterTable:filterTableFilterButton]; +#endif } @@ -3271,6 +3538,14 @@ [[sender window] orderOut:self]; } +/** + * Opens the content filter help page in the default browser. + */ +- (IBAction)showDefaultOperaterHelp:(id)sender +{ + [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:SPLOCALIZEDURL_CONTENTFILTERHELP]]; +} + #pragma mark - #pragma mark Retrieving and setting table state @@ -3426,8 +3701,13 @@ if ([filterSettings objectForKey:@"secondBetweenField"]) secondBetweenValueToRestore = [[NSString alloc] initWithString:[filterSettings objectForKey:@"secondBetweenField"]]; } else { - if ([filterSettings objectForKey:@"filterValue"] && ![[filterSettings objectForKey:@"filterValue"] isNSNull]) - filterValueToRestore = [[NSString alloc] initWithString:[filterSettings objectForKey:@"filterValue"]]; + if ([filterSettings objectForKey:@"filterValue"] && ![[filterSettings objectForKey:@"filterValue"] isNSNull]) { + if ([[filterSettings objectForKey:@"filterValue"] isKindOfClass:[NSData class]]) { + filterValueToRestore = [[NSString alloc] initWithData:[filterSettings objectForKey:@"filterValue"] encoding:[mySQLConnection stringEncoding]]; + } else { + filterValueToRestore = [[NSString alloc] initWithString:[filterSettings objectForKey:@"filterValue"]]; + } + } } } } @@ -3458,21 +3738,27 @@ - (void) setFilterTableData:(NSData*)arcData { +#ifndef SP_REFACTOR if(!arcData) return; NSDictionary *filterData = [NSUnarchiver unarchiveObjectWithData:arcData]; [filterTableData removeAllObjects]; [filterTableData addEntriesFromDictionary:filterData]; [filterTableWindow makeKeyAndOrderFront:nil]; // [filterTableView reloadData]; +#endif } - (NSData*) filterTableData { +#ifndef SP_REFACTOR if(![filterTableWindow isVisible]) return nil; [filterTableView deselectAll:nil]; return [NSArchiver archivedDataWithRootObject:filterTableData]; +#else + return nil; +#endif } #pragma mark - @@ -3496,8 +3782,8 @@ maxNumRowsIsEstimate = NO; [tableDataInstance setStatusValue:[NSString stringWithFormat:@"%ld", (long)maxNumRows] forKey:@"Rows"]; [tableDataInstance setStatusValue:@"y" forKey:@"RowsCountAccurate"]; - [[tableInfoInstance onMainThread] tableChanged:nil]; #ifndef SP_REFACTOR + [[tableInfoInstance onMainThread] tableChanged:nil]; [[[tableDocumentInstance valueForKey:@"extendedTableInfoInstance"] onMainThread] loadTable:selectedTable]; #endif @@ -3530,7 +3816,9 @@ maxNumRowsIsEstimate = NO; [tableDataInstance setStatusValue:[NSString stringWithFormat:@"%ld", (long)maxNumRows] forKey:@"Rows"]; [tableDataInstance setStatusValue:@"y" forKey:@"RowsCountAccurate"]; +#ifndef SP_REFACTOR [[tableInfoInstance onMainThread] tableChanged:nil]; +#endif [[[tableDocumentInstance valueForKey:@"extendedTableInfoInstance"] onMainThread] loadTable:selectedTable]; // Use the estimate count @@ -3565,7 +3853,9 @@ } [tableDataInstance setStatusValue:[NSString stringWithFormat:@"%ld", (long)maxNumRows] forKey:@"Rows"]; [tableDataInstance setStatusValue:maxNumRowsIsEstimate?@"n":@"y" forKey:@"RowsCountAccurate"]; +#ifndef SP_REFACTOR [[tableInfoInstance onMainThread] tableChanged:nil]; +#endif } } @@ -3596,7 +3886,7 @@ // Otherwise set the column width NSTableColumn *aTableColumn = [tableContentView tableColumnWithIdentifier:[columnDefinition objectForKey:@"datacolumnindex"]]; - NSUInteger targetWidth = [[columnWidths objectForKey:[columnDefinition objectForKey:@"datacolumnindex"]] unsignedIntegerValue]; + NSInteger targetWidth = [[columnWidths objectForKey:[columnDefinition objectForKey:@"datacolumnindex"]] integerValue]; [aTableColumn setWidth:targetWidth]; } [tableContentView setDelegate:self]; @@ -3691,13 +3981,16 @@ - (NSInteger)numberOfRowsInTableView:(SPCopyTable *)aTableView { +#ifndef SP_REFACTOR if (aTableView == filterTableView) { if (filterTableIsSwapped) return [filterTableData count]; else return [[[filterTableData objectForKey:[NSNumber numberWithInteger:0]] objectForKey:@"filter"] count]; } - else if (aTableView == tableContentView) { + else +#endif + if (aTableView == tableContentView) { return tableRowsCount; } @@ -3706,6 +3999,7 @@ - (id)tableView:(SPCopyTable *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex { +#ifndef SP_REFACTOR if (aTableView == filterTableView) { if (filterTableIsSwapped) // First column shows the field names @@ -3718,7 +4012,9 @@ return NSArrayObjectAtIndex([[filterTableData objectForKey:[aTableColumn identifier]] objectForKey:@"filter"], rowIndex); } } - else if (aTableView == tableContentView) { + else +#endif + if (aTableView == tableContentView) { NSUInteger columnIndex = [[aTableColumn identifier] integerValue]; id theValue = nil; @@ -3762,7 +4058,7 @@ */ - (void)tableView:(SPCopyTable *)aTableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn*)aTableColumn row:(NSInteger)rowIndex { - +#ifndef SP_REFACTOR if(aTableView == filterTableView) { if(filterTableIsSwapped && [[aTableColumn identifier] integerValue] == 0) { [cell setDrawsBackground:YES]; @@ -3772,7 +4068,9 @@ } return; } - else if(aTableView == tableContentView) { + else +#endif + if(aTableView == tableContentView) { if (![cell respondsToSelector:@selector(setTextColor:)]) return; @@ -3801,7 +4099,7 @@ // writing in gray if value was NULL if ([aTableView editedColumn] != -1 && [aTableView editedRow] == rowIndex - && [[NSArrayObjectAtIndex([aTableView tableColumns], [aTableView editedColumn]) identifier] unsignedIntegerValue] == columnIndex) { + && (NSUInteger)[[NSArrayObjectAtIndex([aTableView tableColumns], [aTableView editedColumn]) identifier] integerValue] == columnIndex) { [cell setTextColor:blackColor]; return; } @@ -3819,6 +4117,7 @@ - (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex { +#ifndef SP_REFACTOR if(aTableView == filterTableView) { if(filterTableIsSwapped) [[[filterTableData objectForKey:[NSNumber numberWithInteger:rowIndex]] objectForKey:@"filter"] replaceObjectAtIndex:([[aTableColumn identifier] integerValue]-1) withObject:(NSString*)anObject]; @@ -3827,137 +4126,20 @@ [self updateFilterTableClause:nil]; return; } - else if(aTableView == tableContentView) { - // If table data come from a view - if([tablesListInstance tableType] == SPTableTypeView) { - - // Field editing - // if (fieldIDQueryString == nil) return; - NSDictionary *columnDefinition; - - // Retrieve the column defintion - for(id c in cqColumnDefinition) { - if([[c objectForKey:@"datacolumnindex"] isEqualToNumber:[aTableColumn identifier]]) { - columnDefinition = [NSDictionary dictionaryWithDictionary:c]; - break; - } - } - - // Resolve the original table name for current column if AS was used - NSString *tableForColumn = [columnDefinition objectForKey:@"org_table"]; - - if(!tableForColumn || ![tableForColumn length]) { - NSPoint pos = [NSEvent mouseLocation]; - pos.y -= 20; - [SPTooltip showWithObject:NSLocalizedString(@"Field is not editable. Field has no or multiple table or database origin(s).",@"field is not editable due to no table/database") - atLocation:pos - ofType:@"text"]; - NSBeep(); - return; - } - - // Resolve the original column name if AS was used - NSString *columnName = [columnDefinition objectForKey:@"org_name"]; - - [tableDocumentInstance startTaskWithDescription:NSLocalizedString(@"Updating field data...", @"updating field task description")]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:tableDocumentInstance]; - - [self storeCurrentDetailsForRestoration]; - - // Check if the IDstring identifies the current field bijectively and get the WHERE clause - NSArray *editStatus = [self fieldEditStatusForRow:rowIndex andColumn:[aTableColumn identifier]]; - NSString *fieldIDQueryStr = [editStatus objectAtIndex:1]; - NSInteger numberOfPossibleUpdateRows = [[editStatus objectAtIndex:0] integerValue]; - - if(numberOfPossibleUpdateRows == 1) { - - NSString *newObject = nil; - if ( [anObject isKindOfClass:[NSCalendarDate class]] ) { - newObject = [NSString stringWithFormat:@"'%@'", [mySQLConnection prepareString:[anObject description]]]; - } else if ( [anObject isKindOfClass:[NSNumber class]] ) { - newObject = [anObject stringValue]; - } else if ( [anObject isKindOfClass:[NSData class]] ) { - newObject = [NSString stringWithFormat:@"X'%@'", [mySQLConnection prepareBinaryData:anObject]]; - } else { - if ( [[anObject description] isEqualToString:@"CURRENT_TIMESTAMP"] ) { - newObject = @"CURRENT_TIMESTAMP"; - } else if([anObject isEqualToString:[prefs stringForKey:SPNullValue]]) { - newObject = @"NULL"; - } else if ([[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"geometry"]) { - newObject = [(NSString*)anObject getGeomFromTextString]; - } else if ([[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"bit"]) { - newObject = [NSString stringWithFormat:@"b'%@'", ((![[anObject description] length] || [[anObject description] isEqualToString:@"0"]) ? @"0" : [anObject description])]; - } else if ([[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"date"] - && [[anObject description] isEqualToString:@"NOW()"]) { - newObject = @"NOW()"; - } else { - newObject = [NSString stringWithFormat:@"'%@'", [mySQLConnection prepareString:[anObject description]]]; - } - } - - [mySQLConnection queryString: - [NSString stringWithFormat:@"UPDATE %@.%@ SET %@.%@.%@ = %@ %@ LIMIT 1", - [[columnDefinition objectForKey:@"db"] backtickQuotedString], [tableForColumn backtickQuotedString], - [[columnDefinition objectForKey:@"db"] backtickQuotedString], [tableForColumn backtickQuotedString], [columnName backtickQuotedString], newObject, fieldIDQueryStr]]; - - - // Check for errors while UPDATE - if ([mySQLConnection queryErrored]) { - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), NSLocalizedString(@"Cancel", @"cancel button"), nil, [tableDocumentInstance parentWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"Couldn't write field.\nMySQL said: %@", @"message of panel when error while updating field to db"), [mySQLConnection getLastErrorMessage]]); - - [tableDocumentInstance endTask]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; - return; - } - - - // This shouldn't happen – for safety reasons - if ( ![mySQLConnection affectedRows] ) { -#ifndef SP_REFACTOR - if ( [prefs boolForKey:SPShowNoAffectedRowsError] ) { - SPBeginAlertSheet(NSLocalizedString(@"Warning", @"warning"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - NSLocalizedString(@"The row was not written to the MySQL database. You probably haven't changed anything.\nReload the table to be sure that the row exists and use a primary key for your table.\n(This error can be turned off in the preferences.)", @"message of panel when no rows have been affected after writing to the db")); - } else { - NSBeep(); - } + else #endif - [tableDocumentInstance endTask]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; - return; - } - - } else { - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"Updating field content failed. Couldn't identify field origin unambiguously (%ld match%@). It's very likely that while editing this field the table `%@` was changed by an other user.", @"message of panel when error while updating field to db after enabling it"), - (long)numberOfPossibleUpdateRows, (numberOfPossibleUpdateRows>1)?@"es":@"", tableForColumn]); - - [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; - [tableDocumentInstance endTask]; - return; - - } - - // Reload table after each editing due to complex declarations - if(isFirstChangeInView) { - // Set up the table details for the new table, and trigger an interface update - // if the view was modified for the very first time - NSDictionary *tableDetails = [NSDictionary dictionaryWithObjectsAndKeys: - selectedTable, @"name", - [tableDataInstance columns], @"columns", - [tableDataInstance columnNames], @"columnNames", - [tableDataInstance getConstraints], @"constraints", - nil]; - [self performSelectorOnMainThread:@selector(setTableDetails:) withObject:tableDetails waitUntilDone:YES]; - isFirstChangeInView = NO; - } - [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; - [tableDocumentInstance endTask]; - - [self loadTableValues]; + if(aTableView == tableContentView) { + // If the current cell should have been edited in a sheet, do nothing - field closing will have already + // updated the field. + if ([tableContentView shouldUseFieldEditorForRow:rowIndex column:[[aTableColumn identifier] integerValue]]) { return; + } + // If table data comes from a view, save back to the view + if([tablesListInstance tableType] == SPTableTypeView) { + [self saveViewCellValue:anObject forTableColumn:aTableColumn row:rowIndex]; + return; } // Catch editing events in the row and if the row isn't currently being edited, @@ -4023,7 +4205,7 @@ // Sets column order as tri-state descending, ascending, no sort, descending, ascending etc. order if the same // header is clicked several times - if ([[tableColumn identifier] isEqualTo:sortCol]) { + if (sortCol && [[tableColumn identifier] integerValue] == [sortCol integerValue]) { if(isDesc) { [sortCol release]; sortCol = nil; @@ -4034,12 +4216,12 @@ } } else { isDesc = NO; - [[tableContentView onMainThread] setIndicatorImage:nil inTableColumn:[tableContentView tableColumnWithIdentifier:sortCol]]; + [[tableContentView onMainThread] setIndicatorImage:nil inTableColumn:[tableContentView tableColumnWithIdentifier:[NSString stringWithFormat:@"%lld", (long long)[sortCol integerValue]]]]; if (sortCol) [sortCol release]; sortCol = [[NSNumber alloc] initWithInteger:[[tableColumn identifier] integerValue]]; } - if(sortCol) { + if (sortCol) { // Set the highlight and indicatorImage [[tableContentView onMainThread] setHighlightedTableColumn:tableColumn]; if (isDesc) { @@ -4078,7 +4260,6 @@ isFirstChangeInView = YES; - [addButton setEnabled:([tablesListInstance tableType] == SPTableTypeTable)]; // If we are editing a row, attempt to save that row - if saving failed, reselect the edit row. @@ -4200,13 +4381,16 @@ { if ([tableDocumentInstance isWorking]) return NO; +#ifndef SP_REFACTOR if(aTableView == filterTableView) { if(filterTableIsSwapped && [[aTableColumn identifier] integerValue] == 0) return NO; else return YES; } - else if ( aTableView == tableContentView ) { + else +#endif + if ( aTableView == tableContentView ) { // Ensure that row is editable since it could contain "(not loaded)" columns together with // issue that the table has no primary key @@ -4231,27 +4415,20 @@ [tableContentView reloadData]; } - BOOL isBlob = [tableDataInstance columnIsBlobOrText:[[aTableColumn headerCell] stringValue]]; - BOOL isFieldEditable = YES; - - // Retrieve the column defintion - NSDictionary *columnDefinition = nil; - for(id c in cqColumnDefinition) { - if([[c objectForKey:@"datacolumnindex"] isEqualToNumber:[aTableColumn identifier]]) { - columnDefinition = [NSDictionary dictionaryWithDictionary:c]; - break; - } - } + // Open the editing sheet if required + if ([tableContentView shouldUseFieldEditorForRow:rowIndex column:[[aTableColumn identifier] integerValue]]) + { - // Open the sheet if the multipleLineEditingButton is enabled or the column was a blob or a text. - if (([multipleLineEditingButton state] == NSOnState || isBlob) && ![[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"enum"]) { + // Retrieve the column definition + NSDictionary *columnDefinition = [cqColumnDefinition objectAtIndex:[[aTableColumn identifier] integerValue]]; + BOOL isBlob = [tableDataInstance columnIsBlobOrText:[[aTableColumn headerCell] stringValue]]; - // A table is per definitionem editable - isFieldEditable = YES; + // A table is per definition editable + BOOL isFieldEditable = YES; // Check for Views if field is editable if([tablesListInstance tableType] == SPTableTypeView) { - NSArray *editStatus = [self fieldEditStatusForRow:rowIndex andColumn:[aTableColumn identifier]]; + NSArray *editStatus = [self fieldEditStatusForRow:rowIndex andColumn:[[aTableColumn identifier] integerValue]]; isFieldEditable = ([[editStatus objectAtIndex:0] integerValue] == 1) ? YES : NO; } @@ -4286,7 +4463,7 @@ NSInteger editedColumn = 0; for(NSTableColumn* col in [tableContentView tableColumns]) { - if([[col identifier] isEqualToNumber:[aTableColumn identifier]]) break; + if([[col identifier] isEqualToString:[aTableColumn identifier]]) break; editedColumn++; } @@ -4298,9 +4475,8 @@ withWindow:[tableDocumentInstance parentWindow] sender:self contextInfo:[NSDictionary dictionaryWithObjectsAndKeys: - [NSNumber numberWithInteger:rowIndex], @"row", - [aTableColumn identifier], @"column", - [NSNumber numberWithInteger:editedColumn], @"editedColumn", + [NSNumber numberWithInteger:rowIndex], @"rowIndex", + [NSNumber numberWithInteger:editedColumn], @"columnIndex", [NSNumber numberWithBool:isFieldEditable], @"isFieldEditable", nil]]; @@ -4348,10 +4524,13 @@ */ - (BOOL)tableView:(NSTableView *)aTableView shouldSelectRow:(NSInteger)rowIndex { +#ifndef SP_REFACTOR if(aTableView == filterTableView) return YES; - else if(aTableView == tableContentView) + else +#endif + if(aTableView == tableContentView) return tableRowsSelectable; else return YES; @@ -4472,7 +4651,9 @@ tableRowsSelectable = NO; [paginationPreviousButton setEnabled:NO]; [paginationNextButton setEnabled:NO]; +#ifndef SP_REFACTOR [paginationButton setEnabled:NO]; +#endif } /** @@ -4493,12 +4674,14 @@ [self updatePaginationState]; [reloadButton setEnabled:YES]; } + if ([tableContentView numberOfSelectedRows] > 0) { if([tablesListInstance tableType] == SPTableTypeTable) { [removeButton setEnabled:YES]; [copyButton setEnabled:YES]; } } + [filterButton setEnabled:[fieldField isEnabled]]; tableRowsSelectable = YES; } @@ -4508,6 +4691,7 @@ - (void)controlTextDidChange:(NSNotification *)notification { +#ifndef SP_REFACTOR if ([notification object] == filterTableView) { NSString *str = [[[[notification userInfo] objectForKey:@"NSFieldEditor"] textStorage] string]; @@ -4518,6 +4702,7 @@ [self updateFilterTableClause:str]; } +#endif } /** * If user selected a table cell which is a blob field and tried to edit it @@ -4539,7 +4724,7 @@ // from the keyboard show an error tooltip // or bypass if numberOfPossibleUpdateRows == 1 if([tableContentView isCellEditingMode]) { - NSArray *editStatus = [self fieldEditStatusForRow:row andColumn:[NSArrayObjectAtIndex([tableContentView tableColumns], column) identifier]]; + NSArray *editStatus = [self fieldEditStatusForRow:row andColumn:[[NSArrayObjectAtIndex([tableContentView tableColumns], column) identifier] integerValue]]; NSInteger numberOfPossibleUpdateRows = [[editStatus objectAtIndex:0] integerValue]; NSPoint pos = [[tableDocumentInstance parentWindow] convertBaseToScreen:[tableContentView convertPoint:[tableContentView frameOfCellAtColumn:column row:row].origin toView:nil]]; pos.y -= 20; @@ -4570,11 +4755,8 @@ } - NSString *fieldType; - - // Check if current edited field is a blob - if ((fieldType = [[tableDataInstance columnWithName:[[NSArrayObjectAtIndex([tableContentView tableColumns], column) headerCell] stringValue]] objectForKey:@"typegrouping"]) - && ![fieldType isEqualToString:@"enum"] && ([fieldType isEqualToString:@"textdata"] || [fieldType isEqualToString:@"blobdata"] || [multipleLineEditingButton state] == NSOnState)) + // Open the field editor sheet if required + if ([tableContentView shouldUseFieldEditorForRow:row column:column]) { [tableContentView setFieldEditorSelectedRange:[aFieldEditor selectedRange]]; @@ -4696,6 +4878,7 @@ */ - (void)updateFilterTableClause:(id)currentValue { +#ifndef SP_REFACTOR NSMutableString *clause = [NSMutableString string]; NSInteger numberOfRows = [self numberOfRowsInTableView:filterTableView]; NSInteger numberOfCols = [[filterTableView tableColumns] count]; @@ -4833,6 +5016,7 @@ // If live search is set perform filtering if([filterTableLiveSearchCheckbox state] == NSOnState) [self filterTable:filterTableFilterButton]; +#endif } /** @@ -4868,17 +5052,6 @@ return [tableContentView fieldEditorSelectedRange]; } -#ifdef SP_REFACTOR /* glue */ -- (void)setDatabaseDocument:(SPDatabaseDocument*)doc -{ - tableDocumentInstance = doc; -} - -- (void)setTableListInstance:(SPTablesList*)list -{ - tablesListInstance = list; -} -#endif #pragma mark - @@ -4901,9 +5074,11 @@ pthread_mutex_destroy(&tableValuesLock); [dataColumns release]; [oldRow release]; +#ifndef SP_REFACTOR [filterTableData release]; if (lastEditedFilterTableValue) [lastEditedFilterTableValue release]; if (filterTableDefaultOperator) [filterTableDefaultOperator release]; +#endif if (selectedTable) [selectedTable release]; if (contentFilters) [contentFilters release]; if (numberOfDefaultFilters) [numberOfDefaultFilters release]; diff --git a/Source/SPTableData.m b/Source/SPTableData.m index 1936d0b1..297718b9 100644 --- a/Source/SPTableData.m +++ b/Source/SPTableData.m @@ -76,7 +76,6 @@ [mySQLConnection retain]; } - /** * Retrieve the encoding for the current table, using or refreshing the cache as appropriate. */ @@ -120,7 +119,6 @@ return [NSString stringWithString:tableCreateSyntax]; } - /** * Retrieve all columns for the current table as an array, using or refreshing the cache as appropriate. */ @@ -191,7 +189,6 @@ return [columns objectAtIndex:columnIndex]; } - /** * Retrieve column names for the current table as an array, using or refreshing the cache as appropriate. */ @@ -210,7 +207,6 @@ return columnNames; } - /** * Retrieve a NSDictionary containing all parameters of the column with a specific index, using or refreshing the cache as appropriate. * @@ -237,7 +233,6 @@ * * @param colName The column name which should be checked. */ - - (BOOL) columnIsBlobOrText:(NSString *)colName { // If processing is already in action, wait for it to complete @@ -260,7 +255,6 @@ * * @param colName The column name which should be checked. */ - - (BOOL) columnIsGeometry:(NSString *)colName { // If processing is already in action, wait for it to complete @@ -277,7 +271,6 @@ return (BOOL) ([[[self columnWithName:colName] objectForKey:@"typegrouping"] isEqualToString:@"geometry"]); } - /** * Retrieve the table status value for a supplied key, using or refreshing the cache as appropriate. * @@ -307,7 +300,6 @@ [status setValue:value forKey:key]; } - /** * Retrieve all known status values as a dictionary, using or refreshing the cache as appropriate. */ @@ -322,7 +314,6 @@ return status; } - /** * Flushes all caches - should be used on major changes, for example table changes. */ @@ -348,7 +339,6 @@ } } - /** * Flushes any status-related caches. */ @@ -367,7 +357,6 @@ [columnNames removeAllObjects]; } - /** * Retrieves the information for the current table and stores it in cache. * Returns a boolean indicating success. @@ -454,7 +443,6 @@ return TRUE; } - /** * Retrieve the CREATE TABLE string for a table and analyse it to extract the field * details, primary key, unique keys, and table encoding. @@ -481,6 +469,11 @@ [mySQLConnection storeEncodingForRestoration]; [mySQLConnection setEncoding:@"utf8"]; } + + // In cases where this method is called directly instead of via -updateInformationForCurrentTable + // (for example, from the exporters) clear the list of constraints to prevent the previous call's table + // constraints being included in the table information (issue 1206). + [constraints removeAllObjects]; // Retrieve the CREATE TABLE syntax for the table MCPResult *theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW CREATE TABLE %@", [tableName backtickQuotedString]]]; @@ -597,7 +590,7 @@ } [fieldsParser setIgnoreCommentStrings:NO]; - [tableColumn setObject:[NSNumber numberWithInteger:[tableColumns count]] forKey:@"datacolumnindex"]; + [tableColumn setObject:[NSString stringWithFormat:@"%llu", (unsigned long long)[tableColumns count]] forKey:@"datacolumnindex"]; [tableColumn setObject:fieldName forKey:@"name"]; // Split the remaining field definition string by spaces and process @@ -614,11 +607,12 @@ [tableColumns addObject:d]; // TODO: Otherwise it's a key definition, check, or other 'metadata'. Would be useful to parse/display these! - } else { + } + else { NSArray *parts = [fieldsParser splitStringByCharacter:' ' skippingBrackets:YES ignoringQuotedStrings:YES]; // Constraints - if( [[parts objectAtIndex:0] hasPrefix:@"CONSTRAINT"] ) { + if ([[parts objectAtIndex:0] hasPrefix:@"CONSTRAINT"]) { NSMutableDictionary *constraintDetails = [[NSMutableDictionary alloc] init]; // Extract the relevant details from the constraint string @@ -643,9 +637,10 @@ [constraintDetails setObject:[fieldsParser unquotedString] forKey:@"ref_columns"]; NSUInteger nextOffs = 12; - if( [parts count] > 8 ) { + + if ([parts count] > 8) { // NOTE: this won't get SET NULL | NO ACTION | RESTRICT - if( [[parts objectAtIndex:9] hasPrefix:@"UPDATE"] ) { + if ([[parts objectAtIndex:9] hasPrefix:@"UPDATE"]) { if( [NSArrayObjectAtIndex(parts, 10) hasPrefix:@"SET"] ) { [constraintDetails setObject:@"SET NULL" forKey:@"update"]; @@ -659,8 +654,8 @@ forKey:@"update"]; } } - else if( [NSArrayObjectAtIndex(parts, 9) hasPrefix:@"DELETE"] ) { - if( [NSArrayObjectAtIndex(parts, 10) hasPrefix:@"SET"] ) { + else if ([NSArrayObjectAtIndex(parts, 9) hasPrefix:@"DELETE"]) { + if ([NSArrayObjectAtIndex(parts, 10) hasPrefix:@"SET"]) { [constraintDetails setObject:@"SET NULL" forKey:@"delete"]; nextOffs = 13; @@ -674,6 +669,7 @@ } } } + if ([parts count] > nextOffs - 1) { if( [NSArrayObjectAtIndex(parts, nextOffs) hasPrefix:@"UPDATE"] ) { if( [NSArrayObjectAtIndex(parts, nextOffs+1) hasPrefix:@"SET"] ) { @@ -700,9 +696,11 @@ } } } + [constraints addObject:constraintDetails]; [constraintDetails release]; } + // primary key // add "isprimarykey" to the corresponding tableColumn // add dict root "primarykeyfield" = <field> for faster accessing @@ -718,6 +716,7 @@ } } } + // unique keys // add to each corresponding tableColumn the tag "unique" if given else if( [NSArrayObjectAtIndex(parts, 0) hasPrefix:@"UNIQUE"] && [parts count] == 4) { @@ -771,7 +770,6 @@ [createTableParser release]; [fieldParser release]; - // this will be 'Table' or 'View' [tableData setObject:[resultFieldNames objectAtIndex:0] forKey:@"type"]; [tableData setObject:[NSString stringWithString:encodingString] forKey:@"encoding"]; @@ -786,8 +784,6 @@ return tableData; } - - /** * Retrieve information which can be used to display views. Unlike tables, all the information * for views cannot be extracted from the CREATE ALGORITHM syntax without selecting all the info @@ -876,7 +872,7 @@ resultRow = [theResult fetchRowAsDictionary]; // Add the column index and name - [tableColumn setObject:[NSNumber numberWithInteger:[tableColumns count]] forKey:@"datacolumnindex"]; + [tableColumn setObject:[NSString stringWithFormat:@"%llu", (unsigned long long)[tableColumns count]] forKey:@"datacolumnindex"]; [tableColumn setObject:[NSString stringWithString:[resultRow objectForKey:@"Field"]] forKey:@"name"]; // Populate type, length, and other available details from the Type columns @@ -916,8 +912,6 @@ return viewData; } - - /** * Retrieve the status of a table as a dictionary and add it to the local cache for reuse. */ @@ -1348,7 +1342,7 @@ /** * Dealloc the class */ -- (void) dealloc +- (void)dealloc { [columns release]; [columnNames release]; @@ -1376,15 +1370,16 @@ #ifdef SP_REFACTOR /* glue */ -- (void)setTableDocumentInstance:(SPDatabaseDocument*)doc +- (void)setTableDocumentInstance:(SPDatabaseDocument *)doc { tableDocumentInstance = doc; } -- (void)setTableListInstance:(SPTablesList*)list +- (void)setTableListInstance:(SPTablesList *)list { tableListInstance = list; } + #endif @end
\ No newline at end of file diff --git a/Source/SPTableRelations.h b/Source/SPTableRelations.h index a71b151a..5a8b9c40 100644 --- a/Source/SPTableRelations.h +++ b/Source/SPTableRelations.h @@ -42,6 +42,7 @@ IBOutlet SPTableView *relationsTableView; IBOutlet NSPanel *addRelationPanel; + IBOutlet NSTextField *constraintName; IBOutlet NSBox *addRelationTableBox; IBOutlet NSPopUpButton *columnPopUpButton; IBOutlet NSPopUpButton *refTablePopUpButton; @@ -49,11 +50,14 @@ IBOutlet NSPopUpButton *onUpdatePopUpButton; IBOutlet NSPopUpButton *onDeletePopUpButton; IBOutlet NSButton *confirmAddRelationButton; + IBOutlet NSProgressIndicator *dataProgressIndicator; + IBOutlet NSTextField *progressStatusTextField; MCPConnection *connection; - NSMutableArray *relationData; NSUserDefaults *prefs; + NSMutableArray *relationData; + NSMutableArray *takenConstraintNames; } @property (readonly) NSMutableArray *relationData; @@ -62,6 +66,7 @@ // IB action methods - (IBAction)addRelation:(id)sender; - (IBAction)removeRelation:(id)sender; +- (IBAction)openRelationSheet:(id)sender; - (IBAction)closeRelationSheet:(id)sender; - (IBAction)confirmAddRelation:(id)sender; - (IBAction)selectTableColumn:(id)sender; diff --git a/Source/SPTableRelations.m b/Source/SPTableRelations.m index 2266229a..eacdc8f6 100644 --- a/Source/SPTableRelations.m +++ b/Source/SPTableRelations.m @@ -29,11 +29,22 @@ #import "SPTableData.h" #import "SPTableView.h" #import "SPAlertSheets.h" +#import "RegexKitLite.h" -@interface SPTableRelations (PrivateAPI) +static NSString *SPRemoveRelation = @"SPRemoveRelation"; + +static NSString *SPRelationNameKey = @"name"; +static NSString *SPRelationColumnsKey = @"columns"; +static NSString *SPRelationFKTableKey = @"fk_table"; +static NSString *SPRelationFKColumnsKey = @"fk_columns"; +static NSString *SPRelationOnUpdateKey = @"on_update"; +static NSString *SPRelationOnDeleteKey = @"on_delete"; + +@interface SPTableRelations () - (void)_refreshRelationDataForcingCacheRefresh:(BOOL)clearAllCaches; - (void)_updateAvailableTableColumns; +- (void)_reopenRelationSheet:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo; @end @@ -48,8 +59,9 @@ - (id)init { if ((self = [super init])) { - relationData = [[NSMutableArray alloc] init]; - prefs = [NSUserDefaults standardUserDefaults]; + relationData = [[NSMutableArray alloc] init]; + prefs = [NSUserDefaults standardUserDefaults]; + takenConstraintNames = [[NSMutableArray alloc] init]; } return self; @@ -97,6 +109,18 @@ #pragma mark IB action methods /** + * Opens the relation sheet, in its current state (without any reset of fields) + */ +- (IBAction)openRelationSheet:(id)sender +{ + [NSApp beginSheet:addRelationPanel + modalForWindow:[tableDocumentInstance parentWindow] + modalDelegate:self + didEndSelector:nil + contextInfo:nil]; +} + +/** * Closes the relation sheet. */ - (IBAction)closeRelationSheet:(id)sender @@ -110,37 +134,72 @@ */ - (IBAction)confirmAddRelation:(id)sender { - [self closeRelationSheet:self]; + [dataProgressIndicator startAnimation:self]; + [dataProgressIndicator setHidden:NO]; NSString *thisTable = [tablesListInstance tableName]; NSString *thisColumn = [columnPopUpButton titleOfSelectedItem]; NSString *thatTable = [refTablePopUpButton titleOfSelectedItem]; NSString *thatColumn = [refColumnPopUpButton titleOfSelectedItem]; - NSString *query = [NSString stringWithFormat:@"ALTER TABLE %@ ADD FOREIGN KEY (%@) REFERENCES %@ (%@)", - [thisTable backtickQuotedString], + NSString *query = [NSString stringWithFormat:@"ALTER TABLE %@ ADD ",[thisTable backtickQuotedString]]; + + // Set constraint name? + if ([[constraintName stringValue] length] > 0) { + query = [query stringByAppendingString:[NSString stringWithFormat:@"CONSTRAINT %@ ", [[constraintName stringValue] backtickQuotedString]]]; + } + + query = [query stringByAppendingString:[NSString stringWithFormat:@"FOREIGN KEY (%@) REFERENCES %@ (%@)", [thisColumn backtickQuotedString], [thatTable backtickQuotedString], - [thatColumn backtickQuotedString]]; + [thatColumn backtickQuotedString]]]; + + NSArray *onActions = [NSArray arrayWithObjects:@"RESTRICT", @"CASCADE", @"SET NULL", @"NO ACTION", nil]; // If required add ON DELETE - if ([onDeletePopUpButton indexOfSelectedItem] > 0) { - query = [query stringByAppendingString:[NSString stringWithFormat:@" ON DELETE %@", [[onDeletePopUpButton titleOfSelectedItem] uppercaseString]]]; + if ([onDeletePopUpButton selectedTag] >= 0) { + query = [query stringByAppendingString:[NSString stringWithFormat:@" ON DELETE %@", [onActions objectAtIndex:[onDeletePopUpButton selectedTag]]]]; } // If required add ON UPDATE - if ([onUpdatePopUpButton indexOfSelectedItem] > 0) { - query = [query stringByAppendingString:[NSString stringWithFormat:@" ON UPDATE %@", [[onUpdatePopUpButton titleOfSelectedItem] uppercaseString]]]; + if ([onUpdatePopUpButton selectedTag] >= 0) { + query = [query stringByAppendingString:[NSString stringWithFormat:@" ON UPDATE %@", [onActions objectAtIndex:[onUpdatePopUpButton selectedTag]]]]; } // Execute query [connection queryString:query]; + [dataProgressIndicator setHidden:YES]; + [dataProgressIndicator stopAnimation:self]; + + [self closeRelationSheet:self]; + if ([connection queryErrored]) { + + // Retrieve the last connection error message. + NSString *errorText = [connection getLastErrorMessage]; + + // An error ID of 1005 indicates a foreign key error. These are thrown for many reasons, but the two + // most common are 121 (name probably in use) and 150 (types don't exactly match). + // Retrieve the InnoDB status and extract the most recent error for more helpful text. + if ([connection getLastErrorID] == 1005) { + NSString *statusText = [connection getFirstFieldFromQuery:@"SHOW INNODB STATUS"]; + NSString *detailErrorString = [statusText stringByMatching:@"latest foreign key error\\s+-----*\\s+[0-9: ]*(.*?)\\s+-----" options:(RKLCaseless | RKLDotAll) inRange:NSMakeRange(0, [statusText length]) capture:1L error:NULL]; + if (detailErrorString) { + errorText = [NSString stringWithFormat:NSLocalizedString(@"%@\n\nDetail: %@", @"Add relation error detail intro"), errorText, [detailErrorString stringByReplacingOccurrencesOfString:@"\n" withString:@" "]]; + } + + // Detect name duplication if appropriate + if ([errorText isMatchedByRegex:@"errno: 121"] && [errorText isMatchedByRegex:@"already exists"]) { + [takenConstraintNames addObject:[[constraintName stringValue] lowercaseString]]; + [self controlTextDidChange:[NSNotification notificationWithName:@"dummy" object:constraintName]]; + } + } + SPBeginAlertSheet(NSLocalizedString(@"Error creating relation", @"error creating relation message"), NSLocalizedString(@"OK", @"OK button"), - nil, nil, [NSApp mainWindow], nil, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"The specified relation was unable to be created.\n\nMySQL said: %@", @"error creating relation informative message"), [connection getLastErrorMessage]]); + nil, nil, [NSApp mainWindow], self, @selector(_reopenRelationSheet:returnCode:contextInfo:), nil, + [NSString stringWithFormat:NSLocalizedString(@"The specified relation was unable to be created.\n\nMySQL said: %@", @"error creating relation informative message"), errorText]); } else { [self _refreshRelationDataForcingCacheRefresh:YES]; @@ -168,36 +227,47 @@ */ - (IBAction)addRelation:(id)sender { - // Check whether table editing is permitted (necessary as some actions - eg table double-click - bypass validation) if ([tableDocumentInstance isWorking] || [tablesListInstance tableType] != SPTableTypeTable) return; // Set up the controls [addRelationTableBox setTitle:[NSString stringWithFormat:NSLocalizedString(@"Table: %@", @"Add Relation sheet title, showing table name"), [tablesListInstance tableName]]]; - + [columnPopUpButton removeAllItems]; NSArray *columnTitles = ([prefs boolForKey:SPAlphabeticalTableSorting])? [[tableDataInstance columnNames] sortedArrayUsingSelector:@selector(compare:)] : [tableDataInstance columnNames]; [columnPopUpButton addItemsWithTitles:columnTitles]; - + [refTablePopUpButton removeAllItems]; - + + BOOL changeEncoding = ![[connection encoding] isEqualToString:@"utf8"]; + + // Use UTF8 for identifier-based queries + if (changeEncoding) { + [connection storeEncodingForRestoration]; + [connection setEncoding:@"utf8"]; + } + // Get all InnoDB tables in the current database + // TODO: MySQL 4 compatibility MCPResult *result = [connection queryString:[NSString stringWithFormat:@"SELECT table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE' AND engine = 'InnoDB' AND table_schema = %@", [[tableDocumentInstance database] tickQuotedString]]]; - + [result dataSeek:0]; - + for (NSUInteger i = 0; i < [result numOfRows]; i++) { [refTablePopUpButton addItemWithTitle:[[result fetchRowAsArray] objectAtIndex:0]]; } - + + // Reset other fields + [constraintName setStringValue:@""]; + [onDeletePopUpButton selectItemAtIndex:0]; + [onUpdatePopUpButton selectItemAtIndex:0]; + + // Restore encoding if appropriate + if (changeEncoding) [connection restoreStoredEncoding]; + [self selectReferenceTable:nil]; - - [NSApp beginSheet:addRelationPanel - modalForWindow:[tableDocumentInstance parentWindow] - modalDelegate:self - didEndSelector:nil - contextInfo:nil]; + [self openRelationSheet:self]; } /** @@ -222,7 +292,7 @@ [[buttons objectAtIndex:0] setKeyEquivalentModifierMask:NSCommandKeyMask]; [[buttons objectAtIndex:1] setKeyEquivalent:@"\r"]; - [alert beginSheetModalForWindow:[tableDocumentInstance parentWindow] modalDelegate:self didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:@"removeRelation"]; + [alert beginSheetModalForWindow:[tableDocumentInstance parentWindow] modalDelegate:self didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:SPRemoveRelation]; } } @@ -257,7 +327,8 @@ [addRelationButton setEnabled:enableInteraction]; [refreshRelationsButton setEnabled:enableInteraction]; [relationsTableView setEnabled:YES]; - } else { + } + else { [addRelationButton setEnabled:NO]; [refreshRelationsButton setEnabled:NO]; [relationsTableView setEnabled:NO]; @@ -269,6 +340,27 @@ } #pragma mark - +#pragma mark TextField delegate methods + +- (void)controlTextDidChange:(NSNotification *)notification +{ + // Make sure the user does not enter a taken name, using the quickly-generated incomplete list + if ([notification object] == constraintName) { + NSString *userValue = [[constraintName stringValue] lowercaseString]; + + // Make field red and disable add button + if ([takenConstraintNames containsObject:userValue]) { + [constraintName setTextColor:[NSColor redColor]]; + [confirmAddRelationButton setEnabled:NO]; + } + else { + [constraintName setTextColor:[NSColor controlTextColor]]; + [confirmAddRelationButton setEnabled:YES]; + } + } +} + +#pragma mark - #pragma mark Tableview datasource methods - (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView @@ -296,7 +388,7 @@ * Double-click action on table cells - for the time being, return * NO to disable editing. */ -- (BOOL)tableView:(NSTableView *)aTableView shouldEditTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex +- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex { if ([tableDocumentInstance isWorking]) return NO; @@ -306,7 +398,7 @@ /** * Disable row selection while the document is working. */ -- (BOOL)tableView:(NSTableView *)aTableView shouldSelectRow:(NSInteger)rowIndex +- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)rowIndex { return ![tableDocumentInstance isWorking]; } @@ -372,12 +464,12 @@ { NSMutableArray *temp = [[NSMutableArray alloc] init]; - [temp addObject:[eachRelation objectForKey:@"name"]]; - [temp addObject:[eachRelation objectForKey:@"columns"]]; - [temp addObject:[eachRelation objectForKey:@"fk_table"]]; - [temp addObject:[eachRelation objectForKey:@"fk_columns"]]; - [temp addObject:([eachRelation objectForKey:@"on_update"]) ? [eachRelation objectForKey:@"on_update"] : @""]; - [temp addObject:([eachRelation objectForKey:@"on_delete"]) ? [eachRelation objectForKey:@"on_delete"] : @""]; + [temp addObject:[eachRelation objectForKey:SPRelationNameKey]]; + [temp addObject:[eachRelation objectForKey:SPRelationColumnsKey]]; + [temp addObject:[eachRelation objectForKey:SPRelationFKTableKey]]; + [temp addObject:[eachRelation objectForKey:SPRelationFKColumnsKey]]; + [temp addObject:([eachRelation objectForKey:SPRelationOnUpdateKey]) ? [eachRelation objectForKey:SPRelationOnUpdateKey] : @""]; + [temp addObject:([eachRelation objectForKey:SPRelationOnDeleteKey]) ? [eachRelation objectForKey:SPRelationOnDeleteKey] : @""]; [data addObject:temp]; @@ -392,7 +484,7 @@ */ - (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo { - if ([contextInfo isEqualToString:@"removeRelation"]) { + if ([contextInfo isEqualToString:SPRemoveRelation]) { if (returnCode == NSAlertDefaultReturn) { @@ -403,7 +495,7 @@ while (row != NSNotFound) { - NSString *relationName = [[relationData objectAtIndex:row] objectForKey:@"name"]; + NSString *relationName = [[relationData objectAtIndex:row] objectForKey:SPRelationNameKey]; NSString *query = [NSString stringWithFormat:@"ALTER TABLE %@ DROP FOREIGN KEY %@", [thisTable backtickQuotedString], [relationName backtickQuotedString]]; [connection queryString:query]; @@ -466,23 +558,7 @@ } #pragma mark - - -/* - * Dealloc. - */ -- (void)dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; - [[NSUserDefaults standardUserDefaults] removeObserver:self forKeyPath:SPUseMonospacedFonts]; - - [relationData release], relationData = nil; - - [super dealloc]; -} - -@end - -@implementation SPTableRelations (PrivateAPI) +#pragma mark Private API /** * Refresh the displayed relations, optionally forcing a refresh of the underlying cache. @@ -490,24 +566,30 @@ - (void)_refreshRelationDataForcingCacheRefresh:(BOOL)clearAllCaches { [relationData removeAllObjects]; + + // Prepare the list of the used constraint names for this table. Ideally this would include all the used names + // in the database, but the MySQL-provided method of requesting that information (table_constraints in + // information_schema) is generated live from db file scans, which cannot be cheaply done on servers with + // many databases. + [takenConstraintNames removeAllObjects]; if ([tablesListInstance tableType] == SPTableTypeTable) { - + if (clearAllCaches) [tableDataInstance updateInformationForCurrentTable]; - + NSArray *constraints = [tableDataInstance getConstraints]; - + for (NSDictionary *constraint in constraints) { [relationData addObject:[NSDictionary dictionaryWithObjectsAndKeys: - [constraint objectForKey:@"name"], @"name", - [[constraint objectForKey:@"columns"] objectAtIndex:0], @"columns", - [constraint objectForKey:@"ref_table"], @"fk_table", - [constraint objectForKey:@"ref_columns"], @"fk_columns", - ([constraint objectForKey:@"update"] ? [constraint objectForKey:@"update"] : @""), @"on_update", - ([constraint objectForKey:@"delete"] ? [constraint objectForKey:@"delete"] : @""), @"on_delete", - nil]]; - + [constraint objectForKey:SPRelationNameKey], SPRelationNameKey, + [[constraint objectForKey:SPRelationColumnsKey] objectAtIndex:0], SPRelationColumnsKey, + [constraint objectForKey:@"ref_table"], SPRelationFKTableKey, + [constraint objectForKey:@"ref_columns"], SPRelationFKColumnsKey, + ([constraint objectForKey:@"update"] ? [constraint objectForKey:@"update"] : @""), SPRelationOnUpdateKey, + ([constraint objectForKey:@"delete"] ? [constraint objectForKey:@"delete"] : @""), SPRelationOnDeleteKey, + nil]]; + [takenConstraintNames addObject:[[constraint objectForKey:SPRelationNameKey] lowercaseString]]; } } @@ -522,42 +604,67 @@ { NSString *column = [columnPopUpButton titleOfSelectedItem]; NSString *table = [refTablePopUpButton titleOfSelectedItem]; - + [tableDataInstance resetAllData]; [tableDataInstance updateInformationForCurrentTable]; - + NSDictionary *columnInfo = [[tableDataInstance columnWithName:column] copy]; - + [refColumnPopUpButton setEnabled:NO]; [confirmAddRelationButton setEnabled:NO]; - + [refColumnPopUpButton removeAllItems]; - + [tableDataInstance resetAllData]; NSDictionary *tableInfo = [tableDataInstance informationForTable:table]; - - NSArray *columns = [tableInfo objectForKey:@"columns"]; - + + NSArray *columns = [tableInfo objectForKey:SPRelationColumnsKey]; + NSMutableArray *validColumns = [NSMutableArray array]; - + // Only add columns of the same data type for (NSDictionary *aColumn in columns) { if ([[columnInfo objectForKey:@"type"] isEqualToString:[aColumn objectForKey:@"type"]]) { - [validColumns addObject:[aColumn objectForKey:@"name"]]; + [validColumns addObject:[aColumn objectForKey:SPRelationNameKey]]; } } - + // Add the valid columns if ([validColumns count] > 0) { NSArray *columnTitles = ([prefs boolForKey:SPAlphabeticalTableSorting])? [validColumns sortedArrayUsingSelector:@selector(compare:)] : validColumns; + [refColumnPopUpButton addItemsWithTitles:columnTitles]; - + [refColumnPopUpButton setEnabled:YES]; [confirmAddRelationButton setEnabled:YES]; } - + [columnInfo release]; } +/** + * Reopen the add relation sheet, usually after an error message, with the previous content. + */ +- (void)_reopenRelationSheet:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo +{ + [self performSelector:@selector(openRelationSheet:) withObject:self afterDelay:0.0]; +} + +#pragma mark - + +/* + * Dealloc. + */ +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [[NSUserDefaults standardUserDefaults] removeObserver:self forKeyPath:SPUseMonospacedFonts]; + + [relationData release], relationData = nil; + [takenConstraintNames release], takenConstraintNames = nil; + + [super dealloc]; +} + @end diff --git a/Source/SPTableStructure.h b/Source/SPTableStructure.h index 40b665b2..b69c5a55 100644 --- a/Source/SPTableStructure.h +++ b/Source/SPTableStructure.h @@ -28,6 +28,9 @@ @class SPDatabaseDocument, SPTableFieldValidation, SPTableData, SPDatabaseData, SPTablesList, SPIndexesController, SPTableView; @interface SPTableStructure : NSObject +#ifdef SP_REFACTOR +<NSTableViewDelegate, NSTableViewDataSource, NSComboBoxCellDataSource> +#endif { IBOutlet SPTablesList* tablesListInstance; IBOutlet SPTableData* tableDataInstance; diff --git a/Source/SPTableStructure.m b/Source/SPTableStructure.m index ebbb9f7e..dd05f1e3 100644 --- a/Source/SPTableStructure.m +++ b/Source/SPTableStructure.m @@ -83,6 +83,7 @@ #ifndef SP_REFACTOR /* ui manipulation */ // Set the structure and index view's vertical gridlines if required [tableSourceView setGridStyleMask:([prefs boolForKey:SPDisplayTableViewVerticalGridlines]) ? NSTableViewSolidVerticalGridLineMask : NSTableViewGridNone]; + [indexesTableView setGridStyleMask:([prefs boolForKey:SPDisplayTableViewVerticalGridlines]) ? NSTableViewSolidVerticalGridLineMask : NSTableViewGridNone]; #endif // Set the double-click action in blank areas of the table to create new rows @@ -1650,6 +1651,9 @@ - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; +#ifndef SP_REFACTOR + [prefs removeObserver:indexesController forKeyPath:SPUseMonospacedFonts]; +#endif [tableFields release]; [oldRow release]; diff --git a/Source/SPTableStructureDelegate.m b/Source/SPTableStructureDelegate.m index 4e04ca05..5c927802 100644 --- a/Source/SPTableStructureDelegate.m +++ b/Source/SPTableStructureDelegate.m @@ -148,8 +148,8 @@ } } // Reset default to "" if field doesn't allow NULL and current default is set to NULL - else if([[aTableColumn identifier] isEqualToString:@"null"]) { - if([[currentRow objectForKey:@"null"] integerValue] != [anObject integerValue]) { + else if ([[aTableColumn identifier] isEqualToString:@"null"]) { + if ([[currentRow objectForKey:@"null"] integerValue] != [anObject integerValue]) { if([anObject integerValue] == 0) { if([[currentRow objectForKey:@"default"] isEqualToString:[prefs objectForKey:SPNullValue]]) [currentRow setObject:@"" forKey:@"default"]; @@ -159,17 +159,19 @@ } // Store new value but not if user choose "---" for type and reset values if required - if([[aTableColumn identifier] isEqualToString:@"type"]) { - if(anObject && [(NSString*)anObject length] && ![(NSString*)anObject hasPrefix:@"--"]) { + if ([[aTableColumn identifier] isEqualToString:@"type"]) { + if (anObject && [(NSString*)anObject length] && ![(NSString*)anObject hasPrefix:@"--"]) { [currentRow setObject:[(NSString*)anObject uppercaseString] forKey:@"type"]; + // If type is BLOB or TEXT reset DEFAULT since these field types don't allow a default - if([[currentRow objectForKey:@"type"] hasSuffix:@"TEXT"] + if ([[currentRow objectForKey:@"type"] hasSuffix:@"TEXT"] || [[currentRow objectForKey:@"type"] hasSuffix:@"BLOB"] || [fieldValidation isFieldTypeGeometry:[currentRow objectForKey:@"type"]] || ([fieldValidation isFieldTypeDate:[currentRow objectForKey:@"type"]] && ![[currentRow objectForKey:@"type"] isEqualToString:@"YEAR"])) { [currentRow setObject:@"" forKey:@"default"]; [currentRow setObject:@"" forKey:@"length"]; } + [tableSourceView reloadData]; } } @@ -192,7 +194,7 @@ } /** - Begin a drag and drop operation from the table - copy a single dragged row to the drag pasteboard. + * Begin a drag and drop operation from the table - copy a single dragged row to the drag pasteboard. */ - (BOOL)tableView:(NSTableView *)aTableView writeRowsWithIndexes:(NSIndexSet *)rows toPasteboard:(NSPasteboard*)pboard { @@ -213,12 +215,11 @@ } /** - Determine whether to allow a drag and drop operation on this table - for the purposes of drag reordering, - validate that the original source is of the correct type and within the same table, and that the drag - would result in a position change. + * Determine whether to allow a drag and drop operation on this table - for the purposes of drag reordering, + * validate that the original source is of the correct type and within the same table, and that the drag + * would result in a position change. */ -- (NSDragOperation)tableView:(NSTableView*)tableView validateDrop:(id <NSDraggingInfo>)info proposedRow:(NSInteger)row - proposedDropOperation:(NSTableViewDropOperation)operation +- (NSDragOperation)tableView:(NSTableView*)tableView validateDrop:(id <NSDraggingInfo>)info proposedRow:(NSInteger)row proposedDropOperation:(NSTableViewDropOperation)operation { //make sure that the drag operation is for the right table view if (tableView!=tableSourceView) return NO; @@ -247,8 +248,8 @@ */ - (BOOL)tableView:(NSTableView*)tableView acceptDrop:(id <NSDraggingInfo>)info row:(NSInteger)destinationRowIndex dropOperation:(NSTableViewDropOperation)operation { - //make sure that the drag operation is for the right table view - if (tableView!=tableSourceView) return NO; + // Make sure that the drag operation is for the right table view + if (tableView != tableSourceView) return NO; NSInteger originalRowIndex; NSMutableString *queryString; @@ -266,41 +267,47 @@ [[originalRow objectForKey:@"type"] uppercaseString]]; // Add the length parameter if necessary - if ( [originalRow objectForKey:@"length"] && ![[originalRow objectForKey:@"length"] isEqualToString:@""]) { + if ([originalRow objectForKey:@"length"] && ![[originalRow objectForKey:@"length"] isEqualToString:@""]) { [queryString appendFormat:@"(%@)", [originalRow objectForKey:@"length"]]; } NSString *fieldEncoding = @""; - if([[originalRow objectForKey:@"encoding"] integerValue] > 0) { + + if ([[originalRow objectForKey:@"encoding"] integerValue] > 0) { NSString *enc = [[encodingPopupCell itemAtIndex:[[originalRow objectForKey:@"encoding"] integerValue]] title]; NSInteger start = [enc rangeOfString:@"("].location+1; NSInteger end = [enc length] - start - 1; fieldEncoding = [enc substringWithRange:NSMakeRange(start, end)]; [queryString appendFormat:@" CHARACTER SET %@", fieldEncoding]; } - if(![fieldEncoding length] && [tableDataInstance tableEncoding]) { + + if (![fieldEncoding length] && [tableDataInstance tableEncoding]) { fieldEncoding = [tableDataInstance tableEncoding]; } - if([fieldEncoding length] && [[originalRow objectForKey:@"collation"] integerValue] > 0) { + + if ([fieldEncoding length] && [[originalRow objectForKey:@"collation"] integerValue] > 0) { NSArray *theCollations = [databaseDataInstance getDatabaseCollationsForEncoding:fieldEncoding]; NSString *col = [[theCollations objectAtIndex:[[originalRow objectForKey:@"collation"] integerValue]-1] objectForKey:@"COLLATION_NAME"]; [queryString appendFormat:@" COLLATE %@", col]; } - - + // Add unsigned, zerofill, binary, not null if necessary if ([[originalRow objectForKey:@"unsigned"] integerValue]) { [queryString appendString:@" UNSIGNED"]; } + if ([[originalRow objectForKey:@"zerofill"] integerValue]) { [queryString appendString:@" ZEROFILL"]; } + if ([[originalRow objectForKey:@"binary"] integerValue]) { [queryString appendString:@" BINARY"]; } + if (![[originalRow objectForKey:@"null"] integerValue]) { [queryString appendString:@" NOT NULL"]; } + if (![[originalRow objectForKey:@"Extra"] isEqualToString:@"None"] ) { [queryString appendString:@" "]; [queryString appendString:[[originalRow objectForKey:@"Extra"] uppercaseString]]; @@ -309,7 +316,7 @@ BOOL isTimestampType = [[[originalRow objectForKey:@"type"] lowercaseString] isEqualToString:@"timestamp"]; // Add the default value, skip it for auto_increment - if([originalRow objectForKey:@"Extra"] && ![[originalRow objectForKey:@"Extra"] isEqualToString:@"auto_increment"]) { + if ([originalRow objectForKey:@"Extra"] && ![[originalRow objectForKey:@"Extra"] isEqualToString:@"auto_increment"]) { if ([[originalRow objectForKey:@"default"] isEqualToString:[prefs objectForKey:SPNullValue]]) { if ([[originalRow objectForKey:@"null"] integerValue] == 1) { [queryString appendString:(isTimestampType) ? @" NULL DEFAULT NULL" : @" DEFAULT NULL"]; @@ -318,7 +325,7 @@ else if (isTimestampType && ([[[originalRow objectForKey:@"default"] uppercaseString] isEqualToString:@"CURRENT_TIMESTAMP"]) ) { [queryString appendString:@" DEFAULT CURRENT_TIMESTAMP"]; } - else { + else if ([(NSString *)[originalRow objectForKey:@"default"] length]) { [queryString appendFormat:@" DEFAULT '%@'", [mySQLConnection prepareString:[originalRow objectForKey:@"default"]]]; } } @@ -399,7 +406,8 @@ // Check if there is currently a field selected and change button state accordingly if ([tableSourceView numberOfSelectedRows] > 0 && [tablesListInstance tableType] == SPTableTypeTable) { [removeFieldButton setEnabled:YES]; - } else { + } + else { [removeFieldButton setEnabled:NO]; [copyFieldButton setEnabled:NO]; } @@ -459,10 +467,10 @@ } // Trap the enter key, triggering a save - else if ( [textView methodForSelector:command] == [textView methodForSelector:@selector(insertNewline:)] ) + else if ([textView methodForSelector:command] == [textView methodForSelector:@selector(insertNewline:)]) { // Suppress enter for non-text fields to allow selecting of chosen items from comboboxes or popups - if(![[[[[[tableSourceView tableColumns] objectAtIndex:column] dataCell] class] description] isEqualToString:@"NSTextFieldCell"]) + if (![[[[[[tableSourceView tableColumns] objectAtIndex:column] dataCell] class] description] isEqualToString:@"NSTextFieldCell"]) return YES; [[control window] makeFirstResponder:control]; @@ -486,7 +494,6 @@ } } - /** * Modify cell display by disabling table cells when a view is selected, meaning structure/index * is uneditable and do cell validation due to row's field type. @@ -503,35 +510,43 @@ // validate cell against current field type NSDictionary *theRow = NSArrayObjectAtIndex(tableFields, rowIndex); NSString *theRowType = @""; - if((theRowType = [theRow objectForKey:@"type"])) + + if ((theRowType = [theRow objectForKey:@"type"])) { theRowType = [[theRowType stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] uppercaseString]; + } // Only string fields allow encoding settings - if(([[aTableColumn identifier] isEqualToString:@"encoding"])) { + if (([[aTableColumn identifier] isEqualToString:@"encoding"])) { [aCell setEnabled:([fieldValidation isFieldTypeString:theRowType] && ![theRowType hasSuffix:@"BINARY"] && ![theRowType hasSuffix:@"BLOB"])]; } + // Only string fields allow collation settings and string field is not set to BINARY since BINARY sets the collation to *_bin - else if([[aTableColumn identifier] isEqualToString:@"collation"]){ + else if ([[aTableColumn identifier] isEqualToString:@"collation"]){ [aCell setEnabled:([fieldValidation isFieldTypeString:theRowType] && [[theRow objectForKey:@"binary"] integerValue] == 0 && ![theRowType hasSuffix:@"BINARY"] && ![theRowType hasSuffix:@"BLOB"])]; } + // Check if UNSIGNED and ZEROFILL is allowed - else if([[aTableColumn identifier] isEqualToString:@"zerofill"] || [[aTableColumn identifier] isEqualToString:@"unsigned"]) { + else if ([[aTableColumn identifier] isEqualToString:@"zerofill"] || [[aTableColumn identifier] isEqualToString:@"unsigned"]) { [aCell setEnabled:([fieldValidation isFieldTypeNumeric:theRowType] && ![theRowType isEqualToString:@"BIT"])]; } + // Check if BINARY is allowed - else if([[aTableColumn identifier] isEqualToString:@"binary"]) { + else if ([[aTableColumn identifier] isEqualToString:@"binary"]) { [aCell setEnabled:([fieldValidation isFieldTypeAllowBinary:theRowType])]; } + // TEXT, BLOB, and GEOMETRY fields don't allow a DEFAULT - else if([[aTableColumn identifier] isEqualToString:@"default"]) { + else if ([[aTableColumn identifier] isEqualToString:@"default"]) { [aCell setEnabled:([theRowType hasSuffix:@"TEXT"] || [theRowType hasSuffix:@"BLOB"] || [fieldValidation isFieldTypeGeometry:theRowType]) ? NO : YES]; } + // Check allow NULL - else if([[aTableColumn identifier] isEqualToString:@"null"]) { + else if ([[aTableColumn identifier] isEqualToString:@"null"]) { [aCell setEnabled:([[theRow objectForKey:@"Key"] isEqualToString:@"PRI"] || [[[theRow objectForKey:@"Extra"] uppercaseString] isEqualToString:@"AUTO_INCREMENT"]) ? NO : YES]; } + // TEXT, BLOB, date, and GEOMETRY fields don't allow a length - else if([[aTableColumn identifier] isEqualToString:@"length"]) { + else if ([[aTableColumn identifier] isEqualToString:@"length"]) { [aCell setEnabled:([theRowType hasSuffix:@"TEXT"] || [theRowType hasSuffix:@"BLOB"] || ([fieldValidation isFieldTypeDate:theRowType] && ![theRowType isEqualToString:@"YEAR"]) || [fieldValidation isFieldTypeGeometry:theRowType]) ? NO : YES]; } else { @@ -608,5 +623,4 @@ return @""; } - @end diff --git a/Source/SPTableView.m b/Source/SPTableView.m index 9139bb79..e135e16b 100644 --- a/Source/SPTableView.m +++ b/Source/SPTableView.m @@ -37,6 +37,8 @@ @interface SPTableView (PrivateAPI) - (void)_doubleClickAction; +- (void)_disableDoubleClickAction:(NSNotification *)notification; +- (void)_enableDoubleClickAction:(NSNotification *)notification; @end @@ -60,6 +62,23 @@ [super awakeFromNib]; } +/** + * Track window changes, in order to add listeners for when sheets are shown + * or hidden; this allows the double-click action to be disabled while sheets + * are open, preventing beeps when using the field editor on double-click. + */ +- (void) viewWillMoveToWindow:(NSWindow *)aWindow { + NSNotificationCenter *notifier = [NSNotificationCenter defaultCenter]; + + [notifier removeObserver:self name:NSWindowWillBeginSheetNotification object:nil]; + [notifier removeObserver:self name:NSWindowDidEndSheetNotification object:nil]; + + if (aWindow) { + [notifier addObserver:self selector:@selector(_disableDoubleClickAction:) name:NSWindowWillBeginSheetNotification object:aWindow]; + [notifier addObserver:self selector:@selector(_enableDoubleClickAction:) name:NSWindowDidEndSheetNotification object:aWindow]; + } +} + #pragma mark - /** @@ -238,4 +257,23 @@ } } +/** + * When a sheet is opened on this window, disable the double-click action. + * This prevents beeping when a double-click results in a rejected cell edit but + * opens a sheet such as the field editor sheet. + */ +- (void)_disableDoubleClickAction:(NSNotification *)notification +{ + [super setDoubleAction:NULL]; +} + +/** + * Restore the double-click action after the sheet is closed. + */ +- (void)_enableDoubleClickAction:(NSNotification *)notification +{ + [super setDoubleAction:@selector(_doubleClickAction)]; +} + + @end diff --git a/Source/SPTablesList.h b/Source/SPTablesList.h index 50a5f4c0..e5e76548 100644 --- a/Source/SPTablesList.h +++ b/Source/SPTablesList.h @@ -26,7 +26,11 @@ #import <MCPKit/MCPKit.h> @class SPHistoryController, SPTableView; -@class SPDatabaseDocument; +@class SPDatabaseDocument, SPDatabaseData, SPTableStructure, SPTableContent; + +#ifdef SP_REFACTOR +@class SQLSidebarViewController; +#endif @interface NSObject (NSSplitView) @@ -38,34 +42,48 @@ @end @interface SPTablesList : NSObject +#ifdef SP_REFACTOR +<NSTextFieldDelegate> +#endif { IBOutlet SPDatabaseDocument* tableDocumentInstance; + IBOutlet SPTableStructure* tableSourceInstance; + IBOutlet SPTableContent* tableContentInstance; #ifndef SP_REFACTOR /* ivars */ - IBOutlet id tableSourceInstance; - IBOutlet id tableContentInstance; IBOutlet id customQueryInstance; IBOutlet id tableDumpInstance; IBOutlet id tableDataInstance; IBOutlet id extendedTableInfoInstance; - IBOutlet id databaseDataInstance; +#endif + IBOutlet SPDatabaseData* databaseDataInstance; +#ifndef SP_REFACTOR /* ivars */ IBOutlet id tableInfoInstance; IBOutlet id tableTriggersInstance; IBOutlet SPHistoryController *spHistoryControllerInstance; IBOutlet id copyTableSheet; +#endif IBOutlet SPTableView *tablesListView; +#ifndef SP_REFACTOR /* ivars */ IBOutlet id copyTableButton; IBOutlet id copyTableNameField; IBOutlet id copyTableMessageField; IBOutlet NSButton *copyTableContentSwitch; +#endif IBOutlet id tableSheet; IBOutlet id tableNameField; IBOutlet id tableEncodingButton; IBOutlet id tableTypeButton; IBOutlet id toolbarAddButton; +#ifdef SP_REFACTOR + id toolbarDeleteButton; +#endif +#ifndef SP_REFACTOR IBOutlet id toolbarActionsButton; IBOutlet id toolbarReloadButton; +#endif IBOutlet id addTableButton; +#ifndef SP_REFACTOR IBOutlet id truncateTableButton; IBOutlet NSSplitView *tableListSplitView; IBOutlet NSSplitView *tableListFilterSplitView; @@ -80,6 +98,7 @@ IBOutlet NSMenuItem *openTableInNewTabMenuItem; IBOutlet NSMenuItem *separatorTableMenuItem; IBOutlet NSMenuItem *showCreateSyntaxMenuItem; + IBOutlet NSMenuItem *copyCreateSyntaxMenuItem; IBOutlet NSMenuItem *separatorTableMenuItem2; IBOutlet NSMenuItem *separatorTableMenuItem3; #endif @@ -95,39 +114,38 @@ IBOutlet NSMenuItem *openTableInNewTabContextMenuItem; IBOutlet NSMenuItem *separatorTableContextMenuItem; IBOutlet NSMenuItem *showCreateSyntaxContextMenuItem; + IBOutlet NSMenuItem *copyCreateSyntaxContextMenuItem; IBOutlet NSMenuItem *separatorTableContextMenuItem2; IBOutlet NSMenuItem *separatorTableContextMenuItem3; #endif NSMutableArray *tables; -#ifndef SP_REFACTOR /* ivars */ NSMutableArray *filteredTables; -#endif NSMutableArray *tableTypes; -#ifndef SP_REFACTOR /* ivars */ NSMutableArray *filteredTableTypes; -#endif NSInteger selectedTableType; NSString *selectedTableName; -#ifndef SP_REFACTOR /* ivars */ BOOL isTableListFiltered; BOOL tableListIsSelectable; -#endif BOOL tableListContainsViews; -#ifndef SP_REFACTOR /* ivars */ BOOL alertSheetOpened; +#ifndef SP_REFACTOR /* ivars */ NSFont *smallSystemFont; #endif + +#ifdef SP_REFACTOR + SQLSidebarViewController* sidebarViewController; +#endif } // IBAction methods - (IBAction)updateTables:(id)sender; -#ifndef SP_REFACTOR /* method decls */ - (IBAction)addTable:(id)sender; - (IBAction)closeSheet:(id)sender; - (IBAction)removeTable:(id)sender; +#ifndef SP_REFACTOR /* method decls */ - (IBAction)copyTable:(id)sender; - (IBAction)renameTable:(id)sender; - (IBAction)truncateTable:(id)sender; @@ -168,17 +186,31 @@ - (void) showFilter; - (void) hideFilter; - (void) clearFilter; +#endif - (IBAction) updateFilter:(id)sender; // Task interaction - (void) startDocumentTaskForTab:(NSNotification *)aNotification; - (void) endDocumentTaskForTab:(NSNotification *)aNotification; - (void) setTableListSelectability:(BOOL)isSelectable; -#endif - (BOOL)isTableNameValid:(NSString *)tableName forType:(SPTableType)tableType; - (BOOL)isTableNameValid:(NSString *)tableName forType:(SPTableType)tableType ignoringSelectedTable:(BOOL)ignoreSelectedTable; #ifdef SP_REFACTOR /* method decls */ +@property (assign) SPTableStructure* tableSourceInstance; +@property (assign) SPTableContent* tableContentInstance; +@property (assign) id toolbarAddButton; +@property (assign) id toolbarDeleteButton; +@property (assign) id tableSheet; +@property (assign) id tableNameField; +@property (assign) id tableEncodingButton; +@property (assign) id tableTypeButton; +@property (assign) id databaseDataInstance; +@property (assign) id addTableButton; +@property (assign) NSTableView* tablesListView; +@property (assign) SQLSidebarViewController* sidebarViewController; + +- (BOOL)selectionShouldChangeInTableView:(NSTableView *)aTableView; - (void)setDatabaseDocument:(SPDatabaseDocument*)val; #endif @end diff --git a/Source/SPTablesList.m b/Source/SPTablesList.m index 3930406f..b66796fa 100644 --- a/Source/SPTablesList.m +++ b/Source/SPTablesList.m @@ -41,34 +41,63 @@ #import "SPTableView.h" #import "ImageAndTextCell.h" #import "RegexKitLite.h" +#endif #import "SPDatabaseData.h" #import "SPAlertSheets.h" +#ifndef SP_REFACTOR /* headers */ #import "SPNavigatorController.h" #import "SPHistoryController.h" +#endif #import "SPServerSupport.h" +#ifndef SP_REFACTOR /* headers */ #import "SPWindowController.h" #import "SPAppController.h" +#endif + +#ifdef SP_REFACTOR +#import "SQLSidebarViewController.h" +#endif // Constants static NSString *SPAddRow = @"SPAddRow"; static NSString *SPAddNewTable = @"SPAddNewTable"; static NSString *SPRemoveTable = @"SPRemoveTable"; +#ifndef SP_REFACTOR static NSString *SPTruncateTable = @"SPTruncateTable"; static NSString *SPDuplicateTable = @"SPDuplicateTable"; +#endif @interface SPTablesList () +#ifndef SP_REFACTOR - (void)_removeTable; - (void)_truncateTable; +#endif - (void)_addTable; +#ifndef SP_REFACTOR - (void)_copyTable; - (void)_renameTableOfType:(SPTableType)tableType from:(NSString *)oldTableName to:(NSString *)newTableName; +#endif @end -#endif @implementation SPTablesList +#ifdef SP_REFACTOR +@synthesize sidebarViewController; +@synthesize databaseDataInstance; +@synthesize toolbarAddButton; +@synthesize toolbarDeleteButton; +@synthesize tableSourceInstance; +@synthesize tableContentInstance; +@synthesize tableSheet; +@synthesize tableNameField; +@synthesize tableEncodingButton; +@synthesize tableTypeButton; +@synthesize addTableButton; +@synthesize tablesListView; +#endif + #pragma mark - #pragma mark IBAction methods @@ -82,9 +111,7 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; NSUInteger i; NSString *previousSelectedTable = nil; NSString *previousFilterString = nil; -#ifndef SP_REFACTOR /* table list filtering */ BOOL previousTableListIsSelectable = tableListIsSelectable; -#endif BOOL changeEncoding = ![[mySQLConnection encoding] isEqualToString:@"utf8"]; if (selectedTableName) previousSelectedTable = [[NSString alloc] initWithString:selectedTableName]; @@ -99,8 +126,9 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; [[self onMainThread] clearFilter]; } tableListContainsViews = NO; - +#endif tableListIsSelectable = YES; +#ifndef SP_REFACTOR [[tablesListView onMainThread] deselectAll:self]; tableListIsSelectable = previousTableListIsSelectable; #endif @@ -278,11 +306,11 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; // or if the table name contains characters which are not supported by the current set encoding if ( ![sender isKindOfClass:[SPTableData class]] && previousSelectedTable != nil && [tables indexOfObject:previousSelectedTable] < [tables count]) { NSInteger itemToReselect = [tables indexOfObject:previousSelectedTable]; -#ifndef SP_REFACTOR /* ui manipulation */ tableListIsSelectable = YES; +#ifndef SP_REFACTOR /* ui manipulation */ [[tablesListView onMainThread] selectRowIndexes:[NSIndexSet indexSetWithIndex:itemToReselect] byExtendingSelection:NO]; - tableListIsSelectable = previousTableListIsSelectable; #endif + tableListIsSelectable = previousTableListIsSelectable; if (selectedTableName) [selectedTableName release]; selectedTableName = [[NSString alloc] initWithString:[tables objectAtIndex:itemToReselect]]; selectedTableType = (SPTableType)[[tableTypes objectAtIndex:itemToReselect] integerValue]; @@ -323,7 +351,6 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; // User press refresh button ergo force update [NSThread detachNewThreadSelector:@selector(queryDbStructureWithUserInfo:) toTarget:mySQLConnection withObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], @"forceUpdate", [NSNumber numberWithBool:YES], @"cancelQuerying", nil]]; } -#ifndef SP_REFACTOR /* whole table operations */ /** * Adds a new table to the tables-array (no changes in mysql-db) @@ -481,6 +508,7 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; [alert beginSheetModalForWindow:[tableDocumentInstance parentWindow] modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:SPRemoveTable]; } +#ifndef SP_REFACTOR /* whole table operations */ /** * Copies a table/view/proc/func, if desired with content */ @@ -651,6 +679,8 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; [tableInfoCollapseButton setToolTip:([tableInfoCollapseButton state] == NSOffState) ? NSLocalizedString(@"Show Table Information", @"Show Table Information") : NSLocalizedString(@"Hide Table Information", @"Hide Table Information")]; } +#endif + #pragma mark - #pragma mark Alert sheet methods @@ -673,28 +703,31 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; [self performSelector:@selector(_removeTable) withObject:nil afterDelay:0.0]; } } +#ifndef SP_REFACTOR else if ([contextInfo isEqualToString:SPTruncateTable]) { if (returnCode == NSAlertDefaultReturn) { [self _truncateTable]; } } - else if ([contextInfo isEqualToString:SPAddNewTable]) { + else +#endif + if ([contextInfo isEqualToString:SPAddNewTable]) { if (returnCode == NSOKButton) { [self _addTable]; } } +#ifndef SP_REFACTOR else if ([contextInfo isEqualToString:SPDuplicateTable]) { if (returnCode == NSOKButton) { [self _copyTable]; } } +#endif } #pragma mark - #pragma mark Additional methods -#endif - /** * Sets the connection (received from SPDatabaseDocument) and makes things that have to be done only once */ @@ -704,8 +737,6 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; [self updateTables:self]; } -#ifndef SP_REFACTOR /* ui validation */ - /** * Performs interface validation for various controls. */ @@ -716,10 +747,12 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; if (object == tableNameField) { [addTableButton setEnabled:[self isTableNameValid:[tableNameField stringValue] forType: SPTableTypeTable]]; } +#ifndef SP_REFACTOR else if (object == copyTableNameField) { [copyTableButton setEnabled:[self isTableNameValid:[copyTableNameField stringValue] forType:[self tableType]]]; } +#endif } /** @@ -737,11 +770,13 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; if (object == tableNameField) { [addTableButton performClick:object]; } +#ifndef SP_REFACTOR else if (object == copyTableNameField) { [copyTableButton performClick:object]; } -} #endif +} + /** * Updates application state to match the current selection, including @@ -755,12 +790,11 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; { // First handle empty or multiple selections if (!selectionDetails || ![selectionDetails objectForKey:@"name"]) { -#ifndef SP_REFACTOR /* ui manipulation */ NSIndexSet *indexes = [tablesListView selectedRowIndexes]; -#endif // Update the selected table name and type if (selectedTableName) [selectedTableName release]; + #ifndef SP_REFACTOR /* ui manipulation */ if ([indexes count]) { selectedTableName = [[NSString alloc] initWithString:@""]; @@ -839,6 +873,8 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; [separatorTableContextMenuItem2 setHidden:NO]; [showCreateSyntaxContextMenuItem setTitle:NSLocalizedString(@"Show Create Syntaxes...", @"show create syntaxes menu item")]; [showCreateSyntaxContextMenuItem setHidden:NO]; + [copyCreateSyntaxContextMenuItem setTitle:NSLocalizedString(@"Copy Create Syntaxes",@"Table List : Context Menu : Copy CREATE syntax (multiple selection)")]; + [copyCreateSyntaxContextMenuItem setHidden:NO]; // 'Gear' menu [renameTableMenuItem setHidden:YES]; @@ -849,6 +885,8 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; [separatorTableMenuItem2 setHidden:NO]; [showCreateSyntaxMenuItem setTitle:NSLocalizedString(@"Show Create Syntaxes...", @"show create syntaxes menu item")]; [showCreateSyntaxMenuItem setHidden:NO]; + [copyCreateSyntaxMenuItem setTitle:NSLocalizedString(@"Copy Create Syntaxes", @"Table List : Gear Menu : Copy CREATE syntax (multiple selection)")]; + [copyCreateSyntaxMenuItem setHidden:NO]; // Get main menu "Table"'s submenu NSMenu *tableSubMenu = [[[NSApp mainMenu] itemWithTag:SPMainMenuTable] submenu]; @@ -936,6 +974,8 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; [openTableInNewTabMenuItem setTitle:NSLocalizedString(@"Open View in New Tab", @"open view in new table title")]; [showCreateSyntaxMenuItem setHidden:NO]; [showCreateSyntaxMenuItem setTitle:NSLocalizedString(@"Show Create View Syntax...", @"show create view syntax menu item")]; + [copyCreateSyntaxMenuItem setHidden:NO]; + [copyCreateSyntaxMenuItem setTitle:NSLocalizedString(@"Copy Create View Syntax",@"Table List : Gear Menu : Copy CREATE view statement")]; [renameTableContextMenuItem setHidden:NO]; // we don't have to check the mysql version [renameTableContextMenuItem setTitle:NSLocalizedString(@"Rename View...", @"rename view menu title")]; @@ -948,6 +988,8 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; [openTableInNewTabContextMenuItem setTitle:NSLocalizedString(@"Open View in New Tab", @"open view in new table title")]; [showCreateSyntaxContextMenuItem setHidden:NO]; [showCreateSyntaxContextMenuItem setTitle:NSLocalizedString(@"Show Create View Syntax...", @"show create view syntax menu item")]; + [copyCreateSyntaxContextMenuItem setHidden:NO]; + [copyCreateSyntaxContextMenuItem setTitle:NSLocalizedString(@"Copy Create View Syntax",@"Table List : Context Menu : Copy CREATE view statement")]; } else if (selectedTableType == SPTableTypeTable) { [[tableSubMenu itemAtIndex:3] setTitle:NSLocalizedString(@"Copy Create Table Syntax", @"copy create table syntax menu item")]; @@ -979,6 +1021,8 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; [separatorTableMenuItem3 setHidden:NO]; [showCreateSyntaxMenuItem setHidden:NO]; [showCreateSyntaxMenuItem setTitle:NSLocalizedString(@"Show Create Table Syntax...", @"show create table syntax menu item")]; + [copyCreateSyntaxMenuItem setHidden:NO]; + [copyCreateSyntaxMenuItem setTitle:NSLocalizedString(@"Copy Create Table Syntax",@"Table List : Context Menu : Copy CREATE syntax (single table)")]; [renameTableContextMenuItem setHidden:NO]; [renameTableContextMenuItem setTitle:NSLocalizedString(@"Rename Table...", @"rename table menu title")]; @@ -992,6 +1036,8 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; [openTableInNewTabContextMenuItem setTitle:NSLocalizedString(@"Open Table in New Tab", @"open table in new table title")]; [showCreateSyntaxContextMenuItem setHidden:NO]; [showCreateSyntaxContextMenuItem setTitle:NSLocalizedString(@"Show Create Table Syntax...", @"show create table syntax menu item")]; + [copyCreateSyntaxContextMenuItem setHidden:NO]; + [copyCreateSyntaxContextMenuItem setTitle:NSLocalizedString(@"Copy Create Table Syntax",@"Table List : Gear Menu : Copy CREATE syntax (single table)")]; } else if (selectedTableType == SPTableTypeProc) { [[tableSubMenu itemAtIndex:3] setTitle:NSLocalizedString(@"Copy Create Procedure Syntax", @"copy create proc syntax menu item")]; @@ -1016,6 +1062,8 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; [separatorTableMenuItem3 setHidden:NO]; [showCreateSyntaxMenuItem setHidden:NO]; [showCreateSyntaxMenuItem setTitle:NSLocalizedString(@"Show Create Procedure Syntax...", @"show create proc syntax menu item")]; + [copyCreateSyntaxMenuItem setHidden:NO]; + [copyCreateSyntaxMenuItem setTitle:NSLocalizedString(@"Copy Create Procedure Syntax",@"Table List : Gear Menu : Copy CREATE PROCEDURE syntax")]; [renameTableContextMenuItem setHidden:NO]; [renameTableContextMenuItem setTitle:NSLocalizedString(@"Rename Procedure...", @"rename proc menu title")]; @@ -1028,6 +1076,8 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; [openTableInNewTabContextMenuItem setTitle:NSLocalizedString(@"Open Procedure in New Tab", @"open procedure in new table title")]; [showCreateSyntaxContextMenuItem setHidden:NO]; [showCreateSyntaxContextMenuItem setTitle:NSLocalizedString(@"Show Create Procedure Syntax...", @"show create proc syntax menu item")]; + [copyCreateSyntaxContextMenuItem setHidden:NO]; + [copyCreateSyntaxContextMenuItem setTitle:NSLocalizedString(@"Copy Create Procedure Syntax",@"Table List : Context Menu : Copy CREATE PROCEDURE syntax")]; } else if (selectedTableType == SPTableTypeFunc) { [[tableSubMenu itemAtIndex:3] setTitle:NSLocalizedString(@"Copy Create Function Syntax", @"copy create func syntax menu item")]; @@ -1052,6 +1102,8 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; [openTableInNewTabMenuItem setTitle:NSLocalizedString(@"Open Function in New Tab", @"open function in new table title")]; [showCreateSyntaxMenuItem setHidden:NO]; [showCreateSyntaxMenuItem setTitle:NSLocalizedString(@"Show Create Function Syntax...", @"show create func syntax menu item")]; + [copyCreateSyntaxMenuItem setHidden:NO]; + [copyCreateSyntaxMenuItem setTitle:NSLocalizedString(@"Copy Create Function Syntax",@"Table List : Context Menu : copy CREATE FUNCTION syntax")]; [renameTableContextMenuItem setHidden:NO]; [renameTableContextMenuItem setTitle:NSLocalizedString(@"Rename Function...", @"rename func menu title")]; @@ -1064,6 +1116,8 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; [openTableInNewTabContextMenuItem setTitle:NSLocalizedString(@"Open Function in New Tab", @"open function in new table title")]; [showCreateSyntaxContextMenuItem setHidden:NO]; [showCreateSyntaxContextMenuItem setTitle:NSLocalizedString(@"Show Create Function Syntax...", @"show create func syntax menu item")]; + [copyCreateSyntaxContextMenuItem setHidden:NO]; + [copyCreateSyntaxContextMenuItem setTitle:NSLocalizedString(@"Copy Create Function Syntax",@"Table List : Context Menu : copy CREATE FUNCTION syntax")]; } #endif } @@ -1278,9 +1332,7 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; if (selectedTableName) [selectedTableName release]; selectedTableName = [[NSString alloc] initWithString:[tables objectAtIndex:itemIndex]]; selectedTableType = [[tableTypes objectAtIndex:itemIndex] integerValue]; -#ifndef SP_REFACTOR /* table list filtering */ [self updateFilter:self]; -#endif [tableDocumentInstance loadTable:selectedTableName ofType:selectedTableType]; #ifndef SP_REFACTOR /* table list filtering */ } @@ -1432,8 +1484,6 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; } -#ifndef SP_REFACTOR - /** * Renames a table (in tables-array and mysql-db). */ @@ -1493,7 +1543,6 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; // Query the structure of all databases in the background (mainly for completion) [NSThread detachNewThreadSelector:@selector(queryDbStructureWithUserInfo:) toTarget:mySQLConnection withObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], @"forceUpdate", nil]]; } -#endif #pragma mark - #pragma mark TableView delegate methods @@ -1521,6 +1570,7 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; return FALSE; } } +#endif /** * Table view delegate method @@ -1542,6 +1592,7 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; return [tableDocumentInstance couldCommitCurrentViewActions]; } +#ifndef SP_REFACTOR /** * Loads a table in content or source view (if tab selected) */ @@ -1752,8 +1803,9 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; */ - (void) showFilter { - if ([tableListFilterSplitView collapsibleSubviewIsCollapsed]) + if ([tableListFilterSplitView collapsibleSubviewIsCollapsed]) { [tableListFilterSplitView performSelectorOnMainThread:@selector(toggleCollapse:) withObject:nil waitUntilDone:NO]; + } } /** @@ -1762,8 +1814,9 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; */ - (void) hideFilter { - if (![tableListFilterSplitView collapsibleSubviewIsCollapsed]) + if (![tableListFilterSplitView collapsibleSubviewIsCollapsed]) { [tableListFilterSplitView performSelectorOnMainThread:@selector(toggleCollapse:) withObject:nil waitUntilDone:NO]; + } } /** @@ -1789,6 +1842,7 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; } } +#endif /** * Update the filter search. @@ -1802,6 +1856,7 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; if (selectedTableName) [selectedTableName release], selectedTableName = nil; } +#ifndef SP_REFACTOR if ([[listFilterField stringValue] length]) { if (isTableListFiltered) { [filteredTables release]; @@ -1859,14 +1914,25 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; } else if (isTableListFiltered) { isTableListFiltered = NO; [filteredTables release]; +#endif filteredTables = tables; +#ifndef SP_REFACTOR [filteredTableTypes release]; filteredTableTypes = tableTypes; } +#endif +#ifdef SP_REFACTOR + [sidebarViewController setTableNames:[self allTableNames]]; +#endif + // Reselect correct row and reload the table view display if ([tablesListView numberOfRows] < (NSInteger)[filteredTables count]) [tablesListView noteNumberOfRowsChanged]; - if (selectedTableName) [tablesListView selectRowIndexes:[NSIndexSet indexSetWithIndex:[filteredTables indexOfObject:selectedTableName]] byExtendingSelection:NO]; + if (selectedTableName) [tablesListView selectRowIndexes:[NSIndexSet indexSetWithIndex:[filteredTables indexOfObject:selectedTableName] +#ifdef SP_REFACTOR + - 1 +#endif + ] byExtendingSelection:NO]; [tablesListView reloadData]; } @@ -1877,8 +1943,13 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; - (void) selectTableAtIndex:(NSNumber *)row { NSUInteger rowIndex = [row unsignedIntegerValue]; +#ifndef SP_REFACTOR if (rowIndex == NSNotFound || rowIndex > [filteredTables count] || [[filteredTableTypes objectAtIndex:rowIndex] integerValue] == SPTableTypeNone) return; +#else + if (rowIndex == NSNotFound) + return; +#endif [tablesListView selectRowIndexes:[NSIndexSet indexSetWithIndex:rowIndex] byExtendingSelection:NO]; } @@ -1893,8 +1964,10 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; { tableListIsSelectable = NO; [toolbarAddButton setEnabled:NO]; +#ifndef SP_REFACTOR [toolbarActionsButton setEnabled:NO]; [toolbarReloadButton setEnabled:NO]; +#endif } /** @@ -1904,8 +1977,10 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; { tableListIsSelectable = YES; [toolbarAddButton setEnabled:YES]; +#ifndef SP_REFACTOR [toolbarActionsButton setEnabled:YES]; [toolbarReloadButton setEnabled:YES]; +#endif } /** @@ -1916,6 +1991,7 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; tableListIsSelectable = isSelectable; } +#ifndef SP_REFACTOR #pragma mark - #pragma mark SplitView Delegate Methods @@ -1936,15 +2012,11 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; { if ((self = [super init])) { tables = [[NSMutableArray alloc] init]; -#ifndef SP_REFACTOR filteredTables = tables; -#endif tableTypes = [[NSMutableArray alloc] init]; -#ifndef SP_REFACTOR filteredTableTypes = tableTypes; isTableListFiltered = NO; tableListIsSelectable = YES; -#endif tableListContainsViews = NO; selectedTableType = SPTableTypeNone; selectedTableName = nil; @@ -1957,13 +2029,12 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; return self; } -#ifndef SP_REFACTOR /* awakeFromNib */ /** * Standard awakeFromNib method for interface loading. */ - (void)awakeFromNib { - +#ifndef SP_REFACTOR // Collapse the table information pane if preference to do so is set if ([[[NSUserDefaults standardUserDefaults] objectForKey:SPTableInformationPanelCollapsed] boolValue] && [tableListSplitView collapsibleSubview]) { @@ -1989,6 +2060,7 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; // Disable tab edit behaviour in the tables list [tablesListView setTabEditingDisabled:YES]; +#endif // Add observers for document task activity [[NSNotificationCenter defaultCenter] addObserver:self @@ -2000,11 +2072,10 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; name:SPDocumentTaskEndNotification object:tableDocumentInstance]; - +#ifndef SP_REFACTOR [tablesListView registerForDraggedTypes:[NSArray arrayWithObjects:SPNavigatorTableDataPasteboardDragType, nil]]; - -} #endif +} /** * Standard dealloc method. @@ -2169,12 +2240,21 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; [tableDataInstance resetStatusData]; } +#endif /** * Adds a new table table to the database using the selected character set encoding and storage engine. */ - (void)_addTable { + // Ensure the task is performed on a background thread to group addition and loads + if ([NSThread isMainThread]) { + [NSThread detachNewThreadSelector:@selector(_addTable) toTarget:self withObject:nil]; + return; + } + NSAutoreleasePool *tableAdditionPool = [[NSAutoreleasePool alloc] init]; + [tableDocumentInstance startTaskWithDescription:[NSString stringWithFormat:NSLocalizedString(@"Creating %@...", @"Creating table task string"), [tableNameField stringValue]]]; + NSString *charSetStatement = @""; NSString *engineStatement = @""; @@ -2197,7 +2277,7 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; // If there is a type selected other than the default we must specify it in CREATE TABLE statement if ([tableTypeButton indexOfSelectedItem] > 0) { - engineStatement = [NSString stringWithFormat:@"%@ = %@", [[tableDocumentInstance serverSupport] engineTypeQueryName], [tableType backtickQuotedString]]; + engineStatement = [NSString stringWithFormat:@"%@ = %@", [[tableDocumentInstance serverSupport] engineTypeQueryName], [[tableDocumentInstance serverSupport] supportsQuotingEngineTypeInCreateSyntax] ? [tableType backtickQuotedString] : tableType]; } NSString *createStatement = [NSString stringWithFormat:@"CREATE TABLE %@ (%@) %@ %@", [tableName backtickQuotedString], ([tableType isEqualToString:@"CSV"]) ? @"id INT NOT NULL" : @"id INT", charSetStatement, engineStatement]; @@ -2241,12 +2321,14 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; selectedTableName = [[NSString alloc] initWithString:tableName]; selectedTableType = SPTableTypeTable; - [self updateFilter:self]; - [tablesListView scrollRowToVisible:[tablesListView selectedRow]]; + [[self onMainThread] updateFilter:self]; + [[tablesListView onMainThread] scrollRowToVisible:[tablesListView selectedRow]]; // Select the newly created table and switch to the table structure view for easier setup [tableDocumentInstance loadTable:selectedTableName ofType:selectedTableType]; +#ifndef SP_REFACTOR [tableDocumentInstance viewStructure:self]; +#endif // Query the structure of all databases in the background (mainly for completion) [NSThread detachNewThreadSelector:@selector(queryDbStructureWithUserInfo:) toTarget:mySQLConnection withObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], @"forceUpdate", nil]]; @@ -2262,13 +2344,17 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; [NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to add the new table '%@'.\n\nMySQL said: %@", @"error adding new table informative message"), tableName, [mySQLConnection getLastErrorMessage]]); if (changeEncoding) [mySQLConnection restoreStoredEncoding]; - [tablesListView reloadData]; + [[tablesListView onMainThread] reloadData]; } // Clear table name - [tableNameField setStringValue:@""]; + [[tableNameField onMainThread] setStringValue:@""]; + + [tableDocumentInstance endTask]; + [tableAdditionPool release]; } +#ifndef SP_REFACTOR /** * Copies the currently selected object (table, view, procedure, function, etc.). */ diff --git a/Source/SPTextView.m b/Source/SPTextView.m index cb0e4d9e..65115813 100644 --- a/Source/SPTextView.m +++ b/Source/SPTextView.m @@ -27,6 +27,7 @@ #import "SPDatabaseDocument.h" #import "SPNarrowDownCompletion.h" #import "SPQueryController.h" +#import "SPQueryDocumentsController.h" #import "SPTooltip.h" #import "SPTablesList.h" #import "SPNavigatorController.h" @@ -234,22 +235,22 @@ NSInteger _alphabeticSort(id string1, id string2, void *reverse) #ifndef SP_REFACTOR if ([keyPath isEqualToString:SPCustomQueryEditorBackgroundColor]) { [self setQueryEditorBackgroundColor:[NSUnarchiver unarchiveObjectWithData:[change objectForKey:NSKeyValueChangeNewKey]]]; - [self setNeedsDisplay:YES]; + [self setNeedsDisplayInRect:[self bounds]]; } else if ([keyPath isEqualToString:SPCustomQueryEditorFont]) { [self setFont:[NSUnarchiver unarchiveObjectWithData:[change objectForKey:NSKeyValueChangeNewKey]]]; - [self setNeedsDisplay:YES]; + [self setNeedsDisplayInRect:[self bounds]]; } else if ([keyPath isEqualToString:SPCustomQueryEditorHighlightQueryColor]) { [self setQueryHiliteColor:[NSUnarchiver unarchiveObjectWithData:[change objectForKey:NSKeyValueChangeNewKey]]]; - [self setNeedsDisplay:YES]; + [self setNeedsDisplayInRect:[self bounds]]; } else if ([keyPath isEqualToString:SPCustomQueryEditorCaretColor]) { [self setInsertionPointColor:[NSUnarchiver unarchiveObjectWithData:[change objectForKey:NSKeyValueChangeNewKey]]]; - [self setNeedsDisplay:YES]; + [self setNeedsDisplayInRect:[self bounds]]; } else if ([keyPath isEqualToString:SPCustomQueryEditorSelectionColor]) { [self setSelectedTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[NSUnarchiver unarchiveObjectWithData:[change objectForKey:NSKeyValueChangeNewKey]], NSBackgroundColorAttributeName, nil]]; - [self setNeedsDisplay:YES]; + [self setNeedsDisplayInRect:[self bounds]]; } else if ([keyPath isEqualToString:SPCustomQueryHighlightCurrentQuery]) { [self setShouldHiliteQuery:[[change objectForKey:NSKeyValueChangeNewKey] boolValue]]; - [self setNeedsDisplay:YES]; + [self setNeedsDisplayInRect:[self bounds]]; } else if ([keyPath isEqualToString:SPCustomQueryEditorCommentColor]) { [self setCommentColor:[NSUnarchiver unarchiveObjectWithData:[change objectForKey:NSKeyValueChangeNewKey]]]; if([[self string] length]<100000 && [self isEditable]) @@ -2714,7 +2715,7 @@ NSInteger _alphabeticSort(id string1, id string2, void *reverse) end = strlength; } else { while(end < strlength && lengthChecker > 0) { - if([selfstr characterAtIndex:end]=='\n') + if(CFStringGetCharacterAtIndex((CFStringRef)selfstr, end)=='\n') break; end++; lengthChecker--; @@ -2735,7 +2736,7 @@ NSInteger _alphabeticSort(id string1, id string2, void *reverse) lengthChecker = SP_SYNTAX_HILITE_BIAS; if (start > 0) while(start>0 && lengthChecker > 0) { - if([selfstr characterAtIndex:start]=='\n') + if(CFStringGetCharacterAtIndex((CFStringRef)selfstr, start)=='\n') break; start--; lengthChecker--; @@ -2757,6 +2758,8 @@ NSInteger _alphabeticSort(id string1, id string2, void *reverse) textRange = NSMakeRange(0,strlength); } + // [textStore beginEditing]; + NSColor *tokenColor; size_t tokenEnd, token; @@ -2873,6 +2876,10 @@ NSInteger _alphabeticSort(id string1, id string2, void *reverse) } + // [textStore endEditing]; + + [self setNeedsDisplayInRect:[self bounds]]; + } - (void) setTabStops @@ -3261,6 +3268,12 @@ NSInteger _alphabeticSort(id string1, id string2, void *reverse) selector:@selector(doAutoCompletion) object:nil]; + // Cancel calling doSyntaxHighlighting for large text + if([[self string] length] > SP_TEXT_SIZE_TRIGGER_FOR_PARTLY_PARSING) + [NSObject cancelPreviousPerformRequestsWithTarget:self + selector:@selector(doSyntaxHighlighting) + object:nil]; + NSInteger editedMask = [textStore editedMask]; // Start autohelp only if the user really changed the text (not e.g. for setting a background color) @@ -3279,12 +3292,6 @@ NSInteger _alphabeticSort(id string1, id string2, void *reverse) [self performSelector:@selector(doAutoCompletion) withObject:nil afterDelay:1.5]; #endif - // Cancel calling doSyntaxHighlighting for large text - if([[self string] length] > SP_TEXT_SIZE_TRIGGER_FOR_PARTLY_PARSING) - [NSObject cancelPreviousPerformRequestsWithTarget:self - selector:@selector(doSyntaxHighlighting) - object:nil]; - // Do syntax highlighting/re-calculate snippet ranges only if the user really changed the text if(editedMask != 1) { @@ -3612,7 +3619,7 @@ NSInteger _alphabeticSort(id string1, id string2, void *reverse) [self setEditable:YES]; [self setFont:nf]; [self setEditable:oldEditable]; - [self setNeedsDisplay:YES]; + [self setNeedsDisplayInRect:[self bounds]]; [prefs setObject:[NSArchiver archivedDataWithRootObject:nf] forKey:SPCustomQueryEditorFont]; } #endif diff --git a/Source/SPWindow.m b/Source/SPWindow.m index 22a096c3..0285ce8a 100644 --- a/Source/SPWindow.m +++ b/Source/SPWindow.m @@ -79,13 +79,17 @@ case '}': if (([theEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask) == (NSCommandKeyMask | NSShiftKeyMask)) { - return [[self windowController] selectNextDocumentTab:self]; + if ([[self windowController] respondsToSelector:@selector(selectNextDocumentTab:)]) + [[self windowController] selectNextDocumentTab:self]; + return; } break; case '{': if (([theEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask) == (NSCommandKeyMask | NSShiftKeyMask)) { - return [[self windowController] selectPreviousDocumentTab:self]; + if ([[self windowController] respondsToSelector:@selector(selectPreviousDocumentTab:)]) + [[self windowController] selectPreviousDocumentTab:self]; + return; } break; @@ -93,13 +97,17 @@ case NSRightArrowFunctionKey: if (([theEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask) == (NSCommandKeyMask | NSAlternateKeyMask | NSNumericPadKeyMask | NSFunctionKeyMask)) { - return [[self windowController] selectNextDocumentTab:self]; + if ([[self windowController] respondsToSelector:@selector(selectNextDocumentTab:)]) + [[self windowController] selectNextDocumentTab:self]; + return; } break; case NSLeftArrowFunctionKey: if (([theEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask) == (NSCommandKeyMask | NSAlternateKeyMask | NSNumericPadKeyMask | NSFunctionKeyMask)) { - return [[self windowController] selectPreviousDocumentTab:self]; + if ([[self windowController] respondsToSelector:@selector(selectPreviousDocumentTab:)]) + [[self windowController] selectPreviousDocumentTab:self]; + return; } break; } @@ -108,5 +116,18 @@ [super sendEvent:theEvent]; } +/** + * If this window is controlled by an SPWindowController, and thus supports being asked + * for the frontmost SPTableDocument, request the undoController for that table + * document. This allows undo to be individual per tab rather than shared across the + * window. + */ +- (NSUndoManager *)undoManager +{ + if ([[self windowController] respondsToSelector:@selector(selectedTableDocument)]) { + return [[[self windowController] selectedTableDocument] undoManager]; + } + return [super undoManager]; +} @end diff --git a/Source/SPWindowController.m b/Source/SPWindowController.m index ed18335d..ca0c816e 100644 --- a/Source/SPWindowController.m +++ b/Source/SPWindowController.m @@ -22,6 +22,14 @@ // // More info at <http://code.google.com/p/sequel-pro/> +// Forward-declare for 10.7 compatibility +#if !defined(MAC_OS_X_VERSION_10_7) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7 +enum { + NSWindowCollectionBehaviorFullScreenPrimary = 1 << 7, + NSWindowCollectionBehaviorFullScreenAuxiliary = 1 << 8 +}; +#endif + #import "SPWindowController.h" #import "SPDatabaseDocument.h" #import "PSMTabDragAssistant.h" @@ -46,6 +54,8 @@ { selectedTableDocument = nil; + [[self window] setCollectionBehavior:[[self window] collectionBehavior] | NSWindowCollectionBehaviorFullScreenPrimary]; + // Disable automatic cascading - this occurs before the size is set, so let the app // controller apply cascading after frame autosaving. [self setShouldCascadeWindows:NO]; @@ -701,6 +711,22 @@ } } +/** + * If the window is entering fullscreen, update the front tab's titlebar status view visibility. + */ +- (void)windowWillEnterFullScreen:(NSNotification *)notification +{ + [selectedTableDocument updateTitlebarStatusVisibilityForcingHide:YES]; +} + +/** + * If the window exits fullscreen, update the front tab's titlebar status view visibility. + */ +- (void)windowDidExitFullScreen:(NSNotification *)notification +{ + [selectedTableDocument updateTitlebarStatusVisibilityForcingHide:NO]; +} + #pragma mark - #pragma mark First responder forwarding to active tab |