diff options
136 files changed, 9896 insertions, 13112 deletions
diff --git a/Source/MGTemplateEngine.m b/Source/MGTemplateEngine.m index 4fe7620b..c10773bf 100644 --- a/Source/MGTemplateEngine.m +++ b/Source/MGTemplateEngine.m @@ -28,7 +28,7 @@ #define GLOBAL_DELIM_EXPR_END @"expressionEnd" #define GLOBAL_DELIM_FILTER @"filter" -@interface MGTemplateEngine (PrivateMethods) +@interface MGTemplateEngine () - (NSObject *)valueForVariable:(NSString *)var parent:(NSObject **)parent parentKey:(NSString **)parentKey; - (void)setValue:(NSObject *)newValue forVariable:(NSString *)var forceCurrentStackFrame:(BOOL)inStackFrame; diff --git a/Source/NoodleLineNumberView.m b/Source/NoodleLineNumberView.m index 8b678434..3894ac8f 100644 --- a/Source/NoodleLineNumberView.m +++ b/Source/NoodleLineNumberView.m @@ -53,7 +53,7 @@ typedef NSRange (*RangeOfLineIMP)(id object, SEL selector, NSRange range); #pragma mark - -@interface NoodleLineNumberView (Private) +@interface NoodleLineNumberView () - (NSArray *)lineIndices; - (void)invalidateLineIndices; diff --git a/Source/SPActivityTextFieldCell.m b/Source/SPActivityTextFieldCell.m index b09454bb..84bf37e9 100644 --- a/Source/SPActivityTextFieldCell.m +++ b/Source/SPActivityTextFieldCell.m @@ -33,7 +33,7 @@ #define FAVORITE_NAME_FONT_SIZE 12.0f -@interface SPActivityTextFieldCell (PrivateAPI) +@interface SPActivityTextFieldCell () - (NSAttributedString *)constructSubStringAttributedString; - (NSAttributedString *)attributedStringForFavoriteName; @@ -332,9 +332,7 @@ static inline NSRect SPTextLinkRectFromCellRect(NSRect inRect) [super dealloc]; } -@end - -@implementation SPActivityTextFieldCell (PrivateAPI) +#pragma mark - Private API /** * Constructs the attributed string to be used as the cell's substring. diff --git a/Source/SPAppController.h b/Source/SPAppController.h index 4dd93839..818a781c 100644 --- a/Source/SPAppController.h +++ b/Source/SPAppController.h @@ -37,6 +37,7 @@ @class SPAboutController; @class SPDatabaseDocument; @class SPBundleEditorController; +@class SPWindowController; @interface SPAppController : NSObject <FRFeedbackReporterDelegate, NSApplicationDelegate, NSOpenSavePanelDelegate, NSFileManagerDelegate> { @@ -116,4 +117,25 @@ - (void)addHTMLOutputController:(id)controller; - (void)removeHTMLOutputController:(id)controller; +#pragma mark - SPAppleScriptSupport + +- (NSArray *)orderedDocuments; +- (void)insertInOrderedDocuments:(SPDatabaseDocument *)doc; +- (NSArray *)orderedWindows; +- (id)handleQuitScriptCommand:(NSScriptCommand *)command; +- (id)handleOpenScriptCommand:(NSScriptCommand *)command; + +#pragma mark - SPWindowManagement + +- (IBAction)newWindow:(id)sender; +- (IBAction)newTab:(id)sender; +- (IBAction)duplicateTab:(id)sender; + +- (SPWindowController *)newWindow; +- (SPDatabaseDocument *)makeNewConnectionTabOrWindow; +- (SPWindowController *)frontController; + +- (NSWindow *)frontDocumentWindow; +- (void)tabDragStarted:(id)sender; + @end diff --git a/Source/SPAppController.m b/Source/SPAppController.m index 657af299..2e62725f 100644 --- a/Source/SPAppController.m +++ b/Source/SPAppController.m @@ -47,7 +47,6 @@ #import "SPFavoritesController.h" #import "SPEditorTokens.h" #import "SPBundleCommandRunner.h" -#import "SPWindowManagement.h" #import "SPCopyTable.h" #import "SPSyntaxParser.h" #import "SPOSInfo.h" @@ -2362,6 +2361,244 @@ } } +#pragma mark - SPAppleScriptSupport + +/** + * Is needed to interact with AppleScript for set/get internal SP variables + */ +- (BOOL)application:(NSApplication *)sender delegateHandlesKey:(NSString *)key +{ + NSLog(@"Not yet implemented."); + + return NO; +} + +/** + * AppleScript call to get the available documents. + */ +- (NSArray *)orderedDocuments +{ + NSMutableArray *orderedDocuments = [NSMutableArray array]; + + for (NSWindow *aWindow in [self orderedWindows]) + { + if ([[aWindow windowController] isMemberOfClass:[SPWindowController class]]) { + [orderedDocuments addObjectsFromArray:[[aWindow windowController] documents]]; + } + } + + return orderedDocuments; +} + +/** + * AppleScript support for 'make new document'. + * + * TODO: following tab support this has been disabled - need to discuss reimplmenting vs syntax. + */ +- (void)insertInOrderedDocuments:(SPDatabaseDocument *)doc +{ + [self newWindow:self]; + + // Set autoconnection if appropriate + if ([[NSUserDefaults standardUserDefaults] boolForKey:SPAutoConnectToDefault]) { + [[self frontDocument] connect]; + } +} + +/** + * AppleScript call to get the available windows. + */ +- (NSArray *)orderedWindows +{ + return [NSApp orderedWindows]; +} + +/** + * AppleScript handler to quit Sequel Pro + * + * This handler is required to allow termination via the Dock or AppleScript event after activating it using AppleScript + */ +- (id)handleQuitScriptCommand:(NSScriptCommand *)command +{ + [NSApp terminate:self]; + + return nil; +} + +/** + * AppleScript open handler + * + * This handler is required to catch the 'open' command if no argument was passed which would cause a crash. + */ +- (id)handleOpenScriptCommand:(NSScriptCommand *)command +{ + return nil; +} + +/** + * AppleScript print handler + * + * This handler prints the active view. + */ +- (id)handlePrintScriptCommand:(NSScriptCommand *)command +{ + SPDatabaseDocument *frontDoc = [self frontDocument]; + + if (frontDoc && ![frontDoc isWorking] && ![[frontDoc connectionID] isEqualToString:@"_"]) { + [frontDoc startPrintDocumentOperation]; + } + + return nil; +} + +#pragma mark - SPWindowManagement + +- (IBAction)newWindow:(id)sender +{ + [self newWindow]; +} + +/** + * Create a new window, containing a single tab. + */ +- (SPWindowController *)newWindow +{ + static NSPoint cascadeLocation = {.x = 0, .y = 0}; + + // Create a new window controller, and set up a new connection view within it. + SPWindowController *newWindowController = [[SPWindowController alloc] initWithWindowNibName:@"MainWindow"]; + NSWindow *newWindow = [newWindowController window]; + + // Cascading defaults to on - retrieve the window origin automatically assigned by cascading, + // and convert to a top left point. + NSPoint topLeftPoint = [newWindow frame].origin; + topLeftPoint.y += [newWindow frame].size.height; + + // The first window should use autosaving; subsequent windows should cascade. + // So attempt to set the frame autosave name; this will succeed for the very + // first window, and fail for others. + BOOL usedAutosave = [newWindow setFrameAutosaveName:@"DBView"]; + + if (!usedAutosave) { + [newWindow setFrameUsingName:@"DBView"]; + } + + // Add the connection view + [newWindowController addNewConnection]; + + // Cascade according to the statically stored cascade location. + cascadeLocation = [newWindow cascadeTopLeftFromPoint:cascadeLocation]; + + // Set the window controller as the window's delegate + [newWindow setDelegate:newWindowController]; + + // Show the window, and perform frontmost tasks again once the window has drawn + [newWindowController showWindow:self]; + [[newWindowController selectedTableDocument] didBecomeActiveTabInWindow]; + + return newWindowController; +} + +/** + * Create a new tab in the frontmost window. + */ +- (IBAction)newTab:(id)sender +{ + SPWindowController *frontController = [self frontController]; + + // If no window was found, create a new one + if (!frontController) { + [self newWindow:self]; + } + else { + if ([[frontController window] isMiniaturized]) { + [[frontController window] deminiaturize:self]; + } + + [frontController addNewConnection:self]; + } +} + +- (SPDatabaseDocument *)makeNewConnectionTabOrWindow +{ + SPWindowController *frontController = [self frontController]; + + SPDatabaseDocument *frontDocument; + // If no window was found or the front most window has no tabs, create a new one + if (!frontController || [[frontController valueForKeyPath:@"tabView"] numberOfTabViewItems] == 1) { + frontController = [self newWindow]; + frontDocument = [frontController selectedTableDocument]; + } + // Open the spf file in a new tab if the tab bar is visible + else { + if ([[frontController window] isMiniaturized]) [[frontController window] deminiaturize:self]; + frontDocument = [frontController addNewConnection]; + } + + return frontDocument; +} + +/** + * Duplicate the current connection tab + */ +- (IBAction)duplicateTab:(id)sender +{ + SPDatabaseDocument *theFrontDocument = [self frontDocument]; + + if (!theFrontDocument) return [self newTab:sender]; + + // Add a new tab to the window + if ([[self frontDocumentWindow] isMiniaturized]) { + [[self frontDocumentWindow] deminiaturize:self]; + } + + SPDatabaseDocument *newConnection = [[self frontController] addNewConnection]; + + // Get the state of the previously-frontmost document + NSDictionary *allStateDetails = @{ + @"connection" : @YES, + @"history" : @YES, + @"session" : @YES, + @"query" : @YES, + @"password" : @YES + }; + + NSMutableDictionary *frontState = [NSMutableDictionary dictionaryWithDictionary:[theFrontDocument stateIncludingDetails:allStateDetails]]; + + // Ensure it's set to autoconnect + [frontState setObject:@YES forKey:@"auto_connect"]; + + // Set the connection on the new tab + [newConnection setState:frontState]; +} + +/** + * Retrieve the frontmost document window; returns nil if not found. + */ +- (NSWindow *)frontDocumentWindow +{ + return [[self frontController] window]; +} + +- (SPWindowController *)frontController +{ + for (NSWindow *aWindow in [NSApp orderedWindows]) { + id ctr = [aWindow windowController]; + if ([ctr isMemberOfClass:[SPWindowController class]]) { + return ctr; + } + } + return nil; +} + +/** + * When tab drags start, bring all the windows in front of other applications. + */ +- (void)tabDragStarted:(id)sender +{ + [NSApp arrangeInFront:self]; +} + #pragma mark - - (void)dealloc diff --git a/Source/SPAppleScriptSupport.h b/Source/SPAppleScriptSupport.h deleted file mode 100644 index 463a4b19..00000000 --- a/Source/SPAppleScriptSupport.h +++ /dev/null @@ -1,41 +0,0 @@ -// -// SPAppleScriptSupport.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on October 14, 2010. -// Copyright (c) 2010 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPAppController.h" - -@interface SPAppController (SPAppleScriptSupport) - -- (NSArray *)orderedDocuments; -- (void)insertInOrderedDocuments:(SPDatabaseDocument *)doc; -- (NSArray *)orderedWindows; -- (id)handleQuitScriptCommand:(NSScriptCommand *)command; -- (id)handleOpenScriptCommand:(NSScriptCommand *)command; - -@end diff --git a/Source/SPAppleScriptSupport.m b/Source/SPAppleScriptSupport.m deleted file mode 100644 index 17b9e436..00000000 --- a/Source/SPAppleScriptSupport.m +++ /dev/null @@ -1,128 +0,0 @@ -// -// SPAppleScriptSupport.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on October 14, 2010. -// Copyright (c) 2010 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPAppleScriptSupport.h" -#import "SPWindowController.h" -#import "SPAppController.h" -#import "SPPrintController.h" -#import "SPDatabaseDocument.h" -#import "SPWindowManagement.h" - -@implementation SPAppController (SPAppleScriptSupport) - -/** - * Is needed to interact with AppleScript for set/get internal SP variables - */ -- (BOOL)application:(NSApplication *)sender delegateHandlesKey:(NSString *)key -{ - NSLog(@"Not yet implemented."); - - return NO; -} - -/** - * AppleScript call to get the available documents. - */ -- (NSArray *)orderedDocuments -{ - NSMutableArray *orderedDocuments = [NSMutableArray array]; - - for (NSWindow *aWindow in [self orderedWindows]) - { - if ([[aWindow windowController] isMemberOfClass:[SPWindowController class]]) { - [orderedDocuments addObjectsFromArray:[[aWindow windowController] documents]]; - } - } - - return orderedDocuments; -} - -/** - * AppleScript support for 'make new document'. - * - * TODO: following tab support this has been disabled - need to discuss reimplmenting vs syntax. - */ -- (void)insertInOrderedDocuments:(SPDatabaseDocument *)doc -{ - [self newWindow:self]; - - // Set autoconnection if appropriate - if ([[NSUserDefaults standardUserDefaults] boolForKey:SPAutoConnectToDefault]) { - [[self frontDocument] connect]; - } -} - -/** - * AppleScript call to get the available windows. - */ -- (NSArray *)orderedWindows -{ - return [NSApp orderedWindows]; -} - -/** - * AppleScript handler to quit Sequel Pro - * - * This handler is required to allow termination via the Dock or AppleScript event after activating it using AppleScript - */ -- (id)handleQuitScriptCommand:(NSScriptCommand *)command -{ - [NSApp terminate:self]; - - return nil; -} - -/** - * AppleScript open handler - * - * This handler is required to catch the 'open' command if no argument was passed which would cause a crash. - */ -- (id)handleOpenScriptCommand:(NSScriptCommand *)command -{ - return nil; -} - -/** - * AppleScript print handler - * - * This handler prints the active view. - */ -- (id)handlePrintScriptCommand:(NSScriptCommand *)command -{ - SPDatabaseDocument *frontDoc = [self frontDocument]; - - if (frontDoc && ![frontDoc isWorking] && ![[frontDoc connectionID] isEqualToString:@"_"]) { - [frontDoc startPrintDocumentOperation]; - } - - return nil; -} - -@end diff --git a/Source/SPCSVExporterDelegate.h b/Source/SPCSVExporterDelegate.h deleted file mode 100644 index b20d9cfb..00000000 --- a/Source/SPCSVExporterDelegate.h +++ /dev/null @@ -1,43 +0,0 @@ -// -// SPCSVExporterDelegate.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on March 21, 2010. -// Copyright (c) 2010 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPExportController.h" -#import "SPCSVExporterProtocol.h" - -/** - * @category SPCSVExporterDelegate SPCSVExporterDelegate.h - * - * @author Stuart Connolly http://stuconnolly.com/ - * - * CSV exporter delegate category. - */ -@interface SPExportController (SPCSVExporterDelegate) <SPCSVExporterProtocol> - -@end diff --git a/Source/SPCSVExporterDelegate.m b/Source/SPCSVExporterDelegate.m deleted file mode 100644 index 38176776..00000000 --- a/Source/SPCSVExporterDelegate.m +++ /dev/null @@ -1,124 +0,0 @@ -// -// SPCSVExporterDelegate.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on March 21, 2010. -// Copyright (c) 2010 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPCSVExporter.h" -#import "SPCSVExporterDelegate.h" -#import "SPExportFile.h" -#import "SPExportInitializer.h" - -@implementation SPExportController (SPCSVExporterDelegate) - -- (void)csvExportProcessWillBegin:(SPCSVExporter *)exporter -{ - [exportProgressText displayIfNeeded]; - - [exportProgressIndicator setIndeterminate:YES]; - [exportProgressIndicator setUsesThreadedAnimation:YES]; - [exportProgressIndicator startAnimation:self]; - - // Only update the progress text if this is a table export - if (exportSource == SPTableExport) { - // Update the current table export index - currentTableExportIndex = (exportTableCount - [exporters count]); - - [exportProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %lu (%@): Fetching data...", @"export label showing that the app is fetching data for a specific table"), currentTableExportIndex, exportTableCount, [exporter csvTableName]]]; - } - else { - [exportProgressText setStringValue:NSLocalizedString(@"Fetching data...", @"export label showing that the app is fetching data")]; - } - - [exportProgressText displayIfNeeded]; -} - -- (void)csvExportProcessComplete:(SPCSVExporter *)exporter -{ - NSUInteger exportCount = [exporters count]; - - // If required add the next exporter to the operation queue - if ((exportCount > 0) && (exportSource == SPTableExport)) { - - // If we're only exporting to a single file then write a header for the next table - if (!exportToMultipleFiles) { - - // If we're exporting multiple tables to a single file then append some space and the next table's - // name, but only if there is at least 2 exportes left. - [[exporter exportOutputFile] writeData:[[NSString stringWithFormat:@"%@%@%@ %@%@%@", - [exporter csvLineEndingString], - [exporter csvLineEndingString], - NSLocalizedString(@"Table", @"csv export table heading"), - [(SPCSVExporter *)[exporters objectAtIndex:0] csvTableName], - [exporter csvLineEndingString], - [exporter csvLineEndingString]] dataUsingEncoding:[exporter exportOutputEncoding]]]; - } - // Otherwise close the file handle of the exporter that just finished - // ensuring it's data is written to disk. - else { - [[exporter exportOutputFile] close]; - } - - [operationQueue addOperation:[exporters objectAtIndex:0]]; - - // Remove the exporter we just added to the operation queue from our list of exporters - // so we know it's already been done. - [exporters removeObjectAtIndex:0]; - } - // Otherwise if the exporter list is empty, close the progress sheet - else { - // Close the last exporter's file handle - [[exporter exportOutputFile] close]; - - [self exportEnded]; - } -} - -- (void)csvExportProcessWillBeginWritingData:(SPCSVExporter *)exporter -{ - // Only update the progress text if this is a table export - if (exportSource == SPTableExport) { - [exportProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %lu (%@): Writing data...", @"export label showing app if writing data for a specific table"), currentTableExportIndex, exportTableCount, [exporter csvTableName]]]; - } - else { - [exportProgressText setStringValue:NSLocalizedString(@"Writing data...", @"export label showing app is writing data")]; - } - - [exportProgressText displayIfNeeded]; - - [exportProgressIndicator stopAnimation:self]; - [exportProgressIndicator setUsesThreadedAnimation:NO]; - [exportProgressIndicator setIndeterminate:NO]; - [exportProgressIndicator setDoubleValue:0]; -} - -- (void)csvExportProcessProgressUpdated:(SPCSVExporter *)exporter -{ - [exportProgressIndicator setDoubleValue:[exporter exportProgressValue]]; -} - -@end diff --git a/Source/SPCSVParser.m b/Source/SPCSVParser.m index 819c34b7..0dee279d 100644 --- a/Source/SPCSVParser.m +++ b/Source/SPCSVParser.m @@ -34,7 +34,7 @@ * Please see the header files for a general description of the purpose of this class. */ -@implementation SPCSVParser : NSObject +@implementation SPCSVParser #pragma mark - #pragma mark Retrieving data from the CSV string diff --git a/Source/SPCharsetCollationHelper.m b/Source/SPCharsetCollationHelper.m index 72387597..487c8a0b 100644 --- a/Source/SPCharsetCollationHelper.m +++ b/Source/SPCharsetCollationHelper.m @@ -33,7 +33,7 @@ #import "SPServerSupport.h" #import "SPDatabaseData.h" -@interface SPCharsetCollationHelper (Hidden) +@interface SPCharsetCollationHelper () - (void)charsetButtonClicked:(id)sender; - (void)collationButtonClicked:(id)sender; diff --git a/Source/SPColorSelectorView.h b/Source/SPColorSelectorView.h index 925034a3..5211bb86 100644 --- a/Source/SPColorSelectorView.h +++ b/Source/SPColorSelectorView.h @@ -70,8 +70,8 @@ @end -@interface NSObject (SPColorSelectorViewDelegate) - +@protocol SPColorSelectorViewDelegate <NSObject> +@optional /** * Called on a delegate when the selection did (really) change * @param aView The changed view diff --git a/Source/SPColorSelectorView.m b/Source/SPColorSelectorView.m index ba577170..15c55f57 100644 --- a/Source/SPColorSelectorView.m +++ b/Source/SPColorSelectorView.m @@ -43,7 +43,7 @@ #import "SPColorSelectorView.h" #import "SPOSInfo.h" -@interface SPColorSelectorView (Private) +@interface SPColorSelectorView () - (void)setupTrackingAreas; - (void)_drawDotBevelStyleWithGradient:(NSGradient *)gradient insideRect:(NSRect)colorSquareRect; @@ -414,7 +414,7 @@ enum trackingAreaIDs } if (delegate != nil && [delegate respondsToSelector:@selector(colorSelectorDidChange:)]) { - [delegate colorSelectorDidChange:self]; + [(id<SPColorSelectorViewDelegate>)delegate colorSelectorDidChange:self]; } [self setNeedsDisplay:YES]; diff --git a/Source/SPComboPopupButton.h b/Source/SPComboPopupButton.h index a3b34b10..035ab8d7 100644 --- a/Source/SPComboPopupButton.h +++ b/Source/SPComboPopupButton.h @@ -44,7 +44,3 @@ @property(readonly, assign) NSUInteger lineOffset; @end - -@interface SPComboPopupButtonCell : NSPopUpButtonCell - -@end diff --git a/Source/SPComboPopupButton.m b/Source/SPComboPopupButton.m index 3df3d6ec..e1506322 100644 --- a/Source/SPComboPopupButton.m +++ b/Source/SPComboPopupButton.m @@ -34,12 +34,16 @@ #define kSPComboPopupButtonLineOffsetSmall 15; #define kSPComboPopupButtonLineOffsetRegular 17; -@interface SPComboPopupButton (PrivateAPI) +@interface SPComboPopupButton () - (void)_initCustomData; @end +@interface SPComboPopupButtonCell : NSPopUpButtonCell + +@end + @implementation SPComboPopupButton @synthesize shouldDrawNonHighlightState; @@ -184,12 +188,8 @@ menuIsOpen = NO; } -@end - #pragma mark - -@implementation SPComboPopupButton (PrivateAPI) - - (void)_initCustomData { @@ -221,12 +221,6 @@ #pragma mark - -@interface SPComboPopupButtonCell (PrivateAPI) - -- (void)_initCustomData; - -@end - @implementation SPComboPopupButtonCell /** diff --git a/Source/SPConnectionController.h b/Source/SPConnectionController.h index dd7ac6e5..2da8f58a 100644 --- a/Source/SPConnectionController.h +++ b/Source/SPConnectionController.h @@ -1,5 +1,5 @@ // -// SPConnectionHandler.h +// SPConnectionController.h // sequel-pro // // Created by Stuart Connolly (stuconnolly.com) on November 15, 2010. @@ -30,6 +30,7 @@ #import "SPConnectionControllerDelegateProtocol.h" #import "SPFavoritesExportProtocol.h" +#import "SPFavoritesImportProtocol.h" #import <SPMySQL/SPMySQL.h> @@ -48,7 +49,7 @@ #endif ; -@interface SPConnectionController : NSViewController <SPMySQLConnectionDelegate, NSOpenSavePanelDelegate, SPFavoritesExportProtocol, NSSplitViewDelegate> +@interface SPConnectionController : NSViewController <SPMySQLConnectionDelegate, NSOpenSavePanelDelegate, SPFavoritesImportProtocol, SPFavoritesExportProtocol, NSSplitViewDelegate> { id <SPConnectionControllerDelegateProtocol, NSObject> delegate; @@ -266,4 +267,27 @@ - (SPFavoritesOutlineView *)favoritesOutlineView; #endif + +#pragma mark - SPConnectionHandler + +- (void)initiateMySQLConnection; +- (void)initiateMySQLConnectionInBackground; +- (void)initiateSSHTunnelConnection; + +- (void)mySQLConnectionEstablished; +- (void)sshTunnelCallback:(SPSSHTunnel *)theTunnel; + +- (void)addConnectionToDocument; + +- (void)failConnectionWithTitle:(NSString *)theTitle errorMessage:(NSString *)theErrorMessage detail:(NSString *)errorDetail rawErrorText:(NSString *)rawErrorText; + +#pragma mark - SPConnectionControllerInitializer + +- (id)initWithDocument:(SPDatabaseDocument *)document; + +- (void)loadNib; +- (void)registerForNotifications; +- (void)setUpFavoritesOutlineView; +- (void)setUpSelectedConnectionFavorite; + @end diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index 1ccde091..018ecf93 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -1,5 +1,5 @@ // -// SPConnectionHandler.m +// SPConnectionController.m // sequel-pro // // Created by Stuart Connolly (stuconnolly.com) on November 15, 2010. @@ -29,9 +29,7 @@ // More info at <https://github.com/sequelpro/sequelpro> #import "SPConnectionController.h" -#import "SPConnectionHandler.h" #import "SPDatabaseDocument.h" - #ifndef SP_CODA /* headers */ #import "SPAppController.h" #import "SPPreferenceController.h" @@ -45,7 +43,6 @@ #import "SPFavoritesController.h" #import "SPFavoriteNode.h" #import "SPGeneralPreferencePane.h" -#import "SPDatabaseViewController.h" #import "SPTreeNode.h" #import "SPFavoritesExporter.h" #import "SPFavoritesImporter.h" @@ -54,6 +51,13 @@ #import "SPNamedNode.h" #import "SPWindowController.h" #import "SPFavoritesOutlineView.h" +#import "SPCategoryAdditions.h" +#ifndef SP_CODA +#import "SPFavoriteTextFieldCell.h" +#import "SPGroupNode.h" +#endif +#import "SPSplitView.h" +#import "SPColorSelectorView.h" #import <SPMySQL/SPMySQL.h> @@ -61,6 +65,13 @@ #ifndef SP_CODA static NSString *SPRemoveNode = @"RemoveNode"; static NSString *SPExportFavoritesFilename = @"SequelProFavorites.plist"; +static NSString *SPLocalhostAddress = @"127.0.0.1"; + +static NSString *SPDatabaseImage = @"database-small"; +static NSString *SPQuickConnectImage = @"quick-connect-icon.pdf"; +static NSString *SPQuickConnectImageWhite = @"quick-connect-icon-white.pdf"; + +static NSString *SPConnectionViewNibName = @"ConnectionView"; #endif /** @@ -106,12 +117,24 @@ static BOOL FindLinesInFile(NSData *fileData,const void *first,size_t first_len, static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, void *key); #endif -@end - -@interface SPConnectionController (SPConnectionControllerDelegate) +#pragma mark - SPConnectionControllerDelegate - (void)_stopEditingConnection; +#pragma mark - SPConnectionHandlerPrivateAPI + +- (void)_showConnectionTestResult:(NSString *)resultString; + +#pragma mark - SPConnectionControllerDelegate_Private_API + +- (void)_setNodeIsExpanded:(BOOL)expanded fromNotification:(NSNotification *)notification; + +#pragma mark - SPConnectionControllerInitializer_Private_API + +- (void)_restoreOutlineViewStateNode:(SPTreeNode *)node; +- (void)_processFavoritesDataChange:(NSNotification *)aNotification; +- (void)scrollViewFrameChanged:(NSNotification *)aNotification; + @end @implementation SPConnectionController @@ -1968,6 +1991,1590 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, if (sshTunnel) [sshTunnel setConnectionStateChangeSelector:nil delegate:nil], SPClear(sshTunnel); } +#pragma mark - SPConnectionHandler + +/** + * Set up the MySQL connection, either through a successful tunnel or directly in the background. + */ +- (void)initiateMySQLConnection +{ +#ifndef SP_CODA + if (isTestingConnection) { + if (sshTunnel) { + [progressIndicatorText setStringValue:NSLocalizedString(@"Testing MySQL...", @"MySQL connection test very short status message")]; + } + else { + [progressIndicatorText setStringValue:NSLocalizedString(@"Testing connection...", @"Connection test very short status message")]; + } + } + else if (sshTunnel) { + [progressIndicatorText setStringValue:NSLocalizedString(@"MySQL connecting...", @"MySQL connecting very short status message")]; + } + else { + [progressIndicatorText setStringValue:NSLocalizedString(@"Connecting...", @"Generic connecting very short status message")]; + } + + [progressIndicatorText display]; + + [connectButton setTitle:NSLocalizedString(@"Cancel", @"cancel button")]; + [connectButton setAction:@selector(cancelConnection:)]; + [connectButton setEnabled:YES]; + [connectButton display]; +#endif + + [NSThread detachNewThreadWithName:SPCtxt(@"SPConnectionController MySQL connection task", dbDocument) target:self selector:@selector(initiateMySQLConnectionInBackground) object:nil]; +} + +/** + * Initiates the core of the MySQL connection process on a background thread. + */ +- (void)initiateMySQLConnectionInBackground +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + mySQLConnection = [[SPMySQLConnection alloc] init]; + + // Set up shared details + [mySQLConnection setUsername:[self user]]; + + // Initialise to socket if appropriate. + if ([self type] == SPSocketConnection) { + [mySQLConnection setUseSocket:YES]; + [mySQLConnection setSocketPath:[self socket]]; + + // Otherwise, initialise to host, using tunnel if appropriate + } + else { + [mySQLConnection setUseSocket:NO]; + + if ([self type] == SPSSHTunnelConnection) { + [mySQLConnection setHost:@"127.0.0.1"]; + + [mySQLConnection setPort:[sshTunnel localPort]]; + [mySQLConnection setProxy:sshTunnel]; + } + else { + [mySQLConnection setHost:[self host]]; + + if ([[self port] length]) [mySQLConnection setPort:[[self port] integerValue]]; + } + } + + // Only set the password if there is no Keychain item set and the connection is not being tested. + // The connection will otherwise ask the delegate for passwords in the Keychain. + if ((!connectionKeychainItemName || isTestingConnection) && [self password]) { + [mySQLConnection setPassword:[self password]]; + } + + // Enable SSL if set + if ([self useSSL]) { + [mySQLConnection setUseSSL:YES]; + + if ([self sslKeyFileLocationEnabled]) { + [mySQLConnection setSslKeyFilePath:[self sslKeyFileLocation]]; + } + + if ([self sslCertificateFileLocationEnabled]) { + [mySQLConnection setSslCertificatePath:[self sslCertificateFileLocation]]; + } + + if ([self sslCACertFileLocationEnabled]) { + [mySQLConnection setSslCACertificatePath:[self sslCACertFileLocation]]; + } + + NSString *userSSLCipherList = [prefs stringForKey:SPSSLCipherListKey]; + if(userSSLCipherList) { + //strip out disabled ciphers (e.g. in "foo:bar:--:baz") + NSRange markerPos = [userSSLCipherList rangeOfRegex:@":?--"]; + if(markerPos.location != NSNotFound) { + userSSLCipherList = [userSSLCipherList substringToIndex:markerPos.location]; + } + [mySQLConnection setSslCipherList:userSSLCipherList]; + } + } + + if(![self useCompression]) + [mySQLConnection removeClientFlags:SPMySQLClientFlagCompression]; + + // Connection delegate must be set before actual connection attempt is made + [mySQLConnection setDelegate:dbDocument]; + + // Set whether or not we should enable delegate logging according to the prefs + [mySQLConnection setDelegateQueryLogging:[prefs boolForKey:SPConsoleEnableLogging]]; + + // Set options from preferences + [mySQLConnection setTimeout:[[prefs objectForKey:SPConnectionTimeoutValue] integerValue]]; + [mySQLConnection setUseKeepAlive:[[prefs objectForKey:SPUseKeepAlive] boolValue]]; + [mySQLConnection setKeepAliveInterval:[[prefs objectForKey:SPKeepAliveInterval] floatValue]]; + + // Connect + [mySQLConnection connect]; + + if (![mySQLConnection isConnected]) { + if (sshTunnel && !cancellingConnection) { + + // This is a race condition we cannot fix "properly": + // For meaningful error handling we need to also consider the debug output from the SSH connection. + // The SSH debug output might be sligthly delayed though (flush, delegates, ...) or + // there might not even by any output at all (when it is purely a libmysql issue). + // TL;DR: No guaranteed events we could wait for, just trying our luck. + [NSThread sleepForTimeInterval:0.1]; // 100ms + + // If the state is connection refused, attempt the MySQL connection again with the host using the hostfield value. + if ([sshTunnel state] == SPMySQLProxyForwardingFailed) { + if ([sshTunnel localPortFallback]) { + [mySQLConnection setPort:[sshTunnel localPortFallback]]; + [mySQLConnection connect]; + + if (![mySQLConnection isConnected]) { + [NSThread sleepForTimeInterval:0.1]; //100ms + } + } + } + } + + if (![mySQLConnection isConnected]) { + if (!cancellingConnection) { + NSString *errorMessage = @""; + if (sshTunnel && [sshTunnel state] == SPMySQLProxyForwardingFailed) { + errorMessage = [NSString stringWithFormat:NSLocalizedString(@"Unable to connect to host %@ because the port connection via SSH was refused.\n\nPlease ensure that your MySQL host is set up to allow TCP/IP connections (no --skip-networking) and is configured to allow connections from the host you are tunnelling via.\n\nYou may also want to check the port is correct and that you have the necessary privileges.\n\nChecking the error detail will show the SSH debug log which may provide more details.\n\nMySQL said: %@", @"message of panel when SSH port forwarding failed"), [self host], [mySQLConnection lastErrorMessage]]; + [[self onMainThread] failConnectionWithTitle:NSLocalizedString(@"SSH port forwarding failed", @"title when ssh tunnel port forwarding failed") errorMessage:errorMessage detail:[sshTunnel debugMessages] rawErrorText:[mySQLConnection lastErrorMessage]]; + } + else if ([mySQLConnection lastErrorID] == 1045) { // "Access denied" error + errorMessage = [NSString stringWithFormat:NSLocalizedString(@"Unable to connect to host %@ because access was denied.\n\nDouble-check your username and password and ensure that access from your current location is permitted.\n\nMySQL said: %@", @"message of panel when connection to host failed due to access denied error"), [self host], [mySQLConnection lastErrorMessage]]; + [[self onMainThread] failConnectionWithTitle:NSLocalizedString(@"Access denied!", @"connection failed due to access denied title") errorMessage:errorMessage detail:nil rawErrorText:[mySQLConnection lastErrorMessage]]; + } + else if ([self type] == SPSocketConnection && (![self socket] || ![[self socket] length]) && ![mySQLConnection socketPath]) { + errorMessage = [NSString stringWithFormat:NSLocalizedString(@"The socket file could not be found in any common location. Please supply the correct socket location.\n\nMySQL said: %@", @"message of panel when connection to socket failed because optional socket could not be found"), [mySQLConnection lastErrorMessage]]; + [[self onMainThread] failConnectionWithTitle:NSLocalizedString(@"Socket not found!", @"socket not found title") errorMessage:errorMessage detail:nil rawErrorText:[mySQLConnection lastErrorMessage]]; + } + else if ([self type] == SPSocketConnection) { + errorMessage = [NSString stringWithFormat:NSLocalizedString(@"Unable to connect via the socket, or the request timed out.\n\nDouble-check that the socket path is correct and that you have the necessary privileges, and that the server is running.\n\nMySQL said: %@", @"message of panel when connection to host failed"), [mySQLConnection lastErrorMessage]]; + [[self onMainThread] failConnectionWithTitle:NSLocalizedString(@"Socket connection failed!", @"socket connection failed title") errorMessage:errorMessage detail:nil rawErrorText:[mySQLConnection lastErrorMessage]]; + } + else { + errorMessage = [NSString stringWithFormat:NSLocalizedString(@"Unable to connect to host %@, or the request timed out.\n\nBe sure that the address is correct and that you have the necessary privileges, or try increasing the connection timeout (currently %ld seconds).\n\nMySQL said: %@", @"message of panel when connection to host failed"), [self host], (long)[[prefs objectForKey:SPConnectionTimeoutValue] integerValue], [mySQLConnection lastErrorMessage]]; + [[self onMainThread] failConnectionWithTitle:NSLocalizedString(@"Connection failed!", @"connection failed title") errorMessage:errorMessage detail:nil rawErrorText:[mySQLConnection lastErrorMessage]]; + } + } + + // Tidy up + isConnecting = NO; + + if (sshTunnel) [sshTunnel disconnect], SPClear(sshTunnel); + + SPClear(mySQLConnection); +#ifndef SP_CODA + if (!cancellingConnection) [self _restoreConnectionInterface]; +#endif + [pool release]; + + return; + } + } + + if ([self database] && ![[self database] isEqualToString:@""]) { + if (![mySQLConnection selectDatabase:[self database]]) { + if (!isTestingConnection) { + [[self onMainThread] failConnectionWithTitle:NSLocalizedString(@"Could not select database", @"message when database selection failed") errorMessage:[NSString stringWithFormat:NSLocalizedString(@"Connected to host, but unable to connect to database %@.\n\nBe sure that the database exists and that you have the necessary privileges.\n\nMySQL said: %@", @"message of panel when connection to db failed"), [self database], [mySQLConnection lastErrorMessage]] detail:nil rawErrorText:[mySQLConnection lastErrorMessage]]; + } + + // Tidy up + isConnecting = NO; + + if (sshTunnel) SPClear(sshTunnel); + + SPClear(mySQLConnection); + [self _restoreConnectionInterface]; + if (isTestingConnection) { + [self _showConnectionTestResult:NSLocalizedString(@"Invalid database", @"Invalid database very short status message")]; + } + + [pool release]; + + return; + } + } + + // Connection established + [self performSelectorOnMainThread:@selector(mySQLConnectionEstablished) withObject:nil waitUntilDone:NO]; + + [pool release]; +} + +/** + * Initiate the SSH connection process. + * This should only be called as part of initiateConnection:, and will indirectly + * call initiateMySQLConnection if it's successful. + */ +- (void)initiateSSHTunnelConnection +{ + if (isTestingConnection) { + [progressIndicatorText setStringValue:NSLocalizedString(@"Testing SSH...", @"SSH testing very short status message")]; + } else { + [progressIndicatorText setStringValue:NSLocalizedString(@"SSH connecting...", @"SSH connecting very short status message")]; + } + [progressIndicatorText display]; + + [connectButton setTitle:NSLocalizedString(@"Cancel", @"cancel button")]; + [connectButton setAction:@selector(cancelConnection:)]; + [connectButton setEnabled:YES]; + [connectButton display]; + + // Trim whitespace and newlines from the SSH host field before attempting to connect + [self setSshHost:[[self sshHost] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]]; + + // Set up the tunnel details + sshTunnel = [[SPSSHTunnel alloc] initToHost:[self sshHost] port:[[self sshPort] integerValue] login:[self sshUser] tunnellingToPort:([[self port] length]?[[self port] integerValue]:3306) onHost:[self host]]; + [sshTunnel setParentWindow:[dbDocument parentWindow]]; + + // Add keychain or plaintext password as appropriate - note the checks in initiateConnection. + if (connectionSSHKeychainItemName && !isTestingConnection) { + [sshTunnel setPasswordKeychainName:connectionSSHKeychainItemName account:connectionSSHKeychainItemAccount]; + } else if (sshPassword) { + [sshTunnel setPassword:[self sshPassword]]; + } + + // Set the public key path if appropriate + if (sshKeyLocationEnabled && sshKeyLocation) { + [sshTunnel setKeyFilePath:sshKeyLocation]; + } + + // Set the callback function on the tunnel + [sshTunnel setConnectionStateChangeSelector:@selector(sshTunnelCallback:) delegate:self]; + + // Ask the tunnel to connect. This will call the callback below on success or failure, passing + // itself as an argument - retain count should be one at this point. + [sshTunnel connect]; +} + +/** + * Called on the main thread once the MySQL connection is established on the background thread. Either the + * connection was cancelled or it was successful. + */ +- (void)mySQLConnectionEstablished +{ + isConnecting = NO; + + // If the user is only testing the connection, kill the connection + // once established and reset the UI. Also catch connection cancels. + if (isTestingConnection || cancellingConnection) { + + // Clean up any connections remaining, and reset the UI + [self cancelConnection:self]; + + if (isTestingConnection) { + [self _showConnectionTestResult:NSLocalizedString(@"Connection succeeded", @"Connection success very short status message")]; + } + + return; + } + +#ifndef SP_CODA + [progressIndicatorText setStringValue:NSLocalizedString(@"Connected", @"connection established message")]; + [progressIndicatorText display]; +#endif + + // Stop the current tab's progress indicator + [dbDocument setIsProcessing:NO]; + + // Successful connection! +#ifndef SP_CODA + [connectButton setEnabled:NO]; + [connectButton display]; + [progressIndicator stopAnimation:self]; + [progressIndicatorText setHidden:YES]; +#endif + + // If SSL was enabled, check it was established correctly + if (useSSL && ([self type] == SPTCPIPConnection || [self type] == SPSocketConnection)) { + if (![mySQLConnection isConnectedViaSSL]) { + SPOnewayAlertSheet( + NSLocalizedString(@"SSL connection not established", @"SSL requested but not used title"), + [dbDocument parentWindow], + NSLocalizedString(@"You requested that the connection should be established using SSL, but MySQL made the connection without SSL.\n\nThis may be because the server does not support SSL connections, or has SSL disabled; or insufficient details were supplied to establish an SSL connection.\n\nThis connection is not encrypted.", @"SSL connection requested but not established error detail") + ); + } + else { +#ifndef SP_CODA + [dbDocument setStatusIconToImageWithName:@"titlebarlock"]; +#endif + } + } + +#ifndef SP_CODA + // Re-enable favorites table view + [favoritesOutlineView setEnabled:YES]; + [(NSView *)favoritesOutlineView display]; +#endif + + // Release the tunnel if set - will now be retained by the connection + if (sshTunnel) SPClear(sshTunnel); + + // Pass the connection to the document and clean up the interface + [self addConnectionToDocument]; +} + +/** + * A callback function for the SSH Tunnel setup process - will be called on a connection + * state change, allowing connection to fail or proceed as appropriate. If successful, + * will call initiateMySQLConnection. + */ +- (void)sshTunnelCallback:(SPSSHTunnel *)theTunnel +{ + if (cancellingConnection) return; + + NSInteger newState = [theTunnel state]; + + // If the user cancelled the password prompt dialog, continue with no further action. + if ([theTunnel passwordPromptCancelled]) { + [self _restoreConnectionInterface]; + + return; + } + + if (newState == SPMySQLProxyIdle) { + +#ifndef SP_CODA + [dbDocument setTitlebarStatus:NSLocalizedString(@"SSH Disconnected", @"SSH disconnected titlebar marker")]; +#endif + + [[self onMainThread] failConnectionWithTitle:NSLocalizedString(@"SSH connection failed!", @"SSH connection failed title") errorMessage:[theTunnel lastError] detail:[sshTunnel debugMessages] rawErrorText:[theTunnel lastError]]; + } + else if (newState == SPMySQLProxyConnected) { +#ifndef SP_CODA + [dbDocument setTitlebarStatus:NSLocalizedString(@"SSH Connected", @"SSH connected titlebar marker")]; +#endif + + [self initiateMySQLConnection]; + } + else { +#ifndef SP_CODA + [dbDocument setTitlebarStatus:NSLocalizedString(@"SSH Connecting…", @"SSH connecting titlebar marker")]; +#endif + } +} + +/** + * Add the connection to the parent document and restore the + * interface, allowing the application to run as normal. + */ +- (void)addConnectionToDocument +{ +#ifndef SP_CODA + // Hide the connection view and restore the main view + [connectionView removeFromSuperviewWithoutNeedingDisplay]; + [databaseConnectionView setHidden:NO]; + + // Restore the toolbar icons + NSArray *toolbarItems = [[[dbDocument parentWindow] toolbar] items]; + + for (NSUInteger i = 0; i < [toolbarItems count]; i++) [[toolbarItems objectAtIndex:i] setEnabled:YES]; +#endif + + if (connectionKeychainID) [dbDocument setKeychainID:connectionKeychainID]; + + // Pass the connection to the table document, allowing it to set + // up the other classes and the rest of the interface. + [dbDocument setConnection:mySQLConnection]; +} + +/** + * Ends a connection attempt by stopping the connection animation and + * displaying a specified error message. + */ +- (void)failConnectionWithTitle:(NSString *)theTitle errorMessage:(NSString *)theErrorMessage detail:(NSString *)errorDetail rawErrorText:(NSString *)rawErrorText +{ + BOOL isSSHTunnelBindError = NO; + +#ifndef SP_CODA + [self _restoreConnectionInterface]; +#endif + + // Release as appropriate + if (sshTunnel) { + [sshTunnel disconnect], SPClear(sshTunnel); + + // If the SSH tunnel connection failed because the port it was trying to bind to was already in use take note + // of it so we can give the user the option of connecting via standard connection and use the existing tunnel. + if ([rawErrorText rangeOfString:@"bind"].location != NSNotFound) { + isSSHTunnelBindError = YES; + } + } + + if (errorDetail) [errorDetailText setString:errorDetail]; + + // Inform the delegate that the connection attempt failed + if (delegate && [delegate respondsToSelector:@selector(connectionControllerConnectAttemptFailed:)]) { + [[(NSObject *)delegate onMainThread] connectionControllerConnectAttemptFailed:self]; + } + + // Only display the connection error message if there is a window visible + if ([[dbDocument parentWindow] isVisible]) { + SPBeginAlertSheet(theTitle, NSLocalizedString(@"OK", @"OK button"), (errorDetail) ? NSLocalizedString(@"Show Detail", @"Show detail button") : nil, (isSSHTunnelBindError) ? NSLocalizedString(@"Use Standard Connection", @"use standard connection button") : nil, [dbDocument parentWindow], self, @selector(connectionFailureSheetDidEnd:returnCode:contextInfo:), @"connect", theErrorMessage); + } +} + +/** + * Alert sheet callback method - invoked when an error sheet is closed. + */ +- (void)connectionFailureSheetDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo +{ + if (returnCode == NSAlertAlternateReturn) { + [errorDetailText setFont:[NSFont userFontOfSize:12]]; + [errorDetailText setAlignment:NSLeftTextAlignment]; + [errorDetailWindow makeKeyAndOrderFront:self]; + } + + // Currently only SSH port bind errors offer a 3rd option in the error dialog, but if this ever changes + // this will definitely need to be updated. + else if (returnCode == NSAlertOtherReturn) { + + // Extract the local port number that SSH attempted to bind to from the debug output + NSString *tunnelPort = [[[errorDetailText string] componentsMatchedByRegex:@"LOCALHOST:([0-9]+)" capture:1L] lastObject]; + + // Change the connection type to standard TCP/IP + [self setType:SPTCPIPConnection]; + + // Change connection details + [self setPort:tunnelPort]; + [self setHost:SPLocalhostAddress]; + +#ifndef SP_CODA + // Change to standard TCP/IP connection view + [self resizeTabViewToConnectionType:SPTCPIPConnection animating:YES]; +#endif + + // Initiate the connection after a half second delay to give the connection view a chance to resize + [self performSelector:@selector(initiateConnection:) withObject:self afterDelay:0.5]; + } +} + +#pragma mark - SPConnectionHandlerPrivateAPI + +/** + * Display a connection test error or success message + */ +- (void)_showConnectionTestResult:(NSString *)resultString +{ + if (![NSThread isMainThread]) { + [[self onMainThread] _showConnectionTestResult:resultString]; + } + + [helpButton setHidden:NO]; + [progressIndicator stopAnimation:self]; + [progressIndicatorText setStringValue:resultString]; + [progressIndicatorText setHidden:NO]; +} + +#pragma mark - SPConnectionControllerDelegate + +#pragma mark SplitView delegate methods + +#ifndef SP_CODA + +/** + * When the split view is resized, trigger a resize in the hidden table + * width as well, to keep the connection view and connected view in sync. + */ +- (void)splitViewDidResizeSubviews:(NSNotification *)notification +{ + if (initComplete) { + [databaseConnectionView setPosition:[[[connectionSplitView subviews] objectAtIndex:0] frame].size.width ofDividerAtIndex:0]; + } +} + +- (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)dividerIndex +{ + return 145.f; +} + +#endif + +#pragma mark - +#pragma mark Outline view delegate methods + +#ifndef SP_CODA + +- (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item +{ + return ([[(SPTreeNode *)item parentNode] parentNode] == nil); +} + +- (void)outlineViewSelectionIsChanging:(NSNotification *)notification +{ + if (isEditingConnection) { + [self _stopEditingConnection]; + + [[notification object] setNeedsDisplay:YES]; + } +} + +- (void)outlineViewSelectionDidChange:(NSNotification *)notification +{ + NSInteger selected = [favoritesOutlineView numberOfSelectedRows]; + + if (isEditingConnection) { + [self _stopEditingConnection]; + [[notification object] setNeedsDisplay:YES]; + } + + if (selected == 1) { + [self updateFavoriteSelection:self]; + + favoriteNameFieldWasAutogenerated = NO; + [connectionResizeContainer setHidden:NO]; + [connectionInstructionsTextField setStringValue:NSLocalizedString(@"Enter connection details below, or choose a favorite", @"enter connection details label")]; + } + else if (selected > 1) { + [connectionResizeContainer setHidden:YES]; + [connectionInstructionsTextField setStringValue:NSLocalizedString(@"Please choose a favorite", @"please choose a favorite connection view label")]; + } +} + +- (NSCell *)outlineView:(NSOutlineView *)outlineView dataCellForTableColumn:(NSTableColumn *)tableColumn item:(id)item +{ + if (item == quickConnectItem) { + return (NSCell *)quickConnectCell; + } + + return [tableColumn dataCellForRow:[outlineView rowForItem:item]]; +} + +- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item +{ + SPTreeNode *node = (SPTreeNode *)item; + SPFavoriteTextFieldCell *favoriteCell = (SPFavoriteTextFieldCell *)cell; + + // Draw entries with the small system font by default + [cell setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; + + // Set an image as appropriate; the quick connect image for that entry, no image for other + // top-level items, the folder image for group nodes, or the database image for other nodes. + if (![[node parentNode] parentNode]) { + if (node == quickConnectItem) { + if ([outlineView rowForItem:item] == [outlineView selectedRow]) { + [favoriteCell setImage:[NSImage imageNamed:SPQuickConnectImageWhite]]; + } + else { + [favoriteCell setImage:[NSImage imageNamed:SPQuickConnectImage]]; + } + } + else { + [favoriteCell setImage:nil]; + } + [favoriteCell setLabelColor:nil]; + } + else { + if ([node isGroup]) { + [favoriteCell setImage:folderImage]; + [favoriteCell setLabelColor:nil]; + } + else { + [favoriteCell setImage:[NSImage imageNamed:SPDatabaseImage]]; + NSColor *bgColor = nil; + NSNumber *colorIndexObj = [[[node representedObject] nodeFavorite] objectForKey:SPFavoriteColorIndexKey]; + if(colorIndexObj != nil) { + bgColor = [[SPFavoriteColorSupport sharedInstance] colorForIndex:[colorIndexObj integerValue]]; + } + [favoriteCell setLabelColor:bgColor]; + } + } + + // If a favourite item is being edited, draw the text in bold to show state + if (isEditingConnection && ![node isGroup] && [outlineView rowForItem:item] == [outlineView selectedRow]) { + NSMutableAttributedString *editedCellString = [[cell attributedStringValue] mutableCopy]; + [editedCellString addAttribute:NSForegroundColorAttributeName value:[NSColor colorWithDeviceWhite:0.25f alpha:1.f] range:NSMakeRange(0, [editedCellString length])]; + [cell setAttributedStringValue:editedCellString]; + [editedCellString release]; + } +} + +- (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item +{ + if (item == quickConnectItem) { + return 24.f; + } + + return ([[item parentNode] parentNode]) ? 17.f : 22.f; +} + +- (NSString *)outlineView:(NSOutlineView *)outlineView toolTipForCell:(NSCell *)cell rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)tableColumn item:(id)item mouseLocation:(NSPoint)mouseLocation +{ + NSString *toolTip = nil; + + SPTreeNode *node = (SPTreeNode *)item; + + if (![node isGroup]) { + + NSString *favoriteName = [[[node representedObject] nodeFavorite] objectForKey:SPFavoriteNameKey]; + NSString *favoriteHostname = [[[node representedObject] nodeFavorite] objectForKey:SPFavoriteHostKey]; + + toolTip = ([favoriteHostname length]) ? [NSString stringWithFormat:@"%@ (%@)", favoriteName, favoriteHostname] : favoriteName; + } + + // Only display a tooltip for group nodes that are a descendant of the root node + else if ([[node parentNode] parentNode]) { + + NSUInteger favCount = 0; + NSUInteger groupCount = 0; + + for (SPTreeNode *eachNode in [node childNodes]) + { + if ([eachNode isGroup]) { + groupCount++; + } + else { + favCount++; + } + } + + NSMutableArray *tooltipParts = [NSMutableArray arrayWithCapacity:2]; + + if (favCount || !groupCount) { + [tooltipParts addObject:[NSString stringWithFormat:((favCount == 1) ? NSLocalizedString(@"%d favorite", @"favorite singular label (%d == 1)") : NSLocalizedString(@"%d favorites", @"favorites plural label (%d != 1)")), favCount]]; + } + + if (groupCount) { + [tooltipParts addObject:[NSString stringWithFormat:((groupCount == 1) ? NSLocalizedString(@"%d group", @"favorite group singular label (%d == 1)") : NSLocalizedString(@"%d groups", @"favorite groups plural label (%d != 1)")), groupCount]]; + } + + toolTip = [NSString stringWithFormat:@"%@ - %@", [[node representedObject] nodeName], [tooltipParts componentsJoinedByString:@", "]]; + } + + return toolTip; +} + +- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item +{ + // If this is a top level item, only allow the "Quick Connect" item to be selectable + if (![[item parentNode] parentNode]) { + return item == quickConnectItem; + } + + // Otherwise allow all items to be selectable + return YES; +} + +- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item +{ + return (item != quickConnectItem && ![item isLeaf]); +} + +- (BOOL)outlineView:(NSOutlineView *)outlineView shouldShowOutlineCellForItem:(id)item +{ + return ([[item parentNode] parentNode] != nil); +} + +- (BOOL)outlineView:(NSOutlineView *)outlineView shouldCollapseItem:(id)item +{ + return ([[item parentNode] parentNode] != nil); +} + +- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item +{ + NSEvent *event = [NSApp currentEvent]; + BOOL shiftTabbedIn = ([event type] == NSKeyDown && [[event characters] length] && [[event characters] characterAtIndex:0] == NSBackTabCharacter); + + if (shiftTabbedIn && [(SPFavoritesOutlineView *)outlineView justGainedFocus]) { + return NO; + } + + return item != quickConnectItem; +} + +- (void)outlineViewItemDidCollapse:(NSNotification *)notification +{ + [self _setNodeIsExpanded:NO fromNotification:notification]; +} + +- (void)outlineViewItemDidExpand:(NSNotification *)notification +{ + [self _setNodeIsExpanded:YES fromNotification:notification]; +} + +#endif + +#pragma mark - +#pragma mark Outline view drag & drop + +#ifndef SP_CODA + +- (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard +{ + // Prevent a drag which includes the outline title group from taking place + for (id item in items) + { + if (![[item parentNode] parentNode]) return NO; + } + + // If the user is in the process of changing a node's name, trigger a save and prevent dragging. + if (isEditingItemName) { + [favoritesController saveFavorites]; + + [self _reloadFavoritesViewData]; + + isEditingItemName = NO; + + return NO; + } + + [pboard declareTypes:@[SPFavoritesPasteboardDragType] owner:self]; + + BOOL result = [pboard setData:[NSData data] forType:SPFavoritesPasteboardDragType]; + + draggedNodes = items; + + return result; +} + +- (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)childIndex +{ + NSDragOperation result = NSDragOperationNone; + + // Prevent the top level or the quick connect item from being a target + if (!item || item == quickConnectItem) return result; + + // Prevent dropping favorites on other favorites (non-groups) + if ((childIndex == NSOutlineViewDropOnItemIndex) && (![item isGroup])) return result; + + // Ensure that none of the dragged nodes are being dragged into children of themselves; if they are, + // prevent the drag. + id itemToCheck = item; + + do { + if ([draggedNodes containsObject:itemToCheck]) { + return result; + } + } + while ((itemToCheck = [itemToCheck parentNode])); + + if ([info draggingSource] == outlineView) { + [outlineView setDropItem:item dropChildIndex:childIndex]; + + result = NSDragOperationMove; + } + + return result; +} + +- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)childIndex +{ + BOOL acceptedDrop = NO; + + if ((!item) || ([info draggingSource] != outlineView)) return acceptedDrop; + + SPTreeNode *node = item ? item : [[[[favoritesRoot childNodes] objectAtIndex:0] childNodes] objectAtIndex:0]; + + // Cache the selected nodes for selection restoration afterwards + NSArray *preDragSelection = [self selectedFavoriteNodes]; + + // Disable all automatic sorting + currentSortItem = -1; + reverseFavoritesSort = NO; + + [prefs setInteger:currentSortItem forKey:SPFavoritesSortedBy]; + [prefs setBool:NO forKey:SPFavoritesSortedInReverse]; + + // Uncheck sort by menu items + for (NSMenuItem *menuItem in [[favoritesSortByMenuItem submenu] itemArray]) + { + [menuItem setState:NSOffState]; + } + + if (![draggedNodes count]) return acceptedDrop; + + if ([node isGroup]) { + if (childIndex == NSOutlineViewDropOnItemIndex) { + childIndex = 0; + } + [outlineView expandItem:node]; + } + else { + if (childIndex == NSOutlineViewDropOnItemIndex) { + childIndex = 0; + } + } + + if (![[node representedObject] nodeName]) { + node = [[favoritesRoot childNodes] objectAtIndex:0]; + } + + NSMutableArray *childNodeArray = [node mutableChildNodes]; + + for (SPTreeNode *treeNode in draggedNodes) + { + // Remove the node from its old location + NSInteger oldIndex = [childNodeArray indexOfObject:treeNode]; + NSInteger newIndex = childIndex; + + if (oldIndex != NSNotFound) { + + [childNodeArray removeObjectAtIndex:oldIndex]; + + if (childIndex > oldIndex) { + newIndex--; + } + } + else { + [[[treeNode parentNode] mutableChildNodes] removeObject:treeNode]; + } + + [childNodeArray insertObject:treeNode atIndex:newIndex]; + + newIndex++; + } + + [favoritesController saveFavorites]; + + [self _reloadFavoritesViewData]; + + [[NSNotificationCenter defaultCenter] postNotificationName:SPConnectionFavoritesChangedNotification object:self]; + + [[[SPAppDelegate preferenceController] generalPreferencePane] updateDefaultFavoritePopup]; + + // Update the selection to account for rearranged faourites + NSMutableIndexSet *restoredSelection = [NSMutableIndexSet indexSet]; + + for (SPTreeNode *eachNode in preDragSelection) + { + [restoredSelection addIndex:[favoritesOutlineView rowForItem:eachNode]]; + } + + [favoritesOutlineView selectRowIndexes:restoredSelection byExtendingSelection:NO]; + + acceptedDrop = YES; + + return acceptedDrop; +} + +#endif + +#pragma mark - +#pragma mark Textfield delegate methods + +#ifndef SP_CODA + +/** + * React to control text changes in the connection interface + */ +- (void)controlTextDidChange:(NSNotification *)notification +{ + id field = [notification object]; + + // Ignore changes in the outline view edit fields + if ([field isKindOfClass:[NSOutlineView class]]) { + return; + } + + // If a 'name' field was edited, and is now of zero length, trigger a replacement + // with a standard suggestion + if (((field == standardNameField) || (field == socketNameField) || (field == sshNameField)) && [self selectedFavoriteNode]) { + if (![[self _stripInvalidCharactersFromString:[field stringValue]] length]) { + [self controlTextDidEndEditing:notification]; + } + } + + [self _startEditingConnection]; + + if (favoriteNameFieldWasAutogenerated && (field != standardNameField && field != socketNameField && field != sshNameField)) { + [self setName:[self _generateNameForConnection]]; + } +} + +/** + * React to the end of control text changes in the connection interface. + */ +- (void)controlTextDidEndEditing:(NSNotification *)notification +{ + id field = [notification object]; + + // Handle updates to the 'name' field of the selected favourite. The favourite name should + // have leading or trailing spaces removed at the end of editing, and if it's left empty, + // should have a default name set. + if (((field == standardNameField) || (field == socketNameField) || (field == sshNameField)) && [self selectedFavoriteNode]) { + + NSString *favoriteName = [self _stripInvalidCharactersFromString:[field stringValue]]; + + if (![favoriteName length]) { + favoriteName = [self _generateNameForConnection]; + + if (favoriteName) { + [self setName:favoriteName]; + } + + // Enable user@host update in reaction to other UI changes + favoriteNameFieldWasAutogenerated = YES; + } + else if (![[field stringValue] isEqualToString:[self _generateNameForConnection]]) { + favoriteNameFieldWasAutogenerated = NO; + [self setName:favoriteName]; + } + } + + // When a host field finishes editing, ensure that it hasn't been set to "localhost" to + // ensure that socket connections don't inadvertently occur. + if (field == standardSQLHostField || field == sshSQLHostField) { + [self _checkHost]; + } +} + +#endif + +#pragma mark - +#pragma mark Tab bar delegate methods + +#ifndef SP_CODA + +/** + * Trigger a resize action whenever the tab view changes. The connection + * detail forms are held within container views, which are of a fixed width; + * the tabview and buttons are contained within a resizable view which + * is set to dimensions based on the container views, allowing the view + * to be sized according to the detail type. + */ +- (void)tabView:(NSTabView *)tabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem +{ + NSInteger selectedTabView = [tabView indexOfTabViewItem:tabViewItem]; + + if (selectedTabView == previousType) return; + + [self _startEditingConnection]; + + [self resizeTabViewToConnectionType:selectedTabView animating:YES]; + + // Update the host as appropriate + if ((selectedTabView != SPSocketConnection) && [[self host] isEqualToString:@"localhost"]) { + [self setHost:@""]; + } + + previousType = selectedTabView; + + [self _favoriteTypeDidChange]; +} + +#endif + +#pragma mark - +#pragma mark Color Selector delegate + +- (void)colorSelectorDidChange:(SPColorSelectorView *)sel +{ + [self _startEditingConnection]; +} + +#pragma mark - +#pragma mark Scroll view notifications + +#ifndef SP_CODA + +/** + * As the scrollview resizes, keep the details centered within it if + * the detail frame is larger than the scrollview size; otherwise, pin + * the detail frame to the top of the scrollview. + */ +- (void)scrollViewFrameChanged:(NSNotification *)aNotification +{ + NSRect scrollViewFrame = [connectionDetailsScrollView frame]; + NSRect scrollDocumentFrame = [[connectionDetailsScrollView documentView] frame]; + NSRect connectionDetailsFrame = [connectionResizeContainer frame]; + + // Scroll view is smaller than contents - keep positioned at top. + if (scrollViewFrame.size.height < connectionDetailsFrame.size.height + 10) { + if (connectionDetailsFrame.origin.y != 0) { + connectionDetailsFrame.origin.y = 0; + [connectionResizeContainer setFrame:connectionDetailsFrame]; + scrollDocumentFrame.size.height = connectionDetailsFrame.size.height + 10; + [[connectionDetailsScrollView documentView] setFrame:scrollDocumentFrame]; + } + } + // Otherwise, center + else { + connectionDetailsFrame.origin.y = (scrollViewFrame.size.height - connectionDetailsFrame.size.height)/3; + [connectionResizeContainer setFrame:connectionDetailsFrame]; + scrollDocumentFrame.size.height = scrollViewFrame.size.height; + [[connectionDetailsScrollView documentView] setFrame:scrollDocumentFrame]; + } +} + +#endif + +#pragma mark - +#pragma mark Menu Validation + +#ifndef SP_CODA + +/** + * Menu item validation. + */ +- (BOOL)validateMenuItem:(NSMenuItem *)menuItem +{ + SEL action = [menuItem action]; + + SPTreeNode *node = [self selectedFavoriteNode]; + NSInteger selectedRows = [favoritesOutlineView numberOfSelectedRows]; + + if ((action == @selector(sortFavorites:)) || (action == @selector(reverseSortFavorites:))) { + + if ([[favoritesRoot allChildLeafs] count] < 2) return NO; + + // Loop all the items in the sort by menu only checking the currently selected one + for (NSMenuItem *item in [[menuItem menu] itemArray]) + { + [item setState:([[menuItem menu] indexOfItem:item] == currentSortItem)]; + } + + // Check or uncheck the reverse sort item + if (action == @selector(reverseSortFavorites:)) { + [menuItem setState:reverseFavoritesSort]; + } + + return YES; + } + + // import does not depend on a selection + if(action == @selector(importFavorites:)) return YES; + + if (node == quickConnectItem) return NO; + + // Remove/rename the selected node + if (action == @selector(removeNode:) || action == @selector(renameNode:)) { + return selectedRows == 1; + } + + // Duplicate and make the selected favorite the default + if (action == @selector(duplicateFavorite:)) { + return ((selectedRows == 1) && (![node isGroup])); + } + + // Make selected favorite the default + if (action == @selector(makeSelectedFavoriteDefault:)) { + NSInteger favoriteID = [[[self selectedFavorite] objectForKey:SPFavoriteIDKey] integerValue]; + + return ((selectedRows == 1) && (![node isGroup]) && (favoriteID != [prefs integerForKey:SPDefaultFavorite])); + } + + // Favorites export + if (action == @selector(exportFavorites:)) { + + if ([[favoritesRoot allChildLeafs] count] == 0 || selectedRows == 0) { + return NO; + } + else if (selectedRows > 1) { + [menuItem setTitle:NSLocalizedString(@"Export Selected...", @"export selected favorites menu item")]; + } + } + + return YES; +} + +#endif + +#pragma mark - +#pragma mark Favorites import/export delegate methods + +#ifndef SP_CODA + +/** + * Called by the favorites importer when the imported data is available. + */ +- (void)favoritesImportData:(NSArray *)data +{ + SPTreeNode *newNode; + NSMutableArray *importedNodes = [NSMutableArray array]; + NSMutableIndexSet *importedIndexSet = [NSMutableIndexSet indexSet]; + + // Add each of the imported favorites to the root node + for (NSMutableDictionary *favorite in data) + { + newNode = [favoritesController addFavoriteNodeWithData:favorite asChildOfNode:nil]; + [importedNodes addObject:newNode]; + } + + if (currentSortItem > SPFavoritesSortUnsorted) { + [self _sortFavorites]; + } + + [self _reloadFavoritesViewData]; + + // Select the new nodes and scroll into view + for (SPTreeNode *eachNode in importedNodes) + { + [importedIndexSet addIndex:[favoritesOutlineView rowForItem:eachNode]]; + } + + [favoritesOutlineView selectRowIndexes:importedIndexSet byExtendingSelection:NO]; + + [self _scrollToSelectedNode]; +} + +/** + * Called by the favorites importer when the import completes. + */ +- (void)favoritesImportCompletedWithError:(NSError *)error +{ + if (error) { + NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Favorites import error", @"favorites import error message") + defaultButton:NSLocalizedString(@"OK", @"OK") + alternateButton:nil + otherButton:nil + informativeTextWithFormat:NSLocalizedString(@"The following error occurred during the import process:\n\n%@", @"favorites import error informative message"), [error localizedDescription]]; + + [alert beginSheetModalForWindow:[dbDocument parentWindow] + modalDelegate:self + didEndSelector:NULL + contextInfo:NULL]; + } +} + +#endif + +#pragma mark - +#pragma mark Private API + +#ifndef SP_CODA + +/** + * Sets the expanded state of the node from the supplied outline view notification. + * + * @param expanded The state of the node + * @param notification The notification genrated from the state change + */ +- (void)_setNodeIsExpanded:(BOOL)expanded fromNotification:(NSNotification *)notification +{ + SPGroupNode *node = [[[notification userInfo] valueForKey:@"NSObject"] representedObject]; + + [node setNodeIsExpanded:expanded]; +} + +#endif + +#pragma mark - SPConnectionControllerInitializer + +/** + * Initialise the connection controller, linking it to the parent document and setting up the parent window. + */ +- (id)initWithDocument:(SPDatabaseDocument *)document +{ + if ((self = [super init])) { + + // Weak reference + dbDocument = document; + +#ifndef SP_CODA + databaseConnectionSuperview = [dbDocument databaseView]; + databaseConnectionView = [dbDocument valueForKey:@"contentViewSplitter"]; +#endif + + // Keychain references + connectionKeychainID = nil; + connectionKeychainItemName = nil; + connectionKeychainItemAccount = nil; + connectionSSHKeychainItemName = nil; + connectionSSHKeychainItemAccount = nil; + + initComplete = NO; + isEditingItemName = NO; + isConnecting = NO; + isTestingConnection = NO; + sshTunnel = nil; + mySQLConnection = nil; + cancellingConnection = NO; + favoriteNameFieldWasAutogenerated = NO; + + [self loadNib]; + + NSArray *colorList = [[SPFavoriteColorSupport sharedInstance] userColorList]; + [sshColorField setColorList:colorList]; + [sshColorField bind:@"selectedTag" toObject:self withKeyPath:@"colorIndex" options:nil]; + [standardColorField setColorList:colorList]; + [standardColorField bind:@"selectedTag" toObject:self withKeyPath:@"colorIndex" options:nil]; + [socketColorField setColorList:colorList]; + [socketColorField bind:@"selectedTag" toObject:self withKeyPath:@"colorIndex" options:nil]; + + [self registerForNotifications]; + +#ifndef SP_CODA + // Hide the main view and position and display the connection view + [databaseConnectionView setHidden:YES]; + [connectionView setFrame:[databaseConnectionView frame]]; + [databaseConnectionSuperview addSubview:connectionView]; + + // Set up the splitview + [connectionSplitView setMinSize:80.f ofSubviewAtIndex:0]; + [connectionSplitView setMinSize:445.f ofSubviewAtIndex:1]; + + // Generic folder image for use in the outline view's groups + folderImage = [[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGenericFolderIcon)] retain]; + [folderImage setSize:NSMakeSize(16, 16)]; + + // Set up a keychain instance and preferences reference, and create the initial favorites list + keychain = [[SPKeychain alloc] init]; + prefs = [[NSUserDefaults standardUserDefaults] retain]; + + // Create a reference to the favorites controller, forcing the data to be loaded from disk + // and the tree to be constructed. + favoritesController = [SPFavoritesController sharedFavoritesController]; + + // Tree references + favoritesRoot = [favoritesController favoritesTree]; + currentFavorite = nil; + + // Create the "Quick Connect" placeholder group + quickConnectItem = [[SPTreeNode treeNodeWithRepresentedObject:[SPGroupNode groupNodeWithName:[NSLocalizedString(@"Quick Connect", @"Quick connect item label") uppercaseString]]] retain]; + [quickConnectItem setIsGroup:YES]; + + // Create a NSOutlineView cell for the Quick Connect group + quickConnectCell = [[SPFavoriteTextFieldCell alloc] init]; + [quickConnectCell setDrawsDividerUnderCell:YES]; + [quickConnectCell setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; + + // Update the UI + [self _reloadFavoritesViewData]; + [self setUpFavoritesOutlineView]; + [self _restoreOutlineViewStateNode:favoritesRoot]; + + // Set up the selected favourite, and scroll after a small delay to fix animation delay on Lion + [self setUpSelectedConnectionFavorite]; + if ([favoritesOutlineView selectedRow] != -1) { + [self performSelector:@selector(_scrollToSelectedNode) withObject:nil afterDelay:0.0]; + } + + // Set sort items + currentSortItem = (SPFavoritesSortItem)[prefs integerForKey:SPFavoritesSortedBy]; + reverseFavoritesSort = [prefs boolForKey:SPFavoritesSortedInReverse]; +#endif + + initComplete = YES; + } + + return self; +} + +/** + * Loads the connection controllers UI nib. + */ +- (void)loadNib +{ +#ifndef SP_CODA + + // Load the connection nib, keeping references to the top-level objects for later release + nibObjectsToRelease = [[NSMutableArray alloc] init]; + + NSArray *connectionViewTopLevelObjects = nil; + NSNib *nibLoader = [[NSNib alloc] initWithNibNamed:SPConnectionViewNibName bundle:[NSBundle mainBundle]]; + + [nibLoader instantiateNibWithOwner:self topLevelObjects:&connectionViewTopLevelObjects]; + [nibObjectsToRelease addObjectsFromArray:connectionViewTopLevelObjects]; + [nibLoader release]; + +#endif +} + +/** + * Registers for various notifications. + */ +- (void)registerForNotifications +{ + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(_documentWillClose:) + name:SPDocumentWillCloseNotification + object:dbDocument]; + +#ifndef SP_CODA + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(scrollViewFrameChanged:) + name:NSViewFrameDidChangeNotification + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(_processFavoritesDataChange:) + name:SPConnectionFavoritesChangedNotification + object:nil]; + + // Registered to be notified of changes to connection information + [self addObserver:self + forKeyPath:SPFavoriteTypeKey + options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) + context:NULL]; + + [self addObserver:self + forKeyPath:SPFavoriteNameKey + options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) + context:NULL]; + + [self addObserver:self + forKeyPath:SPFavoriteHostKey + options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) + context:NULL]; + + [self addObserver:self + forKeyPath:SPFavoriteUserKey + options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) + context:NULL]; + + [self addObserver:self + forKeyPath:SPFavoriteColorIndexKey + options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) + context:NULL]; + + [self addObserver:self + forKeyPath:SPFavoriteDatabaseKey + options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) + context:NULL]; + + [self addObserver:self + forKeyPath:SPFavoriteSocketKey + options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) + context:NULL]; + + [self addObserver:self + forKeyPath:SPFavoritePortKey + options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) + context:NULL]; + + [self addObserver:self + forKeyPath:SPFavoriteUseSSLKey + options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) + context:NULL]; + + [self addObserver:self + forKeyPath:SPFavoriteSSHHostKey + options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) + context:NULL]; + + [self addObserver:self + forKeyPath:SPFavoriteSSHUserKey + options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) + context:NULL]; + + [self addObserver:self + forKeyPath:SPFavoriteSSHPortKey + options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) + context:NULL]; + + [self addObserver:self + forKeyPath:SPFavoriteSSHKeyLocationEnabledKey + options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) + context:NULL]; + + [self addObserver:self + forKeyPath:SPFavoriteSSHKeyLocationKey + options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) + context:NULL]; + + [self addObserver:self + forKeyPath:SPFavoriteSSLKeyFileLocationEnabledKey + options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) + context:NULL]; + + [self addObserver:self + forKeyPath:SPFavoriteSSLKeyFileLocationKey + options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) + context:NULL]; + + [self addObserver:self + forKeyPath:SPFavoriteSSLCertificateFileLocationEnabledKey + options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) + context:NULL]; + + [self addObserver:self + forKeyPath:SPFavoriteSSLCertificateFileLocationKey + options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) + context:NULL]; + + [self addObserver:self + forKeyPath:SPFavoriteSSLCACertFileLocationEnabledKey + options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) + context:NULL]; + + [self addObserver:self + forKeyPath:SPFavoriteSSLCACertFileLocationKey + options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) + context:NULL]; +#endif +} + +/** + * Performs any set up necessary for the favorities outline view. + */ +- (void)setUpFavoritesOutlineView +{ + // Register double click action for the favorites outline view (double click favorite to connect) + [favoritesOutlineView setTarget:self]; + [favoritesOutlineView setDoubleAction:@selector(nodeDoubleClicked:)]; + + // Register drag types for the favorites outline view + [favoritesOutlineView registerForDraggedTypes:@[SPFavoritesPasteboardDragType]]; + [favoritesOutlineView setDraggingSourceOperationMask:NSDragOperationMove forLocal:YES]; +} + +/** + * Sets up the selected connection favorite according to the user's preferences. + */ +- (void)setUpSelectedConnectionFavorite +{ +#ifndef SP_CODA + SPTreeNode *favorite = [self _favoriteNodeForFavoriteID:[prefs integerForKey:[prefs boolForKey:SPSelectLastFavoriteUsed] ? SPLastFavoriteID : SPDefaultFavorite]]; + + if (favorite) { + + if (favorite == quickConnectItem) { + [self _selectNode:favorite]; + } + else { + NSNumber *typeNumber = [[[favorite representedObject] nodeFavorite] objectForKey:SPFavoriteTypeKey]; + previousType = typeNumber ? [typeNumber integerValue] : SPTCPIPConnection; + + [self _selectNode:favorite]; + [self resizeTabViewToConnectionType:[[[[favorite representedObject] nodeFavorite] objectForKey:SPFavoriteTypeKey] integerValue] animating:NO]; + } + + [self _scrollToSelectedNode]; + } + else { + previousType = SPTCPIPConnection; + + [self resizeTabViewToConnectionType:SPTCPIPConnection animating:NO]; + } +#endif +} + +#pragma mark - +#pragma mark Private API + +/** + * Responds to notifications that the favorites root has changed, + * and updates the interface to match. + */ +- (void)_processFavoritesDataChange:(NSNotification *)aNotification +{ +#ifndef SP_CODA + // Check the supplied notification for the sender; if the sender + // was this object, ignore it + if ([aNotification object] == self) return; + + NSArray *selectedFavoriteNodes = [self selectedFavoriteNodes]; + + [self _reloadFavoritesViewData]; + + NSMutableIndexSet *selectionIndexes = [NSMutableIndexSet indexSet]; + + for (SPTreeNode *eachNode in selectedFavoriteNodes) + { + NSInteger anIndex = [favoritesOutlineView rowForItem:eachNode]; + + if (anIndex == -1) continue; + + [selectionIndexes addIndex:anIndex]; + } + + [favoritesOutlineView selectRowIndexes:selectionIndexes byExtendingSelection:NO]; +#endif +} + +/** + * Restores the outline views group nodes expansion state. + * + * @param node The node to traverse + */ +#ifndef SP_CODA +- (void)_restoreOutlineViewStateNode:(SPTreeNode *)node +{ + if ([node isGroup]) { + if ([[node representedObject] nodeIsExpanded]) { + [favoritesOutlineView expandItem:node]; + } + else { + [favoritesOutlineView collapseItem:node]; + } + + for (SPTreeNode *childNode in [node childNodes]) + { + if ([childNode isGroup]) { + [self _restoreOutlineViewStateNode:childNode]; + } + } + } +} +#endif + +#pragma mark - SPConnectionControllerDataSource + +#ifndef SP_CODA + +/** + * Return the number of children for the specified item in the favourites tree. + * Note that to support the "Quick Connect" entry, the returned count is amended + * for the top level. + */ +- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item +{ + SPTreeNode *node = (item == nil ? favoritesRoot : (SPTreeNode *)item); + + // If at the root, return the count plus one for the "Quick Connect" entry + if (!item) { + return [[node childNodes] count] + 1; + } + + return [[node childNodes] count]; +} + +/** + * Return the branch at the specified index of a supplied tree level. + * Note that to support the "Quick Connect" entry, children of the top level + * have their offsets amended. + */ +- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)childIndex ofItem:(id)item +{ + // For the top level of the tree, return the "Quick Connect" child for position zero; + // amend all other positions to compensate for the faked position. + if (!item) { + if (childIndex == 0) { + return quickConnectItem; + } + + childIndex--; + } + + SPTreeNode *node = (item == nil ? favoritesRoot : (SPTreeNode *)item); + + return NSArrayObjectAtIndex([node childNodes], childIndex); +} + +- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item +{ + SPTreeNode *node = (SPTreeNode *)item; + + return (![node isGroup]) ? [[[node representedObject] nodeFavorite] objectForKey:SPFavoriteNameKey] : [[node representedObject] nodeName]; +} + +- (void)outlineView:(NSOutlineView *)outlineView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item +{ + NSString *newName = [object stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + + if ([newName length]) { + + // Get the node that was renamed + SPTreeNode *node = [self selectedFavoriteNode]; + + if (![node isGroup]) { + + // Updating the name triggers a KVO update + [self setName:newName]; + [self _saveCurrentDetailsCreatingNewFavorite:NO validateDetails:NO]; + } + else { + [[node representedObject] setNodeName:newName]; + + [favoritesController saveFavorites]; + + [self _reloadFavoritesViewData]; + } + } +} + +#endif + #pragma mark - - (void)dealloc diff --git a/Source/SPConnectionControllerDataSource.h b/Source/SPConnectionControllerDataSource.h deleted file mode 100644 index 1543b6ca..00000000 --- a/Source/SPConnectionControllerDataSource.h +++ /dev/null @@ -1,42 +0,0 @@ -// -// SPConnectionControllerDataSource.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on February 20, 2011. -// Copyright (c) 2011 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPConnectionController.h" - -/** - * @category SPConnectionControllerDelegate SPConnectionControllerDelegate.h - * - * @author Stuart Connolly http://stuconnolly.com/ - * - * Connection controller data source category. - */ -@interface SPConnectionController (SPConnectionControllerDataSource) - -@end diff --git a/Source/SPConnectionControllerDataSource.m b/Source/SPConnectionControllerDataSource.m deleted file mode 100644 index 2b83938c..00000000 --- a/Source/SPConnectionControllerDataSource.m +++ /dev/null @@ -1,121 +0,0 @@ -// -// SPConnectionControllerDataSource.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on February 20, 2011. -// Copyright (c) 2011 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPConnectionControllerDataSource.h" -#import "SPFavoritesController.h" -#import "SPFavoriteNode.h" -#import "SPGroupNode.h" -#import "SPTreeNode.h" - -@interface SPConnectionController () - -- (void)_reloadFavoritesViewData; -- (void)_saveCurrentDetailsCreatingNewFavorite:(BOOL)createNewFavorite validateDetails:(BOOL)validateDetails; - -@end - -@implementation SPConnectionController (SPConnectionControllerDataSource) - -#ifndef SP_CODA - -/** - * Return the number of children for the specified item in the favourites tree. - * Note that to support the "Quick Connect" entry, the returned count is amended - * for the top level. - */ -- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item -{ - SPTreeNode *node = (item == nil ? favoritesRoot : (SPTreeNode *)item); - - // If at the root, return the count plus one for the "Quick Connect" entry - if (!item) { - return [[node childNodes] count] + 1; - } - - return [[node childNodes] count]; -} - -/** - * Return the branch at the specified index of a supplied tree level. - * Note that to support the "Quick Connect" entry, children of the top level - * have their offsets amended. - */ -- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)childIndex ofItem:(id)item -{ - // For the top level of the tree, return the "Quick Connect" child for position zero; - // amend all other positions to compensate for the faked position. - if (!item) { - if (childIndex == 0) { - return quickConnectItem; - } - - childIndex--; - } - - SPTreeNode *node = (item == nil ? favoritesRoot : (SPTreeNode *)item); - - return NSArrayObjectAtIndex([node childNodes], childIndex); -} - -- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item -{ - SPTreeNode *node = (SPTreeNode *)item; - - return (![node isGroup]) ? [[[node representedObject] nodeFavorite] objectForKey:SPFavoriteNameKey] : [[node representedObject] nodeName]; -} - -- (void)outlineView:(NSOutlineView *)outlineView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item -{ - NSString *newName = [object stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; - - if ([newName length]) { - - // Get the node that was renamed - SPTreeNode *node = [self selectedFavoriteNode]; - - if (![node isGroup]) { - - // Updating the name triggers a KVO update - [self setName:newName]; - [self _saveCurrentDetailsCreatingNewFavorite:NO validateDetails:NO]; - } - else { - [[node representedObject] setNodeName:newName]; - - [favoritesController saveFavorites]; - - [self _reloadFavoritesViewData]; - } - } -} - -#endif - -@end diff --git a/Source/SPConnectionControllerDelegate.h b/Source/SPConnectionControllerDelegate.h deleted file mode 100644 index ad46b662..00000000 --- a/Source/SPConnectionControllerDelegate.h +++ /dev/null @@ -1,47 +0,0 @@ -// -// SPConnectionControllerDelegate.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on October 29, 2010. -// Copyright (c) 2010 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPConnectionController.h" -#import "SPFavoritesExportProtocol.h" -#import "SPFavoritesImportProtocol.h" - -/** - * @category SPConnectionControllerDelegate SPConnectionControllerDelegate.h - * - * @author Stuart Connolly http://stuconnolly.com/ - * - * Connection controller delegate category. - */ -@interface SPConnectionController (SPConnectionControllerDelegate) -#ifndef SP_CODA - <SPFavoritesImportProtocol, SPFavoritesExportProtocol> -#endif - -@end diff --git a/Source/SPConnectionControllerDelegate.m b/Source/SPConnectionControllerDelegate.m deleted file mode 100644 index 4ec0148a..00000000 --- a/Source/SPConnectionControllerDelegate.m +++ /dev/null @@ -1,756 +0,0 @@ -// -// SPConnectionControllerDelegate.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on October 29, 2010. -// Copyright (c) 2010 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPConnectionControllerDelegate.h" -#ifndef SP_CODA -#import "SPFavoritesController.h" -#import "SPTableTextFieldCell.h" -#import "SPFavoriteTextFieldCell.h" -#import "SPPreferenceController.h" -#import "SPGeneralPreferencePane.h" -#import "SPAppController.h" -#import "SPFavoriteNode.h" -#import "SPGroupNode.h" -#import "SPTreeNode.h" -#import "SPFavoritesOutlineView.h" -#import "SPFavoriteColorSupport.h" -#endif - -#ifndef SP_CODA -static NSString *SPDatabaseImage = @"database-small"; -static NSString *SPQuickConnectImage = @"quick-connect-icon.pdf"; -static NSString *SPQuickConnectImageWhite = @"quick-connect-icon-white.pdf"; -#endif - -@interface SPConnectionController (SPConnectionControllerDelegate_Private_API) - -// Privately redeclare as read/write to get the synthesized setter -@property (readwrite, assign) BOOL isEditingConnection; - -- (void)_checkHost; -- (void)_sortFavorites; -- (void)_favoriteTypeDidChange; -- (void)_reloadFavoritesViewData; -- (void)_scrollToSelectedNode; - -- (NSString *)_stripInvalidCharactersFromString:(NSString *)subject; - -- (void)_startEditingConnection; -- (void)_stopEditingConnection; -- (void)_setNodeIsExpanded:(BOOL)expanded fromNotification:(NSNotification *)notification; - -- (NSString *)_generateNameForConnection; - -@end - -@implementation SPConnectionController (SPConnectionControllerDelegate) - -#pragma mark - -#pragma mark SplitView delegate methods - -#ifndef SP_CODA - -/** - * When the split view is resized, trigger a resize in the hidden table - * width as well, to keep the connection view and connected view in sync. - */ -- (void)splitViewDidResizeSubviews:(NSNotification *)notification -{ - if (initComplete) { - [databaseConnectionView setPosition:[[[connectionSplitView subviews] objectAtIndex:0] frame].size.width ofDividerAtIndex:0]; - } -} - -- (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)dividerIndex -{ - return 145.f; -} - -#endif - -#pragma mark - -#pragma mark Outline view delegate methods - -#ifndef SP_CODA - -- (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item -{ - return ([[(SPTreeNode *)item parentNode] parentNode] == nil); -} - -- (void)outlineViewSelectionIsChanging:(NSNotification *)notification -{ - if (isEditingConnection) { - [self _stopEditingConnection]; - - [[notification object] setNeedsDisplay:YES]; - } -} - -- (void)outlineViewSelectionDidChange:(NSNotification *)notification - { - NSInteger selected = [favoritesOutlineView numberOfSelectedRows]; - - if (isEditingConnection) { - [self _stopEditingConnection]; - [[notification object] setNeedsDisplay:YES]; - } - - if (selected == 1) { - [self updateFavoriteSelection:self]; - - favoriteNameFieldWasAutogenerated = NO; - [connectionResizeContainer setHidden:NO]; - [connectionInstructionsTextField setStringValue:NSLocalizedString(@"Enter connection details below, or choose a favorite", @"enter connection details label")]; - } - else if (selected > 1) { - [connectionResizeContainer setHidden:YES]; - [connectionInstructionsTextField setStringValue:NSLocalizedString(@"Please choose a favorite", @"please choose a favorite connection view label")]; - } -} - -- (NSCell *)outlineView:(NSOutlineView *)outlineView dataCellForTableColumn:(NSTableColumn *)tableColumn item:(id)item -{ - if (item == quickConnectItem) { - return (NSCell *)quickConnectCell; - } - - return [tableColumn dataCellForRow:[outlineView rowForItem:item]]; -} - -- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item -{ - SPTreeNode *node = (SPTreeNode *)item; - SPFavoriteTextFieldCell *favoriteCell = (SPFavoriteTextFieldCell *)cell; - - // Draw entries with the small system font by default - [cell setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; - - // Set an image as appropriate; the quick connect image for that entry, no image for other - // top-level items, the folder image for group nodes, or the database image for other nodes. - if (![[node parentNode] parentNode]) { - if (node == quickConnectItem) { - if ([outlineView rowForItem:item] == [outlineView selectedRow]) { - [favoriteCell setImage:[NSImage imageNamed:SPQuickConnectImageWhite]]; - } - else { - [favoriteCell setImage:[NSImage imageNamed:SPQuickConnectImage]]; - } - } - else { - [favoriteCell setImage:nil]; - } - [favoriteCell setLabelColor:nil]; - } - else { - if ([node isGroup]) { - [favoriteCell setImage:folderImage]; - [favoriteCell setLabelColor:nil]; - } - else { - [favoriteCell setImage:[NSImage imageNamed:SPDatabaseImage]]; - NSColor *bgColor = nil; - NSNumber *colorIndexObj = [[[node representedObject] nodeFavorite] objectForKey:SPFavoriteColorIndexKey]; - if(colorIndexObj != nil) { - bgColor = [[SPFavoriteColorSupport sharedInstance] colorForIndex:[colorIndexObj integerValue]]; - } - [favoriteCell setLabelColor:bgColor]; - } - } - - // If a favourite item is being edited, draw the text in bold to show state - if (isEditingConnection && ![node isGroup] && [outlineView rowForItem:item] == [outlineView selectedRow]) { - NSMutableAttributedString *editedCellString = [[cell attributedStringValue] mutableCopy]; - [editedCellString addAttribute:NSForegroundColorAttributeName value:[NSColor colorWithDeviceWhite:0.25f alpha:1.f] range:NSMakeRange(0, [editedCellString length])]; - [cell setAttributedStringValue:editedCellString]; - [editedCellString release]; - } -} - -- (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item -{ - if (item == quickConnectItem) { - return 24.f; - } - - return ([[item parentNode] parentNode]) ? 17.f : 22.f; -} - -- (NSString *)outlineView:(NSOutlineView *)outlineView toolTipForCell:(NSCell *)cell rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)tableColumn item:(id)item mouseLocation:(NSPoint)mouseLocation -{ - NSString *toolTip = nil; - - SPTreeNode *node = (SPTreeNode *)item; - - if (![node isGroup]) { - - NSString *favoriteName = [[[node representedObject] nodeFavorite] objectForKey:SPFavoriteNameKey]; - NSString *favoriteHostname = [[[node representedObject] nodeFavorite] objectForKey:SPFavoriteHostKey]; - - toolTip = ([favoriteHostname length]) ? [NSString stringWithFormat:@"%@ (%@)", favoriteName, favoriteHostname] : favoriteName; - } - - // Only display a tooltip for group nodes that are a descendant of the root node - else if ([[node parentNode] parentNode]) { - - NSUInteger favCount = 0; - NSUInteger groupCount = 0; - - for (SPTreeNode *eachNode in [node childNodes]) - { - if ([eachNode isGroup]) { - groupCount++; - } - else { - favCount++; - } - } - - NSMutableArray *tooltipParts = [NSMutableArray arrayWithCapacity:2]; - - if (favCount || !groupCount) { - [tooltipParts addObject:[NSString stringWithFormat:((favCount == 1) ? NSLocalizedString(@"%d favorite", @"favorite singular label (%d == 1)") : NSLocalizedString(@"%d favorites", @"favorites plural label (%d != 1)")), favCount]]; - } - - if (groupCount) { - [tooltipParts addObject:[NSString stringWithFormat:((groupCount == 1) ? NSLocalizedString(@"%d group", @"favorite group singular label (%d == 1)") : NSLocalizedString(@"%d groups", @"favorite groups plural label (%d != 1)")), groupCount]]; - } - - toolTip = [NSString stringWithFormat:@"%@ - %@", [[node representedObject] nodeName], [tooltipParts componentsJoinedByString:@", "]]; - } - - return toolTip; -} - -- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item -{ - // If this is a top level item, only allow the "Quick Connect" item to be selectable - if (![[item parentNode] parentNode]) { - return item == quickConnectItem; - } - - // Otherwise allow all items to be selectable - return YES; -} - -- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item -{ - return (item != quickConnectItem && ![item isLeaf]); -} - -- (BOOL)outlineView:(NSOutlineView *)outlineView shouldShowOutlineCellForItem:(id)item -{ - return ([[item parentNode] parentNode] != nil); -} - -- (BOOL)outlineView:(NSOutlineView *)outlineView shouldCollapseItem:(id)item -{ - return ([[item parentNode] parentNode] != nil); -} - -- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item -{ - NSEvent *event = [NSApp currentEvent]; - BOOL shiftTabbedIn = ([event type] == NSKeyDown && [[event characters] length] && [[event characters] characterAtIndex:0] == NSBackTabCharacter); - - if (shiftTabbedIn && [(SPFavoritesOutlineView *)outlineView justGainedFocus]) { - return NO; - } - - return item != quickConnectItem; -} - -- (void)outlineViewItemDidCollapse:(NSNotification *)notification -{ - [self _setNodeIsExpanded:NO fromNotification:notification]; - } - -- (void)outlineViewItemDidExpand:(NSNotification *)notification - { - [self _setNodeIsExpanded:YES fromNotification:notification]; -} - -#endif - -#pragma mark - -#pragma mark Outline view drag & drop - -#ifndef SP_CODA - -- (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard -{ - // Prevent a drag which includes the outline title group from taking place - for (id item in items) - { - if (![[item parentNode] parentNode]) return NO; - } - - // If the user is in the process of changing a node's name, trigger a save and prevent dragging. - if (isEditingItemName) { - [favoritesController saveFavorites]; - - [self _reloadFavoritesViewData]; - - isEditingItemName = NO; - - return NO; - } - - [pboard declareTypes:@[SPFavoritesPasteboardDragType] owner:self]; - - BOOL result = [pboard setData:[NSData data] forType:SPFavoritesPasteboardDragType]; - - draggedNodes = items; - - return result; -} - -- (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)childIndex - { - NSDragOperation result = NSDragOperationNone; - - // Prevent the top level or the quick connect item from being a target - if (!item || item == quickConnectItem) return result; - - // Prevent dropping favorites on other favorites (non-groups) - if ((childIndex == NSOutlineViewDropOnItemIndex) && (![item isGroup])) return result; - - // Ensure that none of the dragged nodes are being dragged into children of themselves; if they are, - // prevent the drag. - id itemToCheck = item; - - do { - if ([draggedNodes containsObject:itemToCheck]) { - return result; - } - } - while ((itemToCheck = [itemToCheck parentNode])); - - if ([info draggingSource] == outlineView) { - [outlineView setDropItem:item dropChildIndex:childIndex]; - - result = NSDragOperationMove; - } - - return result; - } - -- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)childIndex -{ - BOOL acceptedDrop = NO; - - if ((!item) || ([info draggingSource] != outlineView)) return acceptedDrop; - - SPTreeNode *node = item ? item : [[[[favoritesRoot childNodes] objectAtIndex:0] childNodes] objectAtIndex:0]; - - // Cache the selected nodes for selection restoration afterwards - NSArray *preDragSelection = [self selectedFavoriteNodes]; - - // Disable all automatic sorting - currentSortItem = -1; - reverseFavoritesSort = NO; - - [prefs setInteger:currentSortItem forKey:SPFavoritesSortedBy]; - [prefs setBool:NO forKey:SPFavoritesSortedInReverse]; - - // Uncheck sort by menu items - for (NSMenuItem *menuItem in [[favoritesSortByMenuItem submenu] itemArray]) - { - [menuItem setState:NSOffState]; - } - - if (![draggedNodes count]) return acceptedDrop; - - if ([node isGroup]) { - if (childIndex == NSOutlineViewDropOnItemIndex) { - childIndex = 0; - } - [outlineView expandItem:node]; - } - else { - if (childIndex == NSOutlineViewDropOnItemIndex) { - childIndex = 0; - } - } - - if (![[node representedObject] nodeName]) { - node = [[favoritesRoot childNodes] objectAtIndex:0]; - } - - NSMutableArray *childNodeArray = [node mutableChildNodes]; - - for (SPTreeNode *treeNode in draggedNodes) - { - // Remove the node from its old location - NSInteger oldIndex = [childNodeArray indexOfObject:treeNode]; - NSInteger newIndex = childIndex; - - if (oldIndex != NSNotFound) { - - [childNodeArray removeObjectAtIndex:oldIndex]; - - if (childIndex > oldIndex) { - newIndex--; - } - } - else { - [[[treeNode parentNode] mutableChildNodes] removeObject:treeNode]; - } - - [childNodeArray insertObject:treeNode atIndex:newIndex]; - - newIndex++; - } - - [favoritesController saveFavorites]; - - [self _reloadFavoritesViewData]; - - [[NSNotificationCenter defaultCenter] postNotificationName:SPConnectionFavoritesChangedNotification object:self]; - - [[[SPAppDelegate preferenceController] generalPreferencePane] updateDefaultFavoritePopup]; - - // Update the selection to account for rearranged faourites - NSMutableIndexSet *restoredSelection = [NSMutableIndexSet indexSet]; - - for (SPTreeNode *eachNode in preDragSelection) - { - [restoredSelection addIndex:[favoritesOutlineView rowForItem:eachNode]]; - } - - [favoritesOutlineView selectRowIndexes:restoredSelection byExtendingSelection:NO]; - - acceptedDrop = YES; - - return acceptedDrop; -} - -#endif - -#pragma mark - -#pragma mark Textfield delegate methods - -#ifndef SP_CODA - -/** - * React to control text changes in the connection interface - */ -- (void)controlTextDidChange:(NSNotification *)notification -{ - id field = [notification object]; - - // Ignore changes in the outline view edit fields - if ([field isKindOfClass:[NSOutlineView class]]) { - return; - } - - // If a 'name' field was edited, and is now of zero length, trigger a replacement - // with a standard suggestion - if (((field == standardNameField) || (field == socketNameField) || (field == sshNameField)) && [self selectedFavoriteNode]) { - if (![[self _stripInvalidCharactersFromString:[field stringValue]] length]) { - [self controlTextDidEndEditing:notification]; - } - } - - [self _startEditingConnection]; - - if (favoriteNameFieldWasAutogenerated && (field != standardNameField && field != socketNameField && field != sshNameField)) { - [self setName:[self _generateNameForConnection]]; - } -} - -/** - * React to the end of control text changes in the connection interface. - */ -- (void)controlTextDidEndEditing:(NSNotification *)notification -{ - id field = [notification object]; - - // Handle updates to the 'name' field of the selected favourite. The favourite name should - // have leading or trailing spaces removed at the end of editing, and if it's left empty, - // should have a default name set. - if (((field == standardNameField) || (field == socketNameField) || (field == sshNameField)) && [self selectedFavoriteNode]) { - - NSString *favoriteName = [self _stripInvalidCharactersFromString:[field stringValue]]; - - if (![favoriteName length]) { - favoriteName = [self _generateNameForConnection]; - - if (favoriteName) { - [self setName:favoriteName]; - } - - // Enable user@host update in reaction to other UI changes - favoriteNameFieldWasAutogenerated = YES; - } - else if (![[field stringValue] isEqualToString:[self _generateNameForConnection]]) { - favoriteNameFieldWasAutogenerated = NO; - [self setName:favoriteName]; - } - } - - // When a host field finishes editing, ensure that it hasn't been set to "localhost" to - // ensure that socket connections don't inadvertently occur. - if (field == standardSQLHostField || field == sshSQLHostField) { - [self _checkHost]; - } -} - -#endif - -#pragma mark - -#pragma mark Tab bar delegate methods - -#ifndef SP_CODA - -/** - * Trigger a resize action whenever the tab view changes. The connection - * detail forms are held within container views, which are of a fixed width; - * the tabview and buttons are contained within a resizable view which - * is set to dimensions based on the container views, allowing the view - * to be sized according to the detail type. - */ -- (void)tabView:(NSTabView *)tabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem -{ - NSInteger selectedTabView = [tabView indexOfTabViewItem:tabViewItem]; - - if (selectedTabView == previousType) return; - - [self _startEditingConnection]; - - [self resizeTabViewToConnectionType:selectedTabView animating:YES]; - - // Update the host as appropriate - if ((selectedTabView != SPSocketConnection) && [[self host] isEqualToString:@"localhost"]) { - [self setHost:@""]; - } - - previousType = selectedTabView; - - [self _favoriteTypeDidChange]; -} - -#endif - -#pragma mark - -#pragma mark Color Selector delegate - -- (void)colorSelectorDidChange:(SPColorSelectorView *)sel -{ - [self _startEditingConnection]; -} - -#pragma mark - -#pragma mark Scroll view notifications - -#ifndef SP_CODA - -/** - * As the scrollview resizes, keep the details centered within it if - * the detail frame is larger than the scrollview size; otherwise, pin - * the detail frame to the top of the scrollview. - */ -- (void)scrollViewFrameChanged:(NSNotification *)aNotification -{ - NSRect scrollViewFrame = [connectionDetailsScrollView frame]; - NSRect scrollDocumentFrame = [[connectionDetailsScrollView documentView] frame]; - NSRect connectionDetailsFrame = [connectionResizeContainer frame]; - - // Scroll view is smaller than contents - keep positioned at top. - if (scrollViewFrame.size.height < connectionDetailsFrame.size.height + 10) { - if (connectionDetailsFrame.origin.y != 0) { - connectionDetailsFrame.origin.y = 0; - [connectionResizeContainer setFrame:connectionDetailsFrame]; - scrollDocumentFrame.size.height = connectionDetailsFrame.size.height + 10; - [[connectionDetailsScrollView documentView] setFrame:scrollDocumentFrame]; - } - } - // Otherwise, center - else { - connectionDetailsFrame.origin.y = (scrollViewFrame.size.height - connectionDetailsFrame.size.height)/3; - [connectionResizeContainer setFrame:connectionDetailsFrame]; - scrollDocumentFrame.size.height = scrollViewFrame.size.height; - [[connectionDetailsScrollView documentView] setFrame:scrollDocumentFrame]; - } -} - -#endif - -#pragma mark - -#pragma mark Menu Validation - -#ifndef SP_CODA - -/** - * Menu item validation. - */ -- (BOOL)validateMenuItem:(NSMenuItem *)menuItem -{ - SEL action = [menuItem action]; - - SPTreeNode *node = [self selectedFavoriteNode]; - NSInteger selectedRows = [favoritesOutlineView numberOfSelectedRows]; - - if ((action == @selector(sortFavorites:)) || (action == @selector(reverseSortFavorites:))) { - - if ([[favoritesRoot allChildLeafs] count] < 2) return NO; - - // Loop all the items in the sort by menu only checking the currently selected one - for (NSMenuItem *item in [[menuItem menu] itemArray]) - { - [item setState:([[menuItem menu] indexOfItem:item] == currentSortItem)]; - } - - // Check or uncheck the reverse sort item - if (action == @selector(reverseSortFavorites:)) { - [menuItem setState:reverseFavoritesSort]; - } - - return YES; - } - - // import does not depend on a selection - if(action == @selector(importFavorites:)) return YES; - - if (node == quickConnectItem) return NO; - - // Remove/rename the selected node - if (action == @selector(removeNode:) || action == @selector(renameNode:)) { - return selectedRows == 1; - } - - // Duplicate and make the selected favorite the default - if (action == @selector(duplicateFavorite:)) { - return ((selectedRows == 1) && (![node isGroup])); - } - - // Make selected favorite the default - if (action == @selector(makeSelectedFavoriteDefault:)) { - NSInteger favoriteID = [[[self selectedFavorite] objectForKey:SPFavoriteIDKey] integerValue]; - - return ((selectedRows == 1) && (![node isGroup]) && (favoriteID != [prefs integerForKey:SPDefaultFavorite])); - } - - // Favorites export - if (action == @selector(exportFavorites:)) { - - if ([[favoritesRoot allChildLeafs] count] == 0 || selectedRows == 0) { - return NO; - } - else if (selectedRows > 1) { - [menuItem setTitle:NSLocalizedString(@"Export Selected...", @"export selected favorites menu item")]; - } - } - - return YES; -} - -#endif - -#pragma mark - -#pragma mark Favorites import/export delegate methods - -#ifndef SP_CODA - -/** - * Called by the favorites importer when the imported data is available. - */ -- (void)favoritesImportData:(NSArray *)data -{ - SPTreeNode *newNode; - NSMutableArray *importedNodes = [NSMutableArray array]; - NSMutableIndexSet *importedIndexSet = [NSMutableIndexSet indexSet]; - - // Add each of the imported favorites to the root node - for (NSMutableDictionary *favorite in data) - { - newNode = [favoritesController addFavoriteNodeWithData:favorite asChildOfNode:nil]; - [importedNodes addObject:newNode]; - } - - if (currentSortItem > SPFavoritesSortUnsorted) { - [self _sortFavorites]; - } - - [self _reloadFavoritesViewData]; - - // Select the new nodes and scroll into view - for (SPTreeNode *eachNode in importedNodes) - { - [importedIndexSet addIndex:[favoritesOutlineView rowForItem:eachNode]]; - } - - [favoritesOutlineView selectRowIndexes:importedIndexSet byExtendingSelection:NO]; - - [self _scrollToSelectedNode]; -} - -/** - * Called by the favorites importer when the import completes. - */ -- (void)favoritesImportCompletedWithError:(NSError *)error -{ - if (error) { - NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Favorites import error", @"favorites import error message") - defaultButton:NSLocalizedString(@"OK", @"OK") - alternateButton:nil - otherButton:nil - informativeTextWithFormat:NSLocalizedString(@"The following error occurred during the import process:\n\n%@", @"favorites import error informative message"), [error localizedDescription]]; - - [alert beginSheetModalForWindow:[dbDocument parentWindow] - modalDelegate:self - didEndSelector:NULL - contextInfo:NULL]; - } -} - -#endif - -#pragma mark - -#pragma mark Private API - -#ifndef SP_CODA - -/** - * Sets the expanded state of the node from the supplied outline view notification. - * - * @param expanded The state of the node - * @param notification The notification genrated from the state change - */ -- (void)_setNodeIsExpanded:(BOOL)expanded fromNotification:(NSNotification *)notification -{ - SPGroupNode *node = [[[notification userInfo] valueForKey:@"NSObject"] representedObject]; - - [node setNodeIsExpanded:expanded]; -} - -#endif - -@end diff --git a/Source/SPConnectionControllerDelegateProtocol.h b/Source/SPConnectionControllerDelegateProtocol.h index 74410309..ae049778 100644 --- a/Source/SPConnectionControllerDelegateProtocol.h +++ b/Source/SPConnectionControllerDelegateProtocol.h @@ -28,6 +28,8 @@ // // More info at <https://github.com/sequelpro/sequelpro> +@class SPConnectionController; + /** * @protocol SPConnectionControllerDelegateProtocol SPConnectionControllerDelegateProtocol.h * @@ -42,13 +44,13 @@ * * @param controller The calling connection controller. */ -- (void)connectionControllerInitiatingConnection:(id)controller; +- (void)connectionControllerInitiatingConnection:(SPConnectionController *)controller; /** * Called when the connection controller's connection attempt failed. * * @param controller The calling connection controller. */ -- (void)connectionControllerConnectAttemptFailed:(id)controller; +- (void)connectionControllerConnectAttemptFailed:(SPConnectionController *)controller; @end diff --git a/Source/SPConnectionControllerInitializer.h b/Source/SPConnectionControllerInitializer.h deleted file mode 100644 index bfa5401a..00000000 --- a/Source/SPConnectionControllerInitializer.h +++ /dev/null @@ -1,49 +0,0 @@ -// -// SPConnectionControllerInitializer.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on January 22, 2012. -// Copyright (c) 2012 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPConnectionController.h" - -/** - * @category SPConnectionControllerInitializer SPConnectionControllerInitializer.h - * - * @author Stuart Connolly http://stuconnolly.com/ - * - * Connection controller initialization category. - */ -@interface SPConnectionController (SPConnectionControllerInitializer) - -- (id)initWithDocument:(SPDatabaseDocument *)document; - -- (void)loadNib; -- (void)registerForNotifications; -- (void)setUpFavoritesOutlineView; -- (void)setUpSelectedConnectionFavorite; - -@end diff --git a/Source/SPConnectionControllerInitializer.m b/Source/SPConnectionControllerInitializer.m deleted file mode 100644 index 5254e36d..00000000 --- a/Source/SPConnectionControllerInitializer.m +++ /dev/null @@ -1,417 +0,0 @@ -// -// SPConnectionControllerInitializer.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on January 22, 2012. -// Copyright (c) 2012 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPConnectionControllerInitializer.h" -#import "SPKeychain.h" -#import "SPFavoritesController.h" -#import "SPFavoriteTextFieldCell.h" -#import "SPTreeNode.h" -#import "SPFavoriteNode.h" -#import "SPGroupNode.h" -#import "SPDatabaseViewController.h" -#import "SPSplitView.h" -#import "SPFavoriteColorSupport.h" -#import "SPColorSelectorView.h" - -#ifndef SP_CODA -static NSString *SPConnectionViewNibName = @"ConnectionView"; -#endif - -@interface SPConnectionController () - -- (void)_processFavoritesDataChange; -- (void)_reloadFavoritesViewData; -- (void)_selectNode:(SPTreeNode *)node; -- (void)_scrollToSelectedNode; -- (void)_documentWillClose:(NSNotification *)notification; - -- (SPTreeNode *)_favoriteNodeForFavoriteID:(NSInteger)favoriteID; - -- (void)scrollViewFrameChanged:(NSNotification *)aNotification; - -@end - -@interface SPConnectionController (SPConnectionControllerInitializer_Private_API) - -- (void)_restoreOutlineViewStateNode:(SPTreeNode *)node; - -@end - - -@implementation SPConnectionController (SPConnectionControllerInitializer) - -/** - * Initialise the connection controller, linking it to the parent document and setting up the parent window. - */ -- (id)initWithDocument:(SPDatabaseDocument *)document -{ - if ((self = [super init])) { - - // Weak reference - dbDocument = document; - -#ifndef SP_CODA - databaseConnectionSuperview = [dbDocument databaseView]; - databaseConnectionView = [dbDocument valueForKey:@"contentViewSplitter"]; -#endif - - // Keychain references - connectionKeychainID = nil; - connectionKeychainItemName = nil; - connectionKeychainItemAccount = nil; - connectionSSHKeychainItemName = nil; - connectionSSHKeychainItemAccount = nil; - - initComplete = NO; - isEditingItemName = NO; - isConnecting = NO; - isTestingConnection = NO; - sshTunnel = nil; - mySQLConnection = nil; - cancellingConnection = NO; - favoriteNameFieldWasAutogenerated = NO; - - [self loadNib]; - - NSArray *colorList = [[SPFavoriteColorSupport sharedInstance] userColorList]; - [sshColorField setColorList:colorList]; - [sshColorField bind:@"selectedTag" toObject:self withKeyPath:@"colorIndex" options:nil]; - [standardColorField setColorList:colorList]; - [standardColorField bind:@"selectedTag" toObject:self withKeyPath:@"colorIndex" options:nil]; - [socketColorField setColorList:colorList]; - [socketColorField bind:@"selectedTag" toObject:self withKeyPath:@"colorIndex" options:nil]; - - [self registerForNotifications]; - -#ifndef SP_CODA - // Hide the main view and position and display the connection view - [databaseConnectionView setHidden:YES]; - [connectionView setFrame:[databaseConnectionView frame]]; - [databaseConnectionSuperview addSubview:connectionView]; - - // Set up the splitview - [connectionSplitView setMinSize:80.f ofSubviewAtIndex:0]; - [connectionSplitView setMinSize:445.f ofSubviewAtIndex:1]; - - // Generic folder image for use in the outline view's groups - folderImage = [[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGenericFolderIcon)] retain]; - [folderImage setSize:NSMakeSize(16, 16)]; - - // Set up a keychain instance and preferences reference, and create the initial favorites list - keychain = [[SPKeychain alloc] init]; - prefs = [[NSUserDefaults standardUserDefaults] retain]; - - // Create a reference to the favorites controller, forcing the data to be loaded from disk - // and the tree to be constructed. - favoritesController = [SPFavoritesController sharedFavoritesController]; - - // Tree references - favoritesRoot = [favoritesController favoritesTree]; - currentFavorite = nil; - - // Create the "Quick Connect" placeholder group - quickConnectItem = [[SPTreeNode treeNodeWithRepresentedObject:[SPGroupNode groupNodeWithName:[NSLocalizedString(@"Quick Connect", @"Quick connect item label") uppercaseString]]] retain]; - [quickConnectItem setIsGroup:YES]; - - // Create a NSOutlineView cell for the Quick Connect group - quickConnectCell = [[SPFavoriteTextFieldCell alloc] init]; - [quickConnectCell setDrawsDividerUnderCell:YES]; - [quickConnectCell setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; - - // Update the UI - [self _reloadFavoritesViewData]; - [self setUpFavoritesOutlineView]; - [self _restoreOutlineViewStateNode:favoritesRoot]; - - // Set up the selected favourite, and scroll after a small delay to fix animation delay on Lion - [self setUpSelectedConnectionFavorite]; - if ([favoritesOutlineView selectedRow] != -1) { - [self performSelector:@selector(_scrollToSelectedNode) withObject:nil afterDelay:0.0]; - } - - // Set sort items - currentSortItem = (SPFavoritesSortItem)[prefs integerForKey:SPFavoritesSortedBy]; - reverseFavoritesSort = [prefs boolForKey:SPFavoritesSortedInReverse]; -#endif - - initComplete = YES; - } - - return self; -} - -/** - * Loads the connection controllers UI nib. - */ -- (void)loadNib -{ -#ifndef SP_CODA - - // Load the connection nib, keeping references to the top-level objects for later release - nibObjectsToRelease = [[NSMutableArray alloc] init]; - - NSArray *connectionViewTopLevelObjects = nil; - NSNib *nibLoader = [[NSNib alloc] initWithNibNamed:SPConnectionViewNibName bundle:[NSBundle mainBundle]]; - - [nibLoader instantiateNibWithOwner:self topLevelObjects:&connectionViewTopLevelObjects]; - [nibObjectsToRelease addObjectsFromArray:connectionViewTopLevelObjects]; - [nibLoader release]; - -#endif -} - -/** - * Registers for various notifications. - */ -- (void)registerForNotifications -{ - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(_documentWillClose:) - name:SPDocumentWillCloseNotification - object:dbDocument]; - -#ifndef SP_CODA - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(scrollViewFrameChanged:) - name:NSViewFrameDidChangeNotification - object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(_processFavoritesDataChange:) - name:SPConnectionFavoritesChangedNotification - object:nil]; - - // Registered to be notified of changes to connection information - [self addObserver:self - forKeyPath:SPFavoriteTypeKey - options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) - context:NULL]; - - [self addObserver:self - forKeyPath:SPFavoriteNameKey - options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) - context:NULL]; - - [self addObserver:self - forKeyPath:SPFavoriteHostKey - options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) - context:NULL]; - - [self addObserver:self - forKeyPath:SPFavoriteUserKey - options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) - context:NULL]; - - [self addObserver:self - forKeyPath:SPFavoriteColorIndexKey - options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) - context:NULL]; - - [self addObserver:self - forKeyPath:SPFavoriteDatabaseKey - options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) - context:NULL]; - - [self addObserver:self - forKeyPath:SPFavoriteSocketKey - options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) - context:NULL]; - - [self addObserver:self - forKeyPath:SPFavoritePortKey - options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) - context:NULL]; - - [self addObserver:self - forKeyPath:SPFavoriteUseSSLKey - options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) - context:NULL]; - - [self addObserver:self - forKeyPath:SPFavoriteSSHHostKey - options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) - context:NULL]; - - [self addObserver:self - forKeyPath:SPFavoriteSSHUserKey - options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) - context:NULL]; - - [self addObserver:self - forKeyPath:SPFavoriteSSHPortKey - options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) - context:NULL]; - - [self addObserver:self - forKeyPath:SPFavoriteSSHKeyLocationEnabledKey - options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) - context:NULL]; - - [self addObserver:self - forKeyPath:SPFavoriteSSHKeyLocationKey - options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) - context:NULL]; - - [self addObserver:self - forKeyPath:SPFavoriteSSLKeyFileLocationEnabledKey - options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) - context:NULL]; - - [self addObserver:self - forKeyPath:SPFavoriteSSLKeyFileLocationKey - options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) - context:NULL]; - - [self addObserver:self - forKeyPath:SPFavoriteSSLCertificateFileLocationEnabledKey - options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) - context:NULL]; - - [self addObserver:self - forKeyPath:SPFavoriteSSLCertificateFileLocationKey - options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) - context:NULL]; - - [self addObserver:self - forKeyPath:SPFavoriteSSLCACertFileLocationEnabledKey - options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) - context:NULL]; - - [self addObserver:self - forKeyPath:SPFavoriteSSLCACertFileLocationKey - options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) - context:NULL]; -#endif -} - -/** - * Performs any set up necessary for the favorities outline view. - */ -- (void)setUpFavoritesOutlineView -{ - // Register double click action for the favorites outline view (double click favorite to connect) - [favoritesOutlineView setTarget:self]; - [favoritesOutlineView setDoubleAction:@selector(nodeDoubleClicked:)]; - - // Register drag types for the favorites outline view - [favoritesOutlineView registerForDraggedTypes:@[SPFavoritesPasteboardDragType]]; - [favoritesOutlineView setDraggingSourceOperationMask:NSDragOperationMove forLocal:YES]; -} - -/** - * Sets up the selected connection favorite according to the user's preferences. - */ -- (void)setUpSelectedConnectionFavorite -{ -#ifndef SP_CODA - SPTreeNode *favorite = [self _favoriteNodeForFavoriteID:[prefs integerForKey:[prefs boolForKey:SPSelectLastFavoriteUsed] ? SPLastFavoriteID : SPDefaultFavorite]]; - - if (favorite) { - - if (favorite == quickConnectItem) { - [self _selectNode:favorite]; - } - else { - NSNumber *typeNumber = [[[favorite representedObject] nodeFavorite] objectForKey:SPFavoriteTypeKey]; - previousType = typeNumber ? [typeNumber integerValue] : SPTCPIPConnection; - - [self _selectNode:favorite]; - [self resizeTabViewToConnectionType:[[[[favorite representedObject] nodeFavorite] objectForKey:SPFavoriteTypeKey] integerValue] animating:NO]; - } - - [self _scrollToSelectedNode]; - } - else { - previousType = SPTCPIPConnection; - - [self resizeTabViewToConnectionType:SPTCPIPConnection animating:NO]; - } -#endif -} - -#pragma mark - -#pragma mark Private API - -/** - * Responds to notifications that the favorites root has changed, - * and updates the interface to match. - */ -- (void)_processFavoritesDataChange:(NSNotification *)aNotification -{ -#ifndef SP_CODA - // Check the supplied notification for the sender; if the sender - // was this object, ignore it - if ([aNotification object] == self) return; - - NSArray *selectedFavoriteNodes = [self selectedFavoriteNodes]; - - [self _reloadFavoritesViewData]; - - NSMutableIndexSet *selectionIndexes = [NSMutableIndexSet indexSet]; - - for (SPTreeNode *eachNode in selectedFavoriteNodes) - { - NSInteger anIndex = [favoritesOutlineView rowForItem:eachNode]; - - if (anIndex == -1) continue; - - [selectionIndexes addIndex:anIndex]; - } - - [favoritesOutlineView selectRowIndexes:selectionIndexes byExtendingSelection:NO]; -#endif -} - -/** - * Restores the outline views group nodes expansion state. - * - * @param node The node to traverse - */ -#ifndef SP_CODA -- (void)_restoreOutlineViewStateNode:(SPTreeNode *)node -{ - if ([node isGroup]) { - if ([[node representedObject] nodeIsExpanded]) { - [favoritesOutlineView expandItem:node]; - } - else { - [favoritesOutlineView collapseItem:node]; - } - - for (SPTreeNode *childNode in [node childNodes]) - { - if ([childNode isGroup]) { - [self _restoreOutlineViewStateNode:childNode]; - } - } - } -} -#endif - -@end diff --git a/Source/SPConnectionDelegate.h b/Source/SPConnectionDelegate.h deleted file mode 100644 index c786d8ac..00000000 --- a/Source/SPConnectionDelegate.h +++ /dev/null @@ -1,42 +0,0 @@ -// -// SPConnectionDelegate.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on November 13, 2009. -// Copyright (c) 2009 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPDatabaseDocument.h" - -#import <SPMySQL/SPMySQLConnectionDelegate.h> - -@interface SPDatabaseDocument (SPConnectionDelegate) <SPMySQLConnectionDelegate> - -- (void) closeAndDisconnect; - -- (NSString *)keychainPasswordForConnection:(SPMySQLConnection *)connection; -- (NSString *)keychainPasswordForSSHConnection:(SPMySQLConnection *)connection; - -@end diff --git a/Source/SPConnectionDelegate.m b/Source/SPConnectionDelegate.m deleted file mode 100644 index 9e5cbd25..00000000 --- a/Source/SPConnectionDelegate.m +++ /dev/null @@ -1,209 +0,0 @@ -// -// SPConnectionDelegate.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on November 13, 2009. -// Copyright (c) 2009 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPConnectionDelegate.h" -#import "SPConnectionController.h" -#import "SPQueryController.h" -#import "SPKeychain.h" -#import "SPAlertSheets.h" - -#import <SPMySQL/SPMySQLConstants.h> - -@implementation SPDatabaseDocument (SPConnectionDelegate) - -#pragma mark - -#pragma mark SPMySQLConnection delegate methods - -/** - * Invoked when the framework is about to perform a query. - */ -- (void)willQueryString:(NSString *)query connection:(id)connection -{ -#ifndef SP_CODA - if ([prefs boolForKey:SPConsoleEnableLogging]) { - if ((_queryMode == SPInterfaceQueryMode && [prefs boolForKey:SPConsoleEnableInterfaceLogging]) || - (_queryMode == SPCustomQueryQueryMode && [prefs boolForKey:SPConsoleEnableCustomQueryLogging]) || - (_queryMode == SPImportExportQueryMode && [prefs boolForKey:SPConsoleEnableImportExportLogging])) - { - [[SPQueryController sharedQueryController] showMessageInConsole:query connection:[self name] database:[self database]]; - } - } -#endif -} - -/** - * Invoked when the query just executed by the framework resulted in an error. - */ -- (void)queryGaveError:(NSString *)error connection:(id)connection -{ -#ifndef SP_CODA - if ([prefs boolForKey:SPConsoleEnableLogging] && [prefs boolForKey:SPConsoleEnableErrorLogging]) { - [[SPQueryController sharedQueryController] showErrorInConsole:error connection:[self name] database:[self database]]; - } -#endif -} - -/** - * Invoked when the current connection needs a password from the Keychain. - */ -- (NSString *)keychainPasswordForConnection:(SPMySQLConnection *)connection -{ - // If no keychain item is available, return an empty password - if (![connectionController connectionKeychainItemName]) return nil; - - // Otherwise, pull the password from the keychain using the details from this connection - SPKeychain *keychain = [[SPKeychain alloc] init]; - - NSString *password = [keychain getPasswordForName:[connectionController connectionKeychainItemName] account:[connectionController connectionKeychainItemAccount]]; - - [keychain release]; - - return password; -} - -/** - * Invoked when the current connection needs a ssh password from the Keychain. - * This isn't actually part of the SPMySQLConnection delegate protocol, but is here - * due to its similarity to the previous method. - */ -- (NSString *)keychainPasswordForSSHConnection:(SPMySQLConnection *)connection -{ - // If no keychain item is available, return an empty password - if (![connectionController connectionKeychainItemName]) return @""; - - // Otherwise, pull the password from the keychain using the details from this connection - SPKeychain *keychain = [[SPKeychain alloc] init]; - - NSString *connectionSSHKeychainItemName = [[keychain nameForSSHForFavoriteName:[connectionController name] id:[self keyChainID]] retain]; - NSString *connectionSSHKeychainItemAccount = [[keychain accountForSSHUser:[connectionController sshUser] sshHost:[connectionController sshHost]] retain]; - NSString *sshPassword = [keychain getPasswordForName:connectionSSHKeychainItemName account:connectionSSHKeychainItemAccount]; - - if (!sshPassword || ![sshPassword length]) { - sshPassword = @""; - } - - if (connectionSSHKeychainItemName) [connectionSSHKeychainItemName release]; - if (connectionSSHKeychainItemAccount) [connectionSSHKeychainItemAccount release]; - - [keychain release]; - - return sshPassword; -} - -/** - * Invoked when an attempt was made to execute a query on the current connection, but the connection is not - * actually active. - */ -- (void)noConnectionAvailable:(id)connection -{ - SPOnewayAlertSheet( - NSLocalizedString(@"No connection available", @"no connection available message"), - [self parentWindow], - NSLocalizedString(@"An error has occured and there doesn't seem to be a connection available.", @"no connection available informatie message") - ); -} - -/** - * Invoked when the connection fails and the framework needs to know how to proceed. - */ -- (SPMySQLConnectionLostDecision)connectionLost:(id)connection -{ - SPMySQLConnectionLostDecision connectionErrorCode = SPMySQLConnectionLostDisconnect; - - // Only display the reconnect dialog if the window is visible - if ([self parentWindow] && [[self parentWindow] isVisible]) { - - // Ensure the window isn't miniaturized - if ([[self parentWindow] isMiniaturized]) [[self parentWindow] deminiaturize:self]; - -#ifndef SP_CODA - // Ensure the window and tab are frontmost - [self makeKeyDocument]; -#endif - - // Display the connection error dialog and wait for the return code - [NSApp beginSheet:connectionErrorDialog modalForWindow:[self parentWindow] modalDelegate:self didEndSelector:nil contextInfo:nil]; - connectionErrorCode = (SPMySQLConnectionLostDecision)[NSApp runModalForWindow:connectionErrorDialog]; - - [NSApp endSheet:connectionErrorDialog]; - [connectionErrorDialog orderOut:nil]; - - // If 'disconnect' was selected, trigger a window close. - if (connectionErrorCode == SPMySQLConnectionLostDisconnect) { - [self performSelectorOnMainThread:@selector(closeAndDisconnect) withObject:nil waitUntilDone:YES]; - } - } - - return connectionErrorCode; -} - -/** - * Invoke to display an informative but non-fatal error directly to the user. - */ -- (void)showErrorWithTitle:(NSString *)theTitle message:(NSString *)theMessage -{ - if ([[self parentWindow] isVisible]) { - SPOnewayAlertSheet(theTitle, [self parentWindow], theMessage); - } -} - -/** - * Invoked when user dismisses the error sheet displayed as a result of the current connection being lost. - */ -- (IBAction)closeErrorConnectionSheet:(id)sender -{ - [NSApp stopModalWithCode:[sender tag]]; -} - -/** - * Close the connection - should be performed on the main thread. - */ -- (void) closeAndDisconnect -{ -#ifndef SP_CODA - NSWindow *theParentWindow = [self parentWindow]; - - _isConnected = NO; - - if ([[[self parentTabViewItem] tabView] numberOfTabViewItems] == 1) { - [theParentWindow orderOut:self]; - [theParentWindow setAlphaValue:0.0f]; - [theParentWindow performSelector:@selector(close) withObject:nil afterDelay:1.0]; - } - else { - [[[self parentTabViewItem] tabView] performSelector:@selector(removeTabViewItem:) withObject:[self parentTabViewItem] afterDelay:0.5]; - [theParentWindow performSelector:@selector(makeKeyAndOrderFront:) withObject:nil afterDelay:0.6]; - } - - [self parentTabDidClose]; -#endif -} - -@end diff --git a/Source/SPConnectionHandler.h b/Source/SPConnectionHandler.h deleted file mode 100644 index 05883608..00000000 --- a/Source/SPConnectionHandler.h +++ /dev/null @@ -1,53 +0,0 @@ -// -// SPConnectionHandler.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on November 15, 2010. -// Copyright (c) 2010 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPConnectionController.h" - -/** - * @category SPConnectionHandler SPConnectionHandler.h - * - * @author Stuart Connolly http://stuconnolly.com/ - * - * Connection handler category. Handles all connection related non-interface processes. - */ -@interface SPConnectionController (SPConnectionHandler) - -- (void)initiateMySQLConnection; -- (void)initiateMySQLConnectionInBackground; -- (void)initiateSSHTunnelConnection; - -- (void)mySQLConnectionEstablished; -- (void)sshTunnelCallback:(SPSSHTunnel *)theTunnel; - -- (void)addConnectionToDocument; - -- (void)failConnectionWithTitle:(NSString *)theTitle errorMessage:(NSString *)theErrorMessage detail:(NSString *)errorDetail rawErrorText:(NSString *)rawErrorText; - -@end diff --git a/Source/SPConnectionHandler.m b/Source/SPConnectionHandler.m deleted file mode 100644 index a1c7a532..00000000 --- a/Source/SPConnectionHandler.m +++ /dev/null @@ -1,538 +0,0 @@ -// -// SPConnectionHandler.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on November 15, 2010. -// Copyright (c) 2010 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPConnectionHandler.h" -#import "SPDatabaseDocument.h" -#import "SPAlertSheets.h" -#import "SPSSHTunnel.h" -#import "SPKeychain.h" -#import "RegexKitLite.h" -#import "SPCategoryAdditions.h" -#import "SPThreadAdditions.h" - -#import <SPMySQL/SPMySQL.h> - -static NSString *SPLocalhostAddress = @"127.0.0.1"; - -@interface SPConnectionController () - -- (void)_restoreConnectionInterface; - -@end - -@interface SPConnectionController (SPConnectionHandlerPrivateAPI) - -- (void)_showConnectionTestResult:(NSString *)resultString; - -@end - -#pragma mark - - -@implementation SPConnectionController (SPConnectionHandler) - -/** - * Set up the MySQL connection, either through a successful tunnel or directly in the background. - */ -- (void)initiateMySQLConnection -{ -#ifndef SP_CODA - if (isTestingConnection) { - if (sshTunnel) { - [progressIndicatorText setStringValue:NSLocalizedString(@"Testing MySQL...", @"MySQL connection test very short status message")]; - } - else { - [progressIndicatorText setStringValue:NSLocalizedString(@"Testing connection...", @"Connection test very short status message")]; - } - } - else if (sshTunnel) { - [progressIndicatorText setStringValue:NSLocalizedString(@"MySQL connecting...", @"MySQL connecting very short status message")]; - } - else { - [progressIndicatorText setStringValue:NSLocalizedString(@"Connecting...", @"Generic connecting very short status message")]; - } - - [progressIndicatorText display]; - - [connectButton setTitle:NSLocalizedString(@"Cancel", @"cancel button")]; - [connectButton setAction:@selector(cancelConnection:)]; - [connectButton setEnabled:YES]; - [connectButton display]; -#endif - - [NSThread detachNewThreadWithName:SPCtxt(@"SPConnectionHandler MySQL connection task", dbDocument) target:self selector:@selector(initiateMySQLConnectionInBackground) object:nil]; -} - -/** - * Initiates the core of the MySQL connection process on a background thread. - */ -- (void)initiateMySQLConnectionInBackground -{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - - mySQLConnection = [[SPMySQLConnection alloc] init]; - - // Set up shared details - [mySQLConnection setUsername:[self user]]; - - // Initialise to socket if appropriate. - if ([self type] == SPSocketConnection) { - [mySQLConnection setUseSocket:YES]; - [mySQLConnection setSocketPath:[self socket]]; - - // Otherwise, initialise to host, using tunnel if appropriate - } - else { - [mySQLConnection setUseSocket:NO]; - - if ([self type] == SPSSHTunnelConnection) { - [mySQLConnection setHost:@"127.0.0.1"]; - - [mySQLConnection setPort:[sshTunnel localPort]]; - [mySQLConnection setProxy:sshTunnel]; - } - else { - [mySQLConnection setHost:[self host]]; - - if ([[self port] length]) [mySQLConnection setPort:[[self port] integerValue]]; - } - } - - // Only set the password if there is no Keychain item set and the connection is not being tested. - // The connection will otherwise ask the delegate for passwords in the Keychain. - if ((!connectionKeychainItemName || isTestingConnection) && [self password]) { - [mySQLConnection setPassword:[self password]]; - } - - // Enable SSL if set - if ([self useSSL]) { - [mySQLConnection setUseSSL:YES]; - - if ([self sslKeyFileLocationEnabled]) { - [mySQLConnection setSslKeyFilePath:[self sslKeyFileLocation]]; - } - - if ([self sslCertificateFileLocationEnabled]) { - [mySQLConnection setSslCertificatePath:[self sslCertificateFileLocation]]; - } - - if ([self sslCACertFileLocationEnabled]) { - [mySQLConnection setSslCACertificatePath:[self sslCACertFileLocation]]; - } - - NSString *userSSLCipherList = [prefs stringForKey:SPSSLCipherListKey]; - if(userSSLCipherList) { - //strip out disabled ciphers (e.g. in "foo:bar:--:baz") - NSRange markerPos = [userSSLCipherList rangeOfRegex:@":?--"]; - if(markerPos.location != NSNotFound) { - userSSLCipherList = [userSSLCipherList substringToIndex:markerPos.location]; - } - [mySQLConnection setSslCipherList:userSSLCipherList]; - } - } - - if(![self useCompression]) - [mySQLConnection removeClientFlags:SPMySQLClientFlagCompression]; - - // Connection delegate must be set before actual connection attempt is made - [mySQLConnection setDelegate:dbDocument]; - - // Set whether or not we should enable delegate logging according to the prefs - [mySQLConnection setDelegateQueryLogging:[prefs boolForKey:SPConsoleEnableLogging]]; - - // Set options from preferences - [mySQLConnection setTimeout:[[prefs objectForKey:SPConnectionTimeoutValue] integerValue]]; - [mySQLConnection setUseKeepAlive:[[prefs objectForKey:SPUseKeepAlive] boolValue]]; - [mySQLConnection setKeepAliveInterval:[[prefs objectForKey:SPKeepAliveInterval] floatValue]]; - - // Connect - [mySQLConnection connect]; - - if (![mySQLConnection isConnected]) { - if (sshTunnel && !cancellingConnection) { - - // This is a race condition we cannot fix "properly": - // For meaningful error handling we need to also consider the debug output from the SSH connection. - // The SSH debug output might be sligthly delayed though (flush, delegates, ...) or - // there might not even by any output at all (when it is purely a libmysql issue). - // TL;DR: No guaranteed events we could wait for, just trying our luck. - [NSThread sleepForTimeInterval:0.1]; // 100ms - - // If the state is connection refused, attempt the MySQL connection again with the host using the hostfield value. - if ([sshTunnel state] == SPMySQLProxyForwardingFailed) { - if ([sshTunnel localPortFallback]) { - [mySQLConnection setPort:[sshTunnel localPortFallback]]; - [mySQLConnection connect]; - - if (![mySQLConnection isConnected]) { - [NSThread sleepForTimeInterval:0.1]; //100ms - } - } - } - } - - if (![mySQLConnection isConnected]) { - if (!cancellingConnection) { - NSString *errorMessage = @""; - if (sshTunnel && [sshTunnel state] == SPMySQLProxyForwardingFailed) { - errorMessage = [NSString stringWithFormat:NSLocalizedString(@"Unable to connect to host %@ because the port connection via SSH was refused.\n\nPlease ensure that your MySQL host is set up to allow TCP/IP connections (no --skip-networking) and is configured to allow connections from the host you are tunnelling via.\n\nYou may also want to check the port is correct and that you have the necessary privileges.\n\nChecking the error detail will show the SSH debug log which may provide more details.\n\nMySQL said: %@", @"message of panel when SSH port forwarding failed"), [self host], [mySQLConnection lastErrorMessage]]; - [[self onMainThread] failConnectionWithTitle:NSLocalizedString(@"SSH port forwarding failed", @"title when ssh tunnel port forwarding failed") errorMessage:errorMessage detail:[sshTunnel debugMessages] rawErrorText:[mySQLConnection lastErrorMessage]]; - } - else if ([mySQLConnection lastErrorID] == 1045) { // "Access denied" error - errorMessage = [NSString stringWithFormat:NSLocalizedString(@"Unable to connect to host %@ because access was denied.\n\nDouble-check your username and password and ensure that access from your current location is permitted.\n\nMySQL said: %@", @"message of panel when connection to host failed due to access denied error"), [self host], [mySQLConnection lastErrorMessage]]; - [[self onMainThread] failConnectionWithTitle:NSLocalizedString(@"Access denied!", @"connection failed due to access denied title") errorMessage:errorMessage detail:nil rawErrorText:[mySQLConnection lastErrorMessage]]; - } - else if ([self type] == SPSocketConnection && (![self socket] || ![[self socket] length]) && ![mySQLConnection socketPath]) { - errorMessage = [NSString stringWithFormat:NSLocalizedString(@"The socket file could not be found in any common location. Please supply the correct socket location.\n\nMySQL said: %@", @"message of panel when connection to socket failed because optional socket could not be found"), [mySQLConnection lastErrorMessage]]; - [[self onMainThread] failConnectionWithTitle:NSLocalizedString(@"Socket not found!", @"socket not found title") errorMessage:errorMessage detail:nil rawErrorText:[mySQLConnection lastErrorMessage]]; - } - else if ([self type] == SPSocketConnection) { - errorMessage = [NSString stringWithFormat:NSLocalizedString(@"Unable to connect via the socket, or the request timed out.\n\nDouble-check that the socket path is correct and that you have the necessary privileges, and that the server is running.\n\nMySQL said: %@", @"message of panel when connection to host failed"), [mySQLConnection lastErrorMessage]]; - [[self onMainThread] failConnectionWithTitle:NSLocalizedString(@"Socket connection failed!", @"socket connection failed title") errorMessage:errorMessage detail:nil rawErrorText:[mySQLConnection lastErrorMessage]]; - } - else { - errorMessage = [NSString stringWithFormat:NSLocalizedString(@"Unable to connect to host %@, or the request timed out.\n\nBe sure that the address is correct and that you have the necessary privileges, or try increasing the connection timeout (currently %ld seconds).\n\nMySQL said: %@", @"message of panel when connection to host failed"), [self host], (long)[[prefs objectForKey:SPConnectionTimeoutValue] integerValue], [mySQLConnection lastErrorMessage]]; - [[self onMainThread] failConnectionWithTitle:NSLocalizedString(@"Connection failed!", @"connection failed title") errorMessage:errorMessage detail:nil rawErrorText:[mySQLConnection lastErrorMessage]]; - } - } - - // Tidy up - isConnecting = NO; - - if (sshTunnel) [sshTunnel disconnect], SPClear(sshTunnel); - - SPClear(mySQLConnection); -#ifndef SP_CODA - if (!cancellingConnection) [self _restoreConnectionInterface]; -#endif - [pool release]; - - return; - } - } - - if ([self database] && ![[self database] isEqualToString:@""]) { - if (![mySQLConnection selectDatabase:[self database]]) { - if (!isTestingConnection) { - [[self onMainThread] failConnectionWithTitle:NSLocalizedString(@"Could not select database", @"message when database selection failed") errorMessage:[NSString stringWithFormat:NSLocalizedString(@"Connected to host, but unable to connect to database %@.\n\nBe sure that the database exists and that you have the necessary privileges.\n\nMySQL said: %@", @"message of panel when connection to db failed"), [self database], [mySQLConnection lastErrorMessage]] detail:nil rawErrorText:[mySQLConnection lastErrorMessage]]; - } - - // Tidy up - isConnecting = NO; - - if (sshTunnel) SPClear(sshTunnel); - - SPClear(mySQLConnection); - [self _restoreConnectionInterface]; - if (isTestingConnection) { - [self _showConnectionTestResult:NSLocalizedString(@"Invalid database", @"Invalid database very short status message")]; - } - - [pool release]; - - return; - } - } - - // Connection established - [self performSelectorOnMainThread:@selector(mySQLConnectionEstablished) withObject:nil waitUntilDone:NO]; - - [pool release]; -} - -/** - * Initiate the SSH connection process. - * This should only be called as part of initiateConnection:, and will indirectly - * call initiateMySQLConnection if it's successful. - */ -- (void)initiateSSHTunnelConnection -{ - if (isTestingConnection) { - [progressIndicatorText setStringValue:NSLocalizedString(@"Testing SSH...", @"SSH testing very short status message")]; - } else { - [progressIndicatorText setStringValue:NSLocalizedString(@"SSH connecting...", @"SSH connecting very short status message")]; - } - [progressIndicatorText display]; - - [connectButton setTitle:NSLocalizedString(@"Cancel", @"cancel button")]; - [connectButton setAction:@selector(cancelConnection:)]; - [connectButton setEnabled:YES]; - [connectButton display]; - - // Trim whitespace and newlines from the SSH host field before attempting to connect - [self setSshHost:[[self sshHost] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]]; - - // Set up the tunnel details - sshTunnel = [[SPSSHTunnel alloc] initToHost:[self sshHost] port:[[self sshPort] integerValue] login:[self sshUser] tunnellingToPort:([[self port] length]?[[self port] integerValue]:3306) onHost:[self host]]; - [sshTunnel setParentWindow:[dbDocument parentWindow]]; - - // Add keychain or plaintext password as appropriate - note the checks in initiateConnection. - if (connectionSSHKeychainItemName && !isTestingConnection) { - [sshTunnel setPasswordKeychainName:connectionSSHKeychainItemName account:connectionSSHKeychainItemAccount]; - } else if (sshPassword) { - [sshTunnel setPassword:[self sshPassword]]; - } - - // Set the public key path if appropriate - if (sshKeyLocationEnabled && sshKeyLocation) { - [sshTunnel setKeyFilePath:sshKeyLocation]; - } - - // Set the callback function on the tunnel - [sshTunnel setConnectionStateChangeSelector:@selector(sshTunnelCallback:) delegate:self]; - - // Ask the tunnel to connect. This will call the callback below on success or failure, passing - // itself as an argument - retain count should be one at this point. - [sshTunnel connect]; -} - -/** - * Called on the main thread once the MySQL connection is established on the background thread. Either the - * connection was cancelled or it was successful. - */ -- (void)mySQLConnectionEstablished -{ - isConnecting = NO; - - // If the user is only testing the connection, kill the connection - // once established and reset the UI. Also catch connection cancels. - if (isTestingConnection || cancellingConnection) { - - // Clean up any connections remaining, and reset the UI - [self cancelConnection:self]; - - if (isTestingConnection) { - [self _showConnectionTestResult:NSLocalizedString(@"Connection succeeded", @"Connection success very short status message")]; - } - - return; - } - -#ifndef SP_CODA - [progressIndicatorText setStringValue:NSLocalizedString(@"Connected", @"connection established message")]; - [progressIndicatorText display]; -#endif - - // Stop the current tab's progress indicator - [dbDocument setIsProcessing:NO]; - - // Successful connection! -#ifndef SP_CODA - [connectButton setEnabled:NO]; - [connectButton display]; - [progressIndicator stopAnimation:self]; - [progressIndicatorText setHidden:YES]; -#endif - - // If SSL was enabled, check it was established correctly - if (useSSL && ([self type] == SPTCPIPConnection || [self type] == SPSocketConnection)) { - if (![mySQLConnection isConnectedViaSSL]) { - SPOnewayAlertSheet( - NSLocalizedString(@"SSL connection not established", @"SSL requested but not used title"), - [dbDocument parentWindow], - NSLocalizedString(@"You requested that the connection should be established using SSL, but MySQL made the connection without SSL.\n\nThis may be because the server does not support SSL connections, or has SSL disabled; or insufficient details were supplied to establish an SSL connection.\n\nThis connection is not encrypted.", @"SSL connection requested but not established error detail") - ); - } - else { -#ifndef SP_CODA - [dbDocument setStatusIconToImageWithName:@"titlebarlock"]; -#endif - } - } - -#ifndef SP_CODA - // Re-enable favorites table view - [favoritesOutlineView setEnabled:YES]; - [(NSView *)favoritesOutlineView display]; -#endif - - // Release the tunnel if set - will now be retained by the connection - if (sshTunnel) SPClear(sshTunnel); - - // Pass the connection to the document and clean up the interface - [self addConnectionToDocument]; -} - -/** - * A callback function for the SSH Tunnel setup process - will be called on a connection - * state change, allowing connection to fail or proceed as appropriate. If successful, - * will call initiateMySQLConnection. - */ -- (void)sshTunnelCallback:(SPSSHTunnel *)theTunnel -{ - if (cancellingConnection) return; - - NSInteger newState = [theTunnel state]; - - // If the user cancelled the password prompt dialog, continue with no further action. - if ([theTunnel passwordPromptCancelled]) { - [self _restoreConnectionInterface]; - - return; - } - - if (newState == SPMySQLProxyIdle) { - -#ifndef SP_CODA - [dbDocument setTitlebarStatus:NSLocalizedString(@"SSH Disconnected", @"SSH disconnected titlebar marker")]; -#endif - - [[self onMainThread] failConnectionWithTitle:NSLocalizedString(@"SSH connection failed!", @"SSH connection failed title") errorMessage:[theTunnel lastError] detail:[sshTunnel debugMessages] rawErrorText:[theTunnel lastError]]; - } - else if (newState == SPMySQLProxyConnected) { -#ifndef SP_CODA - [dbDocument setTitlebarStatus:NSLocalizedString(@"SSH Connected", @"SSH connected titlebar marker")]; -#endif - - [self initiateMySQLConnection]; - } - else { -#ifndef SP_CODA - [dbDocument setTitlebarStatus:NSLocalizedString(@"SSH Connecting…", @"SSH connecting titlebar marker")]; -#endif - } -} - -/** - * Add the connection to the parent document and restore the - * interface, allowing the application to run as normal. - */ -- (void)addConnectionToDocument -{ -#ifndef SP_CODA - // Hide the connection view and restore the main view - [connectionView removeFromSuperviewWithoutNeedingDisplay]; - [databaseConnectionView setHidden:NO]; - - // Restore the toolbar icons - NSArray *toolbarItems = [[[dbDocument parentWindow] toolbar] items]; - - for (NSUInteger i = 0; i < [toolbarItems count]; i++) [[toolbarItems objectAtIndex:i] setEnabled:YES]; -#endif - - if (connectionKeychainID) [dbDocument setKeychainID:connectionKeychainID]; - - // Pass the connection to the table document, allowing it to set - // up the other classes and the rest of the interface. - [dbDocument setConnection:mySQLConnection]; -} - -/** - * Ends a connection attempt by stopping the connection animation and - * displaying a specified error message. - */ -- (void)failConnectionWithTitle:(NSString *)theTitle errorMessage:(NSString *)theErrorMessage detail:(NSString *)errorDetail rawErrorText:(NSString *)rawErrorText -{ - BOOL isSSHTunnelBindError = NO; - -#ifndef SP_CODA - [self _restoreConnectionInterface]; -#endif - - // Release as appropriate - if (sshTunnel) { - [sshTunnel disconnect], SPClear(sshTunnel); - - // If the SSH tunnel connection failed because the port it was trying to bind to was already in use take note - // of it so we can give the user the option of connecting via standard connection and use the existing tunnel. - if ([rawErrorText rangeOfString:@"bind"].location != NSNotFound) { - isSSHTunnelBindError = YES; - } - } - - if (errorDetail) [errorDetailText setString:errorDetail]; - - // Inform the delegate that the connection attempt failed - if (delegate && [delegate respondsToSelector:@selector(connectionControllerConnectAttemptFailed:)]) { - [[(NSObject *)delegate onMainThread] connectionControllerConnectAttemptFailed:self]; - } - - // Only display the connection error message if there is a window visible - if ([[dbDocument parentWindow] isVisible]) { - SPBeginAlertSheet(theTitle, NSLocalizedString(@"OK", @"OK button"), (errorDetail) ? NSLocalizedString(@"Show Detail", @"Show detail button") : nil, (isSSHTunnelBindError) ? NSLocalizedString(@"Use Standard Connection", @"use standard connection button") : nil, [dbDocument parentWindow], self, @selector(connectionFailureSheetDidEnd:returnCode:contextInfo:), @"connect", theErrorMessage); - } -} - -/** - * Alert sheet callback method - invoked when an error sheet is closed. - */ -- (void)connectionFailureSheetDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo -{ - if (returnCode == NSAlertAlternateReturn) { - [errorDetailText setFont:[NSFont userFontOfSize:12]]; - [errorDetailText setAlignment:NSLeftTextAlignment]; - [errorDetailWindow makeKeyAndOrderFront:self]; - } - - // Currently only SSH port bind errors offer a 3rd option in the error dialog, but if this ever changes - // this will definitely need to be updated. - else if (returnCode == NSAlertOtherReturn) { - - // Extract the local port number that SSH attempted to bind to from the debug output - NSString *tunnelPort = [[[errorDetailText string] componentsMatchedByRegex:@"LOCALHOST:([0-9]+)" capture:1L] lastObject]; - - // Change the connection type to standard TCP/IP - [self setType:SPTCPIPConnection]; - - // Change connection details - [self setPort:tunnelPort]; - [self setHost:SPLocalhostAddress]; - -#ifndef SP_CODA - // Change to standard TCP/IP connection view - [self resizeTabViewToConnectionType:SPTCPIPConnection animating:YES]; -#endif - - // Initiate the connection after a half second delay to give the connection view a chance to resize - [self performSelector:@selector(initiateConnection:) withObject:self afterDelay:0.5]; - } -} - -@end - -#pragma mark - - -@implementation SPConnectionController (SPConnectionHandlerPrivateAPI) - -/** - * Display a connection test error or success message - */ -- (void)_showConnectionTestResult:(NSString *)resultString -{ - if (![NSThread isMainThread]) { - [[self onMainThread] _showConnectionTestResult:resultString]; - } - - [helpButton setHidden:NO]; - [progressIndicator stopAnimation:self]; - [progressIndicatorText setStringValue:resultString]; - [progressIndicatorText setHidden:NO]; -} - -@end diff --git a/Source/SPContentFilterManager.m b/Source/SPContentFilterManager.m index 9c340745..d0906c27 100644 --- a/Source/SPContentFilterManager.m +++ b/Source/SPContentFilterManager.m @@ -32,13 +32,11 @@ #import "ImageAndTextCell.h" #import "RegexKitLite.h" #import "SPQueryController.h" -#import "SPQueryDocumentsController.h" #import "SPDatabaseDocument.h" #import "SPTableContent.h" #import "SPConnectionController.h" #import "SPSplitView.h" #import "SPAppController.h" -#import "SPAppleScriptSupport.h" static NSString *SPExportFilterAction = @"SPExportFilter"; diff --git a/Source/SPCustomQuery.m b/Source/SPCustomQuery.m index 374f31db..bddb0b6c 100644 --- a/Source/SPCustomQuery.m +++ b/Source/SPCustomQuery.m @@ -46,7 +46,6 @@ #import "SPQueryFavoriteManager.h" #endif #import "SPQueryController.h" -#import "SPQueryDocumentsController.h" #import "SPEncodingPopupAccessory.h" #import "SPDataStorage.h" #import "SPAlertSheets.h" @@ -66,7 +65,7 @@ #import <pthread.h> #import <SPMySQL/SPMySQL.h> -@interface SPCustomQuery (PrivateAPI) +@interface SPCustomQuery () - (id)_resultDataItemAtRow:(NSInteger)row columnIndex:(NSUInteger)column preserveNULLs:(BOOL)preserveNULLs asPreview:(BOOL)asPreview; + (NSString *)linkToHelpTopic:(NSString *)aTopic; @@ -4027,7 +4026,7 @@ */ - (id)_resultDataItemAtRow:(NSInteger)row columnIndex:(NSUInteger)column preserveNULLs:(BOOL)preserveNULLs asPreview:(BOOL)asPreview; { -#warning duplicate code with SPTableContentDataSource.m tableView:objectValueForTableColumn:… +#warning duplicate code with SPTableContent.m tableView:objectValueForTableColumn:… id value = nil; // While the table is being loaded, additional validation is required - data diff --git a/Source/SPDataImport.m b/Source/SPDataImport.m index 4c680801..f8d145a9 100644 --- a/Source/SPDataImport.m +++ b/Source/SPDataImport.m @@ -31,7 +31,6 @@ #import "SPDataImport.h" #import "SPDatabaseDocument.h" -#import "SPDatabaseViewController.h" #import "SPTablesList.h" #import "SPTableStructure.h" #import "SPDatabaseStructure.h" diff --git a/Source/SPDataStorage.m b/Source/SPDataStorage.m index 5db56b1e..3072ed33 100644 --- a/Source/SPDataStorage.m +++ b/Source/SPDataStorage.m @@ -34,7 +34,7 @@ #include <stdlib.h> #include <mach/mach_time.h> -@interface SPDataStorage (Private_API) +@interface SPDataStorage () - (void) _checkNewRow:(NSMutableArray *)aRow; - (void) _addRowUnsafeUnchecked:(NSMutableArray *)aRow; @@ -559,9 +559,7 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore [super dealloc]; } -@end - -@implementation SPDataStorage (PrivateAPI) +#pragma mark - Private API // DO NOT CALL THIS METHOD UNLESS YOU CURRENTLY HAVE A LOCK ON SELF!!! - (void) _checkNewRow:(NSMutableArray *)aRow diff --git a/Source/SPDatabaseDocument.h b/Source/SPDatabaseDocument.h index 96c78d1a..de7dd55e 100644 --- a/Source/SPDatabaseDocument.h +++ b/Source/SPDatabaseDocument.h @@ -291,7 +291,7 @@ int64_t instanceId; } -@property (assign) NSTableView *dbTablesTableView; +@property (nonatomic, assign) NSTableView *dbTablesTableView; #ifdef SP_CODA /* ivars */ @property (assign) SPDatabaseData* databaseDataInstance; @@ -537,4 +537,45 @@ #endif +#pragma mark - SPDatabaseViewController + +// Accessors +- (NSString *)table; +- (SPTableType)tableType; + +- (BOOL)structureLoaded; +- (BOOL)contentLoaded; +- (BOOL)statusLoaded; + +#ifndef SP_CODA /* method decls */ +// Tab view control +- (IBAction)viewStructure:(id)sender; +- (IBAction)viewContent:(id)sender; +- (IBAction)viewQuery:(id)sender; +- (IBAction)viewStatus:(id)sender; +- (IBAction)viewRelations:(id)sender; +- (IBAction)viewTriggers:(id)sender; +#endif + +- (void)setStructureRequiresReload:(BOOL)reload; +- (void)setContentRequiresReload:(BOOL)reload; +- (void)setStatusRequiresReload:(BOOL)reload; +- (void)setRelationsRequiresReload:(BOOL)reload; + +// Table control +- (void)loadTable:(NSString *)aTable ofType:(SPTableType)aTableType; + +#ifndef SP_CODA /* method decls */ +- (NSView *)databaseView; +#endif + +#pragma mark - SPPrintController + +- (void)startPrintDocumentOperation; +- (void)generateHTMLForPrinting; +- (void)generateTableInfoHTMLForPrinting; + +- (NSArray *)columnNames; +- (NSMutableDictionary *)connectionInformation; + @end diff --git a/Source/SPDatabaseDocument.m b/Source/SPDatabaseDocument.m index d436fbd1..2340b97e 100644 --- a/Source/SPDatabaseDocument.m +++ b/Source/SPDatabaseDocument.m @@ -31,15 +31,11 @@ #import "SPDatabaseDocument.h" #import "SPConnectionController.h" -#import "SPConnectionHandler.h" -#import "SPConnectionControllerInitializer.h" #import "SPTablesList.h" -#import "SPTableStructure.h" #import "SPDatabaseStructure.h" #import "SPFileHandle.h" #import "SPKeychain.h" #import "SPTableContent.h" -#import "SPTableContentFilter.h" #import "SPCustomQuery.h" #import "SPDataImport.h" #import "ImageAndTextCell.h" @@ -47,15 +43,12 @@ #import "SPExportController.h" #import "SPSplitView.h" #import "SPQueryController.h" -#import "SPQueryDocumentsController.h" #import "SPWindowController.h" #import "SPNavigatorController.h" #import "SPSQLParser.h" #import "SPTableData.h" #import "SPDatabaseData.h" #import "SPDatabaseStructure.h" -#import "SPAppController.h" -#import "SPWindowManagement.h" #import "SPExtendedTableInfo.h" #import "SPHistoryController.h" #import "SPPreferenceController.h" @@ -73,9 +66,6 @@ #import "SPCopyTable.h" #import "SPServerSupport.h" #import "SPTooltip.h" -#import "SPDatabaseViewController.h" -#import "SPBundleHTMLOutputController.h" -#import "SPConnectionDelegate.h" #import "SPThreadAdditions.h" #import "RegexKitLite.h" #import "SPTextView.h" @@ -84,6 +74,17 @@ #import "SPGotoDatabaseController.h" #import "SPFunctions.h" #import "SPCreateDatabaseInfo.h" +#ifndef SP_CODA /* headers */ +#import "SPAppController.h" +#import "SPBundleHTMLOutputController.h" +#endif +#import "SPTableTriggers.h" +#ifdef SP_CODA /* headers */ +#import "SPTableStructure.h" +#endif +#import "SPPrintAccessory.h" +#import "MGTemplateEngine.h" +#import "ICUTemplateMatcher.h" #import <SPMySQL/SPMySQL.h> @@ -109,6 +110,18 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; - (void)_addPreferenceObservers; - (void)_removePreferenceObservers; +#pragma mark - SPDatabaseViewControllerPrivateAPI + +- (void)_loadTabTask:(NSNumber *)tabViewItemIndexNumber; +- (void)_loadTableTask; + +#pragma mark - SPConnectionDelegate + +- (void) closeAndDisconnect; + +- (NSString *)keychainPasswordForConnection:(SPMySQLConnection *)connection; +- (NSString *)keychainPasswordForSSHConnection:(SPMySQLConnection *)connection; + @end @implementation SPDatabaseDocument @@ -122,6 +135,7 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; @synthesize databaseStructureRetrieval; @synthesize processID; @synthesize instanceId; +@synthesize dbTablesTableView = dbTablesTableView; #pragma mark - @@ -5229,7 +5243,7 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; /** * Invoked by the connection controller when it starts the process of initiating a connection. */ -- (void)connectionControllerInitiatingConnection:(id)controller +- (void)connectionControllerInitiatingConnection:(SPConnectionController *)controller { #ifndef SP_CODA /* ui manipulation */ // Update the window title to indicate that we are trying to establish a connection @@ -5244,7 +5258,7 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; /** * Invoked by the connection controller when the attempt to initiate a connection failed. */ -- (void)connectionControllerConnectAttemptFailed:(id)controller +- (void)connectionControllerConnectAttemptFailed:(SPConnectionController *)controller { #ifdef SP_CODA /* glue */ if ( delegate && [delegate respondsToSelector:@selector(databaseDocumentConnectionFailed:)] ) @@ -6526,6 +6540,1166 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; [prefs removeObserver:[SPQueryController sharedQueryController] forKeyPath:SPDisplayTableViewVerticalGridlines]; } +#pragma mark - SPDatabaseViewController + +#pragma mark Getters + +#ifndef SP_CODA /* getters */ +/** + * Returns the master database view, containing the tables list and views for + * table setup and contents. + */ +- (NSView *)databaseView +{ + return parentView; +} +#endif + +/** + * Returns the name of the currently selected table/view/procedure/function. + */ +- (NSString *)table +{ + return selectedTableName; +} + +/** + * Returns the currently selected table type, or -1 if no table or multiple tables are selected + */ +- (SPTableType)tableType +{ + return selectedTableType; +} + +/** + * Returns YES if table source has already been loaded + */ +- (BOOL)structureLoaded +{ + return structureLoaded; +} + +/** + * Returns YES if table content has already been loaded + */ +- (BOOL)contentLoaded +{ + return contentLoaded; +} + +/** + * Returns YES if table status has already been loaded + */ +- (BOOL)statusLoaded +{ + return statusLoaded; +} + +#ifndef SP_CODA /* toolbar ibactions */ + +#pragma mark - +#pragma mark Tab view control and delegate methods + +//WARNING: Might be called from code in background threads +- (IBAction)viewStructure:(id)sender +{ + // Cancel the selection if currently editing a view and unable to save + if (![[self onMainThread] couldCommitCurrentViewActions]) { + [[mainToolbar onMainThread] setSelectedItemIdentifier:*SPViewModeToMainToolbarMap[[prefs integerForKey:SPLastViewMode]]]; + return; + } + + [[tableTabView onMainThread] selectTabViewItemAtIndex:0]; + [[mainToolbar onMainThread] setSelectedItemIdentifier:SPMainToolbarTableStructure]; + [spHistoryControllerInstance updateHistoryEntries]; + + [prefs setInteger:SPStructureViewMode forKey:SPLastViewMode]; +} + +- (IBAction)viewContent:(id)sender +{ + // Cancel the selection if currently editing a view and unable to save + if (![self couldCommitCurrentViewActions]) { + [mainToolbar setSelectedItemIdentifier:*SPViewModeToMainToolbarMap[[prefs integerForKey:SPLastViewMode]]]; + return; + } + + [tableTabView selectTabViewItemAtIndex:1]; + [mainToolbar setSelectedItemIdentifier:SPMainToolbarTableContent]; + [spHistoryControllerInstance updateHistoryEntries]; + + [prefs setInteger:SPContentViewMode forKey:SPLastViewMode]; +} + +- (IBAction)viewQuery:(id)sender +{ + // Cancel the selection if currently editing a view and unable to save + if (![self couldCommitCurrentViewActions]) { + [mainToolbar setSelectedItemIdentifier:*SPViewModeToMainToolbarMap[[prefs integerForKey:SPLastViewMode]]]; + return; + } + + [tableTabView selectTabViewItemAtIndex:2]; + [mainToolbar setSelectedItemIdentifier:SPMainToolbarCustomQuery]; + [spHistoryControllerInstance updateHistoryEntries]; + + // Set the focus on the text field + [parentWindow makeFirstResponder:customQueryTextView]; + + [prefs setInteger:SPQueryEditorViewMode forKey:SPLastViewMode]; +} + +- (IBAction)viewStatus:(id)sender +{ + // Cancel the selection if currently editing a view and unable to save + if (![self couldCommitCurrentViewActions]) { + [mainToolbar setSelectedItemIdentifier:*SPViewModeToMainToolbarMap[[prefs integerForKey:SPLastViewMode]]]; + return; + } + + [tableTabView selectTabViewItemAtIndex:3]; + [mainToolbar setSelectedItemIdentifier:SPMainToolbarTableInfo]; + [spHistoryControllerInstance updateHistoryEntries]; + + if ([[self table] length]) { + [extendedTableInfoInstance loadTable:[self table]]; + } + + [parentWindow makeFirstResponder:[extendedTableInfoInstance valueForKeyPath:@"tableCreateSyntaxTextView"]]; + + [prefs setInteger:SPTableInfoViewMode forKey:SPLastViewMode]; +} + +- (IBAction)viewRelations:(id)sender +{ + // Cancel the selection if currently editing a view and unable to save + if (![self couldCommitCurrentViewActions]) { + [mainToolbar setSelectedItemIdentifier:*SPViewModeToMainToolbarMap[[prefs integerForKey:SPLastViewMode]]]; + return; + } + + [tableTabView selectTabViewItemAtIndex:4]; + [mainToolbar setSelectedItemIdentifier:SPMainToolbarTableRelations]; + [spHistoryControllerInstance updateHistoryEntries]; + + [prefs setInteger:SPRelationsViewMode forKey:SPLastViewMode]; +} + +- (IBAction)viewTriggers:(id)sender +{ + // Cancel the selection if currently editing a view and unable to save + if (![self couldCommitCurrentViewActions]) { + [mainToolbar setSelectedItemIdentifier:*SPViewModeToMainToolbarMap[[prefs integerForKey:SPLastViewMode]]]; + return; + } + + [tableTabView selectTabViewItemAtIndex:5]; + [mainToolbar setSelectedItemIdentifier:SPMainToolbarTableTriggers]; + [spHistoryControllerInstance updateHistoryEntries]; + + [prefs setInteger:SPTriggersViewMode forKey:SPLastViewMode]; +} +#endif + +/** + * Mark the structure tab for refresh when it's next switched to, + * or reload the view if it's currently active + */ +- (void)setStructureRequiresReload:(BOOL)reload +{ + BOOL reloadRequired = reload; + +#ifndef SP_CODA + if ([self currentlySelectedView] == SPTableViewStructure) { + reloadRequired = NO; + } +#endif + + if (reloadRequired && selectedTableName) { + [tableSourceInstance loadTable:selectedTableName]; + } + else { + structureLoaded = !reload; + } +} + +/** + * Mark the content tab for refresh when it's next switched to, + * or reload the view if it's currently active + */ +- (void)setContentRequiresReload:(BOOL)reload +{ + if (reload && selectedTableName +#ifndef SP_CODA /* check which tab is selected */ + && [self currentlySelectedView] == SPTableViewContent +#endif + ) { + [tableContentInstance loadTable:selectedTableName]; + } + else { + contentLoaded = !reload; + } +} + +/** + * Mark the extended tab info for refresh when it's next switched to, + * or reload the view if it's currently active + */ +- (void)setStatusRequiresReload:(BOOL)reload +{ + if (reload && selectedTableName +#ifndef SP_CODA /* check which tab is selected */ + && [self currentlySelectedView] == SPTableViewStatus +#endif + ) { + [[extendedTableInfoInstance onMainThread] loadTable:selectedTableName]; + } + else { + statusLoaded = !reload; + } +} + +/** + * Mark the relations tab for refresh when it's next switched to, + * or reload the view if it's currently active + */ +- (void)setRelationsRequiresReload:(BOOL)reload +{ + if (reload && selectedTableName +#ifndef SP_CODA /* check which tab is selected */ + && [self currentlySelectedView] == SPTableViewRelations +#endif + ) { + [[tableRelationsInstance onMainThread] refreshRelations:self]; + } + else { + relationsLoaded = !reload; + } +} + +#ifndef SP_CODA /* !!! respond to tab change */ +/** + * Triggers a task to update the newly selected tab view, ensuring + * the data is fully loaded and up-to-date. + */ +- (void)tabView:(NSTabView *)aTabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem +{ + [self startTaskWithDescription:[NSString stringWithFormat:NSLocalizedString(@"Loading %@...", @"Loading table task string"), [self table]]]; + + // We can't pass aTabView or tabViewItem UI objects to a bg thread, but since the change should already + // be done in *did*SelectTabViewItem we can just ask the tab view for the current selection index and use that + SPTableViewType newView = [self currentlySelectedView]; + + if ([NSThread isMainThread]) { + [NSThread detachNewThreadWithName:SPCtxt(@"SPDatabaseDocument view load task",self) + target:self + selector:@selector(_loadTabTask:) + object:@(newView)]; + } + else { + [self _loadTabTask:@(newView)]; + } +} +#endif + +#pragma mark - +#pragma mark Table control + +/** + * Loads a specified table into the database view, and ensures it's selected in + * the tables list. Passing a table name of nil will deselect any currently selected + * table, but will leave multiple selections intact. + * If this method is supplied with the currently selected name, a reload rather than + * a load will be triggered. + */ +- (void)loadTable:(NSString *)aTable ofType:(SPTableType)aTableType +{ + // Ensure a connection is still present + if (![mySQLConnection isConnected]) return; + + // If the supplied table name was nil, clear the views. + if (!aTable) { + + // Update the selected table name and type + if (selectedTableName) SPClear(selectedTableName); + + selectedTableType = SPTableTypeNone; + + // Clear the views + [[tablesListInstance onMainThread] setSelectionState:nil]; + [tableSourceInstance loadTable:nil]; + [tableContentInstance loadTable:nil]; +#ifndef SP_CODA /* [extendedTableInfoInstance loadTable:] */ + [[extendedTableInfoInstance onMainThread] loadTable:nil]; + [[tableTriggersInstance onMainThread] resetInterface]; + [[tableRelationsInstance onMainThread] refreshRelations:self]; +#endif + structureLoaded = NO; + contentLoaded = NO; + statusLoaded = NO; + triggersLoaded = NO; + relationsLoaded = NO; + +#ifndef SP_CODA + // Update the window title + [self updateWindowTitle:self]; + + // Add a history entry + [spHistoryControllerInstance updateHistoryEntries]; +#endif + + // Notify listeners of the table change + [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:SPTableChangedNotification object:self]; + + return; + } + + BOOL isReloading = (selectedTableName && [selectedTableName isEqualToString:aTable]); + + // Store the new name + if (selectedTableName) [selectedTableName release]; + + selectedTableName = [[NSString alloc] initWithString:aTable]; + selectedTableType = aTableType; + + // Start a task + if (isReloading) { + [self startTaskWithDescription:NSLocalizedString(@"Reloading...", @"Reloading table task string")]; + } + else { + [self startTaskWithDescription:[NSString stringWithFormat:NSLocalizedString(@"Loading %@...", @"Loading table task string"), aTable]]; + } + + // Update the tables list interface - also updates menus to reflect the selected table type + [[tablesListInstance onMainThread] setSelectionState:[NSDictionary dictionaryWithObjectsAndKeys:aTable, @"name", [NSNumber numberWithInteger:aTableType], @"type", nil]]; + + // If on the main thread, fire up a thread to deal with view changes and data loading; + // if already on a background thread, make the changes on the existing thread. + if ([NSThread isMainThread]) { + [NSThread detachNewThreadWithName:SPCtxt(@"SPDatabaseDocument table load task",self) + target:self + selector:@selector(_loadTableTask) + object:nil]; + } + else { + [self _loadTableTask]; + } +} + +/** + * In a threaded task, ensure that the supplied tab is loaded - + * usually as a result of switching to it. + */ +- (void)_loadTabTask:(NSNumber *)tabViewItemIndexNumber +{ + NSAutoreleasePool *tabLoadPool = [[NSAutoreleasePool alloc] init]; + + // If anything other than a single table or view is selected, don't proceed. + if (![self table] || ([tablesListInstance tableType] != SPTableTypeTable && [tablesListInstance tableType] != SPTableTypeView)) + { + [self endTask]; + [tabLoadPool drain]; + return; + } + + // Get the tab view index and ensure the associated view is loaded + SPTableViewType selectedTabViewIndex = [tabViewItemIndexNumber integerValue]; + + switch (selectedTabViewIndex) { + case SPTableViewStructure: + if (!structureLoaded) { + [tableSourceInstance loadTable:selectedTableName]; + structureLoaded = YES; + } + break; + case SPTableViewContent: + if (!contentLoaded) { + [tableContentInstance loadTable:selectedTableName]; + contentLoaded = YES; + } + break; +#ifndef SP_CODA /* case SPTableViewStatus: case SPTableViewTriggers: */ + case SPTableViewStatus: + if (!statusLoaded) { + [[extendedTableInfoInstance onMainThread] loadTable:selectedTableName]; + statusLoaded = YES; + } + break; + case SPTableViewTriggers: + if (!triggersLoaded) { + [[tableTriggersInstance onMainThread] loadTriggers]; + triggersLoaded = YES; + } + break; + case SPTableViewRelations: + if (!relationsLoaded) { + [[tableRelationsInstance onMainThread] refreshRelations:self]; + relationsLoaded = YES; + } + break; +#endif + } + + [self endTask]; + + [tabLoadPool drain]; +} + + +/** + * In a threaded task, load the currently selected table/view/proc/function. + */ +- (void)_loadTableTask +{ + NSAutoreleasePool *loadPool = [[NSAutoreleasePool alloc] init]; + NSString *tableEncoding = nil; + +#ifndef SP_CODA /* Update the window title */ + // Update the window title + [self updateWindowTitle:self]; +#endif + + // Reset table information caches and mark that all loaded views require their data reloading + [tableDataInstance resetAllData]; + + structureLoaded = NO; + contentLoaded = NO; + statusLoaded = NO; + triggersLoaded = NO; + relationsLoaded = NO; + + // Ensure status and details are fetched using UTF8 + NSString *previousEncoding = [mySQLConnection encoding]; + BOOL changeEncoding = ![previousEncoding isEqualToString:@"utf8"]; + + if (changeEncoding) { + [mySQLConnection storeEncodingForRestoration]; + [mySQLConnection setEncoding:@"utf8"]; + } + + // Cache status information on the working thread + [tableDataInstance updateStatusInformationForCurrentTable]; + + // Check the current encoding against the table encoding to see whether + // an encoding change and reset is required. This also caches table information on + // the working thread. + if( selectedTableType == SPTableTypeView || selectedTableType == SPTableTypeTable) { + + // tableEncoding == nil indicates that there was an error while retrieving table data + tableEncoding = [tableDataInstance tableEncoding]; + + // If encoding is set to Autodetect, update the connection character set encoding + // based on the newly selected table's encoding - but only if it differs from the current encoding. + if ([[[NSUserDefaults standardUserDefaults] objectForKey:SPDefaultEncoding] intValue] == SPEncodingAutodetect) { + if (tableEncoding != nil && ![tableEncoding isEqualToString:previousEncoding]) { + [self setConnectionEncoding:tableEncoding reloadingViews:NO]; + changeEncoding = NO; + } + } + } + + if (changeEncoding) [mySQLConnection restoreStoredEncoding]; + + // Notify listeners of the table change now that the state is fully set up. + [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:SPTableChangedNotification object:self]; + +#ifndef SP_CODA /* [spHistoryControllerInstance restoreViewStates] */ + + // Restore view states as appropriate + [spHistoryControllerInstance restoreViewStates]; +#endif + + // Load the currently selected view if looking at a table or view + if (tableEncoding && (selectedTableType == SPTableTypeView || selectedTableType == SPTableTypeTable)) + { +#ifndef SP_CODA /* load everything */ + NSInteger selectedTabViewIndex = [[self onMainThread] currentlySelectedView]; + + switch (selectedTabViewIndex) { + case SPTableViewStructure: +#endif + [tableSourceInstance loadTable:selectedTableName]; + structureLoaded = YES; +#ifndef SP_CODA /* load everything */ + break; + case SPTableViewContent: +#endif + [tableContentInstance loadTable:selectedTableName]; + contentLoaded = YES; +#ifndef SP_CODA /* load everything */ + break; + case SPTableViewStatus: + [[extendedTableInfoInstance onMainThread] loadTable:selectedTableName]; + statusLoaded = YES; + break; + case SPTableViewTriggers: + [[tableTriggersInstance onMainThread] loadTriggers]; + triggersLoaded = YES; + break; + case SPTableViewRelations: + [[tableRelationsInstance onMainThread] refreshRelations:self]; + relationsLoaded = YES; + break; + } +#endif + } + + // Clear any views which haven't been loaded as they weren't visible. Note + // that this should be done after reloading visible views, instead of clearing all + // views, to reduce UI operations and avoid resetting state unnecessarily. + // Some views (eg TableRelations) make use of the SPTableChangedNotification and + // so don't require manual clearing. + if (!structureLoaded) [tableSourceInstance loadTable:nil]; + if (!contentLoaded) [tableContentInstance loadTable:nil]; + if (!statusLoaded) [[extendedTableInfoInstance onMainThread] loadTable:nil]; + if (!triggersLoaded) [[tableTriggersInstance onMainThread] resetInterface]; + + // If the table row counts an inaccurate and require updating, trigger an update - no + // action will be performed if not necessary + [tableDataInstance updateAccurateNumberOfRowsForCurrentTableForcingUpdate:NO]; + +#ifndef SP_CODA /* show Create Table syntax */ + // Update the "Show Create Syntax" window if it's already opened + // according to the selected table/view/proc/func + if ([[[self onMainThread] getCreateTableSyntaxWindow] isVisible]) { + [[self onMainThread] showCreateTableSyntax:self]; + } + + // Add a history entry + [spHistoryControllerInstance updateHistoryEntries]; +#endif + // Empty the loading pool and exit the thread + [self endTask]; + +#ifndef SP_CODA /* triggered commands */ + NSArray *triggeredCommands = [SPAppDelegate bundleCommandsForTrigger:SPBundleTriggerActionTableChanged]; + + for(NSString* cmdPath in triggeredCommands) + { + NSArray *data = [cmdPath componentsSeparatedByString:@"|"]; + NSMenuItem *aMenuItem = [[[NSMenuItem alloc] init] autorelease]; + [aMenuItem setTag:0]; + [aMenuItem setToolTip:[data objectAtIndex:0]]; + + // For HTML output check if corresponding window already exists + BOOL stopTrigger = NO; + if([(NSString*)[data objectAtIndex:2] length]) { + BOOL correspondingWindowFound = NO; + NSString *uuid = [data objectAtIndex:2]; + for(id win in [NSApp windows]) { + if([[[[win delegate] class] description] isEqualToString:@"SPBundleHTMLOutputController"]) { + if([[[win delegate] windowUUID] isEqualToString:uuid]) { + correspondingWindowFound = YES; + break; + } + } + } + if(!correspondingWindowFound) stopTrigger = YES; + } + if(!stopTrigger) { + id firstResponder = [[NSApp keyWindow] firstResponder]; + if([[data objectAtIndex:1] isEqualToString:SPBundleScopeGeneral]) { + [[SPAppDelegate onMainThread] executeBundleItemForApp:aMenuItem]; + } + else if([[data objectAtIndex:1] isEqualToString:SPBundleScopeDataTable]) { + if([[[firstResponder class] description] isEqualToString:@"SPCopyTable"]) + [[firstResponder onMainThread] executeBundleItemForDataTable:aMenuItem]; + } + else if([[data objectAtIndex:1] isEqualToString:SPBundleScopeInputField]) { + if([firstResponder isKindOfClass:[NSTextView class]]) + [[firstResponder onMainThread] executeBundleItemForInputField:aMenuItem]; + } + } + } +#endif + + [loadPool drain]; +} + +#pragma mark - SPMySQLConnection delegate methods + +/** + * Invoked when the framework is about to perform a query. + */ +- (void)willQueryString:(NSString *)query connection:(id)connection +{ +#ifndef SP_CODA + if ([prefs boolForKey:SPConsoleEnableLogging]) { + if ((_queryMode == SPInterfaceQueryMode && [prefs boolForKey:SPConsoleEnableInterfaceLogging]) || + (_queryMode == SPCustomQueryQueryMode && [prefs boolForKey:SPConsoleEnableCustomQueryLogging]) || + (_queryMode == SPImportExportQueryMode && [prefs boolForKey:SPConsoleEnableImportExportLogging])) + { + [[SPQueryController sharedQueryController] showMessageInConsole:query connection:[self name] database:[self database]]; + } + } +#endif +} + +/** + * Invoked when the query just executed by the framework resulted in an error. + */ +- (void)queryGaveError:(NSString *)error connection:(id)connection +{ +#ifndef SP_CODA + if ([prefs boolForKey:SPConsoleEnableLogging] && [prefs boolForKey:SPConsoleEnableErrorLogging]) { + [[SPQueryController sharedQueryController] showErrorInConsole:error connection:[self name] database:[self database]]; + } +#endif +} + +/** + * Invoked when the current connection needs a password from the Keychain. + */ +- (NSString *)keychainPasswordForConnection:(SPMySQLConnection *)connection +{ + // If no keychain item is available, return an empty password + if (![connectionController connectionKeychainItemName]) return nil; + + // Otherwise, pull the password from the keychain using the details from this connection + SPKeychain *keychain = [[SPKeychain alloc] init]; + + NSString *password = [keychain getPasswordForName:[connectionController connectionKeychainItemName] account:[connectionController connectionKeychainItemAccount]]; + + [keychain release]; + + return password; +} + +/** + * Invoked when the current connection needs a ssh password from the Keychain. + * This isn't actually part of the SPMySQLConnection delegate protocol, but is here + * due to its similarity to the previous method. + */ +- (NSString *)keychainPasswordForSSHConnection:(SPMySQLConnection *)connection +{ + // If no keychain item is available, return an empty password + if (![connectionController connectionKeychainItemName]) return @""; + + // Otherwise, pull the password from the keychain using the details from this connection + SPKeychain *keychain = [[SPKeychain alloc] init]; + + NSString *connectionSSHKeychainItemName = [[keychain nameForSSHForFavoriteName:[connectionController name] id:[self keyChainID]] retain]; + NSString *connectionSSHKeychainItemAccount = [[keychain accountForSSHUser:[connectionController sshUser] sshHost:[connectionController sshHost]] retain]; + NSString *sshPassword = [keychain getPasswordForName:connectionSSHKeychainItemName account:connectionSSHKeychainItemAccount]; + + if (!sshPassword || ![sshPassword length]) { + sshPassword = @""; + } + + if (connectionSSHKeychainItemName) [connectionSSHKeychainItemName release]; + if (connectionSSHKeychainItemAccount) [connectionSSHKeychainItemAccount release]; + + [keychain release]; + + return sshPassword; +} + +/** + * Invoked when an attempt was made to execute a query on the current connection, but the connection is not + * actually active. + */ +- (void)noConnectionAvailable:(id)connection +{ + SPOnewayAlertSheet( + NSLocalizedString(@"No connection available", @"no connection available message"), + [self parentWindow], + NSLocalizedString(@"An error has occured and there doesn't seem to be a connection available.", @"no connection available informatie message") + ); +} + +/** + * Invoked when the connection fails and the framework needs to know how to proceed. + */ +- (SPMySQLConnectionLostDecision)connectionLost:(id)connection +{ + SPMySQLConnectionLostDecision connectionErrorCode = SPMySQLConnectionLostDisconnect; + + // Only display the reconnect dialog if the window is visible + if ([self parentWindow] && [[self parentWindow] isVisible]) { + + // Ensure the window isn't miniaturized + if ([[self parentWindow] isMiniaturized]) [[self parentWindow] deminiaturize:self]; + +#ifndef SP_CODA + // Ensure the window and tab are frontmost + [self makeKeyDocument]; +#endif + + // Display the connection error dialog and wait for the return code + [NSApp beginSheet:connectionErrorDialog modalForWindow:[self parentWindow] modalDelegate:self didEndSelector:nil contextInfo:nil]; + connectionErrorCode = (SPMySQLConnectionLostDecision)[NSApp runModalForWindow:connectionErrorDialog]; + + [NSApp endSheet:connectionErrorDialog]; + [connectionErrorDialog orderOut:nil]; + + // If 'disconnect' was selected, trigger a window close. + if (connectionErrorCode == SPMySQLConnectionLostDisconnect) { + [self performSelectorOnMainThread:@selector(closeAndDisconnect) withObject:nil waitUntilDone:YES]; + } + } + + return connectionErrorCode; +} + +/** + * Invoke to display an informative but non-fatal error directly to the user. + */ +- (void)showErrorWithTitle:(NSString *)theTitle message:(NSString *)theMessage +{ + if ([[self parentWindow] isVisible]) { + SPOnewayAlertSheet(theTitle, [self parentWindow], theMessage); + } +} + +/** + * Invoked when user dismisses the error sheet displayed as a result of the current connection being lost. + */ +- (IBAction)closeErrorConnectionSheet:(id)sender +{ + [NSApp stopModalWithCode:[sender tag]]; +} + +/** + * Close the connection - should be performed on the main thread. + */ +- (void) closeAndDisconnect +{ +#ifndef SP_CODA + NSWindow *theParentWindow = [self parentWindow]; + + _isConnected = NO; + + if ([[[self parentTabViewItem] tabView] numberOfTabViewItems] == 1) { + [theParentWindow orderOut:self]; + [theParentWindow setAlphaValue:0.0f]; + [theParentWindow performSelector:@selector(close) withObject:nil afterDelay:1.0]; + } + else { + [[[self parentTabViewItem] tabView] performSelector:@selector(removeTabViewItem:) withObject:[self parentTabViewItem] afterDelay:0.5]; + [theParentWindow performSelector:@selector(makeKeyAndOrderFront:) withObject:nil afterDelay:0.6]; + } + + [self parentTabDidClose]; +#endif +} + +#pragma mark - SPPrintController + +/** + * WebView delegate method. + */ +- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame +{ + // Because we need the webFrame loaded (for preview), we've moved the actual printing here + NSPrintInfo *printInfo = [NSPrintInfo sharedPrintInfo]; + + NSSize paperSize = [printInfo paperSize]; + NSRect printableRect = [printInfo imageablePageBounds]; + + // Calculate page margins + CGFloat marginL = printableRect.origin.x; + CGFloat marginR = paperSize.width - (printableRect.origin.x + printableRect.size.width); + CGFloat marginB = printableRect.origin.y; + CGFloat marginT = paperSize.height - (printableRect.origin.y + printableRect.size.height); + + // Make sure margins are symetric and positive + CGFloat marginLR = MAX(0, MAX(marginL, marginR)); + CGFloat marginTB = MAX(0, MAX(marginT, marginB)); + + // Set the margins + [printInfo setLeftMargin:marginLR]; + [printInfo setRightMargin:marginLR]; + [printInfo setTopMargin:marginTB]; + [printInfo setBottomMargin:marginTB]; + + [printInfo setHorizontalPagination:NSFitPagination]; + [printInfo setVerticalPagination:NSAutoPagination]; + [printInfo setVerticallyCentered:NO]; + + NSPrintOperation *op = [NSPrintOperation printOperationWithView:[[[printWebView mainFrame] frameView] documentView] printInfo:printInfo]; + + // Perform the print operation on a background thread + [op setCanSpawnSeparateThread:YES]; + + // Add the ability to select the orientation to print panel + NSPrintPanel *printPanel = [op printPanel]; + + [printPanel setOptions:[printPanel options] + NSPrintPanelShowsOrientation + NSPrintPanelShowsScaling + NSPrintPanelShowsPaperSize]; + + SPPrintAccessory *printAccessory = [[SPPrintAccessory alloc] initWithNibName:@"PrintAccessory" bundle:nil]; + + [printAccessory setPrintView:printWebView]; + [printPanel addAccessoryController:printAccessory]; + + [[NSPageLayout pageLayout] addAccessoryController:printAccessory]; + [printAccessory release]; + + [op setPrintPanel:printPanel]; + + [op runOperationModalForWindow:[self parentWindow] + delegate:self + didRunSelector:nil + contextInfo:nil]; + + if ([self isWorking]) [self endTask]; +} + +/** + * Loads the print document interface. The actual printing is done in the doneLoading delegate. + */ +- (IBAction)printDocument:(id)sender +{ + // Only display warning for the 'Table Content' view + if ([self currentlySelectedView] == SPTableViewContent) { + + NSInteger rowLimit = [prefs integerForKey:SPPrintWarningRowLimit]; + + // Result count minus one because the first element is the column names + NSInteger resultRows = ([[tableContentInstance currentResult] count] - 1); + + if (resultRows > rowLimit) { + + NSNumberFormatter *numberFormatter = [[[NSNumberFormatter alloc] init] autorelease]; + + [numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle]; + + NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Continue to print?", @"continue to print message") + defaultButton:NSLocalizedString(@"Print", @"print button") + alternateButton:NSLocalizedString(@"Cancel", @"cancel button") + otherButton:nil + informativeTextWithFormat:NSLocalizedString(@"Are you sure you want to print the current content view of the table '%@'?\n\nIt currently contains %@ rows, which may take a significant amount of time to print.", @"continue to print informative message"), [self table], [numberFormatter stringFromNumber:[NSNumber numberWithLongLong:resultRows]]]; + + NSArray *buttons = [alert buttons]; + + // Change the alert's cancel button to have the key equivalent of return + [[buttons objectAtIndex:0] setKeyEquivalent:@"p"]; + [[buttons objectAtIndex:0] setKeyEquivalentModifierMask:NSCommandKeyMask]; + [[buttons objectAtIndex:1] setKeyEquivalent:@"\r"]; + + [alert beginSheetModalForWindow:[self parentWindow] modalDelegate:self didEndSelector:@selector(printWarningDidEnd:returnCode:contextInfo:) contextInfo:NULL]; + + return; + } + } + + [self startPrintDocumentOperation]; +} + +/** + * Called when the print warning dialog is dismissed. + */ +- (void)printWarningDidEnd:(id)sheet returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo +{ + if (returnCode == NSAlertDefaultReturn) { + [self startPrintDocumentOperation]; + } +} + +/** + * Starts tge print document operation by spawning a new thread if required. + */ +- (void)startPrintDocumentOperation +{ + [self startTaskWithDescription:NSLocalizedString(@"Generating print document...", @"generating print document status message")]; + + BOOL isTableInformation = ([self currentlySelectedView] == SPTableViewStatus); + + if ([NSThread isMainThread]) { + printThread = [[NSThread alloc] initWithTarget:self selector:(isTableInformation) ? @selector(generateTableInfoHTMLForPrinting) : @selector(generateHTMLForPrinting) object:nil]; + [printThread setName:@"SPDatabaseDocument document generator"]; + + [self enableTaskCancellationWithTitle:NSLocalizedString(@"Cancel", @"cancel button") callbackObject:self callbackFunction:@selector(generateHTMLForPrintingCallback)]; + + [printThread start]; + } + else { + (isTableInformation) ? [self generateTableInfoHTMLForPrinting] : [self generateHTMLForPrinting]; + } +} + +/** + * HTML generation thread callback method. + */ +- (void)generateHTMLForPrintingCallback +{ + [self setTaskDescription:NSLocalizedString(@"Cancelling...", @"cancelling task status message")]; + + // Cancel the print thread + [printThread cancel]; +} + +/** + * Loads the supplied HTML string in the print WebView. + */ +- (void)loadPrintWebViewWithHTMLString:(NSString *)HTMLString +{ + [[printWebView mainFrame] loadHTMLString:HTMLString baseURL:nil]; + + if (printThread) SPClear(printThread); +} + +/** + * Generates the HTML for the current view that is being printed. + */ +- (void)generateHTMLForPrinting +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + // Set up template engine with your chosen matcher + MGTemplateEngine *engine = [MGTemplateEngine templateEngine]; + + [engine setMatcher:[ICUTemplateMatcher matcherWithTemplateEngine:engine]]; + + NSMutableDictionary *connection = [self connectionInformation]; + + NSString *heading = @""; + NSArray *rows, *indexes, *indexColumns = nil; + + NSArray *columns = [self columnNames]; + + NSMutableDictionary *printData = [NSMutableDictionary dictionary]; + + SPTableViewType view = [self currentlySelectedView]; + + // Table source view + if (view == SPTableViewStructure) { + + NSDictionary *tableSource = [tableSourceInstance tableSourceForPrinting]; + + NSInteger tableType = [tablesListInstance tableType]; + + switch (tableType) { + case SPTableTypeTable: + heading = NSLocalizedString(@"Table Structure", @"table structure print heading"); + break; + case SPTableTypeView: + heading = NSLocalizedString(@"View Structure", @"view structure print heading"); + break; + } + + rows = [[NSArray alloc] initWithArray: + [[tableSource objectForKey:@"structure"] objectsAtIndexes: + [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, [[tableSource objectForKey:@"structure"] count] - 1)]] + ]; + + indexes = [[NSArray alloc] initWithArray: + [[tableSource objectForKey:@"indexes"] objectsAtIndexes: + [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, [[tableSource objectForKey:@"indexes"] count] - 1)]] + ]; + + indexColumns = [[tableSource objectForKey:@"indexes"] objectAtIndex:0]; + + [printData setObject:rows forKey:@"rows"]; + [printData setObject:indexes forKey:@"indexes"]; + [printData setObject:indexColumns forKey:@"indexColumns"]; + + if ([indexes count]) [printData setObject:@1 forKey:@"hasIndexes"]; + + [rows release]; + [indexes release]; + } + // Table content view + else if (view == SPTableViewContent) { + + NSArray *data = [tableContentInstance currentDataResultWithNULLs:NO hideBLOBs:YES]; + + heading = NSLocalizedString(@"Table Content", @"table content print heading"); + + rows = [[NSArray alloc] initWithArray: + [data objectsAtIndexes: + [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, [data count] - 1)]] + ]; + + [printData setObject:rows forKey:@"rows"]; + [connection setValue:[tableContentInstance usedQuery] forKey:@"query"]; + + [rows release]; + } + // Custom query view + else if (view == SPTableViewCustomQuery) { + + NSArray *data = [customQueryInstance currentResult]; + + heading = NSLocalizedString(@"Query Result", @"query result print heading"); + + rows = [[NSArray alloc] initWithArray: + [data objectsAtIndexes: + [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, [data count] - 1)]] + ]; + + [printData setObject:rows forKey:@"rows"]; + [connection setValue:[customQueryInstance usedQuery] forKey:@"query"]; + + [rows release]; + } + // Table relations view + else if (view == SPTableViewRelations) { + + NSArray *data = [tableRelationsInstance relationDataForPrinting]; + + heading = NSLocalizedString(@"Table Relations", @"toolbar item label for switching to the Table Relations tab"); + + rows = [[NSArray alloc] initWithArray: + [data objectsAtIndexes: + [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, ([data count] - 1))]] + ]; + + [printData setObject:rows forKey:@"rows"]; + + [rows release]; + } + // Table triggers view + else if (view == SPTableViewTriggers) { + + NSArray *data = [tableTriggersInstance triggerDataForPrinting]; + + heading = NSLocalizedString(@"Table Triggers", @"toolbar item label for switching to the Table Triggers tab"); + + rows = [[NSArray alloc] initWithArray: + [data objectsAtIndexes: + [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, ([data count] - 1))]] + ]; + + [printData setObject:rows forKey:@"rows"]; + + [rows release]; + } + + [engine setObject:connection forKey:@"c"]; + + [printData setObject:heading forKey:@"heading"]; + [printData setObject:columns forKey:@"columns"]; + [printData setObject:([prefs boolForKey:SPUseMonospacedFonts]) ? SPDefaultMonospacedFontName : @"Lucida Grande" forKey:@"font"]; + [printData setObject:([prefs boolForKey:SPDisplayTableViewVerticalGridlines]) ? @"1px solid #CCCCCC" : @"none" forKey:@"gridlines"]; + + NSString *HTMLString = [engine processTemplateInFileAtPath:[[NSBundle mainBundle] pathForResource:SPHTMLPrintTemplate ofType:@"html"] withVariables:printData]; + + // Check if the operation has been cancelled + if ((printThread != nil) && (![NSThread isMainThread]) && ([printThread isCancelled])) { + [self endTask]; + [pool drain]; + + [NSThread exit]; + return; + } + + [self performSelectorOnMainThread:@selector(loadPrintWebViewWithHTMLString:) withObject:HTMLString waitUntilDone:NO]; + + [pool drain]; +} + +/** + * Generates the HTML for the table information view that is to be printed. + */ +- (void)generateTableInfoHTMLForPrinting +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + // Set up template engine with your chosen matcher + MGTemplateEngine *engine = [MGTemplateEngine templateEngine]; + + [engine setMatcher:[ICUTemplateMatcher matcherWithTemplateEngine:engine]]; + + NSMutableDictionary *connection = [self connectionInformation]; + NSMutableDictionary *printData = [NSMutableDictionary dictionary]; + + NSString *heading = NSLocalizedString(@"Table Information", @"table information print heading"); + + [engine setObject:connection forKey:@"c"]; + [engine setObject:[[extendedTableInfoInstance onMainThread] tableInformationForPrinting] forKey:@"i"]; + + [printData setObject:heading forKey:@"heading"]; + [printData setObject:[[NSUnarchiver unarchiveObjectWithData:[prefs objectForKey:SPCustomQueryEditorFont]] fontName] forKey:@"font"]; + + NSString *HTMLString = [engine processTemplateInFileAtPath:[[NSBundle mainBundle] pathForResource:SPHTMLTableInfoPrintTemplate ofType:@"html"] withVariables:printData]; + + // Check if the operation has been cancelled + if ((printThread != nil) && (![NSThread isMainThread]) && ([printThread isCancelled])) { + [self endTask]; + [pool drain]; + + [NSThread exit]; + return; + } + + [self performSelectorOnMainThread:@selector(loadPrintWebViewWithHTMLString:) withObject:HTMLString waitUntilDone:NO]; + + [pool drain]; +} + +/** + * Returns an array of columns for whichever view is being printed. + */ +- (NSArray *)columnNames +{ + NSArray *columns = nil; + + SPTableViewType view = [self currentlySelectedView]; + + // Table source view + if ((view == SPTableViewStructure) && ([[tableSourceInstance tableSourceForPrinting] count] > 0)) { + + columns = [[NSArray alloc] initWithArray:[[[tableSourceInstance tableSourceForPrinting] objectForKey:@"structure"] objectAtIndex:0] copyItems:YES]; + } + // Table content view + else if ((view == SPTableViewContent) && ([[tableContentInstance currentResult] count] > 0)) { + + columns = [[NSArray alloc] initWithArray:[[tableContentInstance currentResult] objectAtIndex:0] copyItems:YES]; + } + // Custom query view + else if ((view == SPTableViewCustomQuery) && ([[customQueryInstance currentResult] count] > 0)) { + + columns = [[NSArray alloc] initWithArray:[[customQueryInstance currentResult] objectAtIndex:0] copyItems:YES]; + } + // Table relations view + else if ((view == SPTableViewRelations) && ([[tableRelationsInstance relationDataForPrinting] count] > 0)) { + + columns = [[NSArray alloc] initWithArray:[[tableRelationsInstance relationDataForPrinting] objectAtIndex:0] copyItems:YES]; + } + // Table triggers view + else if ((view == SPTableViewTriggers) && ([[tableTriggersInstance triggerDataForPrinting] count] > 0)) { + + columns = [[NSArray alloc] initWithArray:[[tableTriggersInstance triggerDataForPrinting] objectAtIndex:0] copyItems:YES]; + } + + if (columns) [columns autorelease]; + + return columns; +} + +/** + * Generates a dictionary of connection information that is used for printing. + */ +- (NSMutableDictionary *)connectionInformation +{ + NSString *versionForPrint = [NSString stringWithFormat:@"%@ %@ (%@ %@)", + [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"], + [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"], + NSLocalizedString(@"build", @"build label"), + [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"] + ]; + + NSMutableDictionary *connection = [NSMutableDictionary dictionary]; + + if ([[self user] length]) { + [connection setValue:[self user] forKey:@"username"]; + } + + if ([[self table] length]) { + [connection setValue:[self table] forKey:@"table"]; + } + + if ([connectionController port] && [[connectionController port] length]) { + [connection setValue:[connectionController port] forKey:@"port"]; + } + + [connection setValue:[self host] forKey:@"hostname"]; + [connection setValue:selectedDatabase forKey:@"database"]; + [connection setValue:versionForPrint forKey:@"version"]; + + return connection; +} + #pragma mark - - (void)dealloc diff --git a/Source/SPDatabaseStructure.m b/Source/SPDatabaseStructure.m index fdb78916..9289b13b 100644 --- a/Source/SPDatabaseStructure.m +++ b/Source/SPDatabaseStructure.m @@ -30,14 +30,13 @@ #import "SPDatabaseStructure.h" #import "SPDatabaseDocument.h" -#import "SPConnectionDelegate.h" #import "SPTablesList.h" #import "RegexKitLite.h" #import "SPThreadAdditions.h" #import <pthread.h> -@interface SPDatabaseStructure (Private_API) +@interface SPDatabaseStructure () - (void)_destroy:(NSNotification *)notification; @@ -470,13 +469,9 @@ cleanup_thread_and_pool: [super dealloc]; } -@end - #pragma mark - #pragma mark Private API -@implementation SPDatabaseStructure (Private_API) - /** * Ensure that processing is completed. */ diff --git a/Source/SPDatabaseViewController.h b/Source/SPDatabaseViewController.h deleted file mode 100644 index aafa0cdd..00000000 --- a/Source/SPDatabaseViewController.h +++ /dev/null @@ -1,65 +0,0 @@ -// -// SPDatabaseViewController.h -// sequel-pro -// -// Created by Rowan Beentje on October 31, 2010. -// Copyright (c) 2010 Arboreal. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPDatabaseDocument.h" - -@interface SPDatabaseDocument (SPDatabaseViewController) - -// Accessors -- (NSString *)table; -- (SPTableType)tableType; - -- (BOOL)structureLoaded; -- (BOOL)contentLoaded; -- (BOOL)statusLoaded; - -#ifndef SP_CODA /* method decls */ -// Tab view control -- (IBAction)viewStructure:(id)sender; -- (IBAction)viewContent:(id)sender; -- (IBAction)viewQuery:(id)sender; -- (IBAction)viewStatus:(id)sender; -- (IBAction)viewRelations:(id)sender; -- (IBAction)viewTriggers:(id)sender; -#endif - -- (void)setStructureRequiresReload:(BOOL)reload; -- (void)setContentRequiresReload:(BOOL)reload; -- (void)setStatusRequiresReload:(BOOL)reload; -- (void)setRelationsRequiresReload:(BOOL)reload; - -// Table control -- (void)loadTable:(NSString *)aTable ofType:(SPTableType)aTableType; - -#ifndef SP_CODA /* method decls */ -- (NSView *)databaseView; -#endif - -@end diff --git a/Source/SPDatabaseViewController.m b/Source/SPDatabaseViewController.m deleted file mode 100644 index 8f0e8c38..00000000 --- a/Source/SPDatabaseViewController.m +++ /dev/null @@ -1,641 +0,0 @@ -// -// SPDatabaseViewController.m -// sequel-pro -// -// Created by Rowan Beentje on October 31, 2010. -// Copyright (c) 2010 Arboreal. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#ifndef SP_CODA /* headers */ -#import "SPAppController.h" -#import "SPBundleHTMLOutputController.h" -#endif -#import "SPCopyTable.h" -#import "SPDatabaseViewController.h" -#import "SPHistoryController.h" -#import "SPTableContent.h" -#import "SPTableData.h" -#import "SPTablesList.h" -#import "SPTableTriggers.h" -#import "SPThreadAdditions.h" -#import "SPTableRelations.h" -#ifdef SP_CODA /* headers */ -#import "SPTableStructure.h" -#import "SPTableStructureLoading.h" -#endif - -#import <SPMySQL/SPMySQL.h> - -@interface SPDatabaseDocument (SPDatabaseViewControllerPrivateAPI) - -- (void)_loadTabTask:(NSNumber *)tabViewItemIndexNumber; -- (void)_loadTableTask; - -@end - -@implementation SPDatabaseDocument (SPDatabaseViewController) - -#pragma mark - -#pragma mark Getters - -#ifndef SP_CODA /* getters */ -/** - * Returns the master database view, containing the tables list and views for - * table setup and contents. - */ -- (NSView *)databaseView -{ - return parentView; -} -#endif - -/** - * Returns the name of the currently selected table/view/procedure/function. - */ -- (NSString *)table -{ - return selectedTableName; -} - -/** - * Returns the currently selected table type, or -1 if no table or multiple tables are selected - */ -- (SPTableType)tableType -{ - return selectedTableType; -} - -/** - * Returns YES if table source has already been loaded - */ -- (BOOL)structureLoaded -{ - return structureLoaded; -} - -/** - * Returns YES if table content has already been loaded - */ -- (BOOL)contentLoaded -{ - return contentLoaded; -} - -/** - * Returns YES if table status has already been loaded - */ -- (BOOL)statusLoaded -{ - return statusLoaded; -} - -#ifndef SP_CODA /* toolbar ibactions */ - -#pragma mark - -#pragma mark Tab view control and delegate methods - -//WARNING: Might be called from code in background threads -- (IBAction)viewStructure:(id)sender -{ - // Cancel the selection if currently editing a view and unable to save - if (![[self onMainThread] couldCommitCurrentViewActions]) { - [[mainToolbar onMainThread] setSelectedItemIdentifier:*SPViewModeToMainToolbarMap[[prefs integerForKey:SPLastViewMode]]]; - return; - } - - [[tableTabView onMainThread] selectTabViewItemAtIndex:0]; - [[mainToolbar onMainThread] setSelectedItemIdentifier:SPMainToolbarTableStructure]; - [spHistoryControllerInstance updateHistoryEntries]; - - [prefs setInteger:SPStructureViewMode forKey:SPLastViewMode]; -} - -- (IBAction)viewContent:(id)sender -{ - // Cancel the selection if currently editing a view and unable to save - if (![self couldCommitCurrentViewActions]) { - [mainToolbar setSelectedItemIdentifier:*SPViewModeToMainToolbarMap[[prefs integerForKey:SPLastViewMode]]]; - return; - } - - [tableTabView selectTabViewItemAtIndex:1]; - [mainToolbar setSelectedItemIdentifier:SPMainToolbarTableContent]; - [spHistoryControllerInstance updateHistoryEntries]; - - [prefs setInteger:SPContentViewMode forKey:SPLastViewMode]; -} - -- (IBAction)viewQuery:(id)sender -{ - // Cancel the selection if currently editing a view and unable to save - if (![self couldCommitCurrentViewActions]) { - [mainToolbar setSelectedItemIdentifier:*SPViewModeToMainToolbarMap[[prefs integerForKey:SPLastViewMode]]]; - return; - } - - [tableTabView selectTabViewItemAtIndex:2]; - [mainToolbar setSelectedItemIdentifier:SPMainToolbarCustomQuery]; - [spHistoryControllerInstance updateHistoryEntries]; - - // Set the focus on the text field - [parentWindow makeFirstResponder:customQueryTextView]; - - [prefs setInteger:SPQueryEditorViewMode forKey:SPLastViewMode]; -} - -- (IBAction)viewStatus:(id)sender -{ - // Cancel the selection if currently editing a view and unable to save - if (![self couldCommitCurrentViewActions]) { - [mainToolbar setSelectedItemIdentifier:*SPViewModeToMainToolbarMap[[prefs integerForKey:SPLastViewMode]]]; - return; - } - - [tableTabView selectTabViewItemAtIndex:3]; - [mainToolbar setSelectedItemIdentifier:SPMainToolbarTableInfo]; - [spHistoryControllerInstance updateHistoryEntries]; - - if ([[self table] length]) { - [extendedTableInfoInstance loadTable:[self table]]; - } - - [parentWindow makeFirstResponder:[extendedTableInfoInstance valueForKeyPath:@"tableCreateSyntaxTextView"]]; - - [prefs setInteger:SPTableInfoViewMode forKey:SPLastViewMode]; -} - -- (IBAction)viewRelations:(id)sender -{ - // Cancel the selection if currently editing a view and unable to save - if (![self couldCommitCurrentViewActions]) { - [mainToolbar setSelectedItemIdentifier:*SPViewModeToMainToolbarMap[[prefs integerForKey:SPLastViewMode]]]; - return; - } - - [tableTabView selectTabViewItemAtIndex:4]; - [mainToolbar setSelectedItemIdentifier:SPMainToolbarTableRelations]; - [spHistoryControllerInstance updateHistoryEntries]; - - [prefs setInteger:SPRelationsViewMode forKey:SPLastViewMode]; -} - -- (IBAction)viewTriggers:(id)sender -{ - // Cancel the selection if currently editing a view and unable to save - if (![self couldCommitCurrentViewActions]) { - [mainToolbar setSelectedItemIdentifier:*SPViewModeToMainToolbarMap[[prefs integerForKey:SPLastViewMode]]]; - return; - } - - [tableTabView selectTabViewItemAtIndex:5]; - [mainToolbar setSelectedItemIdentifier:SPMainToolbarTableTriggers]; - [spHistoryControllerInstance updateHistoryEntries]; - - [prefs setInteger:SPTriggersViewMode forKey:SPLastViewMode]; -} -#endif - -/** - * Mark the structure tab for refresh when it's next switched to, - * or reload the view if it's currently active - */ -- (void)setStructureRequiresReload:(BOOL)reload -{ - BOOL reloadRequired = reload; - -#ifndef SP_CODA - if ([self currentlySelectedView] == SPTableViewStructure) { - reloadRequired = NO; - } -#endif - - if (reloadRequired && selectedTableName) { - [tableSourceInstance loadTable:selectedTableName]; - } - else { - structureLoaded = !reload; - } -} - -/** - * Mark the content tab for refresh when it's next switched to, - * or reload the view if it's currently active - */ -- (void)setContentRequiresReload:(BOOL)reload -{ - if (reload && selectedTableName -#ifndef SP_CODA /* check which tab is selected */ - && [self currentlySelectedView] == SPTableViewContent -#endif - ) { - [tableContentInstance loadTable:selectedTableName]; - } - else { - contentLoaded = !reload; - } -} - -/** - * Mark the extended tab info for refresh when it's next switched to, - * or reload the view if it's currently active - */ -- (void)setStatusRequiresReload:(BOOL)reload -{ - if (reload && selectedTableName -#ifndef SP_CODA /* check which tab is selected */ - && [self currentlySelectedView] == SPTableViewStatus -#endif - ) { - [[extendedTableInfoInstance onMainThread] loadTable:selectedTableName]; - } - else { - statusLoaded = !reload; - } -} - -/** - * Mark the relations tab for refresh when it's next switched to, - * or reload the view if it's currently active - */ -- (void)setRelationsRequiresReload:(BOOL)reload -{ - if (reload && selectedTableName -#ifndef SP_CODA /* check which tab is selected */ - && [self currentlySelectedView] == SPTableViewRelations -#endif - ) { - [[tableRelationsInstance onMainThread] refreshRelations:self]; - } - else { - relationsLoaded = !reload; - } -} - -#ifndef SP_CODA /* !!! respond to tab change */ -/** - * Triggers a task to update the newly selected tab view, ensuring - * the data is fully loaded and up-to-date. - */ -- (void)tabView:(NSTabView *)aTabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem -{ - [self startTaskWithDescription:[NSString stringWithFormat:NSLocalizedString(@"Loading %@...", @"Loading table task string"), [self table]]]; - - // We can't pass aTabView or tabViewItem UI objects to a bg thread, but since the change should already - // be done in *did*SelectTabViewItem we can just ask the tab view for the current selection index and use that - SPTableViewType newView = [self currentlySelectedView]; - - if ([NSThread isMainThread]) { - [NSThread detachNewThreadWithName:SPCtxt(@"SPDatabaseViewController view load task",self) - target:self - selector:@selector(_loadTabTask:) - object:@(newView)]; - } - else { - [self _loadTabTask:@(newView)]; - } -} -#endif - -#pragma mark - -#pragma mark Table control - -/** - * Loads a specified table into the database view, and ensures it's selected in - * the tables list. Passing a table name of nil will deselect any currently selected - * table, but will leave multiple selections intact. - * If this method is supplied with the currently selected name, a reload rather than - * a load will be triggered. - */ -- (void)loadTable:(NSString *)aTable ofType:(SPTableType)aTableType -{ - // Ensure a connection is still present - if (![mySQLConnection isConnected]) return; - - // If the supplied table name was nil, clear the views. - if (!aTable) { - - // Update the selected table name and type - if (selectedTableName) SPClear(selectedTableName); - - selectedTableType = SPTableTypeNone; - - // Clear the views - [[tablesListInstance onMainThread] setSelectionState:nil]; - [tableSourceInstance loadTable:nil]; - [tableContentInstance loadTable:nil]; -#ifndef SP_CODA /* [extendedTableInfoInstance loadTable:] */ - [[extendedTableInfoInstance onMainThread] loadTable:nil]; - [[tableTriggersInstance onMainThread] resetInterface]; - [[tableRelationsInstance onMainThread] refreshRelations:self]; -#endif - structureLoaded = NO; - contentLoaded = NO; - statusLoaded = NO; - triggersLoaded = NO; - relationsLoaded = NO; - -#ifndef SP_CODA - // Update the window title - [self updateWindowTitle:self]; - - // Add a history entry - [spHistoryControllerInstance updateHistoryEntries]; -#endif - - // Notify listeners of the table change - [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:SPTableChangedNotification object:self]; - - return; - } - - BOOL isReloading = (selectedTableName && [selectedTableName isEqualToString:aTable]); - - // Store the new name - if (selectedTableName) [selectedTableName release]; - - selectedTableName = [[NSString alloc] initWithString:aTable]; - selectedTableType = aTableType; - - // Start a task - if (isReloading) { - [self startTaskWithDescription:NSLocalizedString(@"Reloading...", @"Reloading table task string")]; - } - else { - [self startTaskWithDescription:[NSString stringWithFormat:NSLocalizedString(@"Loading %@...", @"Loading table task string"), aTable]]; - } - - // Update the tables list interface - also updates menus to reflect the selected table type - [[tablesListInstance onMainThread] setSelectionState:[NSDictionary dictionaryWithObjectsAndKeys:aTable, @"name", [NSNumber numberWithInteger:aTableType], @"type", nil]]; - - // If on the main thread, fire up a thread to deal with view changes and data loading; - // if already on a background thread, make the changes on the existing thread. - if ([NSThread isMainThread]) { - [NSThread detachNewThreadWithName:SPCtxt(@"SPDatabaseViewController table load task",self) - target:self - selector:@selector(_loadTableTask) - object:nil]; - } - else { - [self _loadTableTask]; - } -} - -@end - -#pragma mark - - -@implementation SPDatabaseDocument (SPDatabaseViewControllerPrivateAPI) - -/** - * In a threaded task, ensure that the supplied tab is loaded - - * usually as a result of switching to it. - */ -- (void)_loadTabTask:(NSNumber *)tabViewItemIndexNumber -{ - NSAutoreleasePool *tabLoadPool = [[NSAutoreleasePool alloc] init]; - - // If anything other than a single table or view is selected, don't proceed. - if (![self table] || ([tablesListInstance tableType] != SPTableTypeTable && [tablesListInstance tableType] != SPTableTypeView)) - { - [self endTask]; - [tabLoadPool drain]; - return; - } - - // Get the tab view index and ensure the associated view is loaded - SPTableViewType selectedTabViewIndex = [tabViewItemIndexNumber integerValue]; - - switch (selectedTabViewIndex) { - case SPTableViewStructure: - if (!structureLoaded) { - [tableSourceInstance loadTable:selectedTableName]; - structureLoaded = YES; - } - break; - case SPTableViewContent: - if (!contentLoaded) { - [tableContentInstance loadTable:selectedTableName]; - contentLoaded = YES; - } - break; -#ifndef SP_CODA /* case SPTableViewStatus: case SPTableViewTriggers: */ - case SPTableViewStatus: - if (!statusLoaded) { - [[extendedTableInfoInstance onMainThread] loadTable:selectedTableName]; - statusLoaded = YES; - } - break; - case SPTableViewTriggers: - if (!triggersLoaded) { - [[tableTriggersInstance onMainThread] loadTriggers]; - triggersLoaded = YES; - } - break; - case SPTableViewRelations: - if (!relationsLoaded) { - [[tableRelationsInstance onMainThread] refreshRelations:self]; - relationsLoaded = YES; - } - break; -#endif - } - - [self endTask]; - - [tabLoadPool drain]; -} - - -/** - * In a threaded task, load the currently selected table/view/proc/function. - */ -- (void)_loadTableTask -{ - NSAutoreleasePool *loadPool = [[NSAutoreleasePool alloc] init]; - NSString *tableEncoding = nil; - -#ifndef SP_CODA /* Update the window title */ - // Update the window title - [self updateWindowTitle:self]; -#endif - - // Reset table information caches and mark that all loaded views require their data reloading - [tableDataInstance resetAllData]; - - structureLoaded = NO; - contentLoaded = NO; - statusLoaded = NO; - triggersLoaded = NO; - relationsLoaded = NO; - - // Ensure status and details are fetched using UTF8 - NSString *previousEncoding = [mySQLConnection encoding]; - BOOL changeEncoding = ![previousEncoding isEqualToString:@"utf8"]; - - if (changeEncoding) { - [mySQLConnection storeEncodingForRestoration]; - [mySQLConnection setEncoding:@"utf8"]; - } - - // Cache status information on the working thread - [tableDataInstance updateStatusInformationForCurrentTable]; - - // Check the current encoding against the table encoding to see whether - // an encoding change and reset is required. This also caches table information on - // the working thread. - if( selectedTableType == SPTableTypeView || selectedTableType == SPTableTypeTable) { - - // tableEncoding == nil indicates that there was an error while retrieving table data - tableEncoding = [tableDataInstance tableEncoding]; - - // If encoding is set to Autodetect, update the connection character set encoding - // based on the newly selected table's encoding - but only if it differs from the current encoding. - if ([[[NSUserDefaults standardUserDefaults] objectForKey:SPDefaultEncoding] intValue] == SPEncodingAutodetect) { - if (tableEncoding != nil && ![tableEncoding isEqualToString:previousEncoding]) { - [self setConnectionEncoding:tableEncoding reloadingViews:NO]; - changeEncoding = NO; - } - } - } - - if (changeEncoding) [mySQLConnection restoreStoredEncoding]; - - // Notify listeners of the table change now that the state is fully set up. - [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:SPTableChangedNotification object:self]; - -#ifndef SP_CODA /* [spHistoryControllerInstance restoreViewStates] */ - - // Restore view states as appropriate - [spHistoryControllerInstance restoreViewStates]; -#endif - - // Load the currently selected view if looking at a table or view - if (tableEncoding && (selectedTableType == SPTableTypeView || selectedTableType == SPTableTypeTable)) - { -#ifndef SP_CODA /* load everything */ - NSInteger selectedTabViewIndex = [[self onMainThread] currentlySelectedView]; - - switch (selectedTabViewIndex) { - case SPTableViewStructure: -#endif - [tableSourceInstance loadTable:selectedTableName]; - structureLoaded = YES; -#ifndef SP_CODA /* load everything */ - break; - case SPTableViewContent: -#endif - [tableContentInstance loadTable:selectedTableName]; - contentLoaded = YES; -#ifndef SP_CODA /* load everything */ - break; - case SPTableViewStatus: - [[extendedTableInfoInstance onMainThread] loadTable:selectedTableName]; - statusLoaded = YES; - break; - case SPTableViewTriggers: - [[tableTriggersInstance onMainThread] loadTriggers]; - triggersLoaded = YES; - break; - case SPTableViewRelations: - [[tableRelationsInstance onMainThread] refreshRelations:self]; - relationsLoaded = YES; - break; - } -#endif - } - - // Clear any views which haven't been loaded as they weren't visible. Note - // that this should be done after reloading visible views, instead of clearing all - // views, to reduce UI operations and avoid resetting state unnecessarily. - // Some views (eg TableRelations) make use of the SPTableChangedNotification and - // so don't require manual clearing. - if (!structureLoaded) [tableSourceInstance loadTable:nil]; - if (!contentLoaded) [tableContentInstance loadTable:nil]; - if (!statusLoaded) [[extendedTableInfoInstance onMainThread] loadTable:nil]; - if (!triggersLoaded) [[tableTriggersInstance onMainThread] resetInterface]; - - // If the table row counts an inaccurate and require updating, trigger an update - no - // action will be performed if not necessary - [tableDataInstance updateAccurateNumberOfRowsForCurrentTableForcingUpdate:NO]; - -#ifndef SP_CODA /* show Create Table syntax */ - // Update the "Show Create Syntax" window if it's already opened - // according to the selected table/view/proc/func - if ([[[self onMainThread] getCreateTableSyntaxWindow] isVisible]) { - [[self onMainThread] showCreateTableSyntax:self]; - } - - // Add a history entry - [spHistoryControllerInstance updateHistoryEntries]; -#endif - // Empty the loading pool and exit the thread - [self endTask]; - -#ifndef SP_CODA /* triggered commands */ - NSArray *triggeredCommands = [SPAppDelegate bundleCommandsForTrigger:SPBundleTriggerActionTableChanged]; - - for(NSString* cmdPath in triggeredCommands) - { - NSArray *data = [cmdPath componentsSeparatedByString:@"|"]; - NSMenuItem *aMenuItem = [[[NSMenuItem alloc] init] autorelease]; - [aMenuItem setTag:0]; - [aMenuItem setToolTip:[data objectAtIndex:0]]; - - // For HTML output check if corresponding window already exists - BOOL stopTrigger = NO; - if([(NSString*)[data objectAtIndex:2] length]) { - BOOL correspondingWindowFound = NO; - NSString *uuid = [data objectAtIndex:2]; - for(id win in [NSApp windows]) { - if([[[[win delegate] class] description] isEqualToString:@"SPBundleHTMLOutputController"]) { - if([[[win delegate] windowUUID] isEqualToString:uuid]) { - correspondingWindowFound = YES; - break; - } - } - } - if(!correspondingWindowFound) stopTrigger = YES; - } - if(!stopTrigger) { - id firstResponder = [[NSApp keyWindow] firstResponder]; - if([[data objectAtIndex:1] isEqualToString:SPBundleScopeGeneral]) { - [[SPAppDelegate onMainThread] executeBundleItemForApp:aMenuItem]; - } - else if([[data objectAtIndex:1] isEqualToString:SPBundleScopeDataTable]) { - if([[[firstResponder class] description] isEqualToString:@"SPCopyTable"]) - [[firstResponder onMainThread] executeBundleItemForDataTable:aMenuItem]; - } - else if([[data objectAtIndex:1] isEqualToString:SPBundleScopeInputField]) { - if([firstResponder isKindOfClass:[NSTextView class]]) - [[firstResponder onMainThread] executeBundleItemForInputField:aMenuItem]; - } - } - } -#endif - - [loadPool drain]; -} - -@end diff --git a/Source/SPDotExporterDelegate.h b/Source/SPDotExporterDelegate.h deleted file mode 100644 index 497ac2b2..00000000 --- a/Source/SPDotExporterDelegate.h +++ /dev/null @@ -1,43 +0,0 @@ -// -// SPDotExporterDelegate.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on April 17, 2010. -// Copyright (c) 2010 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPExportController.h" -#import "SPDotExporterProtocol.h" - -/** - * @category SPDotExporterDelegate SPDotExporterDelegate.h - * - * @author Stuart Connolly http://stuconnolly.com/ - * - * Dot exporter delegate category. - */ -@interface SPExportController (SPDotExporterDelegate) <SPDotExporterProtocol> - -@end diff --git a/Source/SPDotExporterDelegate.m b/Source/SPDotExporterDelegate.m deleted file mode 100644 index 47054a1e..00000000 --- a/Source/SPDotExporterDelegate.m +++ /dev/null @@ -1,78 +0,0 @@ -// -// SPDotExporterDelegate.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on April 17, 2010. -// Copyright (c) 2010 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPDotExporterDelegate.h" -#import "SPDotExporter.h" -#import "SPExportInitializer.h" - -@implementation SPExportController (SPDotExporterDelegate) - -- (void)dotExportProcessWillBegin:(SPDotExporter *)exporter -{ - [exportProgressTitle setStringValue:NSLocalizedString(@"Exporting Dot File", @"text showing that the application is exporting a Dot file")]; - [exportProgressText setStringValue:NSLocalizedString(@"Dumping...", @"text showing that app is writing dump")]; - - [exportProgressTitle displayIfNeeded]; - [exportProgressText displayIfNeeded]; - [exportProgressIndicator stopAnimation:self]; - [exportProgressIndicator setIndeterminate:NO]; -} - -- (void)dotExportProcessComplete:(SPDotExporter *)exporter -{ - [self exportEnded]; -} - -- (void)dotExportProcessProgressUpdated:(SPDotExporter *)exporter -{ - [exportProgressIndicator setDoubleValue:[exporter exportProgressValue]]; -} - -- (void)dotExportProcessWillBeginFetchingData:(SPDotExporter *)exporter forTableWithIndex:(NSUInteger)tableIndex -{ - // Update the current table export index - currentTableExportIndex = tableIndex; - - [exportProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %lu (%@): Fetching data...", @"export label showing that the app is fetching data for a specific table"), currentTableExportIndex, exportTableCount, [exporter dotExportCurrentTable]]]; - - [exportProgressText displayIfNeeded]; -} - -- (void)dotExportProcessWillBeginFetchingRelationsData:(SPDotExporter *)exporter -{ - [exportProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %lu (%@): Fetching relations data...", @"export label showing app is fetching relations data for a specific table"), currentTableExportIndex, exportTableCount, [exporter dotExportCurrentTable]]]; - - [exportProgressText displayIfNeeded]; - [exportProgressIndicator setIndeterminate:YES]; - [exportProgressIndicator setUsesThreadedAnimation:YES]; - [exportProgressIndicator startAnimation:self]; -} - -@end diff --git a/Source/SPEditorPreferencePane.m b/Source/SPEditorPreferencePane.m index 0e938c0d..54b27ed1 100644 --- a/Source/SPEditorPreferencePane.m +++ b/Source/SPEditorPreferencePane.m @@ -44,7 +44,7 @@ static NSString *SPCustomColorSchemeNameLC = @"user-defined"; #define SP_EXPORT_COLOR_SCHEME_NAME_STRING NSLocalizedString(@"MyTheme", @"Preferences : Themes : Initial filename for 'Export'") -@interface SPEditorPreferencePane (PrivateAPI) +@interface SPEditorPreferencePane () - (BOOL)_checkForUnsavedTheme; - (NSArray *)_getAvailableThemes; diff --git a/Source/SPExportController+SharedPrivateAPI.h b/Source/SPExportController+SharedPrivateAPI.h deleted file mode 100644 index d8382824..00000000 --- a/Source/SPExportController+SharedPrivateAPI.h +++ /dev/null @@ -1,36 +0,0 @@ -// -// SPExportController+SharedPrivateAPI.h -// sequel-pro -// -// Created by Max Lohrmann on 03.02.15. -// Copyright (c) 2015 Max Lohrmann. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPExportController.h" - -@interface SPExportController (SharedPrivateAPI) -- (void)sheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo; -- (void)_hideExportProgress; -@end diff --git a/Source/SPExportController.h b/Source/SPExportController.h index bef547d8..5eb15a9f 100644 --- a/Source/SPExportController.h +++ b/Source/SPExportController.h @@ -35,6 +35,9 @@ @class SPTableData; @class SPMySQLConnection; @class SPServerSupport; +@class SPCSVExporter; +@class SPXMLExporter; +@class SPExportFile; /** * @class SPExportController SPExportController.h @@ -284,4 +287,63 @@ - (IBAction)toggleSQLIncludeDropSyntax:(NSButton *)sender; - (IBAction)toggleNewFilePerTable:(NSButton *)sender; +#pragma mark - SPExportInitializer + +- (void)startExport; +- (void)exportEnded; +- (void)initializeExportUsingSelectedOptions; + +- (void)exportTables:(NSArray *)exportTables orDataArray:(NSArray *)dataArray; + +- (SPCSVExporter *)initializeCSVExporterForTable:(NSString *)table orDataArray:(NSArray *)dataArray; +- (SPXMLExporter *)initializeXMLExporterForTable:(NSString *)table orDataArray:(NSArray *)dataArray; + +#pragma mark - SPExportFileUtilities + +- (void)writeCSVHeaderToExportFile:(SPExportFile *)file; +- (void)writeXMLHeaderToExportFile:(SPExportFile *)file; + +- (void)errorCreatingExportFileHandles:(NSArray *)files; + +#pragma mark - SPExportFilenameUtilities + +- (void)updateDisplayedExportFilename; +- (void)updateAvailableExportFilenameTokens; +- (NSArray *)currentAllowedExportFilenameTokens; +- (NSString *)generateDefaultExportFilename; +- (NSString *)currentDefaultExportFileExtension; +- (NSString *)expandCustomFilenameFormatUsingTableName:(NSString *)table; +- (NSString *)customFilenamePathExtension; +- (BOOL)isTableTokenAllowed; + +#pragma mark - SPExportSettingsPersistence + +- (IBAction)exportCurrentSettings:(id)sender; +- (IBAction)importCurrentSettings:(id)sender; + +/** + * @return The current settings as a dictionary which can be serialized + */ +- (NSDictionary *)currentSettingsAsDictionary; + +/** Overwrite current export settings with those defined in dict + * @param dict The new settings to apply (passing nil is an error.) + * @param err Errors while applying (will mostly be about invalid format, type) + * Can pass NULL, if not interested in details. + * Will NOT be changed unless the method also returns NO + * @return success + */ +- (BOOL)applySettingsFromDictionary:(NSDictionary *)dict error:(NSError **)err; + +/** + * @return A serialized form of the "custom filename" field + */ +- (NSArray *)currentCustomFilenameAsArray; + +/** + * @param tokenList A serialized form of the "custom filename" field + * @see currentCustomFilenameAsArray + */ +- (void)setCustomFilenameFromArray:(NSArray *)tokenList; + @end diff --git a/Source/SPExportController.m b/Source/SPExportController.m index 28e29dac..c6e82baa 100644 --- a/Source/SPExportController.m +++ b/Source/SPExportController.m @@ -29,20 +29,28 @@ // More info at <https://github.com/sequelpro/sequelpro> #import "SPExportController.h" -#import "SPExportInitializer.h" #import "SPTablesList.h" #import "SPTableData.h" #import "SPTableContent.h" #import "SPGrowlController.h" #import "SPExportFile.h" #import "SPAlertSheets.h" -#import "SPExportFilenameUtilities.h" #import "SPExportFileNameTokenObject.h" #import "SPDatabaseDocument.h" #import "SPThreadAdditions.h" #import "SPCustomQuery.h" -#import "SPExportController+SharedPrivateAPI.h" -#import "SPExportSettingsPersistence.h" +#import "SPCSVExporter.h" +#import "SPSQLExporter.h" +#import "SPXMLExporter.h" +#import "SPDotExporter.h" +#import "SPConnectionControllerDelegateProtocol.h" +#import "SPExporter.h" +#import "SPCSVExporterProtocol.h" +#import "SPSQLExporterProtocol.h" +#import "SPXMLExporterProtocol.h" +#import "SPDotExporterProtocol.h" +#import "SPPDFExporterProtocol.h" +#import "SPHTMLExporterProtocol.h" #import <SPMySQL/SPMySQL.h> @@ -57,7 +65,29 @@ static const NSString *SPSQLExportStructureEnabled = @"SQLExportStructureEnable static const NSString *SPSQLExportContentEnabled = @"SQLExportContentEnabled"; static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; -@interface SPExportController (PrivateAPI) +typedef enum +{ + SPExportErrorCancelExport = 0, + SPExportErrorReplaceFiles = 1, + SPExportErrorSkipErrorFiles = 2 +} +SPExportErrorChoice; + +static inline BOOL IS_TOKEN(id x); +static inline BOOL IS_STRING(id x); + +/** + * converts a ([obj state] == NSOnState) to @YES / @NO + * (because doing @([obj state] == NSOnState) will result in an integer 0/1) + */ +static inline NSNumber *IsOn(id obj); + +/** + * Sets the state of obj to NSOnState or NSOffState based on the value of ref + */ +static inline void SetOnOff(NSNumber *ref,id obj); + +@interface SPExportController () <SPCSVExporterProtocol, SPSQLExporterProtocol, SPXMLExporterProtocol, SPDotExporterProtocol, SPPDFExporterProtocol, SPHTMLExporterProtocol> - (void)_switchTab; - (void)_checkForDatabaseChanges; @@ -75,6 +105,61 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; - (void)_waitUntilQueueIsEmptyAfterCancelling:(id)sender; - (void)_queueIsEmptyAfterCancelling:(id)sender; +#pragma mark - SPExportFileUtilitiesPrivateAPI + +- (void)_reopenExportSheet; + +#pragma mark - SPExportControllerDelegate + +- (NSArray *)_updateTokensForMixedContent:(NSArray *)tokens; +- (void)_tokenizeCustomFilenameTokenField; + +#pragma mark - SPExportSettingsPersistence + +// those methods will convert the name of a C enum constant to a NSString ++ (NSString *)describeExportSource:(SPExportSource)es; ++ (NSString *)describeExportType:(SPExportType)et; ++ (NSString *)describeCompressionFormat:(SPFileCompressionFormat)cf; ++ (NSString *)describeXMLExportFormat:(SPXMLExportFormat)xf; ++ (NSString *)describeSQLExportInsertDivider:(SPSQLExportInsertDivider)eid; + +// these will store the C enum constant named by NSString in dst and return YES, +// if a valid mapping exists. Otherwise will just return NO and not modify dst. ++ (BOOL)copyExportSourceForDescription:(NSString *)esd to:(SPExportSource *)dst; ++ (BOOL)copyCompressionFormatForDescription:(NSString *)esd to:(SPFileCompressionFormat *)dst; ++ (BOOL)copyExportTypeForDescription:(NSString *)esd to:(SPExportType *)dst; ++ (BOOL)copyXMLExportFormatForDescription:(NSString *)xfd to:(SPXMLExportFormat *)dst; ++ (BOOL)copySQLExportInsertDividerForDescription:(NSString *)xfd to:(SPSQLExportInsertDivider *)dst; + +- (NSDictionary *)exporterSettings; +- (NSDictionary *)csvSettings; +- (NSDictionary *)dotSettings; +- (NSDictionary *)xmlSettings; +- (NSDictionary *)sqlSettings; + +- (void)applyExporterSettings:(NSDictionary *)settings; +- (void)applyCsvSettings:(NSDictionary *)settings; +- (void)applyDotSettings:(NSDictionary *)settings; +- (void)applyXmlSettings:(NSDictionary *)settings; +- (void)applySqlSettings:(NSDictionary *)settings; + +- (id)exporterSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type; +- (id)dotSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type; +- (id)xmlSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type; +- (id)csvSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type; +- (id)sqlSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type; + +- (void)applyExporterSpecificSettings:(id)settings forSchemaObject:(NSString *)name ofType:(SPTableType)type; +- (void)applyDotSpecificSettings:(id)settings forSchemaObject:(NSString *)name ofType:(SPTableType)type; +- (void)applyXmlSpecificSettings:(id)settings forSchemaObject:(NSString *)name ofType:(SPTableType)type; +- (void)applyCsvSpecificSettings:(id)settings forSchemaObject:(NSString *)name ofType:(SPTableType)type; +- (void)applySqlSpecificSettings:(id)settings forSchemaObject:(NSString *)name ofType:(SPTableType)type; + +#pragma mark - Shared Private + +- (void)sheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo; +- (void)_hideExportProgress; + @end @implementation SPExportController @@ -1073,6 +1158,2586 @@ set_input: [exportButton setEnabled:[enable boolValue]]; } + +#pragma mark - SPExportInitializer + +/** + * Starts the export process by placing the first exporter on the operation queue. Also opens the progress + * sheet if it's not already visible. + */ +- (void)startExport +{ + // Start progress indicator + [exportProgressTitle setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Exporting %@", @"text showing that the application is importing a supplied format"), exportTypeLabel]]; + [exportProgressText setStringValue:NSLocalizedString(@"Writing...", @"text showing that app is writing text file")]; + + [exportProgressIndicator setUsesThreadedAnimation:NO]; + [exportProgressIndicator setIndeterminate:NO]; + [exportProgressIndicator setDoubleValue:0]; + + // If it's not already displayed, open the progress sheet + if (![exportProgressWindow isVisible]) { + [NSApp beginSheet:exportProgressWindow + modalForWindow:[tableDocumentInstance parentWindow] + modalDelegate:self + didEndSelector:nil + contextInfo:nil]; + } + + // cache the current connection encoding so the exporter can do what it wants. + previousConnectionEncoding = [[NSString alloc] initWithString:[connection encoding]]; + previousConnectionEncodingViaLatin1 = [connection encodingUsesLatin1Transport]; + + // Add the first exporter to the operation queue + [operationQueue addOperation:[exporters objectAtIndex:0]]; + + // Remove the exporter we just added to the operation queue from our list of exporters + // so we know it's already been done. + [exporters removeObjectAtIndex:0]; +} + +/** + * @see _queueIsEmptyAfterCancelling: + */ +- (void)exportEnded +{ + [self _hideExportProgress]; + + // Restore query mode + [tableDocumentInstance setQueryMode:SPInterfaceQueryMode]; + + // Display Growl notification + [self displayExportFinishedGrowlNotification]; + + // Restore the connection encoding to it's pre-export value + [tableDocumentInstance setConnectionEncoding:[NSString stringWithFormat:@"%@%@", previousConnectionEncoding, (previousConnectionEncodingViaLatin1) ? @"-" : @""] reloadingViews:NO]; +} + +/** + * Initializes the export process by analysing the selected criteria. + */ +- (void)initializeExportUsingSelectedOptions +{ + NSArray *dataArray = nil; + + // Get rid of the cached connection encoding + if (previousConnectionEncoding) SPClear(previousConnectionEncoding); + + createCustomFilename = ([[exportCustomFilenameTokenField stringValue] length] > 0); + + NSMutableArray *exportTables = [NSMutableArray array]; + + // Set whether or not we are to export to multiple files + [self setExportToMultipleFiles:[exportFilePerTableCheck state]]; + + // Get the data depending on the source + switch (exportSource) + { + case SPFilteredExport: + dataArray = [tableContentInstance currentDataResultWithNULLs:YES hideBLOBs:NO]; + break; + case SPQueryExport: + dataArray = [customQueryInstance currentDataResultWithNULLs:YES truncateDataFields:NO]; + break; + case SPTableExport: + // Create an array of tables to export + for (NSMutableArray *table in tables) + { + if (exportType == SPSQLExport) { + if ([[table objectAtIndex:1] boolValue] || [[table objectAtIndex:2] boolValue] || [[table objectAtIndex:3] boolValue]) { + + // Check the overall export settings + if ([[table objectAtIndex:1] boolValue] && (![exportSQLIncludeStructureCheck state])) { + [table replaceObjectAtIndex:1 withObject:@NO]; + } + + if ([[table objectAtIndex:2] boolValue] && (![exportSQLIncludeContentCheck state])) { + [table replaceObjectAtIndex:2 withObject:@NO]; + } + + if ([[table objectAtIndex:3] boolValue] && (![exportSQLIncludeDropSyntaxCheck state])) { + [table replaceObjectAtIndex:3 withObject:@NO]; + } + + [exportTables addObject:table]; + } + } + else if (exportType == SPDotExport) { + [exportTables addObject:[table objectAtIndex:0]]; + } + else { + if ([[table objectAtIndex:2] boolValue]) { + [exportTables addObject:[table objectAtIndex:0]]; + } + } + } + + break; + } + + // Set the export type label + switch (exportType) + { + case SPSQLExport: + exportTypeLabel = @"SQL"; + break; + case SPCSVExport: + exportTypeLabel = @"CSV"; + break; + case SPXMLExport: + exportTypeLabel = @"XML"; + break; + case SPDotExport: + exportTypeLabel = @"Dot"; + break; + case SPPDFExport: + case SPHTMLExport: + case SPExcelExport: + default: + [NSException raise:NSInvalidArgumentException format:@"unsupported exportType=%lu",exportType]; + return; + } + + // Begin the export based on the source + switch (exportSource) + { + case SPFilteredExport: + case SPQueryExport: + [self exportTables:nil orDataArray:dataArray]; + break; + case SPTableExport: + [self exportTables:exportTables orDataArray:nil]; + break; + } +} + +/** + * Exports the contents of the supplied array of tables or data array. + * + * Note that at least one of these parameters must not be nil. + * + * @param exportTables An array of table/view names to be exported (can be nil). + * @param dataArray A MySQL result set array to be exported (can be nil). + */ +- (void)exportTables:(NSArray *)exportTables orDataArray:(NSArray *)dataArray +{ + BOOL singleFileHandleSet = NO; + SPExportFile *singleExportFile = nil, *file = nil; + + // Change query logging mode + [tableDocumentInstance setQueryMode:SPImportExportQueryMode]; + + // Start the notification timer to allow notifications to be shown even if frontmost for long queries + [[SPGrowlController sharedGrowlController] setVisibilityForNotificationName:@"Export Finished"]; + + // Setup the progress sheet + [exportProgressTitle setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Exporting %@", @"text showing that the application is importing a supplied format"), exportTypeLabel]]; + [exportProgressText setStringValue:NSLocalizedString(@"Initializing...", @"initializing export label")]; + + [exportProgressIndicator setIndeterminate:YES]; + [exportProgressIndicator setUsesThreadedAnimation:YES]; + + // Open the progress sheet + [NSApp beginSheet:exportProgressWindow + modalForWindow:[tableDocumentInstance parentWindow] + modalDelegate:self + didEndSelector:nil + contextInfo:nil]; + + // CSV export + if (exportType == SPCSVExport) { + + SPCSVExporter *csvExporter = nil; + + // If the user has selected to only export to a single file or this is a filtered or custom query + // export, create the single file now and assign it to all subsequently created exporters. + if ((![self exportToMultipleFiles]) || (exportSource == SPFilteredExport) || (exportSource == SPQueryExport)) { + NSString *selectedTableName = nil; + + if (exportSource == SPTableExport && [exportTables count] == 1) selectedTableName = [exportTables objectAtIndex:0]; + + [exportFilename setString:createCustomFilename ? [self expandCustomFilenameFormatUsingTableName:selectedTableName] : [self generateDefaultExportFilename]]; + + // Only append the extension if necessary + if (![[exportFilename pathExtension] length]) { + [exportFilename setString:[exportFilename stringByAppendingPathExtension:[self currentDefaultExportFileExtension]]]; + } + + singleExportFile = [SPExportFile exportFileAtPath:[[exportPathField stringValue] stringByAppendingPathComponent:exportFilename]]; + } + + // Start the export process depending on the data source + if (exportSource == SPTableExport) { + + // Cache the number of tables being exported + exportTableCount = [exportTables count]; + + // Loop through the tables, creating an exporter for each + for (NSString *table in exportTables) + { + csvExporter = [self initializeCSVExporterForTable:table orDataArray:nil]; + + // If required create a single file handle for all CSV exports + if (![self exportToMultipleFiles]) { + if (!singleFileHandleSet) { + [singleExportFile setExportFileNeedsCSVHeader:YES]; + + [exportFiles addObject:singleExportFile]; + + singleFileHandleSet = YES; + } + + [csvExporter setExportOutputFile:singleExportFile]; + } + + [exporters addObject:csvExporter]; + } + } + else { + csvExporter = [self initializeCSVExporterForTable:nil orDataArray:dataArray]; + + [exportFiles addObject:singleExportFile]; + + [csvExporter setExportOutputFile:singleExportFile]; + + [exporters addObject:csvExporter]; + } + } + // SQL export + else if (exportType == SPSQLExport) { + + // Cache the number of tables being exported + exportTableCount = [exportTables count]; + + SPSQLExporter *sqlExporter = [[SPSQLExporter alloc] initWithDelegate:self]; + + [sqlExporter setSqlDatabaseHost:[tableDocumentInstance host]]; + [sqlExporter setSqlDatabaseName:[tableDocumentInstance database]]; + [sqlExporter setSqlDatabaseVersion:[tableDocumentInstance mySQLVersion]]; + + [sqlExporter setSqlOutputIncludeUTF8BOM:[exportUseUTF8BOMButton state]]; + [sqlExporter setSqlOutputEncodeBLOBasHex:[exportSQLBLOBFieldsAsHexCheck state]]; + [sqlExporter setSqlOutputIncludeErrors:[exportSQLIncludeErrorsCheck state]]; + [sqlExporter setSqlOutputIncludeAutoIncrement:([exportSQLIncludeStructureCheck state] && [exportSQLIncludeAutoIncrementValueButton state])]; + + [sqlExporter setSqlInsertAfterNValue:[exportSQLInsertNValueTextField integerValue]]; + [sqlExporter setSqlInsertDivider:[exportSQLInsertDividerPopUpButton indexOfSelectedItem]]; + + [sqlExporter setSqlExportTables:exportTables]; + + // Create custom filename if required + NSString *selectedTableName = (exportSource == SPTableExport && [exportTables count] == 1)? [[exportTables objectAtIndex:0] objectAtIndex:0] : nil; + [exportFilename setString:(createCustomFilename) ? [self expandCustomFilenameFormatUsingTableName:selectedTableName] : [self generateDefaultExportFilename]]; + + // Only append the extension if necessary + if (![[exportFilename pathExtension] length]) { + [exportFilename setString:[exportFilename stringByAppendingPathExtension:[self currentDefaultExportFileExtension]]]; + } + + file = [SPExportFile exportFileAtPath:[[exportPathField stringValue] stringByAppendingPathComponent:exportFilename]]; + + [exportFiles addObject:file]; + + [sqlExporter setExportOutputFile:file]; + + [exporters addObject:sqlExporter]; + + [sqlExporter release]; + } + // XML export + else if (exportType == SPXMLExport) { + + SPXMLExporter *xmlExporter = nil; + + // If the user has selected to only export to a single file or this is a filtered or custom query + // export, create the single file now and assign it to all subsequently created exporters. + if ((![self exportToMultipleFiles]) || (exportSource == SPFilteredExport) || (exportSource == SPQueryExport)) { + NSString *selectedTableName = nil; + if (exportSource == SPTableExport && [exportTables count] == 1) selectedTableName = [exportTables objectAtIndex:0]; + + [exportFilename setString:(createCustomFilename) ? [self expandCustomFilenameFormatUsingTableName:selectedTableName] : [self generateDefaultExportFilename]]; + + // Only append the extension if necessary + if (![[exportFilename pathExtension] length]) { + [exportFilename setString:[exportFilename stringByAppendingPathExtension:[self currentDefaultExportFileExtension]]]; + } + + singleExportFile = [SPExportFile exportFileAtPath:[[exportPathField stringValue] stringByAppendingPathComponent:exportFilename]]; + } + + // Start the export process depending on the data source + if (exportSource == SPTableExport) { + + // Cache the number of tables being exported + exportTableCount = [exportTables count]; + + // Loop through the tables, creating an exporter for each + for (NSString *table in exportTables) + { + xmlExporter = [self initializeXMLExporterForTable:table orDataArray:nil]; + + // If required create a single file handle for all XML exports + if (![self exportToMultipleFiles]) { + if (!singleFileHandleSet) { + [singleExportFile setExportFileNeedsXMLHeader:YES]; + + [exportFiles addObject:singleExportFile]; + + singleFileHandleSet = YES; + } + + [xmlExporter setExportOutputFile:singleExportFile]; + } + + [exporters addObject:xmlExporter]; + } + } + else { + xmlExporter = [self initializeXMLExporterForTable:nil orDataArray:dataArray]; + + [singleExportFile setExportFileNeedsXMLHeader:YES]; + + [exportFiles addObject:singleExportFile]; + + [xmlExporter setExportOutputFile:singleExportFile]; + + [exporters addObject:xmlExporter]; + } + } + // Dot export + else if (exportType == SPDotExport) { + + // Cache the number of tables being exported + exportTableCount = [exportTables count]; + + SPDotExporter *dotExporter = [[SPDotExporter alloc] initWithDelegate:self]; + + [dotExporter setDotTableData:tableDataInstance]; + [dotExporter setDotForceLowerTableNames:[exportDotForceLowerTableNamesCheck state]]; + [dotExporter setDotDatabaseHost:[tableDocumentInstance host]]; + [dotExporter setDotDatabaseName:[tableDocumentInstance database]]; + [dotExporter setDotDatabaseVersion:[tableDocumentInstance mySQLVersion]]; + + [dotExporter setDotExportTables:exportTables]; + + // Create custom filename if required + if (createCustomFilename) { + [exportFilename setString:[self expandCustomFilenameFormatUsingTableName:nil]]; + } + else { + [exportFilename setString:[tableDocumentInstance database]]; + } + + // Only append the extension if necessary + if (![[exportFilename pathExtension] length]) { + [exportFilename setString:[exportFilename stringByAppendingPathExtension:[self currentDefaultExportFileExtension]]]; + } + + file = [SPExportFile exportFileAtPath:[[exportPathField stringValue] stringByAppendingPathComponent:exportFilename]]; + + [exportFiles addObject:file]; + + [dotExporter setExportOutputFile:file]; + + [exporters addObject:dotExporter]; + + [dotExporter release]; + } + + // For each of the created exporters, set their generic properties + for (SPExporter *exporter in exporters) + { + [exporter setConnection:connection]; + [exporter setServerSupport:[self serverSupport]]; + [exporter setExportOutputEncoding:[connection stringEncoding]]; + [exporter setExportMaxProgress:(NSInteger)[exportProgressIndicator bounds].size.width]; + [exporter setExportUsingLowMemoryBlockingStreaming:([exportProcessLowMemoryButton state] == NSOnState)]; + [exporter setExportOutputCompressionFormat:(SPFileCompressionFormat)[exportOutputCompressionFormatPopupButton indexOfSelectedItem]]; + [exporter setExportOutputCompressFile:([exportOutputCompressionFormatPopupButton indexOfSelectedItem] != SPNoCompression)]; + } + + NSMutableArray *problemFiles = [[NSMutableArray alloc] init]; + + // Create the actual file handles while dealing with errors (e.g. file already exists, etc) during creation + for (SPExportFile *exportFile in exportFiles) + { + if ([exportFile createExportFileHandle:NO] == SPExportFileHandleCreated) { + + [exportFile setCompressionFormat:(SPFileCompressionFormat)[exportOutputCompressionFormatPopupButton indexOfSelectedItem]]; + + if ([exportFile exportFileNeedsCSVHeader]) { + [self writeCSVHeaderToExportFile:exportFile]; + } + else if ([exportFile exportFileNeedsXMLHeader]) { + [self writeXMLHeaderToExportFile:exportFile]; + } + } + else { + [problemFiles addObject:exportFile]; + } + } + + // Deal with any file handles that we failed to create for whatever reason + if ([problemFiles count] > 0) { + [self errorCreatingExportFileHandles:problemFiles]; + } + else { + [self startExport]; + } + + [problemFiles release]; +} + +/** + * Initialises a CSV exporter for the supplied table name or data array. + * + * @param table The table name for which the exporter should be cerated for (can be nil). + * @param dataArray The MySQL result data array for which the exporter should be created for (can be nil). + */ +- (SPCSVExporter *)initializeCSVExporterForTable:(NSString *)table orDataArray:(NSArray *)dataArray +{ + SPCSVExporter *csvExporter = [[SPCSVExporter alloc] initWithDelegate:self]; + + // Depeding on the export source, set the table name or data array + if (exportSource == SPTableExport) { + [csvExporter setCsvTableName:table]; + } + else { + [csvExporter setCsvDataArray:dataArray]; + } + + [csvExporter setCsvTableData:tableDataInstance]; + [csvExporter setCsvOutputFieldNames:[exportCSVIncludeFieldNamesCheck state]]; + [csvExporter setCsvFieldSeparatorString:[exportCSVFieldsTerminatedField stringValue]]; + [csvExporter setCsvEnclosingCharacterString:[exportCSVFieldsWrappedField stringValue]]; + [csvExporter setCsvLineEndingString:[exportCSVLinesTerminatedField stringValue]]; + [csvExporter setCsvEscapeString:[exportCSVFieldsEscapedField stringValue]]; + [csvExporter setCsvNULLString:[exportCSVNULLValuesAsTextField stringValue]]; + + // If required create separate files + if (exportSource == SPTableExport && [self exportToMultipleFiles]) { + + if (createCustomFilename) { + + // Create custom filename based on the selected format + [exportFilename setString:[self expandCustomFilenameFormatUsingTableName:table]]; + + // If the user chose to use a custom filename format and we exporting to multiple files, make + // sure the table name is included to ensure the output files are unique. + if (exportTableCount > 1) { + BOOL tableNameInTokens = NO; + NSArray *representedObjects = [exportCustomFilenameTokenField objectValue]; + for (id representedObject in representedObjects) { + if ([representedObject isKindOfClass:[SPExportFileNameTokenObject class]] && [[representedObject tokenId] isEqualToString:SPFileNameTableTokenName]) tableNameInTokens = YES; + } + [exportFilename setString:(tableNameInTokens ? exportFilename : [exportFilename stringByAppendingFormat:@"_%@", table])]; + } + } + else { + [exportFilename setString:(dataArray) ? [tableDocumentInstance database] : table]; + } + + // Only append the extension if necessary + if (![[exportFilename pathExtension] length]) { + [exportFilename setString:[exportFilename stringByAppendingPathExtension:[self currentDefaultExportFileExtension]]]; + } + + SPExportFile *file = [SPExportFile exportFileAtPath:[[exportPathField stringValue] stringByAppendingPathComponent:exportFilename]]; + + [exportFiles addObject:file]; + + [csvExporter setExportOutputFile:file]; + } + + return [csvExporter autorelease]; +} + +/** + * Initialises a XML exporter for the supplied table name or data array. + * + * @param table The table name for which the exporter should be cerated for (can be nil). + * @param dataArray The MySQL result data array for which the exporter should be created for (can be nil). + */ +- (SPXMLExporter *)initializeXMLExporterForTable:(NSString *)table orDataArray:(NSArray *)dataArray +{ + SPXMLExporter *xmlExporter = [[SPXMLExporter alloc] initWithDelegate:self]; + + // if required set the data array + if (exportSource != SPTableExport) { + [xmlExporter setXmlDataArray:dataArray]; + } + + // Regardless of the export source, set exporter's table name as it's used in the output + // of table and table content exports. + [xmlExporter setXmlTableName:table]; + + [xmlExporter setXmlFormat:[exportXMLFormatPopUpButton indexOfSelectedItem]]; + [xmlExporter setXmlOutputIncludeStructure:[exportXMLIncludeStructure state]]; + [xmlExporter setXmlOutputIncludeContent:[exportXMLIncludeContent state]]; + [xmlExporter setXmlNULLString:[exportXMLNULLValuesAsTextField stringValue]]; + + // If required create separate files + if ((exportSource == SPTableExport) && exportToMultipleFiles && (exportTableCount > 0)) { + + if (createCustomFilename) { + + // Create custom filename based on the selected format + [exportFilename setString:[self expandCustomFilenameFormatUsingTableName:table]]; + + // If the user chose to use a custom filename format and we exporting to multiple files, make + // sure the table name is included to ensure the output files are unique. + if (exportTableCount > 1) { + BOOL tableNameInTokens = NO; + NSArray *representedObjects = [exportCustomFilenameTokenField objectValue]; + for (id representedObject in representedObjects) { + if ([representedObject isKindOfClass:[SPExportFileNameTokenObject class]] && [[representedObject tokenId] isEqualToString:SPFileNameTableTokenName]) tableNameInTokens = YES; + } + [exportFilename setString:(tableNameInTokens ? exportFilename : [exportFilename stringByAppendingFormat:@"_%@", table])]; + } + } + else { + [exportFilename setString:(dataArray) ? [tableDocumentInstance database] : table]; + } + + // Only append the extension if necessary + if (![[exportFilename pathExtension] length]) { + [exportFilename setString:[exportFilename stringByAppendingPathExtension:[self currentDefaultExportFileExtension]]]; + } + + SPExportFile *file = [SPExportFile exportFileAtPath:[[exportPathField stringValue] stringByAppendingPathComponent:exportFilename]]; + + [file setExportFileNeedsXMLHeader:YES]; + + [exportFiles addObject:file]; + + [xmlExporter setExportOutputFile:file]; + } + + return [xmlExporter autorelease]; +} + +#pragma mark - SPExportFileUtilitiesPrivateAPI + +/** + * Writes the CSV file header to the supplied export file. + * + * @param file The export file to write the header to. + */ +- (void)writeCSVHeaderToExportFile:(SPExportFile *)file +{ + NSMutableString *lineEnding = [NSMutableString stringWithString:[exportCSVLinesTerminatedField stringValue]]; + + // Escape tabs, line endings and carriage returns + [lineEnding replaceOccurrencesOfString:@"\\t" withString:@"\t" + options:NSLiteralSearch + range:NSMakeRange(0, [lineEnding length])]; + + + [lineEnding replaceOccurrencesOfString:@"\\n" withString:@"\n" + options:NSLiteralSearch + range:NSMakeRange(0, [lineEnding length])]; + + [lineEnding replaceOccurrencesOfString:@"\\r" withString:@"\r" + options:NSLiteralSearch + range:NSMakeRange(0, [lineEnding length])]; + + // Write the file header and the first table name + [file writeData:[[NSMutableString stringWithFormat:@"%@: %@ %@: %@ %@: %@%@%@%@ %@%@%@", + NSLocalizedString(@"Host", @"export header host label"), + [tableDocumentInstance host], + NSLocalizedString(@"Database", @"export header database label"), + [tableDocumentInstance database], + NSLocalizedString(@"Generation Time", @"export header generation time label"), + [NSDate date], + lineEnding, + lineEnding, + NSLocalizedString(@"Table", @"csv export table heading"), + [[tables objectAtIndex:0] objectAtIndex:0], + lineEnding, + lineEnding] dataUsingEncoding:[connection stringEncoding]]]; +} + +/** + * Writes the XML file header to the supplied export file. + * + * @param file The export file to write the header to. + */ +- (void)writeXMLHeaderToExportFile:(SPExportFile *)file +{ + NSMutableString *header = [NSMutableString string]; + + [header setString:@"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n\n"]; + [header appendString:@"<!--\n-\n"]; + [header appendString:@"- Sequel Pro XML dump\n"]; + [header appendFormat:@"- %@ %@\n-\n", NSLocalizedString(@"Version", @"export header version label"), [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]]; + [header appendFormat:@"- %@\n- %@\n-\n", SPLOCALIZEDURL_HOMEPAGE, SPDevURL]; + [header appendFormat:@"- %@: %@ (MySQL %@)\n", NSLocalizedString(@"Host", @"export header host label"), [tableDocumentInstance host], [tableDocumentInstance mySQLVersion]]; + [header appendFormat:@"- %@: %@\n", NSLocalizedString(@"Database", @"export header database label"), [tableDocumentInstance database]]; + [header appendFormat:@"- %@ Time: %@\n", NSLocalizedString(@"Generation Time", @"export header generation time label"), [NSDate date]]; + [header appendString:@"-\n-->\n\n"]; + + if ([exportXMLFormatPopUpButton indexOfSelectedItem] == SPXMLExportMySQLFormat) { + + NSString *tag; + + if (exportSource == SPTableExport) { + tag = [NSString stringWithFormat:@"<mysqldump xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n<database name=\"%@\">\n\n", [tableDocumentInstance database]]; + } + else { + tag = [NSString stringWithFormat:@"<resultset statement=\"%@\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n\n", (exportSource == SPFilteredExport) ? [tableContentInstance usedQuery] : [customQueryInstance usedQuery]]; + } + + [header appendString:tag]; + } + else { + [header appendFormat:@"<%@>\n\n", [[tableDocumentInstance database] HTMLEscapeString]]; + } + + [file writeData:[header dataUsingEncoding:NSUTF8StringEncoding]]; +} + +/** + * Indicates that one or more errors occurred while attempting to create the export file handles. Asks the + * user how to proceed. + * + * @param files An array of export files (SPExportFile) that failed to be created. + */ +- (void)errorCreatingExportFileHandles:(NSArray *)files +{ + // We don't know where "files" came from, but we know 2 things: + // - NSAlert will NOT retain it as contextInfo + // - This method continues execution after [alert beginSheet:...], thus even if files was retained before, it could be released before the alert ends + [files retain]; + + // Get the number of files that already exist as well as couldn't be created because of other reasons + NSUInteger filesAlreadyExisting = 0; + NSUInteger parentFoldersMissing = 0; + NSUInteger parentFoldersNotWritable = 0; + NSUInteger filesFailed = 0; + + for (SPExportFile *file in files) + { + if ([file exportFileHandleStatus] == SPExportFileHandleExists) { + filesAlreadyExisting++; + } + // For file handles that we failed to create for some unknown reason, ignore them and remove any + // exporters that are associated with them. + else if ([file exportFileHandleStatus] == SPExportFileHandleFailed) { + + filesFailed++; + + NSMutableArray *exportersToRemove = [[NSMutableArray alloc] init]; + + for (SPExporter *exporter in exporters) + { + if ([[exporter exportOutputFile] isEqualTo:file]) { + [exportersToRemove addObject:exporter]; + } + } + + [exporters removeObjectsInArray:exportersToRemove]; + + [exportersToRemove release]; + + // Check the parent folder to see if it still is present + BOOL parentIsFolder = NO; + if (![[NSFileManager defaultManager] fileExistsAtPath:[[[file exportFilePath] stringByDeletingLastPathComponent] stringByExpandingTildeInPath] isDirectory:&parentIsFolder] || !parentIsFolder) { + parentFoldersMissing++; + } else if (![[NSFileManager defaultManager] isWritableFileAtPath:[[[file exportFilePath] stringByDeletingLastPathComponent] stringByExpandingTildeInPath]]) { + parentFoldersNotWritable++; + } + } + } + + NSAlert *alert = [[NSAlert alloc] init]; + [alert setAlertStyle:NSCriticalAlertStyle]; + + // If files failed because they already existed, show a OS-like dialog. + if (filesAlreadyExisting) { + + // Set up a string for use if files had to be skipped. + NSString *additionalErrors = filesFailed ? NSLocalizedString(@"\n\n(In addition, one or more errors occurred while attempting to create the export files: %lu could not be created. These files will be ignored.)", @"Additional export file errors") : @""; + + if (filesAlreadyExisting == 1) { + [alert setMessageText:[NSString stringWithFormat:NSLocalizedString(@"“%@” already exists. Do you want to replace it?", @"Export file already exists message"), [[[files objectAtIndex:0] exportFilePath] lastPathComponent]]]; + [alert setInformativeText:[NSString stringWithFormat:@"%@%@", NSLocalizedString(@"A file with the same name already exists in the target folder. Replacing it will overwrite its current contents.", @"Export file already exists explanatory text"), additionalErrors]]; + } + else if (filesAlreadyExisting == [exportFiles count]) { + [alert setMessageText:[NSString stringWithFormat:NSLocalizedString(@"All the export files already exist. Do you want to replace them?", @"All export files already exist message")]]; + [alert setInformativeText:[NSString stringWithFormat:@"%@%@", NSLocalizedString(@"Files with the same names already exist in the target folder. Replacing them will overwrite their current contents.", @"All export files already exist explanatory text"), additionalErrors]]; + } + else { + [alert setMessageText:[NSString stringWithFormat:NSLocalizedString(@"%lu files already exist. Do you want to replace them?", @"Export file already exists message"), filesAlreadyExisting]]; + [alert setInformativeText:[NSString stringWithFormat:@"%@%@", [NSString stringWithFormat:NSLocalizedString(@"%lu files with the same names already exist in the target folder. Replacing them will overwrite their current contents.", @"Some export files already exist explanatory text"), filesAlreadyExisting], additionalErrors]]; + } + + [alert addButtonWithTitle:NSLocalizedString(@"Replace", @"Replace button")]; + [[[alert buttons] objectAtIndex:0] setTag:SPExportErrorReplaceFiles]; + [[[alert buttons] objectAtIndex:0] setKeyEquivalent:@"r"]; + [[[alert buttons] objectAtIndex:0] setKeyEquivalentModifierMask:NSCommandKeyMask]; + + [alert addButtonWithTitle:NSLocalizedString(@"Cancel", @"cancel button")]; + [[[alert buttons] objectAtIndex:1] setTag:SPExportErrorCancelExport]; + [[[alert buttons] objectAtIndex:1] setKeyEquivalent:@"\r"]; + + if ((filesAlreadyExisting + filesFailed) != [exportFiles count]) { + [alert addButtonWithTitle:NSLocalizedString(@"Skip existing", @"skip existing button")]; + [[[alert buttons] objectAtIndex:2] setTag:SPExportErrorSkipErrorFiles]; + [[[alert buttons] objectAtIndex:2] setKeyEquivalent:@"s"]; + [[[alert buttons] objectAtIndex:2] setKeyEquivalentModifierMask:NSCommandKeyMask]; + } + } + // If one or multiple files failed, but only due to unhandled errors, show a short dialog + else { + if (filesFailed == 1) { + [alert setMessageText:[NSString stringWithFormat:NSLocalizedString(@"“%@” could not be created", @"Export file creation error title"), [[[files objectAtIndex:0] exportFilePath] lastPathComponent]]]; + if (parentFoldersMissing) { + [alert setInformativeText:NSLocalizedString(@"The target export folder no longer exists. Please select a new export location and try again.", @"Export folder missing explanatory text")]; + } else if (parentFoldersNotWritable) { + [alert setInformativeText:NSLocalizedString(@"The target export folder is not writable. Please select a new export location and try again.", @"Export folder not writable explanatory text")]; + } else { + [alert setInformativeText:NSLocalizedString(@"An unhandled error occurred when attempting to create the export file. Please check the details and try again.", @"Export file creation error explanatory text")]; + } + } + else if (filesFailed == [exportFiles count]) { + [alert setMessageText:[NSString stringWithFormat:NSLocalizedString(@"No files could be created", @"All export files creation error title")]]; + if (parentFoldersMissing == [exportFiles count]) { + [alert setInformativeText:NSLocalizedString(@"The target export folder no longer exists. Please select a new export location and try again.", @"Export folder missing explanatory text")]; + } else if (parentFoldersMissing) { + [alert setInformativeText:NSLocalizedString(@"Some of the target export folders no longer exist. Please select a new export location and try again.", @"Some export folders missing explanatory text")]; + } else if (parentFoldersNotWritable) { + [alert setInformativeText:NSLocalizedString(@"Some of the target export folders are not writable. Please select a new export location and try again.", @"Some export folders not writable explanatory text")]; + } else { + [alert setInformativeText:NSLocalizedString(@"An unhandled error occurred when attempting to create each of the export files. Please check the details and try again.", @"All export files creation error explanatory text")]; + } + } + else { + [alert setMessageText:[NSString stringWithFormat:NSLocalizedString(@"%lu files could not be created", @"Export files creation error title"), filesFailed]]; + if (parentFoldersMissing) { + [alert setInformativeText:[NSString stringWithFormat:NSLocalizedString(@"%lu of the export files could not be created because their target export folder no longer exists; please select a new export location and try again.", @"Export folder missing for some files explanatory text"), parentFoldersMissing]]; + } else if (parentFoldersNotWritable) { + [alert setInformativeText:[NSString stringWithFormat:NSLocalizedString(@"%lu of the export files could not be created because their target export folder is not writable; please select a new export location and try again.", @"Export folder not writable for some files explanatory text"), parentFoldersNotWritable]]; + } else { + [alert setInformativeText:[NSString stringWithFormat:NSLocalizedString(@"An unhandled error occurred when attempting to create %lu of the export files. Please check the details and try again.", @"Export files creation error explanatory text"), filesFailed]]; + } + } + + [alert addButtonWithTitle:NSLocalizedString(@"Cancel", @"cancel button")]; + [[[alert buttons] objectAtIndex:0] setTag:SPExportErrorCancelExport]; + + if (filesFailed != [exportFiles count]) { + [alert addButtonWithTitle:NSLocalizedString(@"Skip problems", @"skip problems button")]; + [[[alert buttons] objectAtIndex:1] setTag:SPExportErrorSkipErrorFiles]; + [[[alert buttons] objectAtIndex:1] setKeyEquivalent:@"s"]; + [[[alert buttons] objectAtIndex:1] setKeyEquivalentModifierMask:NSCommandKeyMask]; + } + } + + [self _hideExportProgress]; + + [alert beginSheetModalForWindow:[tableDocumentInstance parentWindow] modalDelegate:self didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:files]; + [alert autorelease]; +} + +/** + * NSAlert didEnd method. + */ +- (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo +{ + NSArray *files = (NSArray *)contextInfo; + + // Ignore the files that exist and remove the associated exporters + if (returnCode == SPExportErrorSkipErrorFiles) { + + for (SPExportFile *file in files) { + + // Use a numerically controlled loop to avoid mutating the collection while enumerating + NSUInteger i; + for (i = 0; i < [exporters count]; i++) { + SPExporter *exporter = [exporters objectAtIndex:i]; + if ([[exporter exportOutputFile] isEqualTo:file]) { + [exporters removeObjectAtIndex:i]; + i--; + } + } + } + + [files release]; + + // If we're now left with no exporters, cancel the export operation + if ([exporters count] == 0) { + [exportFiles removeAllObjects]; + + // Trigger restoration of the export interface + [self performSelector:@selector(_reopenExportSheet) withObject:nil afterDelay:0.1]; + } + else { + // Start the export after a short delay to give this sheet a chance to close + [self performSelector:@selector(startExport) withObject:nil afterDelay:0.1]; + } + } + // Overwrite the files and continue + else if (returnCode == SPExportErrorReplaceFiles) { + + for (SPExportFile *file in files) + { + if ([file exportFileHandleStatus] == SPExportFileHandleExists) { + + if ([file createExportFileHandle:YES] == SPExportFileHandleCreated) { + [file setCompressionFormat:(SPFileCompressionFormat)[exportOutputCompressionFormatPopupButton indexOfSelectedItem]]; + + if ([file exportFileNeedsCSVHeader]) { + [self writeCSVHeaderToExportFile:file]; + } + else if ([file exportFileNeedsXMLHeader]) { + [self writeXMLHeaderToExportFile:file]; + } + } + } + } + + [files release]; + + // Start the export after a short delay to give this sheet a chance to close + [self performSelector:@selector(startExport) withObject:nil afterDelay:0.1]; + + } + // Cancel the entire export operation + else if (returnCode == SPExportErrorCancelExport) { + + // Loop the cached export files and remove those we've already created + for (SPExportFile *file in exportFiles) + { + [file delete]; + } + + [files release]; + + // Finally get rid of all the exporters and files + [exportFiles removeAllObjects]; + [exporters removeAllObjects]; + + // Trigger restoration of the export interface + [self performSelector:@selector(_reopenExportSheet) withObject:nil afterDelay:0.1]; + } +} + +/** + * Re-open the export sheet without resetting the interface - for use on error. + */ +- (void)_reopenExportSheet +{ + [NSApp beginSheet:[self window] + modalForWindow:[tableDocumentInstance parentWindow] + modalDelegate:self + didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) + contextInfo:nil]; +} + +#pragma mark - SPExportFilenameUtilities + +/** + * Updates the displayed export filename, either custom or default. + */ +- (void)updateDisplayedExportFilename +{ + NSString *filename = @""; + + if ([[exportCustomFilenameTokenField stringValue] length] > 0) { + + // Get the current export file extension + NSString *extension = [self currentDefaultExportFileExtension]; + + //note that there will be no tableName if the export is done from a query result without a database selected (or empty). + filename = [self expandCustomFilenameFormatUsingTableName:[[tablesListInstance tables] objectOrNilAtIndex:1]]; + + + if (![[self customFilenamePathExtension] length] && [extension length] > 0) filename = [filename stringByAppendingPathExtension:extension]; + } + else { + filename = [self generateDefaultExportFilename]; + } + + [exportCustomFilenameViewLabelButton setTitle:[NSString stringWithFormat:NSLocalizedString(@"Customize Filename (%@)", @"customize file name label"), filename]]; +} + +- (NSString *)customFilenamePathExtension +{ + NSMutableString *flatted = [NSMutableString string]; + + // This time we replace every token with "/a". This has the following effect: + // "{host}.{database}" -> "/a./a" -> extension="" + // "{database}_{date}.sql" -> "/a_/a.sql" -> extension="sql" + // That seems to be the easiest way to let NSString correctly determine if an extension is present + for (id filenamePart in [exportCustomFilenameTokenField objectValue]) + { + if([filenamePart isKindOfClass:[NSString class]]) + [flatted appendString:filenamePart]; + else if([filenamePart isKindOfClass:[SPExportFileNameTokenObject class]]) + [flatted appendString:@"/a"]; + else + [NSException raise:NSInternalInconsistencyException format:@"unknown object in token list: %@",filenamePart]; + } + + return [flatted pathExtension]; +} + +- (BOOL)isTableTokenAllowed +{ + NSUInteger i = 0; + BOOL removeTable = NO; + + BOOL isSQL = exportType == SPSQLExport; + BOOL isCSV = exportType == SPCSVExport; + BOOL isDot = exportType == SPDotExport; + BOOL isXML = exportType == SPXMLExport; + + // Determine whether to remove the table from the tokens list + if (exportSource == SPQueryExport || isDot) { + removeTable = YES; + } + else if (isSQL || isCSV || isXML) { + for (NSArray *table in tables) + { + if ([NSArrayObjectAtIndex(table, 2) boolValue]) { + i++; + if (i == 2) break; + } + } + + if (i > 1) { + removeTable = isSQL ? YES : ![exportFilePerTableCheck state]; + } + } + + return (removeTable == NO); +} + +/** + * Updates the available export filename tokens based on the currently selected options. + */ +- (void)updateAvailableExportFilenameTokens +{ + SPExportFileNameTokenObject *tableObject; + NSMutableArray *exportTokens = [NSMutableArray arrayWithObjects: + [SPExportFileNameTokenObject tokenWithId:SPFileNameDatabaseTokenName], + [SPExportFileNameTokenObject tokenWithId:SPFileNameHostTokenName], + [SPExportFileNameTokenObject tokenWithId:SPFileNameDateTokenName], + [SPExportFileNameTokenObject tokenWithId:SPFileNameYearTokenName], + [SPExportFileNameTokenObject tokenWithId:SPFileNameMonthTokenName], + [SPExportFileNameTokenObject tokenWithId:SPFileNameDayTokenName], + [SPExportFileNameTokenObject tokenWithId:SPFileNameTimeTokenName], + [SPExportFileNameTokenObject tokenWithId:SPFileName24HourTimeTokenName], + [SPExportFileNameTokenObject tokenWithId:SPFileNameFavoriteTokenName], + (tableObject = [SPExportFileNameTokenObject tokenWithId:SPFileNameTableTokenName]), + nil + ]; + + if (![self isTableTokenAllowed]) { + [exportTokens removeObject:tableObject]; + NSArray *tokenParts = [exportCustomFilenameTokenField objectValue]; + + for (id token in tokenParts) + { + if([token isEqual:tableObject]) { + NSMutableArray *newTokens = [NSMutableArray arrayWithArray:tokenParts]; + + [newTokens removeObject:tableObject]; //removes all occurances + + [exportCustomFilenameTokenField setObjectValue:newTokens]; + break; + } + } + } + + [exportCustomFilenameTokenPool setObjectValue:exportTokens]; + //update preview name as programmatically changing the exportCustomFilenameTokenField does not fire a notification + [self updateDisplayedExportFilename]; +} + +- (NSArray *)currentAllowedExportFilenameTokens +{ + NSArray *mixed = [exportCustomFilenameTokenPool objectValue]; + NSMutableArray *tokens = [NSMutableArray arrayWithCapacity:[mixed count]]; // ...or less + + for (id obj in mixed) { + if([obj isKindOfClass:[SPExportFileNameTokenObject class]]) [tokens addObject:obj]; + } + + return tokens; +} + +/** + * Generates the default export filename based on the selected export options. + * + * @return The default filename. + */ +- (NSString *)generateDefaultExportFilename +{ + NSString *filename = @""; + NSString *extension = [self currentDefaultExportFileExtension]; + + // Determine what the file name should be + switch (exportSource) + { + case SPFilteredExport: + filename = [NSString stringWithFormat:@"%@_view", [tableDocumentInstance table]]; + break; + case SPQueryExport: + filename = @"query_result"; + break; + case SPTableExport: + filename = [NSString stringWithFormat:@"%@_%@", [tableDocumentInstance database], [[NSDate date] descriptionWithCalendarFormat:@"%Y-%m-%d" timeZone:nil locale:nil]]; + break; + } + + return ([extension length] > 0) ? [filename stringByAppendingPathExtension:extension] : filename; +} + +/** + * Returns the current default export file extension based on the selected export type. + * + * @return The default filename extension. + */ +- (NSString *)currentDefaultExportFileExtension +{ + NSString *extension = @""; + + switch (exportType) { + case SPSQLExport: + extension = SPFileExtensionSQL; + break; + case SPCSVExport: + // If the tab character (\t) is selected as the feild separator return the extension as .tsv + extension = ([exportCSVFieldsTerminatedField indexOfSelectedItem] == 2) ? @"tsv" : @"csv"; + break; + case SPXMLExport: + extension = @"xml"; + break; + case SPDotExport: + extension = @"dot"; + break; + case SPPDFExport: + case SPHTMLExport: + case SPExcelExport: + default: + [NSException raise:NSInvalidArgumentException format:@"unsupported exportType=%lu",exportType]; + return nil; + } + + if ([exportOutputCompressionFormatPopupButton indexOfSelectedItem] != SPNoCompression) { + + SPFileCompressionFormat compressionFormat = (SPFileCompressionFormat)[exportOutputCompressionFormatPopupButton indexOfSelectedItem]; + + if ([extension length] > 0) { + extension = [extension stringByAppendingPathExtension:(compressionFormat == SPGzipCompression) ? @"gz" : @"bz2"]; + } + else { + extension = (compressionFormat == SPGzipCompression) ? @"gz" : @"bz2"; + } + } + + return extension; +} + +/** + * Expands the custom filename format based on the selected tokens. + * Uses the current custom filename field as a data source. + * + * @param table A table name to be used within the expanded filename. + * Can be nil. + * + * @return The expanded filename. + */ +- (NSString *)expandCustomFilenameFormatUsingTableName:(NSString *)table +{ + NSMutableString *string = [NSMutableString string]; + + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + [dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; + + // Walk through the token field, appending token replacements or strings + NSArray *representedFilenameParts = [exportCustomFilenameTokenField objectValue]; + + for (id filenamePart in representedFilenameParts) + { + if ([filenamePart isKindOfClass:[SPExportFileNameTokenObject class]]) { + NSString *tokenContent = [filenamePart tokenId]; + + if ([tokenContent isEqualToString:SPFileNameHostTokenName]) { + [string appendStringOrNil:[tableDocumentInstance host]]; + + } + else if ([tokenContent isEqualToString:SPFileNameDatabaseTokenName]) { + [string appendStringOrNil:[tableDocumentInstance database]]; + + } + else if ([tokenContent isEqualToString:SPFileNameTableTokenName]) { + [string appendStringOrNil:table]; + } + else if ([tokenContent isEqualToString:SPFileNameDateTokenName]) { + [dateFormatter setDateStyle:NSDateFormatterShortStyle]; + [dateFormatter setTimeStyle:NSDateFormatterNoStyle]; + [string appendString:[dateFormatter stringFromDate:[NSDate date]]]; + + } + else if ([tokenContent isEqualToString:SPFileNameYearTokenName]) { + [string appendString:[[NSDate date] descriptionWithCalendarFormat:@"%Y" timeZone:nil locale:nil]]; + + } + else if ([tokenContent isEqualToString:SPFileNameMonthTokenName]) { + [string appendString:[[NSDate date] descriptionWithCalendarFormat:@"%m" timeZone:nil locale:nil]]; + + } + else if ([tokenContent isEqualToString:SPFileNameDayTokenName]) { + [string appendString:[[NSDate date] descriptionWithCalendarFormat:@"%d" timeZone:nil locale:nil]]; + + } + else if ([tokenContent isEqualToString:SPFileNameTimeTokenName]) { + [dateFormatter setDateStyle:NSDateFormatterNoStyle]; + [dateFormatter setTimeStyle:NSDateFormatterShortStyle]; + [string appendString:[dateFormatter stringFromDate:[NSDate date]]]; + } + else if ([tokenContent isEqualToString:SPFileName24HourTimeTokenName]) { + [string appendString:[[NSDate date] descriptionWithCalendarFormat:@"%H:%M:%S" timeZone:nil locale:nil]]; + } + else if ([tokenContent isEqualToString:SPFileNameFavoriteTokenName]) { + [string appendStringOrNil:[tableDocumentInstance name]]; + } + } + else { + [string appendString:filenamePart]; + } + } + + // Replace colons with hyphens + [string replaceOccurrencesOfString:@":" + withString:@"-" + options:NSLiteralSearch + range:NSMakeRange(0, [string length])]; + + // Replace forward slashes with hyphens + [string replaceOccurrencesOfString:@"/" + withString:@"-" + options:NSLiteralSearch + range:NSMakeRange(0, [string length])]; + + [dateFormatter release]; + + // Don't allow empty strings - if an empty string resulted, revert to the default string + if (![string length]) [string setString:[self generateDefaultExportFilename]]; + + return string; +} + +#pragma mark - SPExportInterfaceController + +/** + * Resizes the export window's height by the supplied delta, while retaining the position of + * all interface controls to accommodate the custom filename view. + * + * @param delta The height delta for which the height should be adjusted for. + */ +- (void)_resizeWindowForCustomFilenameViewByHeightDelta:(NSInteger)delta +{ + NSUInteger popUpMask = [exportInputPopUpButton autoresizingMask]; + NSUInteger fileCheckMask = [exportFilePerTableCheck autoresizingMask]; + NSUInteger scrollMask = [exportTablelistScrollView autoresizingMask]; + NSUInteger buttonBarMask = [exportTableListButtonBar autoresizingMask]; + NSUInteger buttonMask = [exportCustomFilenameViewButton autoresizingMask]; + NSUInteger textFieldMask = [exportCustomFilenameViewLabelButton autoresizingMask]; + NSUInteger customFilenameViewMask = [exportCustomFilenameView autoresizingMask]; + NSUInteger tabBarMask = [exportOptionsTabBar autoresizingMask]; + + NSRect frame = [[self window] frame]; + + if (frame.size.height > 600 && delta > heightOffset1) { + frame.origin.y += [exportCustomFilenameView frame].size.height; + frame.size.height -= [exportCustomFilenameView frame].size.height; + + [[self window] setFrame:frame display:YES animate:YES]; + } + + [exportInputPopUpButton setAutoresizingMask:NSViewNotSizable | NSViewMaxYMargin]; + [exportFilePerTableCheck setAutoresizingMask:NSViewNotSizable | NSViewMaxYMargin]; + [exportTablelistScrollView setAutoresizingMask:NSViewNotSizable | NSViewMaxYMargin]; + [exportTableListButtonBar setAutoresizingMask:NSViewNotSizable | NSViewMaxYMargin]; + [exportOptionsTabBar setAutoresizingMask:NSViewNotSizable | NSViewMaxYMargin]; + [exportCustomFilenameViewButton setAutoresizingMask:NSViewNotSizable | NSViewMinYMargin]; + [exportCustomFilenameViewLabelButton setAutoresizingMask:NSViewNotSizable | NSViewMinYMargin]; + [exportCustomFilenameView setAutoresizingMask:NSViewNotSizable | NSViewMinYMargin]; + + NSInteger newMinHeight = (windowMinHeigth - heightOffset1 + delta < windowMinHeigth) ? windowMinHeigth : windowMinHeigth - heightOffset1 + delta; + + [[self window] setMinSize:NSMakeSize(windowMinWidth, newMinHeight)]; + + frame.origin.y += heightOffset1; + frame.size.height -= heightOffset1; + + heightOffset1 = delta; + + frame.origin.y -= heightOffset1; + frame.size.height += heightOffset1; + + [[self window] setFrame:frame display:YES animate:YES]; + + [exportInputPopUpButton setAutoresizingMask:popUpMask]; + [exportFilePerTableCheck setAutoresizingMask:fileCheckMask]; + [exportTablelistScrollView setAutoresizingMask:scrollMask]; + [exportTableListButtonBar setAutoresizingMask:buttonBarMask]; + [exportCustomFilenameViewButton setAutoresizingMask:buttonMask]; + [exportCustomFilenameViewLabelButton setAutoresizingMask:textFieldMask]; + [exportCustomFilenameView setAutoresizingMask:customFilenameViewMask]; + [exportOptionsTabBar setAutoresizingMask:tabBarMask]; +} + +/** + * Resizes the export window's height by the supplied delta, while retaining the position of + * all interface controls to accommodate the advanced options view. + * + * @param delta The height delta for which the height should be adjusted for. + */ +- (void)_resizeWindowForAdvancedOptionsViewByHeightDelta:(NSInteger)delta +{ + NSUInteger scrollMask = [exportTablelistScrollView autoresizingMask]; + NSUInteger buttonBarMask = [exportTableListButtonBar autoresizingMask]; + NSUInteger tabBarMask = [exportTypeTabBar autoresizingMask]; + NSUInteger optionsTabBarMask = [exportOptionsTabBar autoresizingMask]; + NSUInteger buttonMask = [exportAdvancedOptionsViewButton autoresizingMask]; + NSUInteger textFieldMask = [exportAdvancedOptionsViewLabelButton autoresizingMask]; + NSUInteger advancedViewMask = [exportAdvancedOptionsView autoresizingMask]; + + NSRect frame = [[self window] frame]; + + if (frame.size.height > 600 && delta > heightOffset2) { + frame.origin.y += [exportAdvancedOptionsView frame].size.height; + frame.size.height -= [exportAdvancedOptionsView frame].size.height; + + [[self window] setFrame:frame display:YES animate:YES]; + } + + [exportTablelistScrollView setAutoresizingMask:NSViewNotSizable | NSViewMinYMargin]; + [exportTableListButtonBar setAutoresizingMask:NSViewNotSizable | NSViewMinYMargin]; + [exportTypeTabBar setAutoresizingMask:NSViewNotSizable | NSViewMinYMargin]; + [exportOptionsTabBar setAutoresizingMask:NSViewNotSizable | NSViewMinYMargin]; + [exportAdvancedOptionsViewButton setAutoresizingMask:NSViewNotSizable | NSViewMinYMargin]; + [exportAdvancedOptionsViewLabelButton setAutoresizingMask:NSViewNotSizable | NSViewMinYMargin]; + [exportAdvancedOptionsView setAutoresizingMask:NSViewNotSizable | NSViewMinYMargin]; + + NSInteger newMinHeight = (windowMinHeigth - heightOffset2 + delta < windowMinHeigth) ? windowMinHeigth : windowMinHeigth - heightOffset2 + delta; + + [[self window] setMinSize:NSMakeSize(windowMinWidth, newMinHeight)]; + + frame.origin.y += heightOffset2; + frame.size.height -= heightOffset2; + + heightOffset2 = delta; + + frame.origin.y -= heightOffset2; + frame.size.height += heightOffset2; + + [[self window] setFrame:frame display:YES animate:YES]; + + [exportTablelistScrollView setAutoresizingMask:scrollMask]; + [exportTableListButtonBar setAutoresizingMask:buttonBarMask]; + [exportTypeTabBar setAutoresizingMask:tabBarMask]; + [exportOptionsTabBar setAutoresizingMask:optionsTabBarMask]; + [exportAdvancedOptionsViewButton setAutoresizingMask:buttonMask]; + [exportAdvancedOptionsViewLabelButton setAutoresizingMask:textFieldMask]; + [exportAdvancedOptionsView setAutoresizingMask:advancedViewMask]; +} + +#pragma mark - SPExportControllerDelegate + +#pragma mark Table view datasource methods + +- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView; +{ + return [tables count]; +} + +- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex +{ + return NSArrayObjectAtIndex([tables objectAtIndex:rowIndex], [exportTableList columnWithIdentifier:[tableColumn identifier]]); +} + +- (void)tableView:(NSTableView *)tableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex +{ + [[tables objectAtIndex:rowIndex] replaceObjectAtIndex:[exportTableList columnWithIdentifier:[tableColumn identifier]] withObject:anObject]; + + [self updateAvailableExportFilenameTokens]; + [self _toggleExportButtonOnBackgroundThread]; + [self _updateExportFormatInformation]; +} + +#pragma mark - +#pragma mark Table view delegate methods + +- (BOOL)tableView:(NSTableView *)tableView shouldTrackCell:(NSCell *)cell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex +{ + return (tableView == exportTableList); +} + +- (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex +{ + [cell setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; +} + +#pragma mark - +#pragma mark Tabview delegate methods + +- (void)tabView:(NSTabView *)tabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem +{ + [tabViewItem setView:exporterView]; + + [self _switchTab]; +} + +#pragma mark - +#pragma mark Token field delegate methods + +/** + * Use the default token style for matched tokens, plain text for all other text. + */ +- (NSTokenStyle)tokenField:(NSTokenField *)tokenField styleForRepresentedObject:(id)representedObject +{ + if (IS_TOKEN(representedObject)) return NSDefaultTokenStyle; + + return NSPlainTextTokenStyle; +} + +- (BOOL)tokenField:(NSTokenField *)tokenField writeRepresentedObjects:(NSArray *)objects toPasteboard:(NSPasteboard *)pboard +{ + NSMutableArray *mixed = [NSMutableArray arrayWithCapacity:[objects count]]; + NSMutableString *flatted = [NSMutableString string]; + + for(id item in objects) { + if(IS_TOKEN(item)) { + [mixed addObject:@{@"tokenId": [item tokenId]}]; + [flatted appendFormat:@"{%@}",[item tokenId]]; + } + else if(IS_STRING(item)) { + [mixed addObject:item]; + [flatted appendString:item]; + } + else { + [NSException raise:NSInternalInconsistencyException format:@"tokenField %@ contains unexpected object %@",tokenField,item]; + } + } + + [pboard setString:flatted forType:NSPasteboardTypeString]; + [pboard setPropertyList:mixed forType:SPExportCustomFileNameTokenPlistType]; + return YES; +} + +- (NSArray *)tokenField:(NSTokenField *)tokenField readFromPasteboard:(NSPasteboard *)pboard +{ + NSArray *items = [pboard propertyListForType:SPExportCustomFileNameTokenPlistType]; + // if we have our preferred object type use it + if(items) { + NSMutableArray *res = [NSMutableArray arrayWithCapacity:[items count]]; + for (id item in items) { + if (IS_STRING(item)) { + [res addObject:item]; + } + else if([item isKindOfClass:[NSDictionary class]]) { + NSString *name = [item objectForKey:@"tokenId"]; + if(name) { + SPExportFileNameTokenObject *tok = [SPExportFileNameTokenObject tokenWithId:name]; + [res addObject:tok]; + } + } + else { + [NSException raise:NSInternalInconsistencyException format:@"pasteboard %@ contains unexpected object %@",pboard,item]; + } + } + return res; + } + // if the string came from another app, paste it literal, tokenfield will take care of any conversions + NSString *raw = [pboard stringForType:NSPasteboardTypeString]; + if(raw) { + return @[[raw stringByReplacingCharactersInSet:[NSCharacterSet newlineCharacterSet] withString:@" "]]; + } + + return nil; +} + +/** + * Take the default suggestion of new tokens - all untokenized text, as no tokenizing character is set - and + * split/recombine strings that contain tokens. This preserves all supplied characters and allows tokens to be typed. + */ +- (NSArray *)tokenField:(NSTokenField *)tokenField shouldAddObjects:(NSArray *)tokens atIndex:(NSUInteger)index +{ + return [self _updateTokensForMixedContent:tokens]; +} + +- (NSString *)tokenField:(NSTokenField *)tokenField displayStringForRepresentedObject:(id)representedObject +{ + if (IS_TOKEN(representedObject)) { + return [localizedTokenNames objectForKey:[(SPExportFileNameTokenObject *)representedObject tokenId]]; + } + + return representedObject; +} + +/** + * Return the editing string untouched - implementing this method prevents whitespace trimming. + */ +- (id)tokenField:(NSTokenField *)tokenField representedObjectForEditingString:(NSString *)editingString +{ + return editingString; +} + +/** + * During text entry into the token field, update the displayed filename and also + * trigger tokenization after a short delay. + */ +- (void)controlTextDidChange:(NSNotification *)notification +{ + // this method can either be called by typing, or by copy&paste. + // In the latter case tokenization will already be done by now. + if ([notification object] == exportCustomFilenameTokenField) { + [self updateDisplayedExportFilename]; + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_tokenizeCustomFilenameTokenField) object:nil]; + // do not queue a call if the key causing this change was the return key. + // This is to prevent a loop with _tokenizeCustomFilenameTokenField. + if([[NSApp currentEvent] type] != NSKeyDown || [[NSApp currentEvent] keyCode] != 0x24) { + [self performSelector:@selector(_tokenizeCustomFilenameTokenField) withObject:nil afterDelay:0.5]; + } + } +} + +#pragma mark - +#pragma mark Combo box delegate methods + +- (void)comboBoxSelectionDidChange:(NSNotification *)notification +{ + if ([notification object] == exportCSVFieldsTerminatedField) { + [self updateDisplayedExportFilename]; + } +} + +#pragma mark - + +/** + * Takes a mixed array of strings and tokens and converts + * any valid tokens inside the strings into real tokens + */ +- (NSArray *)_updateTokensForMixedContent:(NSArray *)tokens +{ + //if two consecutive tokens are strings, merge them + NSMutableArray *mergedTokens = [NSMutableArray array]; + for (id inputToken in tokens) + { + if(IS_TOKEN(inputToken)) { + [mergedTokens addObject:inputToken]; + } + else if(IS_STRING(inputToken)) { + id prev = [mergedTokens lastObject]; + if(IS_STRING(prev)) { + [mergedTokens removeLastObject]; + [mergedTokens addObject:[prev stringByAppendingString:inputToken]]; + } + else { + [mergedTokens addObject:inputToken]; + } + } + } + + // create a mapping dict of tokenId => token + NSMutableDictionary *replacement = [NSMutableDictionary dictionary]; + for (SPExportFileNameTokenObject *realToken in [exportCustomFilenameTokenPool objectValue]) { + NSString *serializedName = [NSString stringWithFormat:@"{%@}",[realToken tokenId]]; + [replacement setObject:realToken forKey:serializedName]; + } + + //now we can look for real tokens to convert inside the strings + NSMutableArray *processedTokens = [NSMutableArray array]; + for (id token in mergedTokens) { + if(IS_TOKEN(token)) { + [processedTokens addObject:token]; + continue; + } + + NSString *remainder = token; + while(true) { + NSRange openCurl = [remainder rangeOfString:@"{"]; + if(openCurl.location == NSNotFound) { + break; + } + NSString *before = [remainder substringToIndex:openCurl.location]; + if([before length]) { + [processedTokens addObject:before]; + } + remainder = [remainder substringFromIndex:openCurl.location]; + NSRange closeCurl = [remainder rangeOfString:@"}"]; + if(closeCurl.location == NSNotFound) { + break; //we've hit an unterminated token + } + NSString *tokenString = [remainder substringToIndex:closeCurl.location+1]; + SPExportFileNameTokenObject *tokenObject = [replacement objectForKey:tokenString]; + if(tokenObject) { + [processedTokens addObject:tokenObject]; + } + else { + [processedTokens addObject:tokenString]; // no token with this name, add it as string + } + remainder = [remainder substringFromIndex:closeCurl.location+1]; + } + if([remainder length]) { + [processedTokens addObject:remainder]; + } + } + + return processedTokens; +} + +- (void)_tokenizeCustomFilenameTokenField +{ + // if we are currently inside or at the end of a string segment we can + // call for tokenization to happen by simulating a return press + + if ([exportCustomFilenameTokenField currentEditor] == nil) return; + + NSRange selectedRange = [[exportCustomFilenameTokenField currentEditor] selectedRange]; + + if (selectedRange.location == NSNotFound) return; + if (selectedRange.location == 0) return; // the beginning of the field is not valid for tokenization + if (selectedRange.length > 0) return; + + NSUInteger start = 0; + for(id obj in [exportCustomFilenameTokenField objectValue]) { + NSUInteger length; + BOOL isText = NO; + if(IS_STRING(obj)) { + length = [obj length]; + isText = YES; + } + else if(IS_TOKEN(obj)) { + length = 1; // tokens are seen as one char by the textview + } + else { + [NSException raise:NSInternalInconsistencyException format:@"Unknown object type in token field: %@",obj]; + } + NSUInteger end = start+length; + if(selectedRange.location >= start && selectedRange.location <= end) { + if(!isText) return; // cursor is at the end of a token + break; + } + start += length; + } + + // All conditions met - synthesize the return key to trigger tokenization. + NSEvent *tokenizingEvent = [NSEvent keyEventWithType:NSKeyDown + location:NSMakePoint(0,0) + modifierFlags:0 + timestamp:0 + windowNumber:[[exportCustomFilenameTokenField window] windowNumber] + context:[NSGraphicsContext currentContext] + characters:@"" + charactersIgnoringModifiers:@"" + isARepeat:NO + keyCode:0x24]; + + [NSApp postEvent:tokenizingEvent atStart:NO]; +} + +#pragma mark - SPExportSettingsPersistence + +#define NAMEOF(x) case x: return @#x +#define VALUEOF(x,y,dst) if([y isEqualToString:@#x]) { *dst = x; return YES; } + ++ (NSString *)describeExportSource:(SPExportSource)es +{ + switch (es) { + NAMEOF(SPFilteredExport); + NAMEOF(SPQueryExport); + NAMEOF(SPTableExport); + } + return nil; +} + ++ (BOOL)copyExportSourceForDescription:(NSString *)esd to:(SPExportSource *)dst +{ + VALUEOF(SPFilteredExport, esd,dst); + VALUEOF(SPQueryExport, esd,dst); + VALUEOF(SPTableExport, esd,dst); + return NO; +} + ++ (NSString *)describeExportType:(SPExportType)et +{ + switch (et) { + NAMEOF(SPSQLExport); + NAMEOF(SPCSVExport); + NAMEOF(SPXMLExport); + NAMEOF(SPDotExport); + NAMEOF(SPPDFExport); + NAMEOF(SPHTMLExport); + NAMEOF(SPExcelExport); + NAMEOF(SPAnyExportType); + } + return nil; +} + ++ (BOOL)copyExportTypeForDescription:(NSString *)etd to:(SPExportType *)dst +{ + VALUEOF(SPSQLExport, etd, dst); + VALUEOF(SPCSVExport, etd, dst); + VALUEOF(SPXMLExport, etd, dst); + VALUEOF(SPDotExport, etd, dst); + //VALUEOF(SPPDFExport, etd, dst); + //VALUEOF(SPHTMLExport, etd, dst); + //VALUEOF(SPExcelExport, etd, dst); + return NO; +} + ++ (NSString *)describeCompressionFormat:(SPFileCompressionFormat)cf +{ + switch (cf) { + NAMEOF(SPNoCompression); + NAMEOF(SPGzipCompression); + NAMEOF(SPBzip2Compression); + } + return nil; +} + ++ (BOOL)copyCompressionFormatForDescription:(NSString *)cfd to:(SPFileCompressionFormat *)dst +{ + VALUEOF(SPNoCompression, cfd, dst); + VALUEOF(SPGzipCompression, cfd, dst); + VALUEOF(SPBzip2Compression, cfd, dst); + return NO; +} + ++ (NSString *)describeXMLExportFormat:(SPXMLExportFormat)xf +{ + switch (xf) { + NAMEOF(SPXMLExportMySQLFormat); + NAMEOF(SPXMLExportPlainFormat); + } + return nil; +} + ++ (BOOL)copyXMLExportFormatForDescription:(NSString *)xfd to:(SPXMLExportFormat *)dst +{ + VALUEOF(SPXMLExportMySQLFormat, xfd, dst); + VALUEOF(SPXMLExportPlainFormat, xfd, dst); + return NO; +} + ++ (NSString *)describeSQLExportInsertDivider:(SPSQLExportInsertDivider)eid +{ + switch (eid) { + NAMEOF(SPSQLInsertEveryNDataBytes); + NAMEOF(SPSQLInsertEveryNRows); + } + return nil; +} + ++ (BOOL)copySQLExportInsertDividerForDescription:(NSString *)eidd to:(SPSQLExportInsertDivider *)dst +{ + VALUEOF(SPSQLInsertEveryNDataBytes, eidd, dst); + VALUEOF(SPSQLInsertEveryNRows, eidd, dst); + return NO; +} + +#undef NAMEOF +#undef VALUEOF + +- (IBAction)importCurrentSettings:(id)sender +{ + //show open file dialog + NSOpenPanel *panel = [NSOpenPanel openPanel]; + + [panel setAllowedFileTypes:@[SPFileExtensionDefault]]; + [panel setAllowsOtherFileTypes:YES]; + + [panel beginSheetModalForWindow:[self window] completionHandler:^(NSInteger result) { + if(result != NSFileHandlingPanelOKButton) return; + + [panel orderOut:nil]; // Panel is still on screen. Hide it first. (This is Apple's recommended way) + + NSError *err = nil; + NSData *plist = [NSData dataWithContentsOfURL:[panel URL] + options:0 + error:&err]; + + NSDictionary *settings = nil; + if(!err) { + settings = [NSPropertyListSerialization propertyListWithData:plist + options:NSPropertyListImmutable + format:NULL + error:&err]; + } + + if(!err) { + [self applySettingsFromDictionary:settings error:&err]; + if(!err) return; + } + + // give an explanation for some errors + if([[err domain] isEqualToString:SPErrorDomain]) { + if([err code] == SPErrorWrongTypeOrNil) { + NSDictionary *info = @{ + NSLocalizedDescriptionKey: NSLocalizedString(@"Invalid file supplied!", @"export : import settings : file error title"), + NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"The selected file is either not a valid SPF file or severely corrupted.", @"export : import settings : file error description"), + }; + err = [NSError errorWithDomain:[err domain] code:[err code] userInfo:info]; + } + else if([err code] == SPErrorWrongContentType) { + NSDictionary *info = @{ + NSLocalizedDescriptionKey: NSLocalizedString(@"Wrong SPF content type!", @"export : import settings : spf content type error title"), + NSLocalizedRecoverySuggestionErrorKey: [NSString stringWithFormat:NSLocalizedString(@"The selected file contains data of type “%1$@”, but type “%2$@” is needed. Please choose a different file.", @"export : import settings : spf content type error description"),[[err userInfo] objectForKey:@"isType"],[[err userInfo] objectForKey:@"expectedType"]], + }; + err = [NSError errorWithDomain:[err domain] code:[err code] userInfo:info]; + } + } + + NSAlert *alert = [NSAlert alertWithError:err]; + [alert setAlertStyle:NSCriticalAlertStyle]; + [alert runModal]; + }]; +} + +- (IBAction)exportCurrentSettings:(id)sender +{ + //show save file dialog + NSSavePanel *panel = [NSSavePanel savePanel]; + + [panel setAllowedFileTypes:@[SPFileExtensionDefault]]; + + [panel setExtensionHidden:NO]; + [panel setAllowsOtherFileTypes:NO]; + [panel setCanSelectHiddenExtension:YES]; + [panel setCanCreateDirectories:YES]; + + [panel beginSheetModalForWindow:[self window] completionHandler:^(NSInteger returnCode) { + if(returnCode != NSFileHandlingPanelOKButton) return; + + // Panel is still on screen. Hide it first. (This is Apple's recommended way) + [panel orderOut:nil]; + + NSError *err = nil; + NSData *plist = [NSPropertyListSerialization dataWithPropertyList:[self currentSettingsAsDictionary] + format:NSPropertyListXMLFormat_v1_0 + options:0 + error:&err]; + if(!err) { + [plist writeToURL:[panel URL] options:NSAtomicWrite error:&err]; + if(!err) return; + } + + NSAlert *alert = [NSAlert alertWithError:err]; + [alert setAlertStyle:NSCriticalAlertStyle]; + [alert runModal]; + }]; +} + +- (NSArray *)currentCustomFilenameAsArray +{ + NSArray *tokenListIn = [exportCustomFilenameTokenField objectValue]; + NSMutableArray *tokenListOut = [NSMutableArray arrayWithCapacity:[tokenListIn count]]; + + for (id obj in tokenListIn) { + if([obj isKindOfClass:[NSString class]]) { + [tokenListOut addObject:obj]; + } + else if([obj isKindOfClass:[SPExportFileNameTokenObject class]]) { + NSDictionary *tokenProperties = @{@"tokenId": [obj tokenId]}; + // in the future the dict can be used to store per-token settings + [tokenListOut addObject:tokenProperties]; + } + else { + SPLog(@"unknown object in token list: %@",obj); + } + } + + return tokenListOut; +} + +- (void)setCustomFilenameFromArray:(NSArray *)tokenList +{ + NSMutableArray *tokenListOut = [NSMutableArray arrayWithCapacity:[tokenList count]]; + NSArray *allowedTokens = [self currentAllowedExportFilenameTokens]; + + for (id obj in tokenList) { + if([obj isKindOfClass:[NSString class]]) { + [tokenListOut addObject:obj]; + } + else if([obj isKindOfClass:[NSDictionary class]]) { + //there must be at least a non-empty tokenId that is also in the token pool + NSString *tokenId = [obj objectForKey:@"tokenId"]; + if([tokenId length]) { + SPExportFileNameTokenObject *token = [SPExportFileNameTokenObject tokenWithId:tokenId]; + if([allowedTokens containsObject:token]) { + [tokenListOut addObject:token]; + continue; + } + } + SPLog(@"Ignoring an invalid or unknown token with tokenId=%@",tokenId); + } + else { + SPLog(@"unknown object in import token list: %@",obj); + } + } + + [exportCustomFilenameTokenField setObjectValue:tokenListOut]; + + [self updateDisplayedExportFilename]; +} + +- (NSDictionary *)currentSettingsAsDictionary +{ + NSMutableDictionary *root = [NSMutableDictionary dictionary]; + + [root setObject:SPFExportSettingsContentType forKey:SPFFormatKey]; + [root setObject:@1 forKey:SPFVersionKey]; + + [root setObject:[exportPathField stringValue] forKey:@"exportPath"]; + + [root setObject:[[self class] describeExportSource:exportSource] forKey:@"exportSource"]; + [root setObject:[[self class] describeExportType:exportType] forKey:@"exportType"]; + + if([[exportCustomFilenameTokenField stringValue] length] > 0) { + [root setObject:[self currentCustomFilenameAsArray] forKey:@"customFilename"]; + } + + [root setObject:[self exporterSettings] forKey:@"settings"]; + + if(exportSource == SPTableExport) { + NSMutableDictionary *perObjectSettings = [NSMutableDictionary dictionaryWithCapacity:[tables count]]; + + for (NSMutableArray *table in tables) { + NSString *key = [table objectAtIndex:0]; + id settings = [self exporterSpecificSettingsForSchemaObject:key ofType:SPTableTypeTable]; + if(settings) + [perObjectSettings setObject:settings forKey:key]; + } + + [root setObject:perObjectSettings forKey:@"schemaObjects"]; + } + + [root setObject:IsOn(exportProcessLowMemoryButton) forKey:@"lowMemoryStreaming"]; + [root setObject:[[self class] describeCompressionFormat:(SPFileCompressionFormat)[exportOutputCompressionFormatPopupButton indexOfSelectedItem]] forKey:@"compressionFormat"]; + + return root; +} + +- (BOOL)applySettingsFromDictionary:(NSDictionary *)dict error:(NSError **)err +{ + //check for dict/nil + if(![dict isKindOfClass:[NSDictionary class]]) { + if(err) { + *err = [NSError errorWithDomain:SPErrorDomain + code:SPErrorWrongTypeOrNil + userInfo:nil]; // we don't know where data came from, so we can't provide meaningful help to the user + } + return NO; + } + + //check for export settings + NSString *ctype = [dict objectForKey:SPFFormatKey]; + if (![SPFExportSettingsContentType isEqualToString:ctype]) { + if(err) { + NSDictionary *errInfo = @{ + @"isType": ctype, + @"expectedType": SPFExportSettingsContentType + }; + *err = [NSError errorWithDomain:SPErrorDomain + code:SPErrorWrongContentType + userInfo:errInfo]; + } + return NO; + } + + //check for version + NSInteger version = [[dict objectForKey:SPFVersionKey] integerValue]; + if(version != 1) { + if(err) { + NSDictionary *errInfo = @{ + @"isVersion": @(version), + NSLocalizedDescriptionKey: NSLocalizedString(@"Unsupported version for export settings!", @"export : import settings : file version error title"), + NSLocalizedRecoverySuggestionErrorKey: [NSString stringWithFormat:NSLocalizedString(@"The selected export settings were stored with version\u00A0%1$ld, but only settings with the following versions can be imported: %2$@.\n\nEither save the settings in a backwards compatible way or update your version of Sequel Pro.", @"export : import settings : file version error description ($1 = is version, $2 = list of supported versions); note: the u00A0 is a non-breaking space, do not add more whitespace."),version,@"1"], + }; + *err = [NSError errorWithDomain:SPErrorDomain + code:SPErrorWrongContentVersion + userInfo:errInfo]; + } + return NO; + } + + //ok, we can try to import that... + + [exporters removeAllObjects]; + [exportFiles removeAllObjects]; + + id o; + if((o = [dict objectForKey:@"exportPath"])) [exportPathField setStringValue:o]; + + SPExportType et; + if((o = [dict objectForKey:@"exportType"]) && [[self class] copyExportTypeForDescription:o to:&et]) { + [exportTypeTabBar selectTabViewItemAtIndex:et]; + } + + //exportType should be changed first, as exportSource depends on it + SPExportSource es; + if((o = [dict objectForKey:@"exportSource"]) && [[self class] copyExportSourceForDescription:o to:&es]) { + [self setExportInput:es]; //try to set it. might fail e.g. if the settings were saved with "query result" but right now no custom query result exists + } + + // set exporter specific settings + [self applyExporterSettings:[dict objectForKey:@"settings"]]; + + // load schema object settings + if(exportSource == SPTableExport) { + NSDictionary *perObjectSettings = [dict objectForKey:@"schemaObjects"]; + + for (NSString *table in [perObjectSettings allKeys]) { + id settings = [perObjectSettings objectForKey:table]; + [self applyExporterSpecificSettings:settings forSchemaObject:table ofType:SPTableTypeTable]; + } + + [exportTableList reloadData]; + } + + if((o = [dict objectForKey:@"lowMemoryStreaming"])) [exportProcessLowMemoryButton setState:([o boolValue] ? NSOnState : NSOffState)]; + + SPFileCompressionFormat cf; + if((o = [dict objectForKey:@"compressionFormat"]) && [[self class] copyCompressionFormatForDescription:o to:&cf]) [exportOutputCompressionFormatPopupButton selectItemAtIndex:cf]; + + // might have changed + [self _updateExportAdvancedOptionsLabel]; + + // token pool is only valid once the schema object selection is done + [self updateAvailableExportFilenameTokens]; + if((o = [dict objectForKey:@"customFilename"]) && [o isKindOfClass:[NSArray class]]) [self setCustomFilenameFromArray:o]; + + return YES; +} + +- (NSDictionary *)exporterSettings +{ + switch (exportType) { + case SPCSVExport: + return [self csvSettings]; + case SPSQLExport: + return [self sqlSettings]; + case SPXMLExport: + return [self xmlSettings]; + case SPDotExport: + return [self dotSettings]; + case SPExcelExport: + case SPHTMLExport: + case SPPDFExport: + default: + @throw [NSException exceptionWithName:NSInternalInconsistencyException + reason:@"exportType not implemented!" + userInfo:@{@"exportType": @(exportType)}]; + } +} + +- (void)applyExporterSettings:(NSDictionary *)settings +{ + switch (exportType) { + case SPCSVExport: + return [self applyCsvSettings:settings]; + case SPSQLExport: + return [self applySqlSettings:settings]; + case SPXMLExport: + return [self applyXmlSettings:settings]; + case SPDotExport: + return [self applyDotSettings:settings]; + case SPExcelExport: + case SPHTMLExport: + case SPPDFExport: + default: + @throw [NSException exceptionWithName:NSInternalInconsistencyException + reason:@"exportType not implemented!" + userInfo:@{@"exportType": @(exportType)}]; + } +} + +- (NSDictionary *)csvSettings +{ + return @{ + @"exportToMultipleFiles": IsOn(exportFilePerTableCheck), + @"CSVIncludeFieldNames": IsOn(exportCSVIncludeFieldNamesCheck), + @"CSVFieldsTerminated": [exportCSVFieldsTerminatedField stringValue], + @"CSVFieldsWrapped": [exportCSVFieldsWrappedField stringValue], + @"CSVLinesTerminated": [exportCSVLinesTerminatedField stringValue], + @"CSVFieldsEscaped": [exportCSVFieldsEscapedField stringValue], + @"CSVNULLValuesAsText": [exportCSVNULLValuesAsTextField stringValue] + }; +} + +- (void)applyCsvSettings:(NSDictionary *)settings +{ + id o; + if((o = [settings objectForKey:@"exportToMultipleFiles"])) SetOnOff(o,exportFilePerTableCheck); + [self toggleNewFilePerTable:nil]; + + if((o = [settings objectForKey:@"CSVIncludeFieldNames"])) SetOnOff(o, exportCSVIncludeFieldNamesCheck); + if((o = [settings objectForKey:@"CSVFieldsTerminated"])) [exportCSVFieldsTerminatedField setStringValue:o]; + if((o = [settings objectForKey:@"CSVFieldsWrapped"])) [exportCSVFieldsWrappedField setStringValue:o]; + if((o = [settings objectForKey:@"CSVLinesTerminated"])) [exportCSVLinesTerminatedField setStringValue:o]; + if((o = [settings objectForKey:@"CSVFieldsEscaped"])) [exportCSVFieldsEscapedField setStringValue:o]; + if((o = [settings objectForKey:@"CSVNULLValuesAsText"])) [exportCSVNULLValuesAsTextField setStringValue:o]; +} + +- (NSDictionary *)dotSettings +{ + return @{@"DotForceLowerTableNames": IsOn(exportDotForceLowerTableNamesCheck)}; +} + +- (void)applyDotSettings:(NSDictionary *)settings +{ + id o; + if((o = [settings objectForKey:@"DotForceLowerTableNames"])) SetOnOff(o, exportDotForceLowerTableNamesCheck); +} + +- (NSDictionary *)xmlSettings +{ + return @{ + @"exportToMultipleFiles": IsOn(exportFilePerTableCheck), + @"XMLFormat": [[self class] describeXMLExportFormat:(SPXMLExportFormat)[exportXMLFormatPopUpButton indexOfSelectedItem]], + @"XMLOutputIncludeStructure": IsOn(exportXMLIncludeStructure), + @"XMLOutputIncludeContent": IsOn(exportXMLIncludeContent), + @"XMLNULLString": [exportXMLNULLValuesAsTextField stringValue] + }; +} + +- (void)applyXmlSettings:(NSDictionary *)settings +{ + id o; + SPXMLExportFormat xmlf; + if((o = [settings objectForKey:@"exportToMultipleFiles"])) SetOnOff(o, exportFilePerTableCheck); + [self toggleNewFilePerTable:nil]; + + if((o = [settings objectForKey:@"XMLFormat"]) && [[self class] copyXMLExportFormatForDescription:o to:&xmlf]) [exportXMLFormatPopUpButton selectItemAtIndex:xmlf]; + if((o = [settings objectForKey:@"XMLOutputIncludeStructure"])) SetOnOff(o, exportXMLIncludeStructure); + if((o = [settings objectForKey:@"XMLOutputIncludeContent"])) SetOnOff(o, exportXMLIncludeContent); + if((o = [settings objectForKey:@"XMLNULLString"])) [exportXMLNULLValuesAsTextField setStringValue:o]; + + [self toggleXMLOutputFormat:exportXMLFormatPopUpButton]; +} + +- (NSDictionary *)sqlSettings +{ + BOOL includeStructure = ([exportSQLIncludeStructureCheck state] == NSOnState); + + NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:@{ + @"SQLIncludeStructure": IsOn(exportSQLIncludeStructureCheck), + @"SQLIncludeContent": IsOn(exportSQLIncludeContentCheck), + @"SQLIncludeErrors": IsOn(exportSQLIncludeErrorsCheck), + @"SQLIncludeDROP": IsOn(exportSQLIncludeDropSyntaxCheck), + @"SQLUseUTF8BOM": IsOn(exportUseUTF8BOMButton), + @"SQLBLOBFieldsAsHex": IsOn(exportSQLBLOBFieldsAsHexCheck), + @"SQLInsertNValue": @([exportSQLInsertNValueTextField integerValue]), + @"SQLInsertDivider": [[self class] describeSQLExportInsertDivider:(SPSQLExportInsertDivider)[exportSQLInsertDividerPopUpButton indexOfSelectedItem]] + }]; + + if(includeStructure) { + [dict addEntriesFromDictionary:@{ + @"SQLIncludeAutoIncrementValue": IsOn(exportSQLIncludeAutoIncrementValueButton), + @"SQLIncludeDropSyntax": IsOn(exportSQLIncludeDropSyntaxCheck) + }]; + } + + return dict; +} + +- (void)applySqlSettings:(NSDictionary *)settings +{ + id o; + SPSQLExportInsertDivider div; + + if((o = [settings objectForKey:@"SQLIncludeContent"])) SetOnOff(o, exportSQLIncludeContentCheck); + [self toggleSQLIncludeContent:exportSQLIncludeContentCheck]; + + if((o = [settings objectForKey:@"SQLIncludeDROP"])) SetOnOff(o, exportSQLIncludeDropSyntaxCheck); + [self toggleSQLIncludeDropSyntax:exportSQLIncludeDropSyntaxCheck]; + + if((o = [settings objectForKey:@"SQLIncludeStructure"])) SetOnOff(o, exportSQLIncludeStructureCheck); + [self toggleSQLIncludeStructure:exportSQLIncludeStructureCheck]; + + if((o = [settings objectForKey:@"SQLIncludeErrors"])) SetOnOff(o, exportSQLIncludeErrorsCheck); + if((o = [settings objectForKey:@"SQLUseUTF8BOM"])) SetOnOff(o, exportUseUTF8BOMButton); + if((o = [settings objectForKey:@"SQLBLOBFieldsAsHex"])) SetOnOff(o, exportSQLBLOBFieldsAsHexCheck); + if((o = [settings objectForKey:@"SQLInsertNValue"])) [exportSQLInsertNValueTextField setIntegerValue:[o integerValue]]; + if((o = [settings objectForKey:@"SQLInsertDivider"]) && [[self class] copySQLExportInsertDividerForDescription:o to:&div]) [exportSQLInsertDividerPopUpButton selectItemAtIndex:div]; + + if([exportSQLIncludeStructureCheck state] == NSOnState) { + if((o = [settings objectForKey:@"SQLIncludeAutoIncrementValue"])) SetOnOff(o, exportSQLIncludeAutoIncrementValueButton); + if((o = [settings objectForKey:@"SQLIncludeDropSyntax"])) SetOnOff(o, exportSQLIncludeDropSyntaxCheck); + } +} + +- (id)exporterSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type +{ + switch (exportType) { + case SPCSVExport: + return [self csvSpecificSettingsForSchemaObject:name ofType:type]; + case SPSQLExport: + return [self sqlSpecificSettingsForSchemaObject:name ofType:type]; + case SPXMLExport: + return [self xmlSpecificSettingsForSchemaObject:name ofType:type]; + case SPDotExport: + return [self dotSpecificSettingsForSchemaObject:name ofType:type]; + case SPExcelExport: + case SPHTMLExport: + case SPPDFExport: + default: + @throw [NSException exceptionWithName:NSInternalInconsistencyException + reason:@"exportType not implemented!" + userInfo:@{@"exportType": @(exportType)}]; + } +} + +- (void)applyExporterSpecificSettings:(id)settings forSchemaObject:(NSString *)name ofType:(SPTableType)type +{ + switch (exportType) { + case SPCSVExport: + return [self applyCsvSpecificSettings:settings forSchemaObject:name ofType:type]; + case SPSQLExport: + return [self applySqlSpecificSettings:settings forSchemaObject:name ofType:type]; + case SPXMLExport: + return [self applyXmlSpecificSettings:settings forSchemaObject:name ofType:type]; + case SPDotExport: + return [self applyDotSpecificSettings:settings forSchemaObject:name ofType:type]; + case SPExcelExport: + case SPHTMLExport: + case SPPDFExport: + default: + @throw [NSException exceptionWithName:NSInternalInconsistencyException + reason:@"exportType not implemented!" + userInfo:@{@"exportType": @(exportType)}]; + } +} + +- (id)dotSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type +{ + // Dot is a graph of the whole database - nothing to pick from + return nil; +} + +- (void)applyDotSpecificSettings:(id)settings forSchemaObject:(NSString *)name ofType:(SPTableType)type +{ + //should never be called +} + +- (id)xmlSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type +{ + // XML per table setting is only yes/no + if(type == SPTableTypeTable) { + // we have to look through the table views' rows to find the current checkbox value... + for (NSArray *table in tables) { + if([[table objectAtIndex:0] isEqualTo:name]) { + return @([[table objectAtIndex:2] boolValue]); + } + } + } + return nil; +} + +- (void)applyXmlSpecificSettings:(id)settings forSchemaObject:(NSString *)name ofType:(SPTableType)type +{ + // XML per table setting is only yes/no + if(type == SPTableTypeTable) { + // we have to look through the table views' rows to find the appropriate table... + for (NSMutableArray *table in tables) { + if([[table objectAtIndex:0] isEqualTo:name]) { + [table replaceObjectAtIndex:2 withObject:@([settings boolValue])]; + return; + } + } + } +} + +- (id)csvSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type +{ + // CSV per table setting is only yes/no + if(type == SPTableTypeTable) { + // we have to look through the table views rows to find the current checkbox value... + for (NSArray *table in tables) { + if([[table objectAtIndex:0] isEqualTo:name]) { + return @([[table objectAtIndex:2] boolValue]); + } + } + } + return nil; +} + +- (void)applyCsvSpecificSettings:(id)settings forSchemaObject:(NSString *)name ofType:(SPTableType)type +{ + // CSV per table setting is only yes/no + if(type == SPTableTypeTable) { + // we have to look through the table views' rows to find the appropriate table... + for (NSMutableArray *table in tables) { + if([[table objectAtIndex:0] isEqualTo:name]) { + [table replaceObjectAtIndex:2 withObject:@([settings boolValue])]; + return; + } + } + } +} + +- (id)sqlSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type +{ + BOOL structure = ([exportSQLIncludeStructureCheck state] == NSOnState); + BOOL content = ([exportSQLIncludeContentCheck state] == NSOnState); + BOOL drop = ([exportSQLIncludeDropSyntaxCheck state] == NSOnState); + + // SQL allows per table setting of structure/content/drop table + if(type == SPTableTypeTable) { + // we have to look through the table views rows to find the current checkbox value... + for (NSArray *table in tables) { + if([[table objectAtIndex:0] isEqualTo:name]) { + NSMutableArray *flags = [NSMutableArray arrayWithCapacity:3]; + + if (structure && [[table objectAtIndex:1] boolValue]) { + [flags addObject:@"structure"]; + } + + if (content && [[table objectAtIndex:2] boolValue]) { + [flags addObject:@"content"]; + } + + if (drop && [[table objectAtIndex:3] boolValue]) { + [flags addObject:@"drop"]; + } + + return flags; + } + } + } + return nil; +} + +- (void)applySqlSpecificSettings:(id)settings forSchemaObject:(NSString *)name ofType:(SPTableType)type +{ + BOOL structure = ([exportSQLIncludeStructureCheck state] == NSOnState); + BOOL content = ([exportSQLIncludeContentCheck state] == NSOnState); + BOOL drop = ([exportSQLIncludeDropSyntaxCheck state] == NSOnState); + + // SQL allows per table setting of structure/content/drop table + if(type == SPTableTypeTable) { + // we have to look through the table views' rows to find the appropriate table... + for (NSMutableArray *table in tables) { + if([[table objectAtIndex:0] isEqualTo:name]) { + NSArray *flags = settings; + + [table replaceObjectAtIndex:1 withObject:@((structure && [flags containsObject:@"structure"]))]; + [table replaceObjectAtIndex:2 withObject:@((content && [flags containsObject:@"content"]))]; + [table replaceObjectAtIndex:3 withObject:@((drop && [flags containsObject:@"drop"]))]; + return; + } + } + } +} + +#pragma mark - SPCSVExporterDelegate + +- (void)csvExportProcessWillBegin:(SPCSVExporter *)exporter +{ + [exportProgressText displayIfNeeded]; + + [exportProgressIndicator setIndeterminate:YES]; + [exportProgressIndicator setUsesThreadedAnimation:YES]; + [exportProgressIndicator startAnimation:self]; + + // Only update the progress text if this is a table export + if (exportSource == SPTableExport) { + // Update the current table export index + currentTableExportIndex = (exportTableCount - [exporters count]); + + [exportProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %lu (%@): Fetching data...", @"export label showing that the app is fetching data for a specific table"), currentTableExportIndex, exportTableCount, [exporter csvTableName]]]; + } + else { + [exportProgressText setStringValue:NSLocalizedString(@"Fetching data...", @"export label showing that the app is fetching data")]; + } + + [exportProgressText displayIfNeeded]; +} + +- (void)csvExportProcessComplete:(SPCSVExporter *)exporter +{ + NSUInteger exportCount = [exporters count]; + + // If required add the next exporter to the operation queue + if ((exportCount > 0) && (exportSource == SPTableExport)) { + + // If we're only exporting to a single file then write a header for the next table + if (!exportToMultipleFiles) { + + // If we're exporting multiple tables to a single file then append some space and the next table's + // name, but only if there is at least 2 exportes left. + [[exporter exportOutputFile] writeData:[[NSString stringWithFormat:@"%@%@%@ %@%@%@", + [exporter csvLineEndingString], + [exporter csvLineEndingString], + NSLocalizedString(@"Table", @"csv export table heading"), + [(SPCSVExporter *)[exporters objectAtIndex:0] csvTableName], + [exporter csvLineEndingString], + [exporter csvLineEndingString]] dataUsingEncoding:[exporter exportOutputEncoding]]]; + } + // Otherwise close the file handle of the exporter that just finished + // ensuring it's data is written to disk. + else { + [[exporter exportOutputFile] close]; + } + + [operationQueue addOperation:[exporters objectAtIndex:0]]; + + // Remove the exporter we just added to the operation queue from our list of exporters + // so we know it's already been done. + [exporters removeObjectAtIndex:0]; + } + // Otherwise if the exporter list is empty, close the progress sheet + else { + // Close the last exporter's file handle + [[exporter exportOutputFile] close]; + + [self exportEnded]; + } +} + +- (void)csvExportProcessWillBeginWritingData:(SPCSVExporter *)exporter +{ + // Only update the progress text if this is a table export + if (exportSource == SPTableExport) { + [exportProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %lu (%@): Writing data...", @"export label showing app if writing data for a specific table"), currentTableExportIndex, exportTableCount, [exporter csvTableName]]]; + } + else { + [exportProgressText setStringValue:NSLocalizedString(@"Writing data...", @"export label showing app is writing data")]; + } + + [exportProgressText displayIfNeeded]; + + [exportProgressIndicator stopAnimation:self]; + [exportProgressIndicator setUsesThreadedAnimation:NO]; + [exportProgressIndicator setIndeterminate:NO]; + [exportProgressIndicator setDoubleValue:0]; +} + +- (void)csvExportProcessProgressUpdated:(SPCSVExporter *)exporter +{ + [exportProgressIndicator setDoubleValue:[exporter exportProgressValue]]; +} + +#pragma mark - SPSQLExporterDelegate + +- (void)sqlExportProcessWillBegin:(SPSQLExporter *)exporter +{ + [exportProgressTitle setStringValue:NSLocalizedString(@"Exporting SQL", @"text showing that the application is exporting SQL")]; + [exportProgressText setStringValue:NSLocalizedString(@"Dumping...", @"text showing that app is writing dump")]; + + [exportProgressTitle displayIfNeeded]; + [exportProgressText displayIfNeeded]; +} + +- (void)sqlExportProcessComplete:(SPSQLExporter *)exporter +{ + [self exportEnded]; + + // Check for errors and display the errors sheet if necessary + if ([exporter didExportErrorsOccur]) { + [self openExportErrorsSheetWithString:[exporter sqlExportErrors]]; + } +} + +- (void)sqlExportProcessProgressUpdated:(SPSQLExporter *)exporter +{ + if ([exportProgressIndicator doubleValue] == 0) { + [exportProgressIndicator stopAnimation:self]; + [exportProgressIndicator setIndeterminate:NO]; + } + + [exportProgressIndicator setDoubleValue:[exporter exportProgressValue]]; +} + +- (void)sqlExportProcessWillBeginFetchingData:(SPSQLExporter *)exporter +{ + [exportProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %lu (%@): Fetching data...", @"export label showing that the app is fetching data for a specific table"), [exporter sqlCurrentTableExportIndex], exportTableCount, [exporter sqlExportCurrentTable]]]; + + [exportProgressIndicator startAnimation:self]; + [exportProgressIndicator setUsesThreadedAnimation:YES]; + [exportProgressIndicator setIndeterminate:YES]; + [exportProgressIndicator setDoubleValue:0]; +} + +- (void)sqlExportProcessWillBeginWritingData:(SPSQLExporter *)exporter +{ + [exportProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %lu (%@): Writing data...", @"export label showing app if writing data for a specific table"), [exporter sqlCurrentTableExportIndex], exportTableCount, [exporter sqlExportCurrentTable]]]; +} + +#pragma mark - SPXMLExporterDelegate + +- (void)xmlExportProcessWillBegin:(SPXMLExporter *)exporter +{ + [exportProgressText displayIfNeeded]; + + [exportProgressIndicator setIndeterminate:YES]; + [exportProgressIndicator setUsesThreadedAnimation:YES]; + [exportProgressIndicator startAnimation:self]; + + // Only update the progress text if this is a table export + if (exportSource == SPTableExport) { + + // Update the current table export index + currentTableExportIndex = (exportTableCount - [exporters count]); + + [exportProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %lu (%@): Fetching data...", @"export label showing that the app is fetching data for a specific table"), currentTableExportIndex, exportTableCount, [exporter xmlTableName]]]; + } + else { + [exportProgressText setStringValue:NSLocalizedString(@"Fetching data...", @"export label showing that the app is fetching data")]; + } + + [exportProgressText displayIfNeeded]; +} + +- (void)xmlExportProcessComplete:(SPXMLExporter *)exporter +{ + NSUInteger exportCount = [exporters count]; + + // If required add the next exporter to the operation queue + if ((exportCount > 0) && (exportSource == SPTableExport)) { + + // If we're exporting to multiple files then close the file handle of the exporter + // that just finished, ensuring its data is written to disk. + if (exportToMultipleFiles) { + NSString *string = @""; + + if ([exporter xmlFormat] == SPXMLExportMySQLFormat) { + string = (exportSource == SPTableExport) ? @"</database>\n</mysqldump>\n" : @"</resultset>\n";; + } + else if ([exporter xmlFormat] == SPXMLExportPlainFormat) { + string = [NSString stringWithFormat:@"</%@>\n", [[tableDocumentInstance database] HTMLEscapeString]]; + } + + [[exporter exportOutputFile] writeData:[string dataUsingEncoding:[connection stringEncoding]]]; + [[exporter exportOutputFile] close]; + } + + [operationQueue addOperation:[exporters objectAtIndex:0]]; + + // Remove the exporter we just added to the operation queue from our list of exporters + // so we know it's already been done. + [exporters removeObjectAtIndex:0]; + } + // Otherwise if the exporter list is empty, close the progress sheet + else { + NSString *string = @""; + + if ([exporter xmlFormat] == SPXMLExportMySQLFormat) { + string = (exportSource == SPTableExport) ? @"</database>\n</mysqldump>\n" : @"</resultset>\n";; + } + else if ([exporter xmlFormat] == SPXMLExportPlainFormat) { + string = [NSString stringWithFormat:@"</%@>\n", [[tableDocumentInstance database] HTMLEscapeString]]; + } + + [[exporter exportOutputFile] writeData:[string dataUsingEncoding:[connection stringEncoding]]]; + [[exporter exportOutputFile] close]; + + [self exportEnded]; + } +} + +- (void)xmlExportProcessProgressUpdated:(SPXMLExporter *)exporter +{ + [[exportProgressIndicator onMainThread] setDoubleValue:[exporter exportProgressValue]]; +} + +- (void)xmlExportProcessWillBeginWritingData:(SPXMLExporter *)exporter +{ + // Only update the progress text if this is a table export + if (exportSource == SPTableExport) { + [exportProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %lu (%@): Writing data...", @"export label showing app if writing data for a specific table"), currentTableExportIndex, exportTableCount, [exporter xmlTableName]]]; + } + else { + [exportProgressText setStringValue:NSLocalizedString(@"Writing data...", @"export label showing app is writing data")]; + } + + [exportProgressText displayIfNeeded]; + + [exportProgressIndicator stopAnimation:self]; + [exportProgressIndicator setUsesThreadedAnimation:NO]; + [exportProgressIndicator setIndeterminate:NO]; + [exportProgressIndicator setDoubleValue:0]; +} + +#pragma mark - SPDotExporterDelegate + +- (void)dotExportProcessWillBegin:(SPDotExporter *)exporter +{ + [exportProgressTitle setStringValue:NSLocalizedString(@"Exporting Dot File", @"text showing that the application is exporting a Dot file")]; + [exportProgressText setStringValue:NSLocalizedString(@"Dumping...", @"text showing that app is writing dump")]; + + [exportProgressTitle displayIfNeeded]; + [exportProgressText displayIfNeeded]; + [exportProgressIndicator stopAnimation:self]; + [exportProgressIndicator setIndeterminate:NO]; +} + +- (void)dotExportProcessComplete:(SPDotExporter *)exporter +{ + [self exportEnded]; +} + +- (void)dotExportProcessProgressUpdated:(SPDotExporter *)exporter +{ + [exportProgressIndicator setDoubleValue:[exporter exportProgressValue]]; +} + +- (void)dotExportProcessWillBeginFetchingData:(SPDotExporter *)exporter forTableWithIndex:(NSUInteger)tableIndex +{ + // Update the current table export index + currentTableExportIndex = tableIndex; + + [exportProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %lu (%@): Fetching data...", @"export label showing that the app is fetching data for a specific table"), currentTableExportIndex, exportTableCount, [exporter dotExportCurrentTable]]]; + + [exportProgressText displayIfNeeded]; +} + +- (void)dotExportProcessWillBeginFetchingRelationsData:(SPDotExporter *)exporter +{ + [exportProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %lu (%@): Fetching relations data...", @"export label showing app is fetching relations data for a specific table"), currentTableExportIndex, exportTableCount, [exporter dotExportCurrentTable]]]; + + [exportProgressText displayIfNeeded]; + [exportProgressIndicator setIndeterminate:YES]; + [exportProgressIndicator setUsesThreadedAnimation:YES]; + [exportProgressIndicator startAnimation:self]; +} + +#pragma mark - SPPDFExporterDelegate + +- (void)pdfExportProcessWillBegin:(SPPDFExporter *)exporter +{ +} + +- (void)pdfExportProcessComplete:(SPPDFExporter *)exporter +{ + [self exportEnded]; +} + +- (void)pdfExportProcessWillBeginWritingData:(SPPDFExporter *)exporter +{ +} + +#pragma mark - SPHTMLExporterDelegate + +- (void)htmlExportProcessWillBegin:(SPHTMLExporter *)exporter +{ +} + +- (void)htmlExportProcessComplete:(SPHTMLExporter *)exporter +{ + [self exportEnded]; +} + +- (void)htmlExportProcessWillBeginWritingData:(SPHTMLExporter *)exporter +{ +} + #pragma mark - - (void)dealloc @@ -1090,3 +3755,25 @@ set_input: } @end + +#pragma mark - + +BOOL IS_TOKEN(id x) +{ + return [x isKindOfClass:[SPExportFileNameTokenObject class]]; +} + +BOOL IS_STRING(id x) +{ + return [x isKindOfClass:[NSString class]]; +} + +NSNumber *IsOn(id obj) +{ + return (([obj state] == NSOnState)? @YES : @NO); +} + +void SetOnOff(NSNumber *ref,id obj) +{ + [obj setState:([ref boolValue] ? NSOnState : NSOffState)]; +} diff --git a/Source/SPExportControllerDelegate.h b/Source/SPExportControllerDelegate.h deleted file mode 100644 index 69be91d4..00000000 --- a/Source/SPExportControllerDelegate.h +++ /dev/null @@ -1,42 +0,0 @@ -// -// SPExportControllerDelegate.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on October 23, 2010. -// Copyright (c) 2010 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPExportController.h" - -/** - * @category SPExportControllerDelegate SPExportControllerDelegate.h - * - * @author Stuart Connolly http://stuconnolly.com/ - * - * Export controller delegate category. - */ -@interface SPExportController (SPExportControllerDelegate) - -@end diff --git a/Source/SPExportControllerDelegate.m b/Source/SPExportControllerDelegate.m deleted file mode 100644 index 8c020fc3..00000000 --- a/Source/SPExportControllerDelegate.m +++ /dev/null @@ -1,359 +0,0 @@ -// -// SPExportControllerDelegate.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on October 23, 2010. -// Copyright (c) 2010 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPExportControllerDelegate.h" -#import "SPExportFilenameUtilities.h" -#import "SPExportFileNameTokenObject.h" - -static inline BOOL IS_TOKEN(id x); -static inline BOOL IS_STRING(id x); - -// Defined to suppress warnings -@interface SPExportController (SPExportControllerPrivateAPI) - -- (void)_toggleExportButtonOnBackgroundThread; -- (void)_toggleSQLExportTableNameTokenAvailability; -- (void)_updateExportFormatInformation; -- (void)_switchTab; -- (NSArray *)_updateTokensForMixedContent:(NSArray *)tokens; -- (void)_tokenizeCustomFilenameTokenField; - -@end - -@implementation SPExportController (SPExportControllerDelegate) - -#pragma mark - -#pragma mark Table view datasource methods - -- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView; -{ - return [tables count]; -} - -- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex -{ - return NSArrayObjectAtIndex([tables objectAtIndex:rowIndex], [exportTableList columnWithIdentifier:[tableColumn identifier]]); -} - -- (void)tableView:(NSTableView *)tableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex -{ - [[tables objectAtIndex:rowIndex] replaceObjectAtIndex:[exportTableList columnWithIdentifier:[tableColumn identifier]] withObject:anObject]; - - [self updateAvailableExportFilenameTokens]; - [self _toggleExportButtonOnBackgroundThread]; - [self _updateExportFormatInformation]; -} - -#pragma mark - -#pragma mark Table view delegate methods - -- (BOOL)tableView:(NSTableView *)tableView shouldTrackCell:(NSCell *)cell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex -{ - return (tableView == exportTableList); -} - -- (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex -{ - [cell setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; -} - -#pragma mark - -#pragma mark Tabview delegate methods - -- (void)tabView:(NSTabView *)tabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem -{ - [tabViewItem setView:exporterView]; - - [self _switchTab]; -} - -#pragma mark - -#pragma mark Token field delegate methods - -/** - * Use the default token style for matched tokens, plain text for all other text. - */ -- (NSTokenStyle)tokenField:(NSTokenField *)tokenField styleForRepresentedObject:(id)representedObject -{ - if (IS_TOKEN(representedObject)) return NSDefaultTokenStyle; - - return NSPlainTextTokenStyle; -} - -- (BOOL)tokenField:(NSTokenField *)tokenField writeRepresentedObjects:(NSArray *)objects toPasteboard:(NSPasteboard *)pboard -{ - NSMutableArray *mixed = [NSMutableArray arrayWithCapacity:[objects count]]; - NSMutableString *flatted = [NSMutableString string]; - - for(id item in objects) { - if(IS_TOKEN(item)) { - [mixed addObject:@{@"tokenId": [item tokenId]}]; - [flatted appendFormat:@"{%@}",[item tokenId]]; - } - else if(IS_STRING(item)) { - [mixed addObject:item]; - [flatted appendString:item]; - } - else { - [NSException raise:NSInternalInconsistencyException format:@"tokenField %@ contains unexpected object %@",tokenField,item]; - } - } - - [pboard setString:flatted forType:NSPasteboardTypeString]; - [pboard setPropertyList:mixed forType:SPExportCustomFileNameTokenPlistType]; - return YES; -} - -- (NSArray *)tokenField:(NSTokenField *)tokenField readFromPasteboard:(NSPasteboard *)pboard -{ - NSArray *items = [pboard propertyListForType:SPExportCustomFileNameTokenPlistType]; - // if we have our preferred object type use it - if(items) { - NSMutableArray *res = [NSMutableArray arrayWithCapacity:[items count]]; - for (id item in items) { - if (IS_STRING(item)) { - [res addObject:item]; - } - else if([item isKindOfClass:[NSDictionary class]]) { - NSString *name = [item objectForKey:@"tokenId"]; - if(name) { - SPExportFileNameTokenObject *tok = [SPExportFileNameTokenObject tokenWithId:name]; - [res addObject:tok]; - } - } - else { - [NSException raise:NSInternalInconsistencyException format:@"pasteboard %@ contains unexpected object %@",pboard,item]; - } - } - return res; - } - // if the string came from another app, paste it literal, tokenfield will take care of any conversions - NSString *raw = [pboard stringForType:NSPasteboardTypeString]; - if(raw) { - return @[[raw stringByReplacingCharactersInSet:[NSCharacterSet newlineCharacterSet] withString:@" "]]; - } - - return nil; -} - -/** - * Take the default suggestion of new tokens - all untokenized text, as no tokenizing character is set - and - * split/recombine strings that contain tokens. This preserves all supplied characters and allows tokens to be typed. - */ -- (NSArray *)tokenField:(NSTokenField *)tokenField shouldAddObjects:(NSArray *)tokens atIndex:(NSUInteger)index -{ - return [self _updateTokensForMixedContent:tokens]; -} - -- (NSString *)tokenField:(NSTokenField *)tokenField displayStringForRepresentedObject:(id)representedObject -{ - if (IS_TOKEN(representedObject)) { - return [localizedTokenNames objectForKey:[(SPExportFileNameTokenObject *)representedObject tokenId]]; - } - - return representedObject; -} - -/** - * Return the editing string untouched - implementing this method prevents whitespace trimming. - */ -- (id)tokenField:(NSTokenField *)tokenField representedObjectForEditingString:(NSString *)editingString -{ - return editingString; -} - -/** - * During text entry into the token field, update the displayed filename and also - * trigger tokenization after a short delay. - */ -- (void)controlTextDidChange:(NSNotification *)notification -{ - // this method can either be called by typing, or by copy&paste. - // In the latter case tokenization will already be done by now. - if ([notification object] == exportCustomFilenameTokenField) { - [self updateDisplayedExportFilename]; - [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_tokenizeCustomFilenameTokenField) object:nil]; - // do not queue a call if the key causing this change was the return key. - // This is to prevent a loop with _tokenizeCustomFilenameTokenField. - if([[NSApp currentEvent] type] != NSKeyDown || [[NSApp currentEvent] keyCode] != 0x24) { - [self performSelector:@selector(_tokenizeCustomFilenameTokenField) withObject:nil afterDelay:0.5]; - } - } -} - -#pragma mark - -#pragma mark Combo box delegate methods - -- (void)comboBoxSelectionDidChange:(NSNotification *)notification -{ - if ([notification object] == exportCSVFieldsTerminatedField) { - [self updateDisplayedExportFilename]; - } -} - -#pragma mark - - -/** - * Takes a mixed array of strings and tokens and converts - * any valid tokens inside the strings into real tokens - */ -- (NSArray *)_updateTokensForMixedContent:(NSArray *)tokens -{ - //if two consecutive tokens are strings, merge them - NSMutableArray *mergedTokens = [NSMutableArray array]; - for (id inputToken in tokens) - { - if(IS_TOKEN(inputToken)) { - [mergedTokens addObject:inputToken]; - } - else if(IS_STRING(inputToken)) { - id prev = [mergedTokens lastObject]; - if(IS_STRING(prev)) { - [mergedTokens removeLastObject]; - [mergedTokens addObject:[prev stringByAppendingString:inputToken]]; - } - else { - [mergedTokens addObject:inputToken]; - } - } - } - - // create a mapping dict of tokenId => token - NSMutableDictionary *replacement = [NSMutableDictionary dictionary]; - for (SPExportFileNameTokenObject *realToken in [exportCustomFilenameTokenPool objectValue]) { - NSString *serializedName = [NSString stringWithFormat:@"{%@}",[realToken tokenId]]; - [replacement setObject:realToken forKey:serializedName]; - } - - //now we can look for real tokens to convert inside the strings - NSMutableArray *processedTokens = [NSMutableArray array]; - for (id token in mergedTokens) { - if(IS_TOKEN(token)) { - [processedTokens addObject:token]; - continue; - } - - NSString *remainder = token; - while(true) { - NSRange openCurl = [remainder rangeOfString:@"{"]; - if(openCurl.location == NSNotFound) { - break; - } - NSString *before = [remainder substringToIndex:openCurl.location]; - if([before length]) { - [processedTokens addObject:before]; - } - remainder = [remainder substringFromIndex:openCurl.location]; - NSRange closeCurl = [remainder rangeOfString:@"}"]; - if(closeCurl.location == NSNotFound) { - break; //we've hit an unterminated token - } - NSString *tokenString = [remainder substringToIndex:closeCurl.location+1]; - SPExportFileNameTokenObject *tokenObject = [replacement objectForKey:tokenString]; - if(tokenObject) { - [processedTokens addObject:tokenObject]; - } - else { - [processedTokens addObject:tokenString]; // no token with this name, add it as string - } - remainder = [remainder substringFromIndex:closeCurl.location+1]; - } - if([remainder length]) { - [processedTokens addObject:remainder]; - } - } - - return processedTokens; -} - -- (void)_tokenizeCustomFilenameTokenField -{ - // if we are currently inside or at the end of a string segment we can - // call for tokenization to happen by simulating a return press - - if ([exportCustomFilenameTokenField currentEditor] == nil) return; - - NSRange selectedRange = [[exportCustomFilenameTokenField currentEditor] selectedRange]; - - if (selectedRange.location == NSNotFound) return; - if (selectedRange.location == 0) return; // the beginning of the field is not valid for tokenization - if (selectedRange.length > 0) return; - - NSUInteger start = 0; - for(id obj in [exportCustomFilenameTokenField objectValue]) { - NSUInteger length; - BOOL isText = NO; - if(IS_STRING(obj)) { - length = [obj length]; - isText = YES; - } - else if(IS_TOKEN(obj)) { - length = 1; // tokens are seen as one char by the textview - } - else { - [NSException raise:NSInternalInconsistencyException format:@"Unknown object type in token field: %@",obj]; - } - NSUInteger end = start+length; - if(selectedRange.location >= start && selectedRange.location <= end) { - if(!isText) return; // cursor is at the end of a token - break; - } - start += length; - } - - // All conditions met - synthesize the return key to trigger tokenization. - NSEvent *tokenizingEvent = [NSEvent keyEventWithType:NSKeyDown - location:NSMakePoint(0,0) - modifierFlags:0 - timestamp:0 - windowNumber:[[exportCustomFilenameTokenField window] windowNumber] - context:[NSGraphicsContext currentContext] - characters:@"" - charactersIgnoringModifiers:@"" - isARepeat:NO - keyCode:0x24]; - - [NSApp postEvent:tokenizingEvent atStart:NO]; -} - -@end - -#pragma mark - - -BOOL IS_TOKEN(id x) -{ - return [x isKindOfClass:[SPExportFileNameTokenObject class]]; -} - -BOOL IS_STRING(id x) -{ - return [x isKindOfClass:[NSString class]]; -} - diff --git a/Source/SPExportFileUtilities.h b/Source/SPExportFileUtilities.h deleted file mode 100644 index 54c81eb7..00000000 --- a/Source/SPExportFileUtilities.h +++ /dev/null @@ -1,49 +0,0 @@ -// -// SPExportFileUtilities.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on July 30, 2010. -// Copyright (c) 2010 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPExportController.h" - -@class SPExportFile; - -/** - * @category SPExportFileUtilities SPExportFileUtilities.h - * - * @author Stuart Connolly http://stuconnolly.com/ - * - * Export file utilities category. - */ -@interface SPExportController (SPExportFileUtilities) - -- (void)writeCSVHeaderToExportFile:(SPExportFile *)file; -- (void)writeXMLHeaderToExportFile:(SPExportFile *)file; - -- (void)errorCreatingExportFileHandles:(NSArray *)files; - -@end diff --git a/Source/SPExportFileUtilities.m b/Source/SPExportFileUtilities.m deleted file mode 100644 index 23fb2f4a..00000000 --- a/Source/SPExportFileUtilities.m +++ /dev/null @@ -1,373 +0,0 @@ -// -// SPExportFileUtilities.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on July 30, 2010. -// Copyright (c) 2010 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPExportFileUtilities.h" -#import "SPExportInitializer.h" -#import "SPExporter.h" -#import "SPExportFile.h" -#import "SPDatabaseDocument.h" -#import "SPCustomQuery.h" -#import "SPTableContent.h" -#import "SPTableContentDelegate.h" -#import "SPExportController+SharedPrivateAPI.h" - -#import <SPMySQL/SPMySQL.h> - -typedef enum -{ - SPExportErrorCancelExport = 0, - SPExportErrorReplaceFiles = 1, - SPExportErrorSkipErrorFiles = 2 -} -SPExportErrorChoice; - -@interface SPExportController (SPExportFileUtilitiesPrivateAPI) -- (void)_reopenExportSheet; -@end - -@implementation SPExportController (SPExportFileUtilitiesPrivateAPI) - -/** - * Writes the CSV file header to the supplied export file. - * - * @param file The export file to write the header to. - */ -- (void)writeCSVHeaderToExportFile:(SPExportFile *)file -{ - NSMutableString *lineEnding = [NSMutableString stringWithString:[exportCSVLinesTerminatedField stringValue]]; - - // Escape tabs, line endings and carriage returns - [lineEnding replaceOccurrencesOfString:@"\\t" withString:@"\t" - options:NSLiteralSearch - range:NSMakeRange(0, [lineEnding length])]; - - - [lineEnding replaceOccurrencesOfString:@"\\n" withString:@"\n" - options:NSLiteralSearch - range:NSMakeRange(0, [lineEnding length])]; - - [lineEnding replaceOccurrencesOfString:@"\\r" withString:@"\r" - options:NSLiteralSearch - range:NSMakeRange(0, [lineEnding length])]; - - // Write the file header and the first table name - [file writeData:[[NSMutableString stringWithFormat:@"%@: %@ %@: %@ %@: %@%@%@%@ %@%@%@", - NSLocalizedString(@"Host", @"export header host label"), - [tableDocumentInstance host], - NSLocalizedString(@"Database", @"export header database label"), - [tableDocumentInstance database], - NSLocalizedString(@"Generation Time", @"export header generation time label"), - [NSDate date], - lineEnding, - lineEnding, - NSLocalizedString(@"Table", @"csv export table heading"), - [[tables objectAtIndex:0] objectAtIndex:0], - lineEnding, - lineEnding] dataUsingEncoding:[connection stringEncoding]]]; -} - -/** - * Writes the XML file header to the supplied export file. - * - * @param file The export file to write the header to. - */ -- (void)writeXMLHeaderToExportFile:(SPExportFile *)file -{ - NSMutableString *header = [NSMutableString string]; - - [header setString:@"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n\n"]; - [header appendString:@"<!--\n-\n"]; - [header appendString:@"- Sequel Pro XML dump\n"]; - [header appendFormat:@"- %@ %@\n-\n", NSLocalizedString(@"Version", @"export header version label"), [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]]; - [header appendFormat:@"- %@\n- %@\n-\n", SPLOCALIZEDURL_HOMEPAGE, SPDevURL]; - [header appendFormat:@"- %@: %@ (MySQL %@)\n", NSLocalizedString(@"Host", @"export header host label"), [tableDocumentInstance host], [tableDocumentInstance mySQLVersion]]; - [header appendFormat:@"- %@: %@\n", NSLocalizedString(@"Database", @"export header database label"), [tableDocumentInstance database]]; - [header appendFormat:@"- %@ Time: %@\n", NSLocalizedString(@"Generation Time", @"export header generation time label"), [NSDate date]]; - [header appendString:@"-\n-->\n\n"]; - - if ([exportXMLFormatPopUpButton indexOfSelectedItem] == SPXMLExportMySQLFormat) { - - NSString *tag; - - if (exportSource == SPTableExport) { - tag = [NSString stringWithFormat:@"<mysqldump xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n<database name=\"%@\">\n\n", [tableDocumentInstance database]]; - } - else { - tag = [NSString stringWithFormat:@"<resultset statement=\"%@\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n\n", (exportSource == SPFilteredExport) ? [tableContentInstance usedQuery] : [customQueryInstance usedQuery]]; - } - - [header appendString:tag]; - } - else { - [header appendFormat:@"<%@>\n\n", [[tableDocumentInstance database] HTMLEscapeString]]; - } - - [file writeData:[header dataUsingEncoding:NSUTF8StringEncoding]]; -} - -/** - * Indicates that one or more errors occurred while attempting to create the export file handles. Asks the - * user how to proceed. - * - * @param files An array of export files (SPExportFile) that failed to be created. - */ -- (void)errorCreatingExportFileHandles:(NSArray *)files -{ - // We don't know where "files" came from, but we know 2 things: - // - NSAlert will NOT retain it as contextInfo - // - This method continues execution after [alert beginSheet:...], thus even if files was retained before, it could be released before the alert ends - [files retain]; - - // Get the number of files that already exist as well as couldn't be created because of other reasons - NSUInteger filesAlreadyExisting = 0; - NSUInteger parentFoldersMissing = 0; - NSUInteger parentFoldersNotWritable = 0; - NSUInteger filesFailed = 0; - - for (SPExportFile *file in files) - { - if ([file exportFileHandleStatus] == SPExportFileHandleExists) { - filesAlreadyExisting++; - } - // For file handles that we failed to create for some unknown reason, ignore them and remove any - // exporters that are associated with them. - else if ([file exportFileHandleStatus] == SPExportFileHandleFailed) { - - filesFailed++; - - NSMutableArray *exportersToRemove = [[NSMutableArray alloc] init]; - - for (SPExporter *exporter in exporters) - { - if ([[exporter exportOutputFile] isEqualTo:file]) { - [exportersToRemove addObject:exporter]; - } - } - - [exporters removeObjectsInArray:exportersToRemove]; - - [exportersToRemove release]; - - // Check the parent folder to see if it still is present - BOOL parentIsFolder = NO; - if (![[NSFileManager defaultManager] fileExistsAtPath:[[[file exportFilePath] stringByDeletingLastPathComponent] stringByExpandingTildeInPath] isDirectory:&parentIsFolder] || !parentIsFolder) { - parentFoldersMissing++; - } else if (![[NSFileManager defaultManager] isWritableFileAtPath:[[[file exportFilePath] stringByDeletingLastPathComponent] stringByExpandingTildeInPath]]) { - parentFoldersNotWritable++; - } - } - } - - NSAlert *alert = [[NSAlert alloc] init]; - [alert setAlertStyle:NSCriticalAlertStyle]; - - // If files failed because they already existed, show a OS-like dialog. - if (filesAlreadyExisting) { - - // Set up a string for use if files had to be skipped. - NSString *additionalErrors = filesFailed ? NSLocalizedString(@"\n\n(In addition, one or more errors occurred while attempting to create the export files: %lu could not be created. These files will be ignored.)", @"Additional export file errors") : @""; - - if (filesAlreadyExisting == 1) { - [alert setMessageText:[NSString stringWithFormat:NSLocalizedString(@"“%@” already exists. Do you want to replace it?", @"Export file already exists message"), [[[files objectAtIndex:0] exportFilePath] lastPathComponent]]]; - [alert setInformativeText:[NSString stringWithFormat:@"%@%@", NSLocalizedString(@"A file with the same name already exists in the target folder. Replacing it will overwrite its current contents.", @"Export file already exists explanatory text"), additionalErrors]]; - } - else if (filesAlreadyExisting == [exportFiles count]) { - [alert setMessageText:[NSString stringWithFormat:NSLocalizedString(@"All the export files already exist. Do you want to replace them?", @"All export files already exist message")]]; - [alert setInformativeText:[NSString stringWithFormat:@"%@%@", NSLocalizedString(@"Files with the same names already exist in the target folder. Replacing them will overwrite their current contents.", @"All export files already exist explanatory text"), additionalErrors]]; - } - else { - [alert setMessageText:[NSString stringWithFormat:NSLocalizedString(@"%lu files already exist. Do you want to replace them?", @"Export file already exists message"), filesAlreadyExisting]]; - [alert setInformativeText:[NSString stringWithFormat:@"%@%@", [NSString stringWithFormat:NSLocalizedString(@"%lu files with the same names already exist in the target folder. Replacing them will overwrite their current contents.", @"Some export files already exist explanatory text"), filesAlreadyExisting], additionalErrors]]; - } - - [alert addButtonWithTitle:NSLocalizedString(@"Replace", @"Replace button")]; - [[[alert buttons] objectAtIndex:0] setTag:SPExportErrorReplaceFiles]; - [[[alert buttons] objectAtIndex:0] setKeyEquivalent:@"r"]; - [[[alert buttons] objectAtIndex:0] setKeyEquivalentModifierMask:NSCommandKeyMask]; - - [alert addButtonWithTitle:NSLocalizedString(@"Cancel", @"cancel button")]; - [[[alert buttons] objectAtIndex:1] setTag:SPExportErrorCancelExport]; - [[[alert buttons] objectAtIndex:1] setKeyEquivalent:@"\r"]; - - if ((filesAlreadyExisting + filesFailed) != [exportFiles count]) { - [alert addButtonWithTitle:NSLocalizedString(@"Skip existing", @"skip existing button")]; - [[[alert buttons] objectAtIndex:2] setTag:SPExportErrorSkipErrorFiles]; - [[[alert buttons] objectAtIndex:2] setKeyEquivalent:@"s"]; - [[[alert buttons] objectAtIndex:2] setKeyEquivalentModifierMask:NSCommandKeyMask]; - } - } - // If one or multiple files failed, but only due to unhandled errors, show a short dialog - else { - if (filesFailed == 1) { - [alert setMessageText:[NSString stringWithFormat:NSLocalizedString(@"“%@” could not be created", @"Export file creation error title"), [[[files objectAtIndex:0] exportFilePath] lastPathComponent]]]; - if (parentFoldersMissing) { - [alert setInformativeText:NSLocalizedString(@"The target export folder no longer exists. Please select a new export location and try again.", @"Export folder missing explanatory text")]; - } else if (parentFoldersNotWritable) { - [alert setInformativeText:NSLocalizedString(@"The target export folder is not writable. Please select a new export location and try again.", @"Export folder not writable explanatory text")]; - } else { - [alert setInformativeText:NSLocalizedString(@"An unhandled error occurred when attempting to create the export file. Please check the details and try again.", @"Export file creation error explanatory text")]; - } - } - else if (filesFailed == [exportFiles count]) { - [alert setMessageText:[NSString stringWithFormat:NSLocalizedString(@"No files could be created", @"All export files creation error title")]]; - if (parentFoldersMissing == [exportFiles count]) { - [alert setInformativeText:NSLocalizedString(@"The target export folder no longer exists. Please select a new export location and try again.", @"Export folder missing explanatory text")]; - } else if (parentFoldersMissing) { - [alert setInformativeText:NSLocalizedString(@"Some of the target export folders no longer exist. Please select a new export location and try again.", @"Some export folders missing explanatory text")]; - } else if (parentFoldersNotWritable) { - [alert setInformativeText:NSLocalizedString(@"Some of the target export folders are not writable. Please select a new export location and try again.", @"Some export folders not writable explanatory text")]; - } else { - [alert setInformativeText:NSLocalizedString(@"An unhandled error occurred when attempting to create each of the export files. Please check the details and try again.", @"All export files creation error explanatory text")]; - } - } - else { - [alert setMessageText:[NSString stringWithFormat:NSLocalizedString(@"%lu files could not be created", @"Export files creation error title"), filesFailed]]; - if (parentFoldersMissing) { - [alert setInformativeText:[NSString stringWithFormat:NSLocalizedString(@"%lu of the export files could not be created because their target export folder no longer exists; please select a new export location and try again.", @"Export folder missing for some files explanatory text"), parentFoldersMissing]]; - } else if (parentFoldersNotWritable) { - [alert setInformativeText:[NSString stringWithFormat:NSLocalizedString(@"%lu of the export files could not be created because their target export folder is not writable; please select a new export location and try again.", @"Export folder not writable for some files explanatory text"), parentFoldersNotWritable]]; - } else { - [alert setInformativeText:[NSString stringWithFormat:NSLocalizedString(@"An unhandled error occurred when attempting to create %lu of the export files. Please check the details and try again.", @"Export files creation error explanatory text"), filesFailed]]; - } - } - - [alert addButtonWithTitle:NSLocalizedString(@"Cancel", @"cancel button")]; - [[[alert buttons] objectAtIndex:0] setTag:SPExportErrorCancelExport]; - - if (filesFailed != [exportFiles count]) { - [alert addButtonWithTitle:NSLocalizedString(@"Skip problems", @"skip problems button")]; - [[[alert buttons] objectAtIndex:1] setTag:SPExportErrorSkipErrorFiles]; - [[[alert buttons] objectAtIndex:1] setKeyEquivalent:@"s"]; - [[[alert buttons] objectAtIndex:1] setKeyEquivalentModifierMask:NSCommandKeyMask]; - } - } - - [self _hideExportProgress]; - - [alert beginSheetModalForWindow:[tableDocumentInstance parentWindow] modalDelegate:self didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:files]; - [alert autorelease]; -} - -/** - * NSAlert didEnd method. - */ -- (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo -{ - NSArray *files = (NSArray *)contextInfo; - - // Ignore the files that exist and remove the associated exporters - if (returnCode == SPExportErrorSkipErrorFiles) { - - for (SPExportFile *file in files) { - - // Use a numerically controlled loop to avoid mutating the collection while enumerating - NSUInteger i; - for (i = 0; i < [exporters count]; i++) { - SPExporter *exporter = [exporters objectAtIndex:i]; - if ([[exporter exportOutputFile] isEqualTo:file]) { - [exporters removeObjectAtIndex:i]; - i--; - } - } - } - - [files release]; - - // If we're now left with no exporters, cancel the export operation - if ([exporters count] == 0) { - [exportFiles removeAllObjects]; - - // Trigger restoration of the export interface - [self performSelector:@selector(_reopenExportSheet) withObject:nil afterDelay:0.1]; - } - else { - // Start the export after a short delay to give this sheet a chance to close - [self performSelector:@selector(startExport) withObject:nil afterDelay:0.1]; - } - } - // Overwrite the files and continue - else if (returnCode == SPExportErrorReplaceFiles) { - - for (SPExportFile *file in files) - { - if ([file exportFileHandleStatus] == SPExportFileHandleExists) { - - if ([file createExportFileHandle:YES] == SPExportFileHandleCreated) { - [file setCompressionFormat:(SPFileCompressionFormat)[exportOutputCompressionFormatPopupButton indexOfSelectedItem]]; - - if ([file exportFileNeedsCSVHeader]) { - [self writeCSVHeaderToExportFile:file]; - } - else if ([file exportFileNeedsXMLHeader]) { - [self writeXMLHeaderToExportFile:file]; - } - } - } - } - - [files release]; - - // Start the export after a short delay to give this sheet a chance to close - [self performSelector:@selector(startExport) withObject:nil afterDelay:0.1]; - - } - // Cancel the entire export operation - else if (returnCode == SPExportErrorCancelExport) { - - // Loop the cached export files and remove those we've already created - for (SPExportFile *file in exportFiles) - { - [file delete]; - } - - [files release]; - - // Finally get rid of all the exporters and files - [exportFiles removeAllObjects]; - [exporters removeAllObjects]; - - // Trigger restoration of the export interface - [self performSelector:@selector(_reopenExportSheet) withObject:nil afterDelay:0.1]; - } -} - -/** - * Re-open the export sheet without resetting the interface - for use on error. - */ -- (void)_reopenExportSheet -{ - [NSApp beginSheet:[self window] - modalForWindow:[tableDocumentInstance parentWindow] - modalDelegate:self - didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) - contextInfo:nil]; -} - -@end diff --git a/Source/SPExportFilenameUtilities.h b/Source/SPExportFilenameUtilities.h deleted file mode 100644 index b0ba9763..00000000 --- a/Source/SPExportFilenameUtilities.h +++ /dev/null @@ -1,51 +0,0 @@ -// -// SPExportFilenameUtilities.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on July 25, 2010. -// Copyright (c) 2010 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPExportController.h" - -/** - * @category SPExportFilenameUtilities SPExportFilenameUtilities.h - * - * @author Stuart Connolly http://stuconnolly.com/ - * - * Export filename utilities category. - */ -@interface SPExportController (SPExportFilenameUtilities) - -- (void)updateDisplayedExportFilename; -- (void)updateAvailableExportFilenameTokens; -- (NSArray *)currentAllowedExportFilenameTokens; -- (NSString *)generateDefaultExportFilename; -- (NSString *)currentDefaultExportFileExtension; -- (NSString *)expandCustomFilenameFormatUsingTableName:(NSString *)table; -- (NSString *)customFilenamePathExtension; -- (BOOL)isTableTokenAllowed; - -@end diff --git a/Source/SPExportFilenameUtilities.m b/Source/SPExportFilenameUtilities.m deleted file mode 100644 index 17e01e73..00000000 --- a/Source/SPExportFilenameUtilities.m +++ /dev/null @@ -1,332 +0,0 @@ -// -// SPExportFilenameUtilities.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on July 25, 2010. -// Copyright (c) 2010 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPExportFilenameUtilities.h" -#import "SPTablesList.h" -#import "SPDatabaseViewController.h" -#import "SPExportFileNameTokenObject.h" - -@implementation SPExportController (SPExportFilenameUtilities) - -/** - * Updates the displayed export filename, either custom or default. - */ -- (void)updateDisplayedExportFilename -{ - NSString *filename = @""; - - if ([[exportCustomFilenameTokenField stringValue] length] > 0) { - - // Get the current export file extension - NSString *extension = [self currentDefaultExportFileExtension]; - - //note that there will be no tableName if the export is done from a query result without a database selected (or empty). - filename = [self expandCustomFilenameFormatUsingTableName:[[tablesListInstance tables] objectOrNilAtIndex:1]]; - - - if (![[self customFilenamePathExtension] length] && [extension length] > 0) filename = [filename stringByAppendingPathExtension:extension]; - } - else { - filename = [self generateDefaultExportFilename]; - } - - [exportCustomFilenameViewLabelButton setTitle:[NSString stringWithFormat:NSLocalizedString(@"Customize Filename (%@)", @"customize file name label"), filename]]; -} - -- (NSString *)customFilenamePathExtension -{ - NSMutableString *flatted = [NSMutableString string]; - - // This time we replace every token with "/a". This has the following effect: - // "{host}.{database}" -> "/a./a" -> extension="" - // "{database}_{date}.sql" -> "/a_/a.sql" -> extension="sql" - // That seems to be the easiest way to let NSString correctly determine if an extension is present - for (id filenamePart in [exportCustomFilenameTokenField objectValue]) - { - if([filenamePart isKindOfClass:[NSString class]]) - [flatted appendString:filenamePart]; - else if([filenamePart isKindOfClass:[SPExportFileNameTokenObject class]]) - [flatted appendString:@"/a"]; - else - [NSException raise:NSInternalInconsistencyException format:@"unknown object in token list: %@",filenamePart]; - } - - return [flatted pathExtension]; -} - -- (BOOL)isTableTokenAllowed -{ - NSUInteger i = 0; - BOOL removeTable = NO; - - BOOL isSQL = exportType == SPSQLExport; - BOOL isCSV = exportType == SPCSVExport; - BOOL isDot = exportType == SPDotExport; - BOOL isXML = exportType == SPXMLExport; - - // Determine whether to remove the table from the tokens list - if (exportSource == SPQueryExport || isDot) { - removeTable = YES; - } - else if (isSQL || isCSV || isXML) { - for (NSArray *table in tables) - { - if ([NSArrayObjectAtIndex(table, 2) boolValue]) { - i++; - if (i == 2) break; - } - } - - if (i > 1) { - removeTable = isSQL ? YES : ![exportFilePerTableCheck state]; - } - } - - return (removeTable == NO); -} - -/** - * Updates the available export filename tokens based on the currently selected options. - */ -- (void)updateAvailableExportFilenameTokens -{ - SPExportFileNameTokenObject *tableObject; - NSMutableArray *exportTokens = [NSMutableArray arrayWithObjects: - [SPExportFileNameTokenObject tokenWithId:SPFileNameDatabaseTokenName], - [SPExportFileNameTokenObject tokenWithId:SPFileNameHostTokenName], - [SPExportFileNameTokenObject tokenWithId:SPFileNameDateTokenName], - [SPExportFileNameTokenObject tokenWithId:SPFileNameYearTokenName], - [SPExportFileNameTokenObject tokenWithId:SPFileNameMonthTokenName], - [SPExportFileNameTokenObject tokenWithId:SPFileNameDayTokenName], - [SPExportFileNameTokenObject tokenWithId:SPFileNameTimeTokenName], - [SPExportFileNameTokenObject tokenWithId:SPFileName24HourTimeTokenName], - [SPExportFileNameTokenObject tokenWithId:SPFileNameFavoriteTokenName], - (tableObject = [SPExportFileNameTokenObject tokenWithId:SPFileNameTableTokenName]), - nil - ]; - - if (![self isTableTokenAllowed]) { - [exportTokens removeObject:tableObject]; - NSArray *tokenParts = [exportCustomFilenameTokenField objectValue]; - - for (id token in tokenParts) - { - if([token isEqual:tableObject]) { - NSMutableArray *newTokens = [NSMutableArray arrayWithArray:tokenParts]; - - [newTokens removeObject:tableObject]; //removes all occurances - - [exportCustomFilenameTokenField setObjectValue:newTokens]; - break; - } - } - } - - [exportCustomFilenameTokenPool setObjectValue:exportTokens]; - //update preview name as programmatically changing the exportCustomFilenameTokenField does not fire a notification - [self updateDisplayedExportFilename]; -} - -- (NSArray *)currentAllowedExportFilenameTokens -{ - NSArray *mixed = [exportCustomFilenameTokenPool objectValue]; - NSMutableArray *tokens = [NSMutableArray arrayWithCapacity:[mixed count]]; // ...or less - - for (id obj in mixed) { - if([obj isKindOfClass:[SPExportFileNameTokenObject class]]) [tokens addObject:obj]; - } - - return tokens; -} - -/** - * Generates the default export filename based on the selected export options. - * - * @return The default filename. - */ -- (NSString *)generateDefaultExportFilename -{ - NSString *filename = @""; - NSString *extension = [self currentDefaultExportFileExtension]; - - // Determine what the file name should be - switch (exportSource) - { - case SPFilteredExport: - filename = [NSString stringWithFormat:@"%@_view", [tableDocumentInstance table]]; - break; - case SPQueryExport: - filename = @"query_result"; - break; - case SPTableExport: - filename = [NSString stringWithFormat:@"%@_%@", [tableDocumentInstance database], [[NSDate date] descriptionWithCalendarFormat:@"%Y-%m-%d" timeZone:nil locale:nil]]; - break; - } - - return ([extension length] > 0) ? [filename stringByAppendingPathExtension:extension] : filename; -} - -/** - * Returns the current default export file extension based on the selected export type. - * - * @return The default filename extension. - */ -- (NSString *)currentDefaultExportFileExtension -{ - NSString *extension = @""; - - switch (exportType) { - case SPSQLExport: - extension = SPFileExtensionSQL; - break; - case SPCSVExport: - // If the tab character (\t) is selected as the feild separator return the extension as .tsv - extension = ([exportCSVFieldsTerminatedField indexOfSelectedItem] == 2) ? @"tsv" : @"csv"; - break; - case SPXMLExport: - extension = @"xml"; - break; - case SPDotExport: - extension = @"dot"; - break; - case SPPDFExport: - case SPHTMLExport: - case SPExcelExport: - default: - [NSException raise:NSInvalidArgumentException format:@"unsupported exportType=%lu",exportType]; - return nil; - } - - if ([exportOutputCompressionFormatPopupButton indexOfSelectedItem] != SPNoCompression) { - - SPFileCompressionFormat compressionFormat = (SPFileCompressionFormat)[exportOutputCompressionFormatPopupButton indexOfSelectedItem]; - - if ([extension length] > 0) { - extension = [extension stringByAppendingPathExtension:(compressionFormat == SPGzipCompression) ? @"gz" : @"bz2"]; - } - else { - extension = (compressionFormat == SPGzipCompression) ? @"gz" : @"bz2"; - } - } - - return extension; -} - -/** - * Expands the custom filename format based on the selected tokens. - * Uses the current custom filename field as a data source. - * - * @param table A table name to be used within the expanded filename. - * Can be nil. - * - * @return The expanded filename. - */ -- (NSString *)expandCustomFilenameFormatUsingTableName:(NSString *)table -{ - NSMutableString *string = [NSMutableString string]; - - NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; - [dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; - - // Walk through the token field, appending token replacements or strings - NSArray *representedFilenameParts = [exportCustomFilenameTokenField objectValue]; - - for (id filenamePart in representedFilenameParts) - { - if ([filenamePart isKindOfClass:[SPExportFileNameTokenObject class]]) { - NSString *tokenContent = [filenamePart tokenId]; - - if ([tokenContent isEqualToString:SPFileNameHostTokenName]) { - [string appendStringOrNil:[tableDocumentInstance host]]; - - } - else if ([tokenContent isEqualToString:SPFileNameDatabaseTokenName]) { - [string appendStringOrNil:[tableDocumentInstance database]]; - - } - else if ([tokenContent isEqualToString:SPFileNameTableTokenName]) { - [string appendStringOrNil:table]; - } - else if ([tokenContent isEqualToString:SPFileNameDateTokenName]) { - [dateFormatter setDateStyle:NSDateFormatterShortStyle]; - [dateFormatter setTimeStyle:NSDateFormatterNoStyle]; - [string appendString:[dateFormatter stringFromDate:[NSDate date]]]; - - } - else if ([tokenContent isEqualToString:SPFileNameYearTokenName]) { - [string appendString:[[NSDate date] descriptionWithCalendarFormat:@"%Y" timeZone:nil locale:nil]]; - - } - else if ([tokenContent isEqualToString:SPFileNameMonthTokenName]) { - [string appendString:[[NSDate date] descriptionWithCalendarFormat:@"%m" timeZone:nil locale:nil]]; - - } - else if ([tokenContent isEqualToString:SPFileNameDayTokenName]) { - [string appendString:[[NSDate date] descriptionWithCalendarFormat:@"%d" timeZone:nil locale:nil]]; - - } - else if ([tokenContent isEqualToString:SPFileNameTimeTokenName]) { - [dateFormatter setDateStyle:NSDateFormatterNoStyle]; - [dateFormatter setTimeStyle:NSDateFormatterShortStyle]; - [string appendString:[dateFormatter stringFromDate:[NSDate date]]]; - } - else if ([tokenContent isEqualToString:SPFileName24HourTimeTokenName]) { - [string appendString:[[NSDate date] descriptionWithCalendarFormat:@"%H:%M:%S" timeZone:nil locale:nil]]; - } - else if ([tokenContent isEqualToString:SPFileNameFavoriteTokenName]) { - [string appendStringOrNil:[tableDocumentInstance name]]; - } - } - else { - [string appendString:filenamePart]; - } - } - - // Replace colons with hyphens - [string replaceOccurrencesOfString:@":" - withString:@"-" - options:NSLiteralSearch - range:NSMakeRange(0, [string length])]; - - // Replace forward slashes with hyphens - [string replaceOccurrencesOfString:@"/" - withString:@"-" - options:NSLiteralSearch - range:NSMakeRange(0, [string length])]; - - [dateFormatter release]; - - // Don't allow empty strings - if an empty string resulted, revert to the default string - if (![string length]) [string setString:[self generateDefaultExportFilename]]; - - return string; -} - -@end diff --git a/Source/SPExportInitializer.h b/Source/SPExportInitializer.h deleted file mode 100644 index e8cfec73..00000000 --- a/Source/SPExportInitializer.h +++ /dev/null @@ -1,54 +0,0 @@ -// -// SPExportInitializer.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on March 31, 2010. -// Copyright (c) 2010 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPExportController.h" - -@class SPCSVExporter; -@class SPXMLExporter; - -/** - * @category SPExportInitializer SPExportInitializer.h - * - * @author Stuart Connolly http://stuconnolly.com/ - * - * Export initializer category. - */ -@interface SPExportController (SPExportInitializer) - -- (void)startExport; -- (void)exportEnded; -- (void)initializeExportUsingSelectedOptions; - -- (void)exportTables:(NSArray *)exportTables orDataArray:(NSArray *)dataArray; - -- (SPCSVExporter *)initializeCSVExporterForTable:(NSString *)table orDataArray:(NSArray *)dataArray; -- (SPXMLExporter *)initializeXMLExporterForTable:(NSString *)table orDataArray:(NSArray *)dataArray; - -@end diff --git a/Source/SPExportInitializer.m b/Source/SPExportInitializer.m deleted file mode 100644 index 9269cac8..00000000 --- a/Source/SPExportInitializer.m +++ /dev/null @@ -1,611 +0,0 @@ -// -// SPExportInitializer.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on March 31, 2010. -// Copyright (c) 2010 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPExportInitializer.h" -#import "SPTableData.h" -#import "SPDatabaseDocument.h" -#import "SPTablesList.h" -#import "SPGrowlController.h" -#import "SPDatabaseDocument.h" -#import "SPCustomQuery.h" -#import "SPAlertSheets.h" -#import "SPTableContent.h" -#import "SPCSVExporter.h" -#import "SPSQLExporter.h" -#import "SPXMLExporter.h" -#import "SPDotExporter.h" -#import "SPExportFile.h" -#import "SPExportFileUtilities.h" -#import "SPExportFilenameUtilities.h" -#import "SPExportFileNameTokenObject.h" -#import "SPConnectionControllerDelegateProtocol.h" -#import "SPExportController+SharedPrivateAPI.h" -#import "SPSQLExporterDelegate.h" - -#import <SPMySQL/SPMySQL.h> - -@implementation SPExportController (SPExportInitializer) - -/** - * Starts the export process by placing the first exporter on the operation queue. Also opens the progress - * sheet if it's not already visible. - */ -- (void)startExport -{ - // Start progress indicator - [exportProgressTitle setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Exporting %@", @"text showing that the application is importing a supplied format"), exportTypeLabel]]; - [exportProgressText setStringValue:NSLocalizedString(@"Writing...", @"text showing that app is writing text file")]; - - [exportProgressIndicator setUsesThreadedAnimation:NO]; - [exportProgressIndicator setIndeterminate:NO]; - [exportProgressIndicator setDoubleValue:0]; - - // If it's not already displayed, open the progress sheet - if (![exportProgressWindow isVisible]) { - [NSApp beginSheet:exportProgressWindow - modalForWindow:[tableDocumentInstance parentWindow] - modalDelegate:self - didEndSelector:nil - contextInfo:nil]; - } - - // cache the current connection encoding so the exporter can do what it wants. - previousConnectionEncoding = [[NSString alloc] initWithString:[connection encoding]]; - previousConnectionEncodingViaLatin1 = [connection encodingUsesLatin1Transport]; - - // Add the first exporter to the operation queue - [operationQueue addOperation:[exporters objectAtIndex:0]]; - - // Remove the exporter we just added to the operation queue from our list of exporters - // so we know it's already been done. - [exporters removeObjectAtIndex:0]; -} - -/** - * @see _queueIsEmptyAfterCancelling: - */ -- (void)exportEnded -{ - [self _hideExportProgress]; - - // Restore query mode - [tableDocumentInstance setQueryMode:SPInterfaceQueryMode]; - - // Display Growl notification - [self displayExportFinishedGrowlNotification]; - - // Restore the connection encoding to it's pre-export value - [tableDocumentInstance setConnectionEncoding:[NSString stringWithFormat:@"%@%@", previousConnectionEncoding, (previousConnectionEncodingViaLatin1) ? @"-" : @""] reloadingViews:NO]; -} - -/** - * Initializes the export process by analysing the selected criteria. - */ -- (void)initializeExportUsingSelectedOptions -{ - NSArray *dataArray = nil; - - // Get rid of the cached connection encoding - if (previousConnectionEncoding) SPClear(previousConnectionEncoding); - - createCustomFilename = ([[exportCustomFilenameTokenField stringValue] length] > 0); - - NSMutableArray *exportTables = [NSMutableArray array]; - - // Set whether or not we are to export to multiple files - [self setExportToMultipleFiles:[exportFilePerTableCheck state]]; - - // Get the data depending on the source - switch (exportSource) - { - case SPFilteredExport: - dataArray = [tableContentInstance currentDataResultWithNULLs:YES hideBLOBs:NO]; - break; - case SPQueryExport: - dataArray = [customQueryInstance currentDataResultWithNULLs:YES truncateDataFields:NO]; - break; - case SPTableExport: - // Create an array of tables to export - for (NSMutableArray *table in tables) - { - if (exportType == SPSQLExport) { - if ([[table objectAtIndex:1] boolValue] || [[table objectAtIndex:2] boolValue] || [[table objectAtIndex:3] boolValue]) { - - // Check the overall export settings - if ([[table objectAtIndex:1] boolValue] && (![exportSQLIncludeStructureCheck state])) { - [table replaceObjectAtIndex:1 withObject:@NO]; - } - - if ([[table objectAtIndex:2] boolValue] && (![exportSQLIncludeContentCheck state])) { - [table replaceObjectAtIndex:2 withObject:@NO]; - } - - if ([[table objectAtIndex:3] boolValue] && (![exportSQLIncludeDropSyntaxCheck state])) { - [table replaceObjectAtIndex:3 withObject:@NO]; - } - - [exportTables addObject:table]; - } - } - else if (exportType == SPDotExport) { - [exportTables addObject:[table objectAtIndex:0]]; - } - else { - if ([[table objectAtIndex:2] boolValue]) { - [exportTables addObject:[table objectAtIndex:0]]; - } - } - } - - break; - } - - // Set the export type label - switch (exportType) - { - case SPSQLExport: - exportTypeLabel = @"SQL"; - break; - case SPCSVExport: - exportTypeLabel = @"CSV"; - break; - case SPXMLExport: - exportTypeLabel = @"XML"; - break; - case SPDotExport: - exportTypeLabel = @"Dot"; - break; - case SPPDFExport: - case SPHTMLExport: - case SPExcelExport: - default: - [NSException raise:NSInvalidArgumentException format:@"unsupported exportType=%lu",exportType]; - return; - } - - // Begin the export based on the source - switch (exportSource) - { - case SPFilteredExport: - case SPQueryExport: - [self exportTables:nil orDataArray:dataArray]; - break; - case SPTableExport: - [self exportTables:exportTables orDataArray:nil]; - break; - } -} - -/** - * Exports the contents of the supplied array of tables or data array. - * - * Note that at least one of these parameters must not be nil. - * - * @param exportTables An array of table/view names to be exported (can be nil). - * @param dataArray A MySQL result set array to be exported (can be nil). - */ -- (void)exportTables:(NSArray *)exportTables orDataArray:(NSArray *)dataArray -{ - BOOL singleFileHandleSet = NO; - SPExportFile *singleExportFile = nil, *file = nil; - - // Change query logging mode - [tableDocumentInstance setQueryMode:SPImportExportQueryMode]; - - // Start the notification timer to allow notifications to be shown even if frontmost for long queries - [[SPGrowlController sharedGrowlController] setVisibilityForNotificationName:@"Export Finished"]; - - // Setup the progress sheet - [exportProgressTitle setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Exporting %@", @"text showing that the application is importing a supplied format"), exportTypeLabel]]; - [exportProgressText setStringValue:NSLocalizedString(@"Initializing...", @"initializing export label")]; - - [exportProgressIndicator setIndeterminate:YES]; - [exportProgressIndicator setUsesThreadedAnimation:YES]; - - // Open the progress sheet - [NSApp beginSheet:exportProgressWindow - modalForWindow:[tableDocumentInstance parentWindow] - modalDelegate:self - didEndSelector:nil - contextInfo:nil]; - - // CSV export - if (exportType == SPCSVExport) { - - SPCSVExporter *csvExporter = nil; - - // If the user has selected to only export to a single file or this is a filtered or custom query - // export, create the single file now and assign it to all subsequently created exporters. - if ((![self exportToMultipleFiles]) || (exportSource == SPFilteredExport) || (exportSource == SPQueryExport)) { - NSString *selectedTableName = nil; - - if (exportSource == SPTableExport && [exportTables count] == 1) selectedTableName = [exportTables objectAtIndex:0]; - - [exportFilename setString:createCustomFilename ? [self expandCustomFilenameFormatUsingTableName:selectedTableName] : [self generateDefaultExportFilename]]; - - // Only append the extension if necessary - if (![[exportFilename pathExtension] length]) { - [exportFilename setString:[exportFilename stringByAppendingPathExtension:[self currentDefaultExportFileExtension]]]; - } - - singleExportFile = [SPExportFile exportFileAtPath:[[exportPathField stringValue] stringByAppendingPathComponent:exportFilename]]; - } - - // Start the export process depending on the data source - if (exportSource == SPTableExport) { - - // Cache the number of tables being exported - exportTableCount = [exportTables count]; - - // Loop through the tables, creating an exporter for each - for (NSString *table in exportTables) - { - csvExporter = [self initializeCSVExporterForTable:table orDataArray:nil]; - - // If required create a single file handle for all CSV exports - if (![self exportToMultipleFiles]) { - if (!singleFileHandleSet) { - [singleExportFile setExportFileNeedsCSVHeader:YES]; - - [exportFiles addObject:singleExportFile]; - - singleFileHandleSet = YES; - } - - [csvExporter setExportOutputFile:singleExportFile]; - } - - [exporters addObject:csvExporter]; - } - } - else { - csvExporter = [self initializeCSVExporterForTable:nil orDataArray:dataArray]; - - [exportFiles addObject:singleExportFile]; - - [csvExporter setExportOutputFile:singleExportFile]; - - [exporters addObject:csvExporter]; - } - } - // SQL export - else if (exportType == SPSQLExport) { - - // Cache the number of tables being exported - exportTableCount = [exportTables count]; - - SPSQLExporter *sqlExporter = [[SPSQLExporter alloc] initWithDelegate:self]; - - [sqlExporter setSqlDatabaseHost:[tableDocumentInstance host]]; - [sqlExporter setSqlDatabaseName:[tableDocumentInstance database]]; - [sqlExporter setSqlDatabaseVersion:[tableDocumentInstance mySQLVersion]]; - - [sqlExporter setSqlOutputIncludeUTF8BOM:[exportUseUTF8BOMButton state]]; - [sqlExporter setSqlOutputEncodeBLOBasHex:[exportSQLBLOBFieldsAsHexCheck state]]; - [sqlExporter setSqlOutputIncludeErrors:[exportSQLIncludeErrorsCheck state]]; - [sqlExporter setSqlOutputIncludeAutoIncrement:([exportSQLIncludeStructureCheck state] && [exportSQLIncludeAutoIncrementValueButton state])]; - - [sqlExporter setSqlInsertAfterNValue:[exportSQLInsertNValueTextField integerValue]]; - [sqlExporter setSqlInsertDivider:[exportSQLInsertDividerPopUpButton indexOfSelectedItem]]; - - [sqlExporter setSqlExportTables:exportTables]; - - // Create custom filename if required - NSString *selectedTableName = (exportSource == SPTableExport && [exportTables count] == 1)? [[exportTables objectAtIndex:0] objectAtIndex:0] : nil; - [exportFilename setString:(createCustomFilename) ? [self expandCustomFilenameFormatUsingTableName:selectedTableName] : [self generateDefaultExportFilename]]; - - // Only append the extension if necessary - if (![[exportFilename pathExtension] length]) { - [exportFilename setString:[exportFilename stringByAppendingPathExtension:[self currentDefaultExportFileExtension]]]; - } - - file = [SPExportFile exportFileAtPath:[[exportPathField stringValue] stringByAppendingPathComponent:exportFilename]]; - - [exportFiles addObject:file]; - - [sqlExporter setExportOutputFile:file]; - - [exporters addObject:sqlExporter]; - - [sqlExporter release]; - } - // XML export - else if (exportType == SPXMLExport) { - - SPXMLExporter *xmlExporter = nil; - - // If the user has selected to only export to a single file or this is a filtered or custom query - // export, create the single file now and assign it to all subsequently created exporters. - if ((![self exportToMultipleFiles]) || (exportSource == SPFilteredExport) || (exportSource == SPQueryExport)) { - NSString *selectedTableName = nil; - if (exportSource == SPTableExport && [exportTables count] == 1) selectedTableName = [exportTables objectAtIndex:0]; - - [exportFilename setString:(createCustomFilename) ? [self expandCustomFilenameFormatUsingTableName:selectedTableName] : [self generateDefaultExportFilename]]; - - // Only append the extension if necessary - if (![[exportFilename pathExtension] length]) { - [exportFilename setString:[exportFilename stringByAppendingPathExtension:[self currentDefaultExportFileExtension]]]; - } - - singleExportFile = [SPExportFile exportFileAtPath:[[exportPathField stringValue] stringByAppendingPathComponent:exportFilename]]; - } - - // Start the export process depending on the data source - if (exportSource == SPTableExport) { - - // Cache the number of tables being exported - exportTableCount = [exportTables count]; - - // Loop through the tables, creating an exporter for each - for (NSString *table in exportTables) - { - xmlExporter = [self initializeXMLExporterForTable:table orDataArray:nil]; - - // If required create a single file handle for all XML exports - if (![self exportToMultipleFiles]) { - if (!singleFileHandleSet) { - [singleExportFile setExportFileNeedsXMLHeader:YES]; - - [exportFiles addObject:singleExportFile]; - - singleFileHandleSet = YES; - } - - [xmlExporter setExportOutputFile:singleExportFile]; - } - - [exporters addObject:xmlExporter]; - } - } - else { - xmlExporter = [self initializeXMLExporterForTable:nil orDataArray:dataArray]; - - [singleExportFile setExportFileNeedsXMLHeader:YES]; - - [exportFiles addObject:singleExportFile]; - - [xmlExporter setExportOutputFile:singleExportFile]; - - [exporters addObject:xmlExporter]; - } - } - // Dot export - else if (exportType == SPDotExport) { - - // Cache the number of tables being exported - exportTableCount = [exportTables count]; - - SPDotExporter *dotExporter = [[SPDotExporter alloc] initWithDelegate:self]; - - [dotExporter setDotTableData:tableDataInstance]; - [dotExporter setDotForceLowerTableNames:[exportDotForceLowerTableNamesCheck state]]; - [dotExporter setDotDatabaseHost:[tableDocumentInstance host]]; - [dotExporter setDotDatabaseName:[tableDocumentInstance database]]; - [dotExporter setDotDatabaseVersion:[tableDocumentInstance mySQLVersion]]; - - [dotExporter setDotExportTables:exportTables]; - - // Create custom filename if required - if (createCustomFilename) { - [exportFilename setString:[self expandCustomFilenameFormatUsingTableName:nil]]; - } - else { - [exportFilename setString:[tableDocumentInstance database]]; - } - - // Only append the extension if necessary - if (![[exportFilename pathExtension] length]) { - [exportFilename setString:[exportFilename stringByAppendingPathExtension:[self currentDefaultExportFileExtension]]]; - } - - file = [SPExportFile exportFileAtPath:[[exportPathField stringValue] stringByAppendingPathComponent:exportFilename]]; - - [exportFiles addObject:file]; - - [dotExporter setExportOutputFile:file]; - - [exporters addObject:dotExporter]; - - [dotExporter release]; - } - - // For each of the created exporters, set their generic properties - for (SPExporter *exporter in exporters) - { - [exporter setConnection:connection]; - [exporter setServerSupport:[self serverSupport]]; - [exporter setExportOutputEncoding:[connection stringEncoding]]; - [exporter setExportMaxProgress:(NSInteger)[exportProgressIndicator bounds].size.width]; - [exporter setExportUsingLowMemoryBlockingStreaming:([exportProcessLowMemoryButton state] == NSOnState)]; - [exporter setExportOutputCompressionFormat:(SPFileCompressionFormat)[exportOutputCompressionFormatPopupButton indexOfSelectedItem]]; - [exporter setExportOutputCompressFile:([exportOutputCompressionFormatPopupButton indexOfSelectedItem] != SPNoCompression)]; - } - - NSMutableArray *problemFiles = [[NSMutableArray alloc] init]; - - // Create the actual file handles while dealing with errors (e.g. file already exists, etc) during creation - for (SPExportFile *exportFile in exportFiles) - { - if ([exportFile createExportFileHandle:NO] == SPExportFileHandleCreated) { - - [exportFile setCompressionFormat:(SPFileCompressionFormat)[exportOutputCompressionFormatPopupButton indexOfSelectedItem]]; - - if ([exportFile exportFileNeedsCSVHeader]) { - [self writeCSVHeaderToExportFile:exportFile]; - } - else if ([exportFile exportFileNeedsXMLHeader]) { - [self writeXMLHeaderToExportFile:exportFile]; - } - } - else { - [problemFiles addObject:exportFile]; - } - } - - // Deal with any file handles that we failed to create for whatever reason - if ([problemFiles count] > 0) { - [self errorCreatingExportFileHandles:problemFiles]; - } - else { - [self startExport]; - } - - [problemFiles release]; -} - -/** - * Initialises a CSV exporter for the supplied table name or data array. - * - * @param table The table name for which the exporter should be cerated for (can be nil). - * @param dataArray The MySQL result data array for which the exporter should be created for (can be nil). - */ -- (SPCSVExporter *)initializeCSVExporterForTable:(NSString *)table orDataArray:(NSArray *)dataArray -{ - SPCSVExporter *csvExporter = [[SPCSVExporter alloc] initWithDelegate:self]; - - // Depeding on the export source, set the table name or data array - if (exportSource == SPTableExport) { - [csvExporter setCsvTableName:table]; - } - else { - [csvExporter setCsvDataArray:dataArray]; - } - - [csvExporter setCsvTableData:tableDataInstance]; - [csvExporter setCsvOutputFieldNames:[exportCSVIncludeFieldNamesCheck state]]; - [csvExporter setCsvFieldSeparatorString:[exportCSVFieldsTerminatedField stringValue]]; - [csvExporter setCsvEnclosingCharacterString:[exportCSVFieldsWrappedField stringValue]]; - [csvExporter setCsvLineEndingString:[exportCSVLinesTerminatedField stringValue]]; - [csvExporter setCsvEscapeString:[exportCSVFieldsEscapedField stringValue]]; - [csvExporter setCsvNULLString:[exportCSVNULLValuesAsTextField stringValue]]; - - // If required create separate files - if (exportSource == SPTableExport && [self exportToMultipleFiles]) { - - if (createCustomFilename) { - - // Create custom filename based on the selected format - [exportFilename setString:[self expandCustomFilenameFormatUsingTableName:table]]; - - // If the user chose to use a custom filename format and we exporting to multiple files, make - // sure the table name is included to ensure the output files are unique. - if (exportTableCount > 1) { - BOOL tableNameInTokens = NO; - NSArray *representedObjects = [exportCustomFilenameTokenField objectValue]; - for (id representedObject in representedObjects) { - if ([representedObject isKindOfClass:[SPExportFileNameTokenObject class]] && [[representedObject tokenId] isEqualToString:SPFileNameTableTokenName]) tableNameInTokens = YES; - } - [exportFilename setString:(tableNameInTokens ? exportFilename : [exportFilename stringByAppendingFormat:@"_%@", table])]; - } - } - else { - [exportFilename setString:(dataArray) ? [tableDocumentInstance database] : table]; - } - - // Only append the extension if necessary - if (![[exportFilename pathExtension] length]) { - [exportFilename setString:[exportFilename stringByAppendingPathExtension:[self currentDefaultExportFileExtension]]]; - } - - SPExportFile *file = [SPExportFile exportFileAtPath:[[exportPathField stringValue] stringByAppendingPathComponent:exportFilename]]; - - [exportFiles addObject:file]; - - [csvExporter setExportOutputFile:file]; - } - - return [csvExporter autorelease]; -} - -/** - * Initialises a XML exporter for the supplied table name or data array. - * - * @param table The table name for which the exporter should be cerated for (can be nil). - * @param dataArray The MySQL result data array for which the exporter should be created for (can be nil). - */ -- (SPXMLExporter *)initializeXMLExporterForTable:(NSString *)table orDataArray:(NSArray *)dataArray -{ - SPXMLExporter *xmlExporter = [[SPXMLExporter alloc] initWithDelegate:self]; - - // if required set the data array - if (exportSource != SPTableExport) { - [xmlExporter setXmlDataArray:dataArray]; - } - - // Regardless of the export source, set exporter's table name as it's used in the output - // of table and table content exports. - [xmlExporter setXmlTableName:table]; - - [xmlExporter setXmlFormat:[exportXMLFormatPopUpButton indexOfSelectedItem]]; - [xmlExporter setXmlOutputIncludeStructure:[exportXMLIncludeStructure state]]; - [xmlExporter setXmlOutputIncludeContent:[exportXMLIncludeContent state]]; - [xmlExporter setXmlNULLString:[exportXMLNULLValuesAsTextField stringValue]]; - - // If required create separate files - if ((exportSource == SPTableExport) && exportToMultipleFiles && (exportTableCount > 0)) { - - if (createCustomFilename) { - - // Create custom filename based on the selected format - [exportFilename setString:[self expandCustomFilenameFormatUsingTableName:table]]; - - // If the user chose to use a custom filename format and we exporting to multiple files, make - // sure the table name is included to ensure the output files are unique. - if (exportTableCount > 1) { - BOOL tableNameInTokens = NO; - NSArray *representedObjects = [exportCustomFilenameTokenField objectValue]; - for (id representedObject in representedObjects) { - if ([representedObject isKindOfClass:[SPExportFileNameTokenObject class]] && [[representedObject tokenId] isEqualToString:SPFileNameTableTokenName]) tableNameInTokens = YES; - } - [exportFilename setString:(tableNameInTokens ? exportFilename : [exportFilename stringByAppendingFormat:@"_%@", table])]; - } - } - else { - [exportFilename setString:(dataArray) ? [tableDocumentInstance database] : table]; - } - - // Only append the extension if necessary - if (![[exportFilename pathExtension] length]) { - [exportFilename setString:[exportFilename stringByAppendingPathExtension:[self currentDefaultExportFileExtension]]]; - } - - SPExportFile *file = [SPExportFile exportFileAtPath:[[exportPathField stringValue] stringByAppendingPathComponent:exportFilename]]; - - [file setExportFileNeedsXMLHeader:YES]; - - [exportFiles addObject:file]; - - [xmlExporter setExportOutputFile:file]; - } - - return [xmlExporter autorelease]; -} - -@end diff --git a/Source/SPExportInterfaceController.h b/Source/SPExportInterfaceController.h deleted file mode 100644 index 8205f99b..00000000 --- a/Source/SPExportInterfaceController.h +++ /dev/null @@ -1,42 +0,0 @@ -// -// SPExportInterfaceController.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on March 31, 2012. -// Copyright (c) 2012 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPExportController.h" - -/** - * @category SPExportInterfaceController SPExportInterfaceController.h - * - * @author Stuart Connolly http://stuconnolly.com/ - * - * Export interface category. - */ -@interface SPExportController (SPExportInterfaceController) - -@end diff --git a/Source/SPExportInterfaceController.m b/Source/SPExportInterfaceController.m deleted file mode 100644 index 0011a0a8..00000000 --- a/Source/SPExportInterfaceController.m +++ /dev/null @@ -1,150 +0,0 @@ -// -// SPExportInterfaceController.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on March 31, 2012. -// Copyright (c) 2012 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPExportInterfaceController.h" - -@implementation SPExportController (SPExportInterfaceController) - -/** - * Resizes the export window's height by the supplied delta, while retaining the position of - * all interface controls to accommodate the custom filename view. - * - * @param delta The height delta for which the height should be adjusted for. - */ -- (void)_resizeWindowForCustomFilenameViewByHeightDelta:(NSInteger)delta -{ - NSUInteger popUpMask = [exportInputPopUpButton autoresizingMask]; - NSUInteger fileCheckMask = [exportFilePerTableCheck autoresizingMask]; - NSUInteger scrollMask = [exportTablelistScrollView autoresizingMask]; - NSUInteger buttonBarMask = [exportTableListButtonBar autoresizingMask]; - NSUInteger buttonMask = [exportCustomFilenameViewButton autoresizingMask]; - NSUInteger textFieldMask = [exportCustomFilenameViewLabelButton autoresizingMask]; - NSUInteger customFilenameViewMask = [exportCustomFilenameView autoresizingMask]; - NSUInteger tabBarMask = [exportOptionsTabBar autoresizingMask]; - - NSRect frame = [[self window] frame]; - - if (frame.size.height > 600 && delta > heightOffset1) { - frame.origin.y += [exportCustomFilenameView frame].size.height; - frame.size.height -= [exportCustomFilenameView frame].size.height; - - [[self window] setFrame:frame display:YES animate:YES]; - } - - [exportInputPopUpButton setAutoresizingMask:NSViewNotSizable | NSViewMaxYMargin]; - [exportFilePerTableCheck setAutoresizingMask:NSViewNotSizable | NSViewMaxYMargin]; - [exportTablelistScrollView setAutoresizingMask:NSViewNotSizable | NSViewMaxYMargin]; - [exportTableListButtonBar setAutoresizingMask:NSViewNotSizable | NSViewMaxYMargin]; - [exportOptionsTabBar setAutoresizingMask:NSViewNotSizable | NSViewMaxYMargin]; - [exportCustomFilenameViewButton setAutoresizingMask:NSViewNotSizable | NSViewMinYMargin]; - [exportCustomFilenameViewLabelButton setAutoresizingMask:NSViewNotSizable | NSViewMinYMargin]; - [exportCustomFilenameView setAutoresizingMask:NSViewNotSizable | NSViewMinYMargin]; - - NSInteger newMinHeight = (windowMinHeigth - heightOffset1 + delta < windowMinHeigth) ? windowMinHeigth : windowMinHeigth - heightOffset1 + delta; - - [[self window] setMinSize:NSMakeSize(windowMinWidth, newMinHeight)]; - - frame.origin.y += heightOffset1; - frame.size.height -= heightOffset1; - - heightOffset1 = delta; - - frame.origin.y -= heightOffset1; - frame.size.height += heightOffset1; - - [[self window] setFrame:frame display:YES animate:YES]; - - [exportInputPopUpButton setAutoresizingMask:popUpMask]; - [exportFilePerTableCheck setAutoresizingMask:fileCheckMask]; - [exportTablelistScrollView setAutoresizingMask:scrollMask]; - [exportTableListButtonBar setAutoresizingMask:buttonBarMask]; - [exportCustomFilenameViewButton setAutoresizingMask:buttonMask]; - [exportCustomFilenameViewLabelButton setAutoresizingMask:textFieldMask]; - [exportCustomFilenameView setAutoresizingMask:customFilenameViewMask]; - [exportOptionsTabBar setAutoresizingMask:tabBarMask]; -} - -/** - * Resizes the export window's height by the supplied delta, while retaining the position of - * all interface controls to accommodate the advanced options view. - * - * @param delta The height delta for which the height should be adjusted for. - */ -- (void)_resizeWindowForAdvancedOptionsViewByHeightDelta:(NSInteger)delta -{ - NSUInteger scrollMask = [exportTablelistScrollView autoresizingMask]; - NSUInteger buttonBarMask = [exportTableListButtonBar autoresizingMask]; - NSUInteger tabBarMask = [exportTypeTabBar autoresizingMask]; - NSUInteger optionsTabBarMask = [exportOptionsTabBar autoresizingMask]; - NSUInteger buttonMask = [exportAdvancedOptionsViewButton autoresizingMask]; - NSUInteger textFieldMask = [exportAdvancedOptionsViewLabelButton autoresizingMask]; - NSUInteger advancedViewMask = [exportAdvancedOptionsView autoresizingMask]; - - NSRect frame = [[self window] frame]; - - if (frame.size.height > 600 && delta > heightOffset2) { - frame.origin.y += [exportAdvancedOptionsView frame].size.height; - frame.size.height -= [exportAdvancedOptionsView frame].size.height; - - [[self window] setFrame:frame display:YES animate:YES]; - } - - [exportTablelistScrollView setAutoresizingMask:NSViewNotSizable | NSViewMinYMargin]; - [exportTableListButtonBar setAutoresizingMask:NSViewNotSizable | NSViewMinYMargin]; - [exportTypeTabBar setAutoresizingMask:NSViewNotSizable | NSViewMinYMargin]; - [exportOptionsTabBar setAutoresizingMask:NSViewNotSizable | NSViewMinYMargin]; - [exportAdvancedOptionsViewButton setAutoresizingMask:NSViewNotSizable | NSViewMinYMargin]; - [exportAdvancedOptionsViewLabelButton setAutoresizingMask:NSViewNotSizable | NSViewMinYMargin]; - [exportAdvancedOptionsView setAutoresizingMask:NSViewNotSizable | NSViewMinYMargin]; - - NSInteger newMinHeight = (windowMinHeigth - heightOffset2 + delta < windowMinHeigth) ? windowMinHeigth : windowMinHeigth - heightOffset2 + delta; - - [[self window] setMinSize:NSMakeSize(windowMinWidth, newMinHeight)]; - - frame.origin.y += heightOffset2; - frame.size.height -= heightOffset2; - - heightOffset2 = delta; - - frame.origin.y -= heightOffset2; - frame.size.height += heightOffset2; - - [[self window] setFrame:frame display:YES animate:YES]; - - [exportTablelistScrollView setAutoresizingMask:scrollMask]; - [exportTableListButtonBar setAutoresizingMask:buttonBarMask]; - [exportTypeTabBar setAutoresizingMask:tabBarMask]; - [exportOptionsTabBar setAutoresizingMask:optionsTabBarMask]; - [exportAdvancedOptionsViewButton setAutoresizingMask:buttonMask]; - [exportAdvancedOptionsViewLabelButton setAutoresizingMask:textFieldMask]; - [exportAdvancedOptionsView setAutoresizingMask:advancedViewMask]; -} - -@end diff --git a/Source/SPExportSettingsPersistence.h b/Source/SPExportSettingsPersistence.h deleted file mode 100644 index 41609125..00000000 --- a/Source/SPExportSettingsPersistence.h +++ /dev/null @@ -1,64 +0,0 @@ -// -// SPExportSettingsPersistence.h -// sequel-pro -// -// Created by Max Lohrmann on 09.10.15. -// Copyright (c) 2015 Max Lohrmann. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import <Foundation/Foundation.h> -#import "SPExportController.h" - -@interface SPExportController (SPExportSettingsPersistence) - -- (IBAction)exportCurrentSettings:(id)sender; -- (IBAction)importCurrentSettings:(id)sender; - -/** - * @return The current settings as a dictionary which can be serialized - */ -- (NSDictionary *)currentSettingsAsDictionary; - -/** Overwrite current export settings with those defined in dict - * @param dict The new settings to apply (passing nil is an error.) - * @param err Errors while applying (will mostly be about invalid format, type) - * Can pass NULL, if not interested in details. - * Will NOT be changed unless the method also returns NO - * @return success - */ -- (BOOL)applySettingsFromDictionary:(NSDictionary *)dict error:(NSError **)err; - -/** - * @return A serialized form of the "custom filename" field - */ -- (NSArray *)currentCustomFilenameAsArray; - -/** - * @param tokenList A serialized form of the "custom filename" field - * @see currentCustomFilenameAsArray - */ -- (void)setCustomFilenameFromArray:(NSArray *)tokenList; - -@end diff --git a/Source/SPExportSettingsPersistence.m b/Source/SPExportSettingsPersistence.m deleted file mode 100644 index 33118e84..00000000 --- a/Source/SPExportSettingsPersistence.m +++ /dev/null @@ -1,803 +0,0 @@ -// -// SPExportSettingsPersistence.m -// sequel-pro -// -// Created by Max Lohrmann on 09.10.15. -// Copyright (c) 2015 Max Lohrmann. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPExportSettingsPersistence.h" -#import "SPExportFileNameTokenObject.h" -#import "SPExportFilenameUtilities.h" -#import "SPExportController+SharedPrivateAPI.h" - -/** - * converts a ([obj state] == NSOnState) to @YES / @NO - * (because doing @([obj state] == NSOnState) will result in an integer 0/1) - */ -static inline NSNumber *IsOn(id obj); -/** - * Sets the state of obj to NSOnState or NSOffState based on the value of ref - */ -static inline void SetOnOff(NSNumber *ref,id obj); - -@interface SPExportController (Private) - -- (void)_updateExportAdvancedOptionsLabel; - -@end - -@interface SPExportController (SPExportSettingsPersistence_Private) - -// those methods will convert the name of a C enum constant to a NSString -+ (NSString *)describeExportSource:(SPExportSource)es; -+ (NSString *)describeExportType:(SPExportType)et; -+ (NSString *)describeCompressionFormat:(SPFileCompressionFormat)cf; -+ (NSString *)describeXMLExportFormat:(SPXMLExportFormat)xf; -+ (NSString *)describeSQLExportInsertDivider:(SPSQLExportInsertDivider)eid; - -// these will store the C enum constant named by NSString in dst and return YES, -// if a valid mapping exists. Otherwise will just return NO and not modify dst. -+ (BOOL)copyExportSourceForDescription:(NSString *)esd to:(SPExportSource *)dst; -+ (BOOL)copyCompressionFormatForDescription:(NSString *)esd to:(SPFileCompressionFormat *)dst; -+ (BOOL)copyExportTypeForDescription:(NSString *)esd to:(SPExportType *)dst; -+ (BOOL)copyXMLExportFormatForDescription:(NSString *)xfd to:(SPXMLExportFormat *)dst; -+ (BOOL)copySQLExportInsertDividerForDescription:(NSString *)xfd to:(SPSQLExportInsertDivider *)dst; - -- (NSDictionary *)exporterSettings; -- (NSDictionary *)csvSettings; -- (NSDictionary *)dotSettings; -- (NSDictionary *)xmlSettings; -- (NSDictionary *)sqlSettings; - -- (void)applyExporterSettings:(NSDictionary *)settings; -- (void)applyCsvSettings:(NSDictionary *)settings; -- (void)applyDotSettings:(NSDictionary *)settings; -- (void)applyXmlSettings:(NSDictionary *)settings; -- (void)applySqlSettings:(NSDictionary *)settings; - -- (id)exporterSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type; -- (id)dotSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type; -- (id)xmlSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type; -- (id)csvSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type; -- (id)sqlSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type; - -- (void)applyExporterSpecificSettings:(id)settings forSchemaObject:(NSString *)name ofType:(SPTableType)type; -- (void)applyDotSpecificSettings:(id)settings forSchemaObject:(NSString *)name ofType:(SPTableType)type; -- (void)applyXmlSpecificSettings:(id)settings forSchemaObject:(NSString *)name ofType:(SPTableType)type; -- (void)applyCsvSpecificSettings:(id)settings forSchemaObject:(NSString *)name ofType:(SPTableType)type; -- (void)applySqlSpecificSettings:(id)settings forSchemaObject:(NSString *)name ofType:(SPTableType)type; - -@end - -#pragma mark - - -@implementation SPExportController (SPExportSettingsPersistence) - -#define NAMEOF(x) case x: return @#x -#define VALUEOF(x,y,dst) if([y isEqualToString:@#x]) { *dst = x; return YES; } - -+ (NSString *)describeExportSource:(SPExportSource)es -{ - switch (es) { - NAMEOF(SPFilteredExport); - NAMEOF(SPQueryExport); - NAMEOF(SPTableExport); - } - return nil; -} - -+ (BOOL)copyExportSourceForDescription:(NSString *)esd to:(SPExportSource *)dst -{ - VALUEOF(SPFilteredExport, esd,dst); - VALUEOF(SPQueryExport, esd,dst); - VALUEOF(SPTableExport, esd,dst); - return NO; -} - -+ (NSString *)describeExportType:(SPExportType)et -{ - switch (et) { - NAMEOF(SPSQLExport); - NAMEOF(SPCSVExport); - NAMEOF(SPXMLExport); - NAMEOF(SPDotExport); - NAMEOF(SPPDFExport); - NAMEOF(SPHTMLExport); - NAMEOF(SPExcelExport); - NAMEOF(SPAnyExportType); - } - return nil; -} - -+ (BOOL)copyExportTypeForDescription:(NSString *)etd to:(SPExportType *)dst -{ - VALUEOF(SPSQLExport, etd, dst); - VALUEOF(SPCSVExport, etd, dst); - VALUEOF(SPXMLExport, etd, dst); - VALUEOF(SPDotExport, etd, dst); - //VALUEOF(SPPDFExport, etd, dst); - //VALUEOF(SPHTMLExport, etd, dst); - //VALUEOF(SPExcelExport, etd, dst); - return NO; -} - -+ (NSString *)describeCompressionFormat:(SPFileCompressionFormat)cf -{ - switch (cf) { - NAMEOF(SPNoCompression); - NAMEOF(SPGzipCompression); - NAMEOF(SPBzip2Compression); - } - return nil; -} - -+ (BOOL)copyCompressionFormatForDescription:(NSString *)cfd to:(SPFileCompressionFormat *)dst -{ - VALUEOF(SPNoCompression, cfd, dst); - VALUEOF(SPGzipCompression, cfd, dst); - VALUEOF(SPBzip2Compression, cfd, dst); - return NO; -} - -+ (NSString *)describeXMLExportFormat:(SPXMLExportFormat)xf -{ - switch (xf) { - NAMEOF(SPXMLExportMySQLFormat); - NAMEOF(SPXMLExportPlainFormat); - } - return nil; -} - -+ (BOOL)copyXMLExportFormatForDescription:(NSString *)xfd to:(SPXMLExportFormat *)dst -{ - VALUEOF(SPXMLExportMySQLFormat, xfd, dst); - VALUEOF(SPXMLExportPlainFormat, xfd, dst); - return NO; -} - -+ (NSString *)describeSQLExportInsertDivider:(SPSQLExportInsertDivider)eid -{ - switch (eid) { - NAMEOF(SPSQLInsertEveryNDataBytes); - NAMEOF(SPSQLInsertEveryNRows); - } - return nil; -} - -+ (BOOL)copySQLExportInsertDividerForDescription:(NSString *)eidd to:(SPSQLExportInsertDivider *)dst -{ - VALUEOF(SPSQLInsertEveryNDataBytes, eidd, dst); - VALUEOF(SPSQLInsertEveryNRows, eidd, dst); - return NO; -} - -#undef NAMEOF -#undef VALUEOF - -- (IBAction)importCurrentSettings:(id)sender -{ - //show open file dialog - NSOpenPanel *panel = [NSOpenPanel openPanel]; - - [panel setAllowedFileTypes:@[SPFileExtensionDefault]]; - [panel setAllowsOtherFileTypes:YES]; - - [panel beginSheetModalForWindow:[self window] completionHandler:^(NSInteger result) { - if(result != NSFileHandlingPanelOKButton) return; - - [panel orderOut:nil]; // Panel is still on screen. Hide it first. (This is Apple's recommended way) - - NSError *err = nil; - NSData *plist = [NSData dataWithContentsOfURL:[panel URL] - options:0 - error:&err]; - - NSDictionary *settings = nil; - if(!err) { - settings = [NSPropertyListSerialization propertyListWithData:plist - options:NSPropertyListImmutable - format:NULL - error:&err]; - } - - if(!err) { - [self applySettingsFromDictionary:settings error:&err]; - if(!err) return; - } - - // give an explanation for some errors - if([[err domain] isEqualToString:SPErrorDomain]) { - if([err code] == SPErrorWrongTypeOrNil) { - NSDictionary *info = @{ - NSLocalizedDescriptionKey: NSLocalizedString(@"Invalid file supplied!", @"export : import settings : file error title"), - NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"The selected file is either not a valid SPF file or severely corrupted.", @"export : import settings : file error description"), - }; - err = [NSError errorWithDomain:[err domain] code:[err code] userInfo:info]; - } - else if([err code] == SPErrorWrongContentType) { - NSDictionary *info = @{ - NSLocalizedDescriptionKey: NSLocalizedString(@"Wrong SPF content type!", @"export : import settings : spf content type error title"), - NSLocalizedRecoverySuggestionErrorKey: [NSString stringWithFormat:NSLocalizedString(@"The selected file contains data of type “%1$@”, but type “%2$@” is needed. Please choose a different file.", @"export : import settings : spf content type error description"),[[err userInfo] objectForKey:@"isType"],[[err userInfo] objectForKey:@"expectedType"]], - }; - err = [NSError errorWithDomain:[err domain] code:[err code] userInfo:info]; - } - } - - NSAlert *alert = [NSAlert alertWithError:err]; - [alert setAlertStyle:NSCriticalAlertStyle]; - [alert runModal]; - }]; -} - -- (IBAction)exportCurrentSettings:(id)sender -{ - //show save file dialog - NSSavePanel *panel = [NSSavePanel savePanel]; - - [panel setAllowedFileTypes:@[SPFileExtensionDefault]]; - - [panel setExtensionHidden:NO]; - [panel setAllowsOtherFileTypes:NO]; - [panel setCanSelectHiddenExtension:YES]; - [panel setCanCreateDirectories:YES]; - - [panel beginSheetModalForWindow:[self window] completionHandler:^(NSInteger returnCode) { - if(returnCode != NSFileHandlingPanelOKButton) return; - - // Panel is still on screen. Hide it first. (This is Apple's recommended way) - [panel orderOut:nil]; - - NSError *err = nil; - NSData *plist = [NSPropertyListSerialization dataWithPropertyList:[self currentSettingsAsDictionary] - format:NSPropertyListXMLFormat_v1_0 - options:0 - error:&err]; - if(!err) { - [plist writeToURL:[panel URL] options:NSAtomicWrite error:&err]; - if(!err) return; - } - - NSAlert *alert = [NSAlert alertWithError:err]; - [alert setAlertStyle:NSCriticalAlertStyle]; - [alert runModal]; - }]; -} - -- (NSArray *)currentCustomFilenameAsArray -{ - NSArray *tokenListIn = [exportCustomFilenameTokenField objectValue]; - NSMutableArray *tokenListOut = [NSMutableArray arrayWithCapacity:[tokenListIn count]]; - - for (id obj in tokenListIn) { - if([obj isKindOfClass:[NSString class]]) { - [tokenListOut addObject:obj]; - } - else if([obj isKindOfClass:[SPExportFileNameTokenObject class]]) { - NSDictionary *tokenProperties = @{@"tokenId": [obj tokenId]}; - // in the future the dict can be used to store per-token settings - [tokenListOut addObject:tokenProperties]; - } - else { - SPLog(@"unknown object in token list: %@",obj); - } - } - - return tokenListOut; -} - -- (void)setCustomFilenameFromArray:(NSArray *)tokenList -{ - NSMutableArray *tokenListOut = [NSMutableArray arrayWithCapacity:[tokenList count]]; - NSArray *allowedTokens = [self currentAllowedExportFilenameTokens]; - - for (id obj in tokenList) { - if([obj isKindOfClass:[NSString class]]) { - [tokenListOut addObject:obj]; - } - else if([obj isKindOfClass:[NSDictionary class]]) { - //there must be at least a non-empty tokenId that is also in the token pool - NSString *tokenId = [obj objectForKey:@"tokenId"]; - if([tokenId length]) { - SPExportFileNameTokenObject *token = [SPExportFileNameTokenObject tokenWithId:tokenId]; - if([allowedTokens containsObject:token]) { - [tokenListOut addObject:token]; - continue; - } - } - SPLog(@"Ignoring an invalid or unknown token with tokenId=%@",tokenId); - } - else { - SPLog(@"unknown object in import token list: %@",obj); - } - } - - [exportCustomFilenameTokenField setObjectValue:tokenListOut]; - - [self updateDisplayedExportFilename]; -} - -- (NSDictionary *)currentSettingsAsDictionary -{ - NSMutableDictionary *root = [NSMutableDictionary dictionary]; - - [root setObject:SPFExportSettingsContentType forKey:SPFFormatKey]; - [root setObject:@1 forKey:SPFVersionKey]; - - [root setObject:[exportPathField stringValue] forKey:@"exportPath"]; - - [root setObject:[[self class] describeExportSource:exportSource] forKey:@"exportSource"]; - [root setObject:[[self class] describeExportType:exportType] forKey:@"exportType"]; - - if([[exportCustomFilenameTokenField stringValue] length] > 0) { - [root setObject:[self currentCustomFilenameAsArray] forKey:@"customFilename"]; - } - - [root setObject:[self exporterSettings] forKey:@"settings"]; - - if(exportSource == SPTableExport) { - NSMutableDictionary *perObjectSettings = [NSMutableDictionary dictionaryWithCapacity:[tables count]]; - - for (NSMutableArray *table in tables) { - NSString *key = [table objectAtIndex:0]; - id settings = [self exporterSpecificSettingsForSchemaObject:key ofType:SPTableTypeTable]; - if(settings) - [perObjectSettings setObject:settings forKey:key]; - } - - [root setObject:perObjectSettings forKey:@"schemaObjects"]; - } - - [root setObject:IsOn(exportProcessLowMemoryButton) forKey:@"lowMemoryStreaming"]; - [root setObject:[[self class] describeCompressionFormat:(SPFileCompressionFormat)[exportOutputCompressionFormatPopupButton indexOfSelectedItem]] forKey:@"compressionFormat"]; - - return root; -} - -- (BOOL)applySettingsFromDictionary:(NSDictionary *)dict error:(NSError **)err -{ - //check for dict/nil - if(![dict isKindOfClass:[NSDictionary class]]) { - if(err) { - *err = [NSError errorWithDomain:SPErrorDomain - code:SPErrorWrongTypeOrNil - userInfo:nil]; // we don't know where data came from, so we can't provide meaningful help to the user - } - return NO; - } - - //check for export settings - NSString *ctype = [dict objectForKey:SPFFormatKey]; - if (![SPFExportSettingsContentType isEqualToString:ctype]) { - if(err) { - NSDictionary *errInfo = @{ - @"isType": ctype, - @"expectedType": SPFExportSettingsContentType - }; - *err = [NSError errorWithDomain:SPErrorDomain - code:SPErrorWrongContentType - userInfo:errInfo]; - } - return NO; - } - - //check for version - NSInteger version = [[dict objectForKey:SPFVersionKey] integerValue]; - if(version != 1) { - if(err) { - NSDictionary *errInfo = @{ - @"isVersion": @(version), - NSLocalizedDescriptionKey: NSLocalizedString(@"Unsupported version for export settings!", @"export : import settings : file version error title"), - NSLocalizedRecoverySuggestionErrorKey: [NSString stringWithFormat:NSLocalizedString(@"The selected export settings were stored with version\u00A0%1$ld, but only settings with the following versions can be imported: %2$@.\n\nEither save the settings in a backwards compatible way or update your version of Sequel Pro.", @"export : import settings : file version error description ($1 = is version, $2 = list of supported versions); note: the u00A0 is a non-breaking space, do not add more whitespace."),version,@"1"], - }; - *err = [NSError errorWithDomain:SPErrorDomain - code:SPErrorWrongContentVersion - userInfo:errInfo]; - } - return NO; - } - - //ok, we can try to import that... - - [exporters removeAllObjects]; - [exportFiles removeAllObjects]; - - id o; - if((o = [dict objectForKey:@"exportPath"])) [exportPathField setStringValue:o]; - - SPExportType et; - if((o = [dict objectForKey:@"exportType"]) && [[self class] copyExportTypeForDescription:o to:&et]) { - [exportTypeTabBar selectTabViewItemAtIndex:et]; - } - - //exportType should be changed first, as exportSource depends on it - SPExportSource es; - if((o = [dict objectForKey:@"exportSource"]) && [[self class] copyExportSourceForDescription:o to:&es]) { - [self setExportInput:es]; //try to set it. might fail e.g. if the settings were saved with "query result" but right now no custom query result exists - } - - // set exporter specific settings - [self applyExporterSettings:[dict objectForKey:@"settings"]]; - - // load schema object settings - if(exportSource == SPTableExport) { - NSDictionary *perObjectSettings = [dict objectForKey:@"schemaObjects"]; - - for (NSString *table in [perObjectSettings allKeys]) { - id settings = [perObjectSettings objectForKey:table]; - [self applyExporterSpecificSettings:settings forSchemaObject:table ofType:SPTableTypeTable]; - } - - [exportTableList reloadData]; - } - - if((o = [dict objectForKey:@"lowMemoryStreaming"])) [exportProcessLowMemoryButton setState:([o boolValue] ? NSOnState : NSOffState)]; - - SPFileCompressionFormat cf; - if((o = [dict objectForKey:@"compressionFormat"]) && [[self class] copyCompressionFormatForDescription:o to:&cf]) [exportOutputCompressionFormatPopupButton selectItemAtIndex:cf]; - - // might have changed - [self _updateExportAdvancedOptionsLabel]; - - // token pool is only valid once the schema object selection is done - [self updateAvailableExportFilenameTokens]; - if((o = [dict objectForKey:@"customFilename"]) && [o isKindOfClass:[NSArray class]]) [self setCustomFilenameFromArray:o]; - - return YES; -} - -- (NSDictionary *)exporterSettings -{ - switch (exportType) { - case SPCSVExport: - return [self csvSettings]; - case SPSQLExport: - return [self sqlSettings]; - case SPXMLExport: - return [self xmlSettings]; - case SPDotExport: - return [self dotSettings]; - case SPExcelExport: - case SPHTMLExport: - case SPPDFExport: - default: - @throw [NSException exceptionWithName:NSInternalInconsistencyException - reason:@"exportType not implemented!" - userInfo:@{@"exportType": @(exportType)}]; - } -} - -- (void)applyExporterSettings:(NSDictionary *)settings -{ - switch (exportType) { - case SPCSVExport: - return [self applyCsvSettings:settings]; - case SPSQLExport: - return [self applySqlSettings:settings]; - case SPXMLExport: - return [self applyXmlSettings:settings]; - case SPDotExport: - return [self applyDotSettings:settings]; - case SPExcelExport: - case SPHTMLExport: - case SPPDFExport: - default: - @throw [NSException exceptionWithName:NSInternalInconsistencyException - reason:@"exportType not implemented!" - userInfo:@{@"exportType": @(exportType)}]; - } -} - -- (NSDictionary *)csvSettings -{ - return @{ - @"exportToMultipleFiles": IsOn(exportFilePerTableCheck), - @"CSVIncludeFieldNames": IsOn(exportCSVIncludeFieldNamesCheck), - @"CSVFieldsTerminated": [exportCSVFieldsTerminatedField stringValue], - @"CSVFieldsWrapped": [exportCSVFieldsWrappedField stringValue], - @"CSVLinesTerminated": [exportCSVLinesTerminatedField stringValue], - @"CSVFieldsEscaped": [exportCSVFieldsEscapedField stringValue], - @"CSVNULLValuesAsText": [exportCSVNULLValuesAsTextField stringValue] - }; -} - -- (void)applyCsvSettings:(NSDictionary *)settings -{ - id o; - if((o = [settings objectForKey:@"exportToMultipleFiles"])) SetOnOff(o,exportFilePerTableCheck); - [self toggleNewFilePerTable:nil]; - - if((o = [settings objectForKey:@"CSVIncludeFieldNames"])) SetOnOff(o, exportCSVIncludeFieldNamesCheck); - if((o = [settings objectForKey:@"CSVFieldsTerminated"])) [exportCSVFieldsTerminatedField setStringValue:o]; - if((o = [settings objectForKey:@"CSVFieldsWrapped"])) [exportCSVFieldsWrappedField setStringValue:o]; - if((o = [settings objectForKey:@"CSVLinesTerminated"])) [exportCSVLinesTerminatedField setStringValue:o]; - if((o = [settings objectForKey:@"CSVFieldsEscaped"])) [exportCSVFieldsEscapedField setStringValue:o]; - if((o = [settings objectForKey:@"CSVNULLValuesAsText"])) [exportCSVNULLValuesAsTextField setStringValue:o]; -} - -- (NSDictionary *)dotSettings -{ - return @{@"DotForceLowerTableNames": IsOn(exportDotForceLowerTableNamesCheck)}; -} - -- (void)applyDotSettings:(NSDictionary *)settings -{ - id o; - if((o = [settings objectForKey:@"DotForceLowerTableNames"])) SetOnOff(o, exportDotForceLowerTableNamesCheck); -} - -- (NSDictionary *)xmlSettings -{ - return @{ - @"exportToMultipleFiles": IsOn(exportFilePerTableCheck), - @"XMLFormat": [[self class] describeXMLExportFormat:(SPXMLExportFormat)[exportXMLFormatPopUpButton indexOfSelectedItem]], - @"XMLOutputIncludeStructure": IsOn(exportXMLIncludeStructure), - @"XMLOutputIncludeContent": IsOn(exportXMLIncludeContent), - @"XMLNULLString": [exportXMLNULLValuesAsTextField stringValue] - }; -} - -- (void)applyXmlSettings:(NSDictionary *)settings -{ - id o; - SPXMLExportFormat xmlf; - if((o = [settings objectForKey:@"exportToMultipleFiles"])) SetOnOff(o, exportFilePerTableCheck); - [self toggleNewFilePerTable:nil]; - - if((o = [settings objectForKey:@"XMLFormat"]) && [[self class] copyXMLExportFormatForDescription:o to:&xmlf]) [exportXMLFormatPopUpButton selectItemAtIndex:xmlf]; - if((o = [settings objectForKey:@"XMLOutputIncludeStructure"])) SetOnOff(o, exportXMLIncludeStructure); - if((o = [settings objectForKey:@"XMLOutputIncludeContent"])) SetOnOff(o, exportXMLIncludeContent); - if((o = [settings objectForKey:@"XMLNULLString"])) [exportXMLNULLValuesAsTextField setStringValue:o]; - - [self toggleXMLOutputFormat:exportXMLFormatPopUpButton]; -} - -- (NSDictionary *)sqlSettings -{ - BOOL includeStructure = ([exportSQLIncludeStructureCheck state] == NSOnState); - - NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:@{ - @"SQLIncludeStructure": IsOn(exportSQLIncludeStructureCheck), - @"SQLIncludeContent": IsOn(exportSQLIncludeContentCheck), - @"SQLIncludeErrors": IsOn(exportSQLIncludeErrorsCheck), - @"SQLIncludeDROP": IsOn(exportSQLIncludeDropSyntaxCheck), - @"SQLUseUTF8BOM": IsOn(exportUseUTF8BOMButton), - @"SQLBLOBFieldsAsHex": IsOn(exportSQLBLOBFieldsAsHexCheck), - @"SQLInsertNValue": @([exportSQLInsertNValueTextField integerValue]), - @"SQLInsertDivider": [[self class] describeSQLExportInsertDivider:(SPSQLExportInsertDivider)[exportSQLInsertDividerPopUpButton indexOfSelectedItem]] - }]; - - if(includeStructure) { - [dict addEntriesFromDictionary:@{ - @"SQLIncludeAutoIncrementValue": IsOn(exportSQLIncludeAutoIncrementValueButton), - @"SQLIncludeDropSyntax": IsOn(exportSQLIncludeDropSyntaxCheck) - }]; - } - - return dict; -} - -- (void)applySqlSettings:(NSDictionary *)settings -{ - id o; - SPSQLExportInsertDivider div; - - if((o = [settings objectForKey:@"SQLIncludeContent"])) SetOnOff(o, exportSQLIncludeContentCheck); - [self toggleSQLIncludeContent:exportSQLIncludeContentCheck]; - - if((o = [settings objectForKey:@"SQLIncludeDROP"])) SetOnOff(o, exportSQLIncludeDropSyntaxCheck); - [self toggleSQLIncludeDropSyntax:exportSQLIncludeDropSyntaxCheck]; - - if((o = [settings objectForKey:@"SQLIncludeStructure"])) SetOnOff(o, exportSQLIncludeStructureCheck); - [self toggleSQLIncludeStructure:exportSQLIncludeStructureCheck]; - - if((o = [settings objectForKey:@"SQLIncludeErrors"])) SetOnOff(o, exportSQLIncludeErrorsCheck); - if((o = [settings objectForKey:@"SQLUseUTF8BOM"])) SetOnOff(o, exportUseUTF8BOMButton); - if((o = [settings objectForKey:@"SQLBLOBFieldsAsHex"])) SetOnOff(o, exportSQLBLOBFieldsAsHexCheck); - if((o = [settings objectForKey:@"SQLInsertNValue"])) [exportSQLInsertNValueTextField setIntegerValue:[o integerValue]]; - if((o = [settings objectForKey:@"SQLInsertDivider"]) && [[self class] copySQLExportInsertDividerForDescription:o to:&div]) [exportSQLInsertDividerPopUpButton selectItemAtIndex:div]; - - if([exportSQLIncludeStructureCheck state] == NSOnState) { - if((o = [settings objectForKey:@"SQLIncludeAutoIncrementValue"])) SetOnOff(o, exportSQLIncludeAutoIncrementValueButton); - if((o = [settings objectForKey:@"SQLIncludeDropSyntax"])) SetOnOff(o, exportSQLIncludeDropSyntaxCheck); - } -} - -- (id)exporterSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type -{ - switch (exportType) { - case SPCSVExport: - return [self csvSpecificSettingsForSchemaObject:name ofType:type]; - case SPSQLExport: - return [self sqlSpecificSettingsForSchemaObject:name ofType:type]; - case SPXMLExport: - return [self xmlSpecificSettingsForSchemaObject:name ofType:type]; - case SPDotExport: - return [self dotSpecificSettingsForSchemaObject:name ofType:type]; - case SPExcelExport: - case SPHTMLExport: - case SPPDFExport: - default: - @throw [NSException exceptionWithName:NSInternalInconsistencyException - reason:@"exportType not implemented!" - userInfo:@{@"exportType": @(exportType)}]; - } -} - -- (void)applyExporterSpecificSettings:(id)settings forSchemaObject:(NSString *)name ofType:(SPTableType)type -{ - switch (exportType) { - case SPCSVExport: - return [self applyCsvSpecificSettings:settings forSchemaObject:name ofType:type]; - case SPSQLExport: - return [self applySqlSpecificSettings:settings forSchemaObject:name ofType:type]; - case SPXMLExport: - return [self applyXmlSpecificSettings:settings forSchemaObject:name ofType:type]; - case SPDotExport: - return [self applyDotSpecificSettings:settings forSchemaObject:name ofType:type]; - case SPExcelExport: - case SPHTMLExport: - case SPPDFExport: - default: - @throw [NSException exceptionWithName:NSInternalInconsistencyException - reason:@"exportType not implemented!" - userInfo:@{@"exportType": @(exportType)}]; - } -} - -- (id)dotSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type -{ - // Dot is a graph of the whole database - nothing to pick from - return nil; -} - -- (void)applyDotSpecificSettings:(id)settings forSchemaObject:(NSString *)name ofType:(SPTableType)type -{ - //should never be called -} - -- (id)xmlSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type -{ - // XML per table setting is only yes/no - if(type == SPTableTypeTable) { - // we have to look through the table views' rows to find the current checkbox value... - for (NSArray *table in tables) { - if([[table objectAtIndex:0] isEqualTo:name]) { - return @([[table objectAtIndex:2] boolValue]); - } - } - } - return nil; -} - -- (void)applyXmlSpecificSettings:(id)settings forSchemaObject:(NSString *)name ofType:(SPTableType)type -{ - // XML per table setting is only yes/no - if(type == SPTableTypeTable) { - // we have to look through the table views' rows to find the appropriate table... - for (NSMutableArray *table in tables) { - if([[table objectAtIndex:0] isEqualTo:name]) { - [table replaceObjectAtIndex:2 withObject:@([settings boolValue])]; - return; - } - } - } -} - -- (id)csvSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type -{ - // CSV per table setting is only yes/no - if(type == SPTableTypeTable) { - // we have to look through the table views rows to find the current checkbox value... - for (NSArray *table in tables) { - if([[table objectAtIndex:0] isEqualTo:name]) { - return @([[table objectAtIndex:2] boolValue]); - } - } - } - return nil; -} - -- (void)applyCsvSpecificSettings:(id)settings forSchemaObject:(NSString *)name ofType:(SPTableType)type -{ - // CSV per table setting is only yes/no - if(type == SPTableTypeTable) { - // we have to look through the table views' rows to find the appropriate table... - for (NSMutableArray *table in tables) { - if([[table objectAtIndex:0] isEqualTo:name]) { - [table replaceObjectAtIndex:2 withObject:@([settings boolValue])]; - return; - } - } - } -} - -- (id)sqlSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type -{ - BOOL structure = ([exportSQLIncludeStructureCheck state] == NSOnState); - BOOL content = ([exportSQLIncludeContentCheck state] == NSOnState); - BOOL drop = ([exportSQLIncludeDropSyntaxCheck state] == NSOnState); - - // SQL allows per table setting of structure/content/drop table - if(type == SPTableTypeTable) { - // we have to look through the table views rows to find the current checkbox value... - for (NSArray *table in tables) { - if([[table objectAtIndex:0] isEqualTo:name]) { - NSMutableArray *flags = [NSMutableArray arrayWithCapacity:3]; - - if (structure && [[table objectAtIndex:1] boolValue]) { - [flags addObject:@"structure"]; - } - - if (content && [[table objectAtIndex:2] boolValue]) { - [flags addObject:@"content"]; - } - - if (drop && [[table objectAtIndex:3] boolValue]) { - [flags addObject:@"drop"]; - } - - return flags; - } - } - } - return nil; -} - -- (void)applySqlSpecificSettings:(id)settings forSchemaObject:(NSString *)name ofType:(SPTableType)type -{ - BOOL structure = ([exportSQLIncludeStructureCheck state] == NSOnState); - BOOL content = ([exportSQLIncludeContentCheck state] == NSOnState); - BOOL drop = ([exportSQLIncludeDropSyntaxCheck state] == NSOnState); - - // SQL allows per table setting of structure/content/drop table - if(type == SPTableTypeTable) { - // we have to look through the table views' rows to find the appropriate table... - for (NSMutableArray *table in tables) { - if([[table objectAtIndex:0] isEqualTo:name]) { - NSArray *flags = settings; - - [table replaceObjectAtIndex:1 withObject:@((structure && [flags containsObject:@"structure"]))]; - [table replaceObjectAtIndex:2 withObject:@((content && [flags containsObject:@"content"]))]; - [table replaceObjectAtIndex:3 withObject:@((drop && [flags containsObject:@"drop"]))]; - return; - } - } - } -} - -@end - -#pragma mark - - -NSNumber *IsOn(id obj) -{ - return (([obj state] == NSOnState)? @YES : @NO); -} - -void SetOnOff(NSNumber *ref,id obj) -{ - [obj setState:([ref boolValue] ? NSOnState : NSOffState)]; -} diff --git a/Source/SPExtendedTableInfo.m b/Source/SPExtendedTableInfo.m index 35a102a9..57d765de 100644 --- a/Source/SPExtendedTableInfo.m +++ b/Source/SPExtendedTableInfo.m @@ -34,7 +34,6 @@ #import "RegexKitLite.h" #import "SPDatabaseData.h" #import "SPDatabaseDocument.h" -#import "SPDatabaseViewController.h" #import "SPTablesList.h" #import "SPAlertSheets.h" #import "SPTableStructure.h" diff --git a/Source/SPFavoritesOutlineView.m b/Source/SPFavoritesOutlineView.m index 8dbec5da..6a64cca1 100644 --- a/Source/SPFavoritesOutlineView.m +++ b/Source/SPFavoritesOutlineView.m @@ -29,7 +29,7 @@ // More info at <https://github.com/sequelpro/sequelpro> #import "SPFavoritesOutlineView.h" -#import "SPConnectionControllerDelegate.h" +#import "SPConnectionController.h" @interface SPFavoritesOutlineView () @@ -191,12 +191,12 @@ static NSUInteger SPFavoritesOutlineViewUnindent = 6; /** - * If the delegate is a SPConnectionControllerDelegate, and editing is currently in + * If the delegate is a SPConnectionController, and editing is currently in * progress, draw a custom highlight. */ - (void)highlightSelectionInClipRect:(NSRect)clipRect { - // Only proceed if a the delegate is a SPConnectionControllerDelegate and a favoruite being edited + // Only proceed if a the delegate is a SPConnectionController and a favorite being edited if ([[self delegate] isKindOfClass:[SPConnectionController class]] && [(SPConnectionController *)[self delegate] isEditingConnection] && [(SPConnectionController *)[self delegate] selectedFavorite]) diff --git a/Source/SPFieldEditorController.h b/Source/SPFieldEditorController.h index 0c0d0a0e..2c47aee6 100644 --- a/Source/SPFieldEditorController.h +++ b/Source/SPFieldEditorController.h @@ -254,3 +254,10 @@ - (void)setDoGroupDueToChars; @end + +@protocol SPFieldEditorControllerDelegate <NSObject> + +@optional +- (void)processFieldEditorResult:(id)data contextInfo:(NSDictionary*)contextInfo; + +@end diff --git a/Source/SPFieldEditorController.m b/Source/SPFieldEditorController.m index f1212c81..865816d7 100644 --- a/Source/SPFieldEditorController.m +++ b/Source/SPFieldEditorController.m @@ -47,12 +47,6 @@ typedef enum { HexSegment } FieldEditorSegment; -@interface SPFieldEditorController (SPFieldEditorControllerDelegate) - -- (void)processFieldEditorResult:(id)data contextInfo:(NSDictionary*)contextInfo; - -@end - @implementation SPFieldEditorController @synthesize editedFieldInfo; @@ -682,7 +676,9 @@ typedef enum { else if ( [callerInstance isKindOfClass:[SPTableContent class]] ) [(SPTableContent*)callerInstance processFieldEditorResult:returnData contextInfo:contextInfo]; #else - [callerInstance processFieldEditorResult:returnData contextInfo:contextInfo]; + if([callerInstance respondsToSelector:@selector(processFieldEditorResult:contextInfo:)]) { + [(id <SPFieldEditorControllerDelegate>)callerInstance processFieldEditorResult:returnData contextInfo:contextInfo]; + } #endif } } diff --git a/Source/SPFieldMapperController.m b/Source/SPFieldMapperController.m index ee80174a..4dbf8aa9 100644 --- a/Source/SPFieldMapperController.m +++ b/Source/SPFieldMapperController.m @@ -52,7 +52,7 @@ static NSString *SPTableViewSqlColumnID = @"sql"; static NSUInteger SPSourceColumnTypeText = 0; static NSUInteger SPSourceColumnTypeInteger = 1; -@interface SPFieldMapperController (Private) +@interface SPFieldMapperController () - (void)_setupFieldMappingPopUpMenus; @end diff --git a/Source/SPGeometryDataView.m b/Source/SPGeometryDataView.m index 996c1cf8..a94b0f43 100644 --- a/Source/SPGeometryDataView.m +++ b/Source/SPGeometryDataView.m @@ -30,7 +30,7 @@ #import "SPGeometryDataView.h" -@interface SPGeometryDataView (PrivateAPI) +@interface SPGeometryDataView () - (NSPoint)_normalizePoint:(NSPoint)aPoint; - (void)_drawPoint:(NSPoint)aPoint; @@ -282,12 +282,8 @@ [super dealloc]; } -@end - #pragma mark - -#pragma mark PrivateAPI - -@implementation SPGeometryDataView (PrivateAPI) +#pragma mark Private API /** * Converts original NSPoint to target coordinates diff --git a/Source/SPGotoDatabaseController.m b/Source/SPGotoDatabaseController.m index 61ec8962..96a81ca8 100644 --- a/Source/SPGotoDatabaseController.m +++ b/Source/SPGotoDatabaseController.m @@ -30,7 +30,7 @@ #import "SPGotoDatabaseController.h" -@interface SPGotoDatabaseController (Private) +@interface SPGotoDatabaseController () /** Update the list of matched names * @param filter The string to be matched. diff --git a/Source/SPHTMLExporterDelegate.h b/Source/SPHTMLExporterDelegate.h deleted file mode 100644 index a206839b..00000000 --- a/Source/SPHTMLExporterDelegate.h +++ /dev/null @@ -1,43 +0,0 @@ -// -// SPHTMLExporterDelegate.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on April 24, 2010. -// Copyright (c) 2010 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPExportController.h" -#import "SPHTMLExporterProtocol.h" - -/** - * @category SPHTMLExporterDelegate SPHTMLExporterDelegate.h - * - * @author Stuart Connolly http://stuconnolly.com/ - * - * HTML exporter delegate category. - */ -@interface SPExportController (SPHTMLExporterDelegate) <SPHTMLExporterProtocol> - -@end diff --git a/Source/SPHTMLExporterDelegate.m b/Source/SPHTMLExporterDelegate.m deleted file mode 100644 index 0d69f651..00000000 --- a/Source/SPHTMLExporterDelegate.m +++ /dev/null @@ -1,49 +0,0 @@ -// -// SPHTMLExporterDelegate.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on April 24, 2010. -// Copyright (c) 2010 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPHTMLExporterDelegate.h" -#import "SPExportInitializer.h" - -@implementation SPExportController (SPHTMLExporterDelegate) - -- (void)htmlExportProcessWillBegin:(SPHTMLExporter *)exporter -{ -} - -- (void)htmlExportProcessComplete:(SPHTMLExporter *)exporter -{ - [self exportEnded]; -} - -- (void)htmlExportProcessWillBeginWritingData:(SPHTMLExporter *)exporter -{ -} - -@end diff --git a/Source/SPHistoryController.m b/Source/SPHistoryController.m index ec05c4af..2abac6bb 100644 --- a/Source/SPHistoryController.m +++ b/Source/SPHistoryController.m @@ -32,7 +32,6 @@ #import "SPTableContent.h" #import "SPTablesList.h" #import "SPHistoryController.h" -#import "SPDatabaseViewController.h" #import "SPThreadAdditions.h" @implementation SPHistoryController diff --git a/Source/SPImageView.h b/Source/SPImageView.h index 873f4075..3db5dee0 100644 --- a/Source/SPImageView.h +++ b/Source/SPImageView.h @@ -29,8 +29,8 @@ // // More info at <https://github.com/sequelpro/sequelpro> -@interface NSObject (SPImageViewDelegate) - +@protocol SPImageViewDelegate <NSObject> +@optional - (void)processUpdatedImageData:(NSData *)data; - (void)processPasteImageData; diff --git a/Source/SPImageView.m b/Source/SPImageView.m index d56b7fa5..76a9b1ff 100644 --- a/Source/SPImageView.m +++ b/Source/SPImageView.m @@ -39,7 +39,7 @@ */ - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender { - id delegateForUse = nil; + id<SPImageViewDelegate> delegateForUse = nil; // If the delegate or the delegate's content instance doesn't implement processUpdatedImageData:, // return the super's implementation @@ -113,7 +113,7 @@ - (void)paste:(id)sender { // [super paste:sender]; - id delegateForUse = nil; + id<SPImageViewDelegate> delegateForUse = nil; // If the delegate or the delegate's content instance doesn't implement processUpdatedImageData:, // return the super's implementation diff --git a/Source/SPIndexesController.m b/Source/SPIndexesController.m index dcf01ccb..c5b5d14c 100644 --- a/Source/SPIndexesController.m +++ b/Source/SPIndexesController.m @@ -36,9 +36,7 @@ #import "SPDatabaseDocument.h" #import "SPTablesList.h" #import "SPTableView.h" -#import "SPDatabaseViewController.h" #import "SPTableStructure.h" -#import "SPTableStructureLoading.h" #import "SPThreadAdditions.h" #import "SPFunctions.h" diff --git a/Source/SPNarrowDownCompletion.m b/Source/SPNarrowDownCompletion.m index c2158299..fd6fd38c 100644 --- a/Source/SPNarrowDownCompletion.m +++ b/Source/SPNarrowDownCompletion.m @@ -38,7 +38,6 @@ #import "SPQueryController.h" #import "RegexKitLite.h" #import "SPTextView.h" -#import "SPQueryDocumentsController.h" #import "SPDatabaseStructure.h" #pragma mark - @@ -53,7 +52,7 @@ @end -@interface SPNarrowDownCompletion (Private) +@interface SPNarrowDownCompletion () - (NSRect)rectOfMainScreen; - (NSString*)filterString; diff --git a/Source/SPNavigatorController.m b/Source/SPNavigatorController.m index 108727fb..a7d8b61b 100644 --- a/Source/SPNavigatorController.m +++ b/Source/SPNavigatorController.m @@ -39,8 +39,6 @@ #import "SPLogger.h" #import "SPTooltip.h" #import "SPAppController.h" -#import "SPAppleScriptSupport.h" -#import "SPDatabaseViewController.h" #import "SPDatabaseStructure.h" #import "SPThreadAdditions.h" diff --git a/Source/SPNetworkPreferencePane.m b/Source/SPNetworkPreferencePane.m index c734f07b..95387871 100644 --- a/Source/SPNetworkPreferencePane.m +++ b/Source/SPNetworkPreferencePane.m @@ -33,7 +33,7 @@ static NSString *SPSSLCipherListMarkerItem = @"--"; static NSString *SPSSLCipherPboardTypeName = @"SSLCipherPboardType"; -@interface SPNetworkPreferencePane (Private) +@interface SPNetworkPreferencePane () - (void)updateHiddenFiles; - (void)loadSSLCiphers; - (void)storeSSLCiphers; diff --git a/Source/SPPDFExporterDelegate.h b/Source/SPPDFExporterDelegate.h deleted file mode 100644 index a44b1fa8..00000000 --- a/Source/SPPDFExporterDelegate.h +++ /dev/null @@ -1,43 +0,0 @@ -// -// SPPDFExporterDelegate.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on April 24, 2010. -// Copyright (c) 2010 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPExportController.h" -#import "SPPDFExporterProtocol.h" - -/** - * @category SPPDFExporterDelegate SPPDFExporterDelegate.h - * - * @author Stuart Connolly http://stuconnolly.com/ - * - * PDF exporter delegate category. - */ -@interface SPExportController (SPPDFExporterDelegate) <SPPDFExporterProtocol> - -@end diff --git a/Source/SPPDFExporterDelegate.m b/Source/SPPDFExporterDelegate.m deleted file mode 100644 index b6b6eef7..00000000 --- a/Source/SPPDFExporterDelegate.m +++ /dev/null @@ -1,49 +0,0 @@ -// -// SPPDFExporterDelegate.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on April 24, 2010. -// Copyright (c) 2010 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPPDFExporterDelegate.h" -#import "SPExportInitializer.h" - -@implementation SPExportController (SPPDFExporterDelegate) - -- (void)pdfExportProcessWillBegin:(SPPDFExporter *)exporter -{ -} - -- (void)pdfExportProcessComplete:(SPPDFExporter *)exporter -{ - [self exportEnded]; -} - -- (void)pdfExportProcessWillBeginWritingData:(SPPDFExporter *)exporter -{ -} - -@end diff --git a/Source/SPPreferenceController.m b/Source/SPPreferenceController.m index fad1d95b..c782c591 100644 --- a/Source/SPPreferenceController.m +++ b/Source/SPPreferenceController.m @@ -33,7 +33,7 @@ #import "SPEditorPreferencePane.h" #import "SPGeneralPreferencePane.h" -@interface SPPreferenceController (PrivateAPI) +@interface SPPreferenceController () <NSWindowDelegate> - (void)_setupToolbar; - (void)_resizeWindowForContentView:(NSView *)view; @@ -226,6 +226,103 @@ [view setFrameOrigin:NSMakePoint(0, 0)]; } +#pragma mark - SPPreferenceControllerDelegate + +#pragma mark Window delegate methods + +/** + * Trap window close notifications and use them to ensure changes are saved. + */ +- (void)windowWillClose:(NSNotification *)notification +{ + [[NSColorPanel sharedColorPanel] close]; + + // Mark the currently selected field in the window as having finished editing, to trigger saves. + if ([[self window] firstResponder]) { + [[self window] endEditingFor:[[self window] firstResponder]]; + } +} + +/** + * Trap window resize notifications and use them to disable resizing on most tabs + * - except for the favourites tab. + */ +- (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)frameSize +{ + [[NSColorPanel sharedColorPanel] close]; + + return [sender showsResizeIndicator] ? frameSize : [sender frame].size; +} + +#pragma mark - +#pragma mark Toolbar delegate methods + +- (NSToolbarItem *)toolbar:(NSToolbar *)aToolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag +{ + if ([itemIdentifier isEqualToString:SPPreferenceToolbarGeneral]) { + return generalItem; + } + else if ([itemIdentifier isEqualToString:SPPreferenceToolbarTables]) { + return tablesItem; + } + else if ([itemIdentifier isEqualToString:SPPreferenceToolbarNotifications]) { + return notificationsItem; + } + else if ([itemIdentifier isEqualToString:SPPreferenceToolbarAutoUpdate]) { + return autoUpdateItem; + } + else if ([itemIdentifier isEqualToString:SPPreferenceToolbarNetwork]) { + return networkItem; + } + else if ([itemIdentifier isEqualToString:SPPreferenceToolbarEditor]) { + return editorItem; + } + else if ([itemIdentifier isEqualToString:SPPreferenceToolbarShortcuts]) { + return shortcutItem; + } + + return [[[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier] autorelease]; +} + +- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)aToolbar +{ + return @[ + SPPreferenceToolbarGeneral, + SPPreferenceToolbarTables, + SPPreferenceToolbarNotifications, + SPPreferenceToolbarEditor, + SPPreferenceToolbarShortcuts, + SPPreferenceToolbarAutoUpdate, + SPPreferenceToolbarNetwork + ]; +} + +- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)aToolbar +{ + return @[ + SPPreferenceToolbarGeneral, + SPPreferenceToolbarTables, + SPPreferenceToolbarNotifications, + SPPreferenceToolbarEditor, + SPPreferenceToolbarShortcuts, + SPPreferenceToolbarAutoUpdate, + SPPreferenceToolbarNetwork + ]; +} + +- (NSArray *)toolbarSelectableItemIdentifiers:(NSToolbar *)aToolbar +{ + return @[ + SPPreferenceToolbarGeneral, + SPPreferenceToolbarTables, + SPPreferenceToolbarNotifications, + SPPreferenceToolbarEditor, + SPPreferenceToolbarShortcuts, + SPPreferenceToolbarAutoUpdate, + SPPreferenceToolbarNetwork + ]; +} + #pragma mark - - (void)dealloc diff --git a/Source/SPPreferenceControllerDelegate.h b/Source/SPPreferenceControllerDelegate.h deleted file mode 100644 index bcfcb271..00000000 --- a/Source/SPPreferenceControllerDelegate.h +++ /dev/null @@ -1,42 +0,0 @@ -// -// SPPreferenceControllerDelegate.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on October 31, 2010. -// Copyright (c) 2010 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPPreferenceController.h" - -/** - * @category SPPreferenceControllerDelegate SPPreferenceControllerDelegate.h - * - * @author Stuart Connolly http://stuconnolly.com/ - * - * Preference controller delegate category. - */ -@interface SPPreferenceController (SPPreferenceControllerDelegate) <NSWindowDelegate> - -@end diff --git a/Source/SPPreferenceControllerDelegate.m b/Source/SPPreferenceControllerDelegate.m deleted file mode 100644 index 6a16de0f..00000000 --- a/Source/SPPreferenceControllerDelegate.m +++ /dev/null @@ -1,131 +0,0 @@ -// -// SPPreferenceControllerDelegate.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on October 31, 2010. -// Copyright (c) 2010 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPPreferenceControllerDelegate.h" - -@implementation SPPreferenceController (SPPreferenceControllerDelegate) - -#pragma mark - -#pragma mark Window delegate methods - -/** - * Trap window close notifications and use them to ensure changes are saved. - */ -- (void)windowWillClose:(NSNotification *)notification -{ - [[NSColorPanel sharedColorPanel] close]; - - // Mark the currently selected field in the window as having finished editing, to trigger saves. - if ([[self window] firstResponder]) { - [[self window] endEditingFor:[[self window] firstResponder]]; - } -} - -/** - * Trap window resize notifications and use them to disable resizing on most tabs - * - except for the favourites tab. - */ -- (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)frameSize -{ - [[NSColorPanel sharedColorPanel] close]; - - return [sender showsResizeIndicator] ? frameSize : [sender frame].size; -} - -#pragma mark - -#pragma mark Toolbar delegate methods - -- (NSToolbarItem *)toolbar:(NSToolbar *)aToolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag -{ - if ([itemIdentifier isEqualToString:SPPreferenceToolbarGeneral]) { - return generalItem; - } - else if ([itemIdentifier isEqualToString:SPPreferenceToolbarTables]) { - return tablesItem; - } - else if ([itemIdentifier isEqualToString:SPPreferenceToolbarNotifications]) { - return notificationsItem; - } - else if ([itemIdentifier isEqualToString:SPPreferenceToolbarAutoUpdate]) { - return autoUpdateItem; - } - else if ([itemIdentifier isEqualToString:SPPreferenceToolbarNetwork]) { - return networkItem; - } - else if ([itemIdentifier isEqualToString:SPPreferenceToolbarEditor]) { - return editorItem; - } - else if ([itemIdentifier isEqualToString:SPPreferenceToolbarShortcuts]) { - return shortcutItem; - } - - return [[[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier] autorelease]; -} - -- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)aToolbar -{ - return @[ - SPPreferenceToolbarGeneral, - SPPreferenceToolbarTables, - SPPreferenceToolbarNotifications, - SPPreferenceToolbarEditor, - SPPreferenceToolbarShortcuts, - SPPreferenceToolbarAutoUpdate, - SPPreferenceToolbarNetwork - ]; -} - -- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)aToolbar -{ - return @[ - SPPreferenceToolbarGeneral, - SPPreferenceToolbarTables, - SPPreferenceToolbarNotifications, - SPPreferenceToolbarEditor, - SPPreferenceToolbarShortcuts, - SPPreferenceToolbarAutoUpdate, - SPPreferenceToolbarNetwork - ]; -} - -- (NSArray *)toolbarSelectableItemIdentifiers:(NSToolbar *)aToolbar -{ - return @[ - SPPreferenceToolbarGeneral, - SPPreferenceToolbarTables, - SPPreferenceToolbarNotifications, - SPPreferenceToolbarEditor, - SPPreferenceToolbarShortcuts, - SPPreferenceToolbarAutoUpdate, - SPPreferenceToolbarNetwork - ]; -} - -@end diff --git a/Source/SPPrintController.h b/Source/SPPrintController.h deleted file mode 100644 index a9780a2d..00000000 --- a/Source/SPPrintController.h +++ /dev/null @@ -1,42 +0,0 @@ -// -// SPPrintController.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on March 11, 2010. -// Copyright (c) 2010 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPDatabaseDocument.h" - -@interface SPDatabaseDocument (SPPrintController) - -- (void)startPrintDocumentOperation; -- (void)generateHTMLForPrinting; -- (void)generateTableInfoHTMLForPrinting; - -- (NSArray *)columnNames; -- (NSMutableDictionary *)connectionInformation; - -@end diff --git a/Source/SPPrintController.m b/Source/SPPrintController.m deleted file mode 100644 index ab2cda9a..00000000 --- a/Source/SPPrintController.m +++ /dev/null @@ -1,461 +0,0 @@ -// -// SPPrintController.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on March 11, 2010. -// Copyright (c) 2010 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPPrintController.h" -#import "SPTableContent.h" -#import "SPTableStructure.h" -#import "SPCustomQuery.h" -#import "SPTableRelations.h" -#import "SPPrintAccessory.h" -#import "MGTemplateEngine.h" -#import "ICUTemplateMatcher.h" -#import "SPConnectionController.h" -#import "SPExtendedTableInfo.h" -#import "SPTableTriggers.h" -#import "SPDatabaseViewController.h" - -@implementation SPDatabaseDocument (SPPrintController) - -/** - * WebView delegate method. - */ -- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame -{ - // Because we need the webFrame loaded (for preview), we've moved the actual printing here - NSPrintInfo *printInfo = [NSPrintInfo sharedPrintInfo]; - - NSSize paperSize = [printInfo paperSize]; - NSRect printableRect = [printInfo imageablePageBounds]; - - // Calculate page margins - CGFloat marginL = printableRect.origin.x; - CGFloat marginR = paperSize.width - (printableRect.origin.x + printableRect.size.width); - CGFloat marginB = printableRect.origin.y; - CGFloat marginT = paperSize.height - (printableRect.origin.y + printableRect.size.height); - - // Make sure margins are symetric and positive - CGFloat marginLR = MAX(0, MAX(marginL, marginR)); - CGFloat marginTB = MAX(0, MAX(marginT, marginB)); - - // Set the margins - [printInfo setLeftMargin:marginLR]; - [printInfo setRightMargin:marginLR]; - [printInfo setTopMargin:marginTB]; - [printInfo setBottomMargin:marginTB]; - - [printInfo setHorizontalPagination:NSFitPagination]; - [printInfo setVerticalPagination:NSAutoPagination]; - [printInfo setVerticallyCentered:NO]; - - NSPrintOperation *op = [NSPrintOperation printOperationWithView:[[[printWebView mainFrame] frameView] documentView] printInfo:printInfo]; - - // Perform the print operation on a background thread - [op setCanSpawnSeparateThread:YES]; - - // Add the ability to select the orientation to print panel - NSPrintPanel *printPanel = [op printPanel]; - - [printPanel setOptions:[printPanel options] + NSPrintPanelShowsOrientation + NSPrintPanelShowsScaling + NSPrintPanelShowsPaperSize]; - - SPPrintAccessory *printAccessory = [[SPPrintAccessory alloc] initWithNibName:@"PrintAccessory" bundle:nil]; - - [printAccessory setPrintView:printWebView]; - [printPanel addAccessoryController:printAccessory]; - - [[NSPageLayout pageLayout] addAccessoryController:printAccessory]; - [printAccessory release]; - - [op setPrintPanel:printPanel]; - - [op runOperationModalForWindow:[self parentWindow] - delegate:self - didRunSelector:nil - contextInfo:nil]; - - if ([self isWorking]) [self endTask]; -} - -/** - * Loads the print document interface. The actual printing is done in the doneLoading delegate. - */ -- (IBAction)printDocument:(id)sender -{ - // Only display warning for the 'Table Content' view - if ([self currentlySelectedView] == SPTableViewContent) { - - NSInteger rowLimit = [prefs integerForKey:SPPrintWarningRowLimit]; - - // Result count minus one because the first element is the column names - NSInteger resultRows = ([[tableContentInstance currentResult] count] - 1); - - if (resultRows > rowLimit) { - - NSNumberFormatter *numberFormatter = [[[NSNumberFormatter alloc] init] autorelease]; - - [numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle]; - - NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Continue to print?", @"continue to print message") - defaultButton:NSLocalizedString(@"Print", @"print button") - alternateButton:NSLocalizedString(@"Cancel", @"cancel button") - otherButton:nil - informativeTextWithFormat:NSLocalizedString(@"Are you sure you want to print the current content view of the table '%@'?\n\nIt currently contains %@ rows, which may take a significant amount of time to print.", @"continue to print informative message"), [self table], [numberFormatter stringFromNumber:[NSNumber numberWithLongLong:resultRows]]]; - - NSArray *buttons = [alert buttons]; - - // Change the alert's cancel button to have the key equivalent of return - [[buttons objectAtIndex:0] setKeyEquivalent:@"p"]; - [[buttons objectAtIndex:0] setKeyEquivalentModifierMask:NSCommandKeyMask]; - [[buttons objectAtIndex:1] setKeyEquivalent:@"\r"]; - - [alert beginSheetModalForWindow:[self parentWindow] modalDelegate:self didEndSelector:@selector(printWarningDidEnd:returnCode:contextInfo:) contextInfo:NULL]; - - return; - } - } - - [self startPrintDocumentOperation]; -} - -/** - * Called when the print warning dialog is dismissed. - */ -- (void)printWarningDidEnd:(id)sheet returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo -{ - if (returnCode == NSAlertDefaultReturn) { - [self startPrintDocumentOperation]; - } -} - -/** - * Starts tge print document operation by spawning a new thread if required. - */ -- (void)startPrintDocumentOperation -{ - [self startTaskWithDescription:NSLocalizedString(@"Generating print document...", @"generating print document status message")]; - - BOOL isTableInformation = ([self currentlySelectedView] == SPTableViewStatus); - - if ([NSThread isMainThread]) { - printThread = [[NSThread alloc] initWithTarget:self selector:(isTableInformation) ? @selector(generateTableInfoHTMLForPrinting) : @selector(generateHTMLForPrinting) object:nil]; - [printThread setName:@"SPPrintController document generator"]; - - [self enableTaskCancellationWithTitle:NSLocalizedString(@"Cancel", @"cancel button") callbackObject:self callbackFunction:@selector(generateHTMLForPrintingCallback)]; - - [printThread start]; - } - else { - (isTableInformation) ? [self generateTableInfoHTMLForPrinting] : [self generateHTMLForPrinting]; - } -} - -/** - * HTML generation thread callback method. - */ -- (void)generateHTMLForPrintingCallback -{ - [self setTaskDescription:NSLocalizedString(@"Cancelling...", @"cancelling task status message")]; - - // Cancel the print thread - [printThread cancel]; -} - -/** - * Loads the supplied HTML string in the print WebView. - */ -- (void)loadPrintWebViewWithHTMLString:(NSString *)HTMLString -{ - [[printWebView mainFrame] loadHTMLString:HTMLString baseURL:nil]; - - if (printThread) SPClear(printThread); -} - -/** - * Generates the HTML for the current view that is being printed. - */ -- (void)generateHTMLForPrinting -{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - - // Set up template engine with your chosen matcher - MGTemplateEngine *engine = [MGTemplateEngine templateEngine]; - - [engine setMatcher:[ICUTemplateMatcher matcherWithTemplateEngine:engine]]; - - NSMutableDictionary *connection = [self connectionInformation]; - - NSString *heading = @""; - NSArray *rows, *indexes, *indexColumns = nil; - - NSArray *columns = [self columnNames]; - - NSMutableDictionary *printData = [NSMutableDictionary dictionary]; - - SPTableViewType view = [self currentlySelectedView]; - - // Table source view - if (view == SPTableViewStructure) { - - NSDictionary *tableSource = [tableSourceInstance tableSourceForPrinting]; - - NSInteger tableType = [tablesListInstance tableType]; - - switch (tableType) { - case SPTableTypeTable: - heading = NSLocalizedString(@"Table Structure", @"table structure print heading"); - break; - case SPTableTypeView: - heading = NSLocalizedString(@"View Structure", @"view structure print heading"); - break; - } - - rows = [[NSArray alloc] initWithArray: - [[tableSource objectForKey:@"structure"] objectsAtIndexes: - [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, [[tableSource objectForKey:@"structure"] count] - 1)]] - ]; - - indexes = [[NSArray alloc] initWithArray: - [[tableSource objectForKey:@"indexes"] objectsAtIndexes: - [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, [[tableSource objectForKey:@"indexes"] count] - 1)]] - ]; - - indexColumns = [[tableSource objectForKey:@"indexes"] objectAtIndex:0]; - - [printData setObject:rows forKey:@"rows"]; - [printData setObject:indexes forKey:@"indexes"]; - [printData setObject:indexColumns forKey:@"indexColumns"]; - - if ([indexes count]) [printData setObject:@1 forKey:@"hasIndexes"]; - - [rows release]; - [indexes release]; - } - // Table content view - else if (view == SPTableViewContent) { - - NSArray *data = [tableContentInstance currentDataResultWithNULLs:NO hideBLOBs:YES]; - - heading = NSLocalizedString(@"Table Content", @"table content print heading"); - - rows = [[NSArray alloc] initWithArray: - [data objectsAtIndexes: - [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, [data count] - 1)]] - ]; - - [printData setObject:rows forKey:@"rows"]; - [connection setValue:[tableContentInstance usedQuery] forKey:@"query"]; - - [rows release]; - } - // Custom query view - else if (view == SPTableViewCustomQuery) { - - NSArray *data = [customQueryInstance currentResult]; - - heading = NSLocalizedString(@"Query Result", @"query result print heading"); - - rows = [[NSArray alloc] initWithArray: - [data objectsAtIndexes: - [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, [data count] - 1)]] - ]; - - [printData setObject:rows forKey:@"rows"]; - [connection setValue:[customQueryInstance usedQuery] forKey:@"query"]; - - [rows release]; - } - // Table relations view - else if (view == SPTableViewRelations) { - - NSArray *data = [tableRelationsInstance relationDataForPrinting]; - - heading = NSLocalizedString(@"Table Relations", @"toolbar item label for switching to the Table Relations tab"); - - rows = [[NSArray alloc] initWithArray: - [data objectsAtIndexes: - [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, ([data count] - 1))]] - ]; - - [printData setObject:rows forKey:@"rows"]; - - [rows release]; - } - // Table triggers view - else if (view == SPTableViewTriggers) { - - NSArray *data = [tableTriggersInstance triggerDataForPrinting]; - - heading = NSLocalizedString(@"Table Triggers", @"toolbar item label for switching to the Table Triggers tab"); - - rows = [[NSArray alloc] initWithArray: - [data objectsAtIndexes: - [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, ([data count] - 1))]] - ]; - - [printData setObject:rows forKey:@"rows"]; - - [rows release]; - } - - [engine setObject:connection forKey:@"c"]; - - [printData setObject:heading forKey:@"heading"]; - [printData setObject:columns forKey:@"columns"]; - [printData setObject:([prefs boolForKey:SPUseMonospacedFonts]) ? SPDefaultMonospacedFontName : @"Lucida Grande" forKey:@"font"]; - [printData setObject:([prefs boolForKey:SPDisplayTableViewVerticalGridlines]) ? @"1px solid #CCCCCC" : @"none" forKey:@"gridlines"]; - - NSString *HTMLString = [engine processTemplateInFileAtPath:[[NSBundle mainBundle] pathForResource:SPHTMLPrintTemplate ofType:@"html"] withVariables:printData]; - - // Check if the operation has been cancelled - if ((printThread != nil) && (![NSThread isMainThread]) && ([printThread isCancelled])) { - [self endTask]; - [pool drain]; - - [NSThread exit]; - return; - } - - [self performSelectorOnMainThread:@selector(loadPrintWebViewWithHTMLString:) withObject:HTMLString waitUntilDone:NO]; - - [pool drain]; -} - -/** - * Generates the HTML for the table information view that is to be printed. - */ -- (void)generateTableInfoHTMLForPrinting -{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - - // Set up template engine with your chosen matcher - MGTemplateEngine *engine = [MGTemplateEngine templateEngine]; - - [engine setMatcher:[ICUTemplateMatcher matcherWithTemplateEngine:engine]]; - - NSMutableDictionary *connection = [self connectionInformation]; - NSMutableDictionary *printData = [NSMutableDictionary dictionary]; - - NSString *heading = NSLocalizedString(@"Table Information", @"table information print heading"); - - [engine setObject:connection forKey:@"c"]; - [engine setObject:[[extendedTableInfoInstance onMainThread] tableInformationForPrinting] forKey:@"i"]; - - [printData setObject:heading forKey:@"heading"]; - [printData setObject:[[NSUnarchiver unarchiveObjectWithData:[prefs objectForKey:SPCustomQueryEditorFont]] fontName] forKey:@"font"]; - - NSString *HTMLString = [engine processTemplateInFileAtPath:[[NSBundle mainBundle] pathForResource:SPHTMLTableInfoPrintTemplate ofType:@"html"] withVariables:printData]; - - // Check if the operation has been cancelled - if ((printThread != nil) && (![NSThread isMainThread]) && ([printThread isCancelled])) { - [self endTask]; - [pool drain]; - - [NSThread exit]; - return; - } - - [self performSelectorOnMainThread:@selector(loadPrintWebViewWithHTMLString:) withObject:HTMLString waitUntilDone:NO]; - - [pool drain]; -} - -/** - * Returns an array of columns for whichever view is being printed. - */ -- (NSArray *)columnNames -{ - NSArray *columns = nil; - - SPTableViewType view = [self currentlySelectedView]; - - // Table source view - if ((view == SPTableViewStructure) && ([[tableSourceInstance tableSourceForPrinting] count] > 0)) { - - columns = [[NSArray alloc] initWithArray:[[[tableSourceInstance tableSourceForPrinting] objectForKey:@"structure"] objectAtIndex:0] copyItems:YES]; - } - // Table content view - else if ((view == SPTableViewContent) && ([[tableContentInstance currentResult] count] > 0)) { - - columns = [[NSArray alloc] initWithArray:[[tableContentInstance currentResult] objectAtIndex:0] copyItems:YES]; - } - // Custom query view - else if ((view == SPTableViewCustomQuery) && ([[customQueryInstance currentResult] count] > 0)) { - - columns = [[NSArray alloc] initWithArray:[[customQueryInstance currentResult] objectAtIndex:0] copyItems:YES]; - } - // Table relations view - else if ((view == SPTableViewRelations) && ([[tableRelationsInstance relationDataForPrinting] count] > 0)) { - - columns = [[NSArray alloc] initWithArray:[[tableRelationsInstance relationDataForPrinting] objectAtIndex:0] copyItems:YES]; - } - // Table triggers view - else if ((view == SPTableViewTriggers) && ([[tableTriggersInstance triggerDataForPrinting] count] > 0)) { - - columns = [[NSArray alloc] initWithArray:[[tableTriggersInstance triggerDataForPrinting] objectAtIndex:0] copyItems:YES]; - } - - if (columns) [columns autorelease]; - - return columns; -} - -/** - * Generates a dictionary of connection information that is used for printing. - */ -- (NSMutableDictionary *)connectionInformation -{ - NSString *versionForPrint = [NSString stringWithFormat:@"%@ %@ (%@ %@)", - [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"], - [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"], - NSLocalizedString(@"build", @"build label"), - [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"] - ]; - - NSMutableDictionary *connection = [NSMutableDictionary dictionary]; - - if ([[self user] length]) { - [connection setValue:[self user] forKey:@"username"]; - } - - if ([[self table] length]) { - [connection setValue:[self table] forKey:@"table"]; - } - - if ([connectionController port] && [[connectionController port] length]) { - [connection setValue:[connectionController port] forKey:@"port"]; - } - - [connection setValue:[self host] forKey:@"hostname"]; - [connection setValue:selectedDatabase forKey:@"database"]; - [connection setValue:versionForPrint forKey:@"version"]; - - return connection; -} - -@end diff --git a/Source/SPProcessListController.m b/Source/SPProcessListController.m index eb22d484..a256835c 100644 --- a/Source/SPProcessListController.m +++ b/Source/SPProcessListController.m @@ -45,7 +45,7 @@ static NSString *SPTableViewIDColumnIdentifier = @"Id"; static NSString * const SPKillModeKey = @"SPKillMode"; static NSString * const SPKillIdKey = @"SPKillId"; -@interface SPProcessListController (PrivateAPI) +@interface SPProcessListController () - (void)_processListRefreshed; - (void)_startAutoRefreshTimer; @@ -803,6 +803,70 @@ static NSString * const SPKillIdKey = @"SPKillId"; [prefs removeObserver:self forKeyPath:SPDisplayTableViewVerticalGridlines]; } +#pragma mark - SPProcessListControllerDataSource + +#pragma mark Tableview delegate methods + +/** + * Table view delegate method. Returns the number of rows in the table veiw. + */ +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView +{ + return [processesFiltered 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 +{ + id object = ((NSUInteger)row < [processesFiltered count]) ? [[processesFiltered objectAtIndex:row] valueForKey:[tableColumn identifier]] : @""; + + if ([object isNSNull]) { + return [prefs stringForKey:SPNullValue]; + } + + // If the string is exactly 100 characters long, and FULL process lists are not enabled, it's a safe + // bet that the string is truncated + if (!showFullProcessList && [object isKindOfClass:[NSString class]] && [(NSString *)object length] == 100) { + return [object stringByAppendingString:@"…"]; + } + + return object; +} + +/** + * Table view delegate method. Called when the user changes the sort by column. + */ +- (void)tableView:(NSTableView *)tableView sortDescriptorsDidChange:(NSArray *)oldDescriptors +{ + [processesFiltered sortUsingDescriptors:[tableView sortDescriptors]]; + + [tableView reloadData]; +} + +/** + * Table view delegate method. Called whenever the user changes a column width. + */ +- (void)tableViewColumnDidResize:(NSNotification *)notification +{ + NSTableColumn *column = [[notification userInfo] objectForKey:@"NSTableColumn"]; + + // Get the existing table column widths dictionary if it exists + NSMutableDictionary *tableColumnWidths = ([prefs objectForKey:SPProcessListTableColumnWidths]) ? + [NSMutableDictionary dictionaryWithDictionary:[prefs objectForKey:SPProcessListTableColumnWidths]] : + [NSMutableDictionary dictionary]; + + // Save column size + NSString *columnName = [[column headerCell] stringValue]; + + if (columnName) { + [tableColumnWidths setObject:[NSNumber numberWithDouble:[column width]] forKey:columnName]; + + [prefs setObject:tableColumnWidths forKey:SPProcessListTableColumnWidths]; + } +} + #pragma mark - - (void)dealloc diff --git a/Source/SPProcessListControllerDataSource.h b/Source/SPProcessListControllerDataSource.h deleted file mode 100644 index 0934d591..00000000 --- a/Source/SPProcessListControllerDataSource.h +++ /dev/null @@ -1,33 +0,0 @@ -// -// SPProcessListControllerDataSource.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on April 3, 2013. -// Copyright (c) 2013 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. - -#import "SPProcessListController.h" - -@interface SPProcessListController (SPProcessListControllerDataSource) - -@end diff --git a/Source/SPProcessListControllerDataSource.m b/Source/SPProcessListControllerDataSource.m deleted file mode 100644 index 7306e382..00000000 --- a/Source/SPProcessListControllerDataSource.m +++ /dev/null @@ -1,96 +0,0 @@ -// -// SPProcessListControllerDataSource.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on April 3, 2013. -// Copyright (c) 2013 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. - -#import "SPProcessListControllerDataSource.h" - -@implementation SPProcessListController (SPProcessListControllerDataSource) - -#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 [processesFiltered 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 -{ - id object = ((NSUInteger)row < [processesFiltered count]) ? [[processesFiltered objectAtIndex:row] valueForKey:[tableColumn identifier]] : @""; - - if ([object isNSNull]) { - return [prefs stringForKey:SPNullValue]; - } - - // If the string is exactly 100 characters long, and FULL process lists are not enabled, it's a safe - // bet that the string is truncated - if (!showFullProcessList && [object isKindOfClass:[NSString class]] && [(NSString *)object length] == 100) { - return [object stringByAppendingString:@"…"]; - } - - return object; -} - -/** - * Table view delegate method. Called when the user changes the sort by column. - */ -- (void)tableView:(NSTableView *)tableView sortDescriptorsDidChange:(NSArray *)oldDescriptors -{ - [processesFiltered sortUsingDescriptors:[tableView sortDescriptors]]; - - [tableView reloadData]; -} - -/** - * Table view delegate method. Called whenever the user changes a column width. - */ -- (void)tableViewColumnDidResize:(NSNotification *)notification -{ - NSTableColumn *column = [[notification userInfo] objectForKey:@"NSTableColumn"]; - - // Get the existing table column widths dictionary if it exists - NSMutableDictionary *tableColumnWidths = ([prefs objectForKey:SPProcessListTableColumnWidths]) ? - [NSMutableDictionary dictionaryWithDictionary:[prefs objectForKey:SPProcessListTableColumnWidths]] : - [NSMutableDictionary dictionary]; - - // Save column size - NSString *columnName = [[column headerCell] stringValue]; - - if (columnName) { - [tableColumnWidths setObject:[NSNumber numberWithDouble:[column width]] forKey:columnName]; - - [prefs setObject:tableColumnWidths forKey:SPProcessListTableColumnWidths]; - } -} - -@end diff --git a/Source/SPQueryConsoleDataSource.h b/Source/SPQueryConsoleDataSource.h deleted file mode 100644 index ac0ce88b..00000000 --- a/Source/SPQueryConsoleDataSource.h +++ /dev/null @@ -1,35 +0,0 @@ -// -// SPQueryConsoleDataSource.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on August 30, 2011. -// Copyright (c) 2011 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPQueryController.h" - -@interface SPQueryController (SPQueryConsoleDataSource) - -@end diff --git a/Source/SPQueryConsoleDataSource.m b/Source/SPQueryConsoleDataSource.m deleted file mode 100644 index eb669051..00000000 --- a/Source/SPQueryConsoleDataSource.m +++ /dev/null @@ -1,111 +0,0 @@ -// -// SPQueryConsoleDataSource.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on August 30, 2011. -// Copyright (c) 2011 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#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_CODA - return [messagesVisibleSet count]; -#else - return 0; -#endif -} - -/** - * Table view delegate method. Returns the specific object for the requested column and row. - */ -- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row -{ -#ifndef SP_CODA - NSString *returnValue = nil; - - NSString *identifier = [tableColumn identifier]; - - if (!identifier) return returnValue; - - id object = [[messagesVisibleSet objectAtIndex:row] valueForKey: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; - } - - if (!returnValue) return returnValue; - - 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 -} - -- (BOOL)tableView:(NSTableView *)aTableView writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard *)pboard -{ - NSString *string = [self sqlStringForRowIndexes:rowIndexes]; - if([string length]) { - [pboard declareTypes:@[NSStringPboardType] owner:self]; - return [pboard setString:string forType:NSStringPboardType]; - } - - return NO; -} - -@end diff --git a/Source/SPQueryController.h b/Source/SPQueryController.h index 12ff6d7b..ea292dd9 100644 --- a/Source/SPQueryController.h +++ b/Source/SPQueryController.h @@ -120,4 +120,36 @@ extern NSString *SPTableViewDatabaseColumnID; */ - (NSString *)sqlStringForRowIndexes:(NSIndexSet *)indexes; +#pragma mark - SPQueryControllerInitializer + +- (NSError *)loadCompletionLists; + +#pragma mark - 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/SPQueryController.m b/Source/SPQueryController.m index 41d71154..7f48330d 100644 --- a/Source/SPQueryController.m +++ b/Source/SPQueryController.m @@ -31,7 +31,7 @@ #import "SPQueryController.h" #import "SPConsoleMessage.h" #import "SPCustomQuery.h" -#import "SPQueryControllerInitializer.h" +#import "SPAppController.h" #import "pthread.h" @@ -40,8 +40,16 @@ NSString *SPQueryConsoleWindowAutoSaveName = @"QueryConsole"; NSString *SPTableViewDateColumnID = @"messageDate"; NSString *SPTableViewConnectionColumnID = @"messageConnection"; NSString *SPTableViewDatabaseColumnID = @"messageDatabase"; + +static NSString *SPCompletionTokensFilename = @"CompletionTokens.plist"; + +static NSString *SPCompletionTokensKeywordsKey = @"core_keywords"; +static NSString *SPCompletionTokensFunctionsKey = @"core_builtin_functions"; +static NSString *SPCompletionTokensSnippetsKey = @"function_argument_snippets"; #endif +static NSUInteger SPMessageTruncateCharacterLength = 256; + @interface SPQueryController () - (void)_updateFilterState; @@ -640,6 +648,561 @@ static SPQueryController *sharedQueryController = nil; #endif } +#pragma mark - SPQueryControllerInitializer + +/** + * Set the window's auto save name and initialise display. + */ +- (void)awakeFromNib +{ +#ifndef SP_CODA /* 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]]; + [[consoleTableView tableColumnWithIdentifier:SPTableViewDatabaseColumnID] setHidden:![prefs boolForKey:SPConsoleShowDatabases]]; + + 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]; + CGFloat monospacedFontSize = [prefs floatForKey:SPMonospacedFontSize] > 0 ? [prefs floatForKey:SPMonospacedFontSize] : [NSFont smallSystemFontSize]; + + for (NSTableColumn *column in [consoleTableView tableColumns]) + { + [[column dataCell] setFont:(useMonospacedFont) ? [NSFont fontWithName:SPDefaultMonospacedFontName size:monospacedFontSize] : [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; + } + + //allow drag-out copying of selected rows + [consoleTableView setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO]; +#endif +} + +/** + * Loads the query controller's completion tokens data. + */ +- (NSError *)loadCompletionLists +{ + NSError *readError = nil; + NSString *errorDescription = nil; + + NSString *filePath = [NSBundle pathForResource:SPCompletionTokensFilename + ofType:nil + inDirectory:[[NSBundle mainBundle] bundlePath]]; + + NSData *completionTokensData = [NSData dataWithContentsOfFile:filePath + options:NSMappedRead + error:&readError]; + + NSDictionary *completionPlist = nil; + if(completionTokensData && !readError) { + NSDictionary *plistDict = [NSPropertyListSerialization propertyListWithData:completionTokensData + options:NSPropertyListMutableContainersAndLeaves + format:NULL + error:&readError]; + + if(plistDict && !readError) { + completionPlist = [NSDictionary dictionaryWithDictionary:plistDict]; + } + } + + if (completionPlist == nil || readError) { + errorDescription = [NSString stringWithFormat:@"Error reading '%@': %@", SPCompletionTokensFilename, readError]; + } + 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:@{NSLocalizedDescriptionKey : errorDescription}] : nil; +} + +#pragma mark - SPQueryConsoleDataSource + +/** + * Table view delegate method. Returns the number of rows in the table veiw. + */ +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView +{ +#ifndef SP_CODA + return [messagesVisibleSet count]; +#else + return 0; +#endif +} + +/** + * Table view delegate method. Returns the specific object for the requested column and row. + */ +- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row +{ +#ifndef SP_CODA + NSString *returnValue = nil; + + NSString *identifier = [tableColumn identifier]; + + if (!identifier) return returnValue; + + id object = [[messagesVisibleSet objectAtIndex:row] valueForKey: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; + } + + if (!returnValue) return returnValue; + + 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 +} + +- (BOOL)tableView:(NSTableView *)aTableView writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard *)pboard +{ + NSString *string = [self sqlStringForRowIndexes:rowIndexes]; + if([string length]) { + [pboard declareTypes:@[NSStringPboardType] owner:self]; + return [pboard setString:string forType:NSStringPboardType]; + } + + return NO; +} + +#pragma mark - SPQueryDocumentsController + +- (NSURL *)registerDocumentWithFileURL:(NSURL *)fileURL andContextInfo:(NSMutableDictionary *)contextInfo +{ +#ifndef SP_CODA + // 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_CODA + // Check for multiple instance of the same document. + // Remove it if only one instance was registerd. + NSArray *allDocs = [SPAppDelegate 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_CODA + 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_CODA + 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_CODA + [[favoritesContainer objectForKey:[fileURL absoluteString]] removeObjectAtIndex:index]; +#endif +} + +- (void)insertFavorite:(NSDictionary *)favorite atIndex:(NSUInteger)index forFileURL:(NSURL *)fileURL +{ +#ifndef SP_CODA + [[favoritesContainer objectForKey:[fileURL absoluteString]] insertObject:favorite atIndex:index]; +#endif +} + +- (void)replaceHistoryByArray:(NSArray *)historyArray forFileURL:(NSURL *)fileURL +{ +#ifndef SP_CODA + if ([historyContainer objectForKey:[fileURL absoluteString]]) { + [historyContainer setObject:historyArray forKey:[fileURL absoluteString]]; + } + + // Inform all opened documents to update the history list + [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:SPHistoryItemsHaveBeenUpdatedNotification object:self]; + + // 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_CODA + if ([favoritesContainer objectForKey:[fileURL absoluteString]]) { + [[favoritesContainer objectForKey:[fileURL absoluteString]] addObject:favorite]; + } +#endif +} + +- (void)addHistory:(NSString *)history forFileURL:(NSURL *)fileURL +{ +#ifndef SP_CODA + 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_CODA + if ([favoritesContainer objectForKey:[fileURL absoluteString]]) { + return [favoritesContainer objectForKey:[fileURL absoluteString]]; + } +#endif + + return [NSMutableArray array]; +} + +- (NSMutableArray *)historyForFileURL:(NSURL *)fileURL +{ +#ifndef SP_CODA + if ([historyContainer objectForKey:[fileURL absoluteString]]) { + return [historyContainer objectForKey:[fileURL absoluteString]]; + } +#endif + + return [NSMutableArray array]; +} + +- (NSArray *)historyMenuItemsForFileURL:(NSURL *)fileURL +{ +#ifndef SP_CODA + 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 @[]; +} + +/** + * 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_CODA + 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_CODA + 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 @[]; + + 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_CODA + 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 : @[]; +} + +/** + * Return an array of all pre-defined SQL keywords for completion. + */ +- (NSArray*)keywordList +{ + return (completionKeywordList != nil && [completionKeywordList count]) ? completionKeywordList : @[]; +} + +/** + * 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]] : @""; +} + #pragma mark - - (void)dealloc diff --git a/Source/SPQueryControllerInitializer.h b/Source/SPQueryControllerInitializer.h deleted file mode 100644 index fa082a90..00000000 --- a/Source/SPQueryControllerInitializer.h +++ /dev/null @@ -1,37 +0,0 @@ -// -// SPQueryControllerInitializer.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on September 1, 2011. -// Copyright (c) 2011 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPQueryController.h" - -@interface SPQueryController (SPQueryControllerInitializer) - -- (NSError *)loadCompletionLists; - -@end diff --git a/Source/SPQueryControllerInitializer.m b/Source/SPQueryControllerInitializer.m deleted file mode 100644 index 6dc703ba..00000000 --- a/Source/SPQueryControllerInitializer.m +++ /dev/null @@ -1,151 +0,0 @@ -// -// SPQueryControllerInitializer.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on September 1, 2011. -// Copyright (c) 2011 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#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_CODA /* 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]]; - [[consoleTableView tableColumnWithIdentifier:SPTableViewDatabaseColumnID] setHidden:![prefs boolForKey:SPConsoleShowDatabases]]; - - 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]; - CGFloat monospacedFontSize = [prefs floatForKey:SPMonospacedFontSize] > 0 ? [prefs floatForKey:SPMonospacedFontSize] : [NSFont smallSystemFontSize]; - - for (NSTableColumn *column in [consoleTableView tableColumns]) - { - [[column dataCell] setFont:(useMonospacedFont) ? [NSFont fontWithName:SPDefaultMonospacedFontName size:monospacedFontSize] : [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; - } - - //allow drag-out copying of selected rows - [consoleTableView setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO]; -#endif -} - -/** - * Loads the query controller's completion tokens data. - */ -- (NSError *)loadCompletionLists -{ - NSError *readError = nil; - NSString *errorDescription = nil; - - NSString *filePath = [NSBundle pathForResource:SPCompletionTokensFilename - ofType:nil - inDirectory:[[NSBundle mainBundle] bundlePath]]; - - NSData *completionTokensData = [NSData dataWithContentsOfFile:filePath - options:NSMappedRead - error:&readError]; - - NSDictionary *completionPlist = nil; - if(completionTokensData && !readError) { - NSDictionary *plistDict = [NSPropertyListSerialization propertyListWithData:completionTokensData - options:NSPropertyListMutableContainersAndLeaves - format:NULL - error:&readError]; - - if(plistDict && !readError) { - completionPlist = [NSDictionary dictionaryWithDictionary:plistDict]; - } - } - - if (completionPlist == nil || readError) { - errorDescription = [NSString stringWithFormat:@"Error reading '%@': %@", SPCompletionTokensFilename, readError]; - } - 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:@{NSLocalizedDescriptionKey : errorDescription}] : nil; -} - -@end diff --git a/Source/SPQueryDocumentsController.h b/Source/SPQueryDocumentsController.h deleted file mode 100644 index a57eff2e..00000000 --- a/Source/SPQueryDocumentsController.h +++ /dev/null @@ -1,61 +0,0 @@ -// -// SPQueryDocumentsController.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on August 30, 2011. -// Copyright (c) 2011 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#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 deleted file mode 100644 index d3081338..00000000 --- a/Source/SPQueryDocumentsController.m +++ /dev/null @@ -1,410 +0,0 @@ -// -// SPQueryDocumentsController.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on August 30, 2011. -// Copyright (c) 2011 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPQueryDocumentsController.h" -#import "SPCustomQuery.h" -#import "SPAppController.h" -#import "SPAppleScriptSupport.h" - -@implementation SPQueryController (SPQueryDocumentsController) - -- (NSURL *)registerDocumentWithFileURL:(NSURL *)fileURL andContextInfo:(NSMutableDictionary *)contextInfo -{ -#ifndef SP_CODA - // 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_CODA - // Check for multiple instance of the same document. - // Remove it if only one instance was registerd. - NSArray *allDocs = [SPAppDelegate 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_CODA - 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_CODA - 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_CODA - [[favoritesContainer objectForKey:[fileURL absoluteString]] removeObjectAtIndex:index]; -#endif -} - -- (void)insertFavorite:(NSDictionary *)favorite atIndex:(NSUInteger)index forFileURL:(NSURL *)fileURL -{ -#ifndef SP_CODA - [[favoritesContainer objectForKey:[fileURL absoluteString]] insertObject:favorite atIndex:index]; -#endif -} - -- (void)replaceHistoryByArray:(NSArray *)historyArray forFileURL:(NSURL *)fileURL -{ -#ifndef SP_CODA - if ([historyContainer objectForKey:[fileURL absoluteString]]) { - [historyContainer setObject:historyArray forKey:[fileURL absoluteString]]; - } - - // Inform all opened documents to update the history list - [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:SPHistoryItemsHaveBeenUpdatedNotification object:self]; - - // 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_CODA - if ([favoritesContainer objectForKey:[fileURL absoluteString]]) { - [[favoritesContainer objectForKey:[fileURL absoluteString]] addObject:favorite]; - } -#endif -} - -- (void)addHistory:(NSString *)history forFileURL:(NSURL *)fileURL -{ -#ifndef SP_CODA - 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_CODA - if ([favoritesContainer objectForKey:[fileURL absoluteString]]) { - return [favoritesContainer objectForKey:[fileURL absoluteString]]; - } -#endif - - return [NSMutableArray array]; -} - -- (NSMutableArray *)historyForFileURL:(NSURL *)fileURL -{ -#ifndef SP_CODA - if ([historyContainer objectForKey:[fileURL absoluteString]]) { - return [historyContainer objectForKey:[fileURL absoluteString]]; - } -#endif - - return [NSMutableArray array]; -} - -- (NSArray *)historyMenuItemsForFileURL:(NSURL *)fileURL -{ -#ifndef SP_CODA - 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 @[]; -} - -/** - * 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_CODA - 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_CODA - 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 @[]; - - 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_CODA - 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 : @[]; -} - -/** - * Return an array of all pre-defined SQL keywords for completion. - */ -- (NSArray*)keywordList -{ - return (completionKeywordList != nil && [completionKeywordList count]) ? completionKeywordList : @[]; -} - -/** - * 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 296f465e..2ab8ec82 100644 --- a/Source/SPQueryFavoriteManager.m +++ b/Source/SPQueryFavoriteManager.m @@ -32,12 +32,12 @@ #import "ImageAndTextCell.h" #import "SPEncodingPopupAccessory.h" #import "SPQueryController.h" -#import "SPQueryDocumentsController.h" #import "SPDatabaseDocument.h" #import "SPConnectionController.h" #import "RegexKitLite.h" #import "SPTextView.h" #import "SPSplitView.h" +#import "SPAppController.h" #define SP_MULTIPLE_SELECTION_PLACEHOLDER_STRING NSLocalizedString(@"[multiple selection]", @"[multiple selection]") #define SP_NO_SELECTION_PLACEHOLDER_STRING NSLocalizedString(@"[no selection]", @"[no selection]") diff --git a/Source/SPSQLExporterDelegate.h b/Source/SPSQLExporterDelegate.h deleted file mode 100644 index a4a3e1cb..00000000 --- a/Source/SPSQLExporterDelegate.h +++ /dev/null @@ -1,43 +0,0 @@ -// -// SPSQLExporterDelegate.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on March 28, 2010. -// Copyright (c) 2010 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPExportController.h" -#import "SPSQLExporterProtocol.h" - -/** - * @category SPSQLExporterDelegate SPSQLExporterDelegate.h - * - * @author Stuart Connolly http://stuconnolly.com/ - * - * SQL exporter delegate category. - */ -@interface SPExportController (SPSQLExporterDelegate) <SPSQLExporterProtocol> - -@end diff --git a/Source/SPSQLExporterDelegate.m b/Source/SPSQLExporterDelegate.m deleted file mode 100644 index 09e87cf8..00000000 --- a/Source/SPSQLExporterDelegate.m +++ /dev/null @@ -1,82 +0,0 @@ -// -// SPSQLExporterDelegate.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on March 28, 2010. -// Copyright (c) 2010 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPSQLExporterDelegate.h" -#import "SPSQLExporter.h" -#import "SPDatabaseDocument.h" -#import "SPExportInitializer.h" - -@implementation SPExportController (SPSQLExporterDelegate) - -- (void)sqlExportProcessWillBegin:(SPSQLExporter *)exporter -{ - [exportProgressTitle setStringValue:NSLocalizedString(@"Exporting SQL", @"text showing that the application is exporting SQL")]; - [exportProgressText setStringValue:NSLocalizedString(@"Dumping...", @"text showing that app is writing dump")]; - - [exportProgressTitle displayIfNeeded]; - [exportProgressText displayIfNeeded]; -} - -- (void)sqlExportProcessComplete:(SPSQLExporter *)exporter -{ - [self exportEnded]; - - // Check for errors and display the errors sheet if necessary - if ([exporter didExportErrorsOccur]) { - [self openExportErrorsSheetWithString:[exporter sqlExportErrors]]; - } -} - -- (void)sqlExportProcessProgressUpdated:(SPSQLExporter *)exporter -{ - if ([exportProgressIndicator doubleValue] == 0) { - [exportProgressIndicator stopAnimation:self]; - [exportProgressIndicator setIndeterminate:NO]; - } - - [exportProgressIndicator setDoubleValue:[exporter exportProgressValue]]; -} - -- (void)sqlExportProcessWillBeginFetchingData:(SPSQLExporter *)exporter -{ - [exportProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %lu (%@): Fetching data...", @"export label showing that the app is fetching data for a specific table"), [exporter sqlCurrentTableExportIndex], exportTableCount, [exporter sqlExportCurrentTable]]]; - - [exportProgressIndicator startAnimation:self]; - [exportProgressIndicator setUsesThreadedAnimation:YES]; - [exportProgressIndicator setIndeterminate:YES]; - [exportProgressIndicator setDoubleValue:0]; -} - -- (void)sqlExportProcessWillBeginWritingData:(SPSQLExporter *)exporter -{ - [exportProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %lu (%@): Writing data...", @"export label showing app if writing data for a specific table"), [exporter sqlCurrentTableExportIndex], exportTableCount, [exporter sqlExportCurrentTable]]]; -} - -@end diff --git a/Source/SPSQLParser.m b/Source/SPSQLParser.m index d9bec2eb..3e37cc0a 100644 --- a/Source/SPSQLParser.m +++ b/Source/SPSQLParser.m @@ -41,7 +41,7 @@ typedef struct to_buffer_state *TO_BUFFER_STATE; void to_switch_to_buffer(TO_BUFFER_STATE); TO_BUFFER_STATE to_scan_string (const char *); -@interface SPSQLParser (PrivateAPI) +@interface SPSQLParser () - (unichar) _charAtIndex:(NSInteger)index; - (void) _clearCharCache; @@ -53,7 +53,7 @@ TO_BUFFER_STATE to_scan_string (const char *); * Please see the header files for a general description of the purpose of this class, * and increased overview detail for the functions below. */ -@implementation SPSQLParser : NSMutableString +@implementation SPSQLParser #pragma mark - #pragma mark Parser information @@ -1044,11 +1044,7 @@ TO_BUFFER_STATE to_scan_string (const char *); [super dealloc]; } -@end - -#pragma mark - - -@implementation SPSQLParser (PrivateAPI) +#pragma mark - Private API /** * Provide a method to retrieve a character from the local cache. diff --git a/Source/SPSplitView.m b/Source/SPSplitView.m index 467e0cd1..34cb4250 100644 --- a/Source/SPSplitView.m +++ b/Source/SPSplitView.m @@ -32,7 +32,7 @@ #import "SPDateAdditions.h" #include <stdlib.h> -@interface SPSplitView (Private_API) +@interface SPSplitView () - (void)_initCustomProperties; - (void)_ensureDefaultSubviewSizesToIndex:(NSUInteger)anIndex; @@ -692,13 +692,9 @@ } } -@end - #pragma mark - #pragma mark Private API -@implementation SPSplitView (Private_API) - - (void)_initCustomProperties { collapseToggleButton = nil; diff --git a/Source/SPTableContent.h b/Source/SPTableContent.h index 74329e20..4c6627de 100644 --- a/Source/SPTableContent.h +++ b/Source/SPTableContent.h @@ -52,7 +52,7 @@ #import "SPDatabaseContentViewDelegate.h" -@interface SPTableContent : NSObject <NSTableViewDelegate, NSTableViewDataSource, NSComboBoxDataSource, NSComboBoxDelegate> +@interface SPTableContent : NSObject <NSTableViewDelegate, NSTableViewDataSource, NSComboBoxDataSource, NSComboBoxDelegate, SPDatabaseContentViewDelegate> { IBOutlet SPDatabaseDocument *tableDocumentInstance; IBOutlet id tablesListInstance; @@ -309,4 +309,14 @@ - (NSArray *)fieldEditStatusForRow:(NSInteger)rowIndex andColumn:(NSInteger)columnIndex; +#pragma mark - SPTableContentDataSource + +- (BOOL)cellValueIsDisplayedAsHexForColumn:(NSUInteger)columnIndex; + +#pragma mark - SPTableContentFilter + +- (void)makeContentFilterHaveFocus; +- (void)updateFilterTableClause:(id)currentValue; +- (NSString*)escapeFilterTableDefaultOperator:(NSString*)op; + @end diff --git a/Source/SPTableContent.m b/Source/SPTableContent.m index 27207fca..16f5ac99 100644 --- a/Source/SPTableContent.m +++ b/Source/SPTableContent.m @@ -30,8 +30,6 @@ // More info at <https://github.com/sequelpro/sequelpro> #import "SPTableContent.h" -#import "SPTableContentFilter.h" -#import "SPTableContentDataSource.h" #import "SPDatabaseDocument.h" #import "SPTableStructure.h" #import "SPTableInfo.h" @@ -41,7 +39,6 @@ #import "SPDataCellFormatter.h" #import "SPTableData.h" #import "SPQueryController.h" -#import "SPQueryDocumentsController.h" #import "SPTextAndLinkCell.h" #ifndef SP_CODA #import "SPSplitView.h" @@ -55,7 +52,6 @@ #import "SPHistoryController.h" #import "SPGeometryDataView.h" #import "SPTextView.h" -#import "SPDatabaseViewController.h" #ifndef SP_CODA /* headers */ #import "SPAppController.h" #import "SPBundleHTMLOutputController.h" @@ -73,18 +69,16 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOperator"; #endif -@interface SPTableContent (SPTableContentDataSource_Private_API) - -- (id)_contentValueForTableColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex asPreview:(BOOL)asPreview; - -@end - @interface SPTableContent () - (BOOL)cancelRowEditing; - (void)documentWillClose:(NSNotification *)notification; - (void)contentFiltersHaveBeenUpdated:(NSNotification *)notification; +#pragma mark - SPTableContentDataSource_Private_API + +- (id)_contentValueForTableColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex asPreview:(BOOL)asPreview; + @end @implementation SPTableContent @@ -4256,6 +4250,1203 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper return [tableContentView fieldEditorSelectedRange]; } +#pragma mark - +#pragma mark TableView datasource methods + +- (NSInteger)numberOfRowsInTableView:(SPCopyTable *)tableView +{ +#ifndef SP_CODA + if (tableView == filterTableView) { + return filterTableIsSwapped ? [filterTableData count] : [[[filterTableData objectForKey:@"0"] objectForKey:SPTableContentFilterKey] count]; + } +#endif + if (tableView == tableContentView) { + return tableRowsCount; + } + + return 0; +} + +- (id)tableView:(SPCopyTable *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex +{ + NSUInteger columnIndex = [[tableColumn identifier] integerValue]; +#ifndef SP_CODA + if (tableView == filterTableView) { + if (filterTableIsSwapped) { + // First column shows the field names + if (columnIndex == 0) { + return [[[NSTableHeaderCell alloc] initTextCell:[[filterTableData objectForKey:[NSNumber numberWithInteger:rowIndex]] objectForKey:@"name"]] autorelease]; + } + + return NSArrayObjectAtIndex([[filterTableData objectForKey:[NSNumber numberWithInteger:rowIndex]] objectForKey:SPTableContentFilterKey], columnIndex - 1); + } + + return NSArrayObjectAtIndex([[filterTableData objectForKey:[tableColumn identifier]] objectForKey:SPTableContentFilterKey], rowIndex); + } +#endif + if (tableView == tableContentView) { + + 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(&tableValuesLock); + + if (rowIndex < (NSInteger)tableRowsCount && columnIndex < [tableValues columnCount]) { + value = [self _contentValueForTableColumn:columnIndex row:rowIndex asPreview:YES]; + } + + pthread_mutex_unlock(&tableValuesLock); + + if (!value) return @"..."; + } + else { + if ([tableView editedColumn] == (NSInteger)columnIndex && [tableView editedRow] == rowIndex) { + value = [self _contentValueForTableColumn:columnIndex row:rowIndex asPreview:NO]; + } + else { + value = [self _contentValueForTableColumn:columnIndex row:rowIndex asPreview:YES]; + } + } + + if ([value isKindOfClass:[SPMySQLGeometryData class]]) { + return [value wktString]; + } + + if ([value isNSNull]) { + return [prefs objectForKey:SPNullValue]; + } + + if ([value isKindOfClass:[NSData class]]) { + + if ([self cellValueIsDisplayedAsHexForColumn:columnIndex]) { + if ([(NSData *)value length] > 255) { + return [NSString stringWithFormat:@"0x%@…", [[(NSData *)value subdataWithRange:NSMakeRange(0, 255)] dataToHexString]]; + } + return [NSString stringWithFormat:@"0x%@", [(NSData *)value dataToHexString]]; + } + + pthread_mutex_t *fieldEditorCheckLock = NULL; + if (isWorking) { + fieldEditorCheckLock = &tableValuesLock; + } + + // Unless we're editing, always retrieve the short string representation, truncating the value where necessary + if ([tableView editedColumn] == (NSInteger)columnIndex || [tableView editedRow] == rowIndex) { + return [value stringRepresentationUsingEncoding:[mySQLConnection stringEncoding]]; + } else { + return [value shortStringRepresentationUsingEncoding:[mySQLConnection stringEncoding]]; + } + } + + if ([value isSPNotLoaded]) { + return NSLocalizedString(@"(not loaded)", @"value shown for hidden blob and text fields"); + } + + return value; + } + + return nil; +} + +- (void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex +{ +#ifndef SP_CODA + if(tableView == filterTableView) { + if (filterTableIsSwapped) { + [[[filterTableData objectForKey:[NSNumber numberWithInteger:rowIndex]] objectForKey:SPTableContentFilterKey] replaceObjectAtIndex:([[tableColumn identifier] integerValue] - 1) withObject:(NSString *)object]; + } + else { + [[[filterTableData objectForKey:[tableColumn identifier]] objectForKey:SPTableContentFilterKey] replaceObjectAtIndex:rowIndex withObject:(NSString *)object]; + } + + [self updateFilterTableClause:nil]; + + return; + } +#endif + if (tableView == tableContentView) { + NSInteger columnIndex = [[tableColumn identifier] integerValue]; + // 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:columnIndex checkWithLock:NULL]) { + return; + } + + // If table data comes from a view, save back to the view + if ([tablesListInstance tableType] == SPTableTypeView) { + [self saveViewCellValue:object forTableColumn:tableColumn row:rowIndex]; + return; + } + + // Catch editing events in the row and if the row isn't currently being edited, + // start an edit. This allows edits including enum changes to save correctly. + if (isEditingRow && [tableContentView selectedRow] != currentlyEditingRow) { + [self saveRowOnDeselect]; + } + + if (!isEditingRow) { + [oldRow setArray:[tableValues rowContentsAtIndex:rowIndex]]; + + isEditingRow = YES; + currentlyEditingRow = rowIndex; + } + + NSDictionary *column = NSArrayObjectAtIndex(dataColumns, columnIndex); + + if (object) { + // Restore NULLs if necessary + if ([object isEqualToString:[prefs objectForKey:SPNullValue]] && [[column objectForKey:@"null"] boolValue]) { + object = [NSNull null]; + } + else if ([self cellValueIsDisplayedAsHexForColumn:columnIndex]) { + // This is a binary object being edited as a hex string. + // Convert the string back to binary. + // Error checking is done in -control:textShouldEndEditing: + NSData *data = [NSData dataWithHexString:object]; + if (!data) { + NSBeep(); + return; + } + object = data; + } + + [tableValues replaceObjectInRow:rowIndex column:columnIndex withObject:object]; + } + else { + [tableValues replaceObjectInRow:rowIndex column:columnIndex withObject:@""]; + } + } +} + +- (BOOL)cellValueIsDisplayedAsHexForColumn:(NSUInteger)columnIndex +{ + if (![prefs boolForKey:SPDisplayBinaryDataAsHex]) { + return NO; + } + + NSDictionary *columnDefinition = [[(id <SPDatabaseContentViewDelegate>)[tableContentView delegate] dataColumnDefinitions] objectAtIndex:columnIndex]; + NSString *typeGrouping = columnDefinition[@"typegrouping"]; + + if ([typeGrouping isEqual:@"binary"]) { + return YES; + } + + if ([typeGrouping isEqual:@"blobdata"]) { + return YES; + } + + + return NO; +} + +#pragma mark - SPTableContentDataSource_Private_API + +- (id)_contentValueForTableColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex asPreview:(BOOL)asPreview +{ + if (asPreview) { + return SPDataStoragePreviewAtRowAndColumn(tableValues, rowIndex, columnIndex, 150); + } + + return SPDataStorageObjectAtRowAndColumn(tableValues, rowIndex, columnIndex); +} + +#pragma mark - SPTableContentFilter + +#ifndef SP_CODA + +/** + * Escape passed operator for usage as filterTableDefaultOperator. + */ +- (NSString*)escapeFilterTableDefaultOperator:(NSString *)op +{ + if (!op) return @""; + + NSMutableString *newOp = [[[NSMutableString alloc] initWithCapacity:[op length]] autorelease]; + + [newOp setString:op]; + [newOp replaceOccurrencesOfRegex:@"%" withString:@"%%"]; + [newOp replaceOccurrencesOfRegex:@"(?<!`)@(?!=`)" withString:@"%@"]; + + return newOp; +} + +/** + * Update WHERE clause in filter table window. + * + * @param currentValue If currentValue == nil take the data from filterTableData, if currentValue == filterTableSearchAllFields + * generate a WHERE clause to search in all given fields, if currentValue == a string take this string as table cell data of the + * currently edited table cell + */ +- (void)updateFilterTableClause:(id)currentValue +{ + NSMutableString *clause = [NSMutableString string]; + NSInteger numberOfRows = [self numberOfRowsInTableView:filterTableView]; + NSInteger numberOfCols = [[filterTableView tableColumns] count]; + NSInteger numberOfValues = 0; + NSRange opRange, defopRange; + + BOOL lookInAllFields = NO; + + NSString *re1 = @"^\\s*(<[=>]?|>=?|!?=|≠|≤|≥)\\s*(.*?)\\s*$"; + NSString *re2 = @"^\\s*(.*)\\s+(.*?)\\s*$"; + + NSInteger editedRow = [filterTableView editedRow]; + + if (currentValue == filterTableSearchAllFields) { + numberOfRows = 1; + lookInAllFields = YES; + } + + [filterTableWhereClause setString:@""]; + + for (NSInteger i = 0; i < numberOfRows; i++) + { + numberOfValues = 0; + + for (NSInteger anIndex = 0; anIndex < numberOfCols; anIndex++) + { + NSString *filterCell = nil; + NSDictionary *filterCellData = [NSDictionary dictionaryWithDictionary:[filterTableData objectForKey:[NSString stringWithFormat:@"%ld", (long)anIndex]]]; + + // Take filterTableData + if (!currentValue) { + filterCell = NSArrayObjectAtIndex([filterCellData objectForKey:SPTableContentFilterKey], i); + } + // Take last edited value to create the OR clause + else if (lookInAllFields) { + if (lastEditedFilterTableValue && [lastEditedFilterTableValue length]) { + filterCell = lastEditedFilterTableValue; + } + else { + [filterTableWhereClause setString:@""]; + [filterTableWhereClause insertText:@""]; + [filterTableWhereClause scrollRangeToVisible:NSMakeRange(0, 0)]; + + // If live search is set perform filtering + if ([filterTableLiveSearchCheckbox state] == NSOnState) { + [self filterTable:filterTableFilterButton]; + } + } + } + // Take value from currently edited table cell + else if ([currentValue isKindOfClass:[NSString class]]) { + if (i == editedRow && anIndex == [[NSArrayObjectAtIndex([filterTableView tableColumns], [filterTableView editedColumn]) identifier] integerValue]) { + filterCell = (NSString*)currentValue; + } + else { + filterCell = NSArrayObjectAtIndex([filterCellData objectForKey:SPTableContentFilterKey], i); + } + } + + if ([filterCell length]) { + + // Recode special operators + filterCell = [filterCell stringByReplacingOccurrencesOfRegex:@"^\\s*≠" withString:@"!="]; + filterCell = [filterCell stringByReplacingOccurrencesOfRegex:@"^\\s*≤" withString:@"<="]; + filterCell = [filterCell stringByReplacingOccurrencesOfRegex:@"^\\s*≥" withString:@">="]; + + if (numberOfValues) { + [clause appendString:(lookInAllFields) ? @" OR " : @" AND "]; + } + + NSString *fieldName = [[filterCellData objectForKey:@"name"] backtickQuotedString]; + NSString *filterTableDefaultOperatorWithFieldName = [filterTableDefaultOperator stringByReplacingOccurrencesOfString:@"`@`" withString:fieldName]; + + opRange = [filterCell rangeOfString:@"`@`"]; + defopRange = [filterTableDefaultOperator rangeOfString:@"`@`"]; + + // if cell data begins with ' or " treat it as it is + // by checking if default operator by itself contains a ' or " - if so + // remove first and if given the last ' or " + if ([filterCell isMatchedByRegex:@"^\\s*['\"]"]) { + if ([filterTableDefaultOperator isMatchedByRegex:@"['\"]"]) { + NSArray *matches = [filterCell arrayOfCaptureComponentsMatchedByRegex:@"^\\s*(['\"])(.*)\\1\\s*$"]; + + if ([matches count] && [matches = NSArrayObjectAtIndex(matches, 0) count] == 3) { + [clause appendFormat:[NSString stringWithFormat:@"%%@ %@", filterTableDefaultOperatorWithFieldName], fieldName, NSArrayObjectAtIndex(matches, 2)]; + } + else { + matches = [filterCell arrayOfCaptureComponentsMatchedByRegex:@"^\\s*(['\"])(.*)\\s*$"]; + + if ([matches count] && [matches = NSArrayObjectAtIndex(matches, 0) count] == 3) { + [clause appendFormat:[NSString stringWithFormat:@"%%@ %@", filterTableDefaultOperatorWithFieldName], fieldName, NSArrayObjectAtIndex(matches, 2)]; + } + } + } + else { + [clause appendFormat:[NSString stringWithFormat:@"%%@ %@", filterTableDefaultOperatorWithFieldName], fieldName, filterCell]; + } + } + // If cell contains the field name placeholder + else if (opRange.length || defopRange.length) { + filterCell = [filterCell stringByReplacingOccurrencesOfString:@"`@`" withString:fieldName]; + + if (defopRange.length) { + [clause appendFormat:filterTableDefaultOperatorWithFieldName, [filterCell stringByReplacingOccurrencesOfString:@"`@`" withString:fieldName]]; + } + else { + [clause appendString:[filterCell stringByReplacingOccurrencesOfString:@"`@`" withString:fieldName]]; + } + } + // If cell is equal to NULL + else if ([filterCell isMatchedByRegex:@"(?i)^\\s*null\\s*$"]) { + [clause appendFormat:@"%@ IS NULL", fieldName]; + } + // If cell starts with an operator + else if ([filterCell isMatchedByRegex:re1]) { + NSArray *matches = [filterCell arrayOfCaptureComponentsMatchedByRegex:re1]; + + if ([matches count] && [matches = NSArrayObjectAtIndex(matches, 0) count] == 3) { + [clause appendFormat:@"%@ %@ %@", fieldName, NSArrayObjectAtIndex(matches, 1), NSArrayObjectAtIndex(matches, 2)]; + } + } + // If cell consists of at least two words treat the first as operator and the rest as argument + else if ([filterCell isMatchedByRegex:re2]) { + NSArray *matches = [filterCell arrayOfCaptureComponentsMatchedByRegex:re2]; + + if ([matches count] && [matches = NSArrayObjectAtIndex(matches,0) count] == 3) { + [clause appendFormat:@"%@ %@ %@", fieldName, [NSArrayObjectAtIndex(matches, 1) uppercaseString], NSArrayObjectAtIndex(matches, 2)]; + } + } + // Apply the default operator + else { + [clause appendFormat:[NSString stringWithFormat:@"%%@ %@", filterTableDefaultOperatorWithFieldName], fieldName, filterCell]; + } + + numberOfValues++; + } + } + + if (numberOfValues) { + [clause appendString:@"\nOR\n"]; + } + } + + // Remove last " OR " if any + [filterTableWhereClause setString:[clause length] > 3 ? [clause substringToIndex:([clause length] - 4)] : @""]; + + // Update syntax highlighting and uppercasing + [filterTableWhereClause insertText:@""]; + [filterTableWhereClause scrollRangeToVisible:NSMakeRange(0, 0)]; + + // If live search is set perform filtering + if ([filterTableLiveSearchCheckbox state] == NSOnState) { + [self filterTable:filterTableFilterButton]; + } +} + +/** + * Makes the content filter field have focus by making it the first responder. + */ +- (void)makeContentFilterHaveFocus +{ + NSDictionary *filter = [[contentFilters objectForKey:compareType] objectAtIndex:[[compareField selectedItem] tag]]; + + if ([filter objectForKey:@"NumberOfArguments"]) { + + NSUInteger numOfArgs = [[filter objectForKey:@"NumberOfArguments"] integerValue]; + + switch (numOfArgs) + { + case 2: + [[firstBetweenField window] makeFirstResponder:firstBetweenField]; + break; + case 1: + [[argumentField window] makeFirstResponder:argumentField]; + break; + default: + [[compareField window] makeFirstResponder:compareField]; + } + } +} + +#endif + +#pragma mark - +#pragma mark TableView delegate methods + +/** + * Sorts the tableView by the clicked column. If clicked twice, order is altered to descending. + * Performs the task in a new thread if necessary. + */ +- (void)tableView:(NSTableView*)tableView didClickTableColumn:(NSTableColumn *)tableColumn +{ + if ([selectedTable isEqualToString:@""] || !selectedTable || tableView != tableContentView) return; + + // Prevent sorting while the table is still loading + if ([tableDocumentInstance isWorking]) return; + + // Start the task + [tableDocumentInstance startTaskWithDescription:NSLocalizedString(@"Sorting table...", @"Sorting table task description")]; + + if ([NSThread isMainThread]) { + [NSThread detachNewThreadWithName:SPCtxt(@"SPTableContent table sort task", tableDocumentInstance) target:self selector:@selector(sortTableTaskWithColumn:) object:tableColumn]; + } + else { + [self sortTableTaskWithColumn:tableColumn]; + } +} + +- (void)tableViewSelectionDidChange:(NSNotification *)aNotification +{ + // Check our notification object is our table content view + if ([aNotification object] != tableContentView) return; + + 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. + if (isEditingRow && [tableContentView selectedRow] != currentlyEditingRow && ![self saveRowOnDeselect]) return; + + if (![tableDocumentInstance isWorking]) { + // Update the row selection count + // and update the status of the delete/duplicate buttons + if([tablesListInstance tableType] == SPTableTypeTable) { + if ([tableContentView numberOfSelectedRows] > 0) { + [duplicateButton setEnabled:([tableContentView numberOfSelectedRows] == 1)]; + [removeButton setEnabled:YES]; + } + else { + [duplicateButton setEnabled:NO]; + [removeButton setEnabled:NO]; + } + } + else { + [duplicateButton setEnabled:NO]; + [removeButton setEnabled:NO]; + } + } + + [self updateCountText]; + +#ifndef SP_CODA /* triggered commands */ + NSArray *triggeredCommands = [SPAppDelegate bundleCommandsForTrigger:SPBundleTriggerActionTableRowChanged]; + + for (NSString *cmdPath in triggeredCommands) + { + NSArray *data = [cmdPath componentsSeparatedByString:@"|"]; + NSMenuItem *aMenuItem = [[[NSMenuItem alloc] init] autorelease]; + + [aMenuItem setTag:0]; + [aMenuItem setToolTip:[data objectAtIndex:0]]; + + // For HTML output check if corresponding window already exists + BOOL stopTrigger = NO; + + if ([(NSString *)[data objectAtIndex:2] length]) { + BOOL correspondingWindowFound = NO; + NSString *uuid = [data objectAtIndex:2]; + + for (id win in [NSApp windows]) + { + if ([[[[win delegate] class] description] isEqualToString:@"SPBundleHTMLOutputController"]) { + if ([[[win delegate] windowUUID] isEqualToString:uuid]) { + correspondingWindowFound = YES; + break; + } + } + } + + if (!correspondingWindowFound) stopTrigger = YES; + } + if (!stopTrigger) { + id firstResponder = [[NSApp keyWindow] firstResponder]; + if ([[data objectAtIndex:1] isEqualToString:SPBundleScopeGeneral]) { + [[SPAppDelegate onMainThread] executeBundleItemForApp:aMenuItem]; + } + else if ([[data objectAtIndex:1] isEqualToString:SPBundleScopeDataTable]) { + if ([[[firstResponder class] description] isEqualToString:@"SPCopyTable"]) { + [[firstResponder onMainThread] executeBundleItemForDataTable:aMenuItem]; + } + } + else if ([[data objectAtIndex:1] isEqualToString:SPBundleScopeInputField]) { + if ([firstResponder isKindOfClass:[NSTextView class]]) { + [[firstResponder onMainThread] executeBundleItemForInputField:aMenuItem]; + } + } + } + } +#endif +} + +/** + * Saves the new column size in the preferences. + */ +- (void)tableViewColumnDidResize:(NSNotification *)notification +{ + // Check our notification object is our table content view + if ([notification object] != tableContentView) return; + + // Sometimes the column has no identifier. I can't figure out what is causing it, so we just skip over this item + if (![[[notification userInfo] objectForKey:@"NSTableColumn"] identifier]) return; + + NSMutableDictionary *tableColumnWidths; + NSString *database = [NSString stringWithFormat:@"%@@%@", [tableDocumentInstance database], [tableDocumentInstance host]]; + NSString *table = [tablesListInstance tableName]; + + // Get tableColumnWidths object +#ifndef SP_CODA + if ([prefs objectForKey:SPTableColumnWidths] != nil ) { + tableColumnWidths = [NSMutableDictionary dictionaryWithDictionary:[prefs objectForKey:SPTableColumnWidths]]; + } + else { +#endif + tableColumnWidths = [NSMutableDictionary dictionary]; +#ifndef SP_CODA + } +#endif + + // Get the database object + if ([tableColumnWidths objectForKey:database] == nil) { + [tableColumnWidths setObject:[NSMutableDictionary dictionary] forKey:database]; + } + else { + [tableColumnWidths setObject:[NSMutableDictionary dictionaryWithDictionary:[tableColumnWidths objectForKey:database]] forKey:database]; + } + + // Get the table object + if ([[tableColumnWidths objectForKey:database] objectForKey:table] == nil) { + [[tableColumnWidths objectForKey:database] setObject:[NSMutableDictionary dictionary] forKey:table]; + } + else { + [[tableColumnWidths objectForKey:database] setObject:[NSMutableDictionary dictionaryWithDictionary:[[tableColumnWidths objectForKey:database] objectForKey:table]] forKey:table]; + } + + // Save column size + [[[tableColumnWidths objectForKey:database] objectForKey:table] + setObject:[NSNumber numberWithDouble:[(NSTableColumn *)[[notification userInfo] objectForKey:@"NSTableColumn"] width]] + forKey:[[[[notification userInfo] objectForKey:@"NSTableColumn"] headerCell] stringValue]]; +#ifndef SP_CODA + [prefs setObject:tableColumnWidths forKey:SPTableColumnWidths]; +#endif +} + +/** + * Confirm whether to allow editing of a row. Returns YES by default, unless the multipleLineEditingButton is in + * the ON state, or for blob or text fields - in those cases opens a sheet for editing instead and returns NO. + */ +- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex +{ + if ([tableDocumentInstance isWorking]) return NO; + +#ifndef SP_CODA + if (tableView == filterTableView) { + return (filterTableIsSwapped && [[tableColumn identifier] integerValue] == 0) ? NO : YES; + } + else +#endif + if (tableView == tableContentView) { + + // Nothing is editable while the field editor is running. + // This guards against a special case where accessibility services might + // check if a table field is editable while the sheet is running. + if (fieldEditor) return NO; + + // Ensure that row is editable since it could contain "(not loaded)" columns together with + // issue that the table has no primary key + NSString *wherePart = [NSString stringWithString:[self argumentForRow:[tableContentView selectedRow]]]; + + if (![wherePart length]) return NO; + + // If the selected cell hasn't been loaded, load it. + if ([[tableValues cellDataAtRow:rowIndex column:[[tableColumn identifier] integerValue]] isSPNotLoaded]) { + + // Only get the data for the selected column, not all of them + NSString *query = [NSString stringWithFormat:@"SELECT %@ FROM %@ WHERE %@", [[[tableColumn headerCell] stringValue] backtickQuotedString], [selectedTable backtickQuotedString], wherePart]; + + SPMySQLResult *tempResult = [mySQLConnection queryString:query]; + + if (![tempResult numberOfRows]) { + SPOnewayAlertSheet( + NSLocalizedString(@"Error", @"error"), + [tableDocumentInstance parentWindow], + NSLocalizedString(@"Couldn't load the row. Reload the table to be sure that the row exists and use a primary key for your table.", @"message of panel when loading of row failed") + ); + return NO; + } + + NSArray *tempRow = [tempResult getRowAsArray]; + + [tableValues replaceObjectInRow:rowIndex column:[[tableContentView tableColumns] indexOfObject:tableColumn] withObject:[tempRow objectAtIndex:0]]; + [tableContentView reloadData]; + } + + // Retrieve the column definition + NSDictionary *columnDefinition = [cqColumnDefinition objectAtIndex:[[tableColumn identifier] integerValue]]; + + // Open the editing sheet if required + if ([tableContentView shouldUseFieldEditorForRow:rowIndex column:[[tableColumn identifier] integerValue] checkWithLock:NULL]) { + + BOOL isBlob = [tableDataInstance columnIsBlobOrText:[[tableColumn headerCell] stringValue]]; + + // 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:[[tableColumn identifier] integerValue]]; + isFieldEditable = [[editStatus objectAtIndex:0] integerValue] == 1; + } + + NSUInteger fieldLength = 0; + NSString *fieldEncoding = nil; + BOOL allowNULL = YES; + + NSString *fieldType = [columnDefinition objectForKey:@"type"]; + + if ([columnDefinition objectForKey:@"char_length"]) { + fieldLength = [[columnDefinition objectForKey:@"char_length"] integerValue]; + } + + if ([columnDefinition objectForKey:@"null"]) { + allowNULL = (![[columnDefinition objectForKey:@"null"] integerValue]); + } + + if ([columnDefinition objectForKey:@"charset_name"] && ![[columnDefinition objectForKey:@"charset_name"] isEqualToString:@"binary"]) { + fieldEncoding = [columnDefinition objectForKey:@"charset_name"]; + } + + fieldEditor = [[SPFieldEditorController alloc] init]; + + [fieldEditor setEditedFieldInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [[tableColumn headerCell] stringValue], @"colName", + [self usedQuery], @"usedQuery", + @"content", @"tableSource", + nil]]; + + [fieldEditor setTextMaxLength:fieldLength]; + [fieldEditor setFieldType:fieldType == nil ? @"" : fieldType]; + [fieldEditor setFieldEncoding:fieldEncoding == nil ? @"" : fieldEncoding]; + [fieldEditor setAllowNULL:allowNULL]; + + id cellValue = [tableValues cellDataAtRow:rowIndex column:[[tableColumn identifier] integerValue]]; + + if ([cellValue isNSNull]) { + cellValue = [NSString stringWithString:[prefs objectForKey:SPNullValue]]; + } + + if ([self cellValueIsDisplayedAsHexForColumn:[[tableColumn identifier] integerValue]]) { + [fieldEditor setTextMaxLength:[[self tableView:tableContentView objectValueForTableColumn:tableColumn row:rowIndex] length]]; + isFieldEditable = NO; + } + + NSInteger editedColumn = 0; + + for (NSTableColumn* col in [tableContentView tableColumns]) + { + if ([[col identifier] isEqualToString:[tableColumn identifier]]) break; + + editedColumn++; + } + + [fieldEditor editWithObject:cellValue + fieldName:[[tableColumn headerCell] stringValue] + usingEncoding:[mySQLConnection stringEncoding] + isObjectBlob:isBlob + isEditable:isFieldEditable + withWindow:[tableDocumentInstance parentWindow] + sender:self + contextInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInteger:rowIndex], @"rowIndex", + [NSNumber numberWithInteger:editedColumn], @"columnIndex", + [NSNumber numberWithBool:isFieldEditable], @"isFieldEditable", + nil]]; + + return NO; + } + + return YES; + } + + return YES; +} + +/** + * Enable drag from tableview + */ +- (BOOL)tableView:(NSTableView *)tableView writeRowsWithIndexes:(NSIndexSet *)rows toPasteboard:(NSPasteboard*)pboard +{ + if (tableView == tableContentView) { + NSString *tmp; + + // By holding ⌘, ⇧, or/and ⌥ copies selected rows as SQL INSERTS + // otherwise \t delimited lines + if ([[NSApp currentEvent] modifierFlags] & (NSCommandKeyMask|NSShiftKeyMask|NSAlternateKeyMask)) { + tmp = [tableContentView rowsAsSqlInsertsOnlySelectedRows:YES]; + } + else { + tmp = [tableContentView draggedRowsAsTabString]; + } + + if (tmp && [tmp length]) + { + [pboard declareTypes:@[NSTabularTextPboardType, NSStringPboardType] owner:nil]; + + [pboard setString:tmp forType:NSStringPboardType]; + [pboard setString:tmp forType:NSTabularTextPboardType]; + + return YES; + } + } + + return NO; +} + +/** + * Disable row selection while the document is working. + */ +- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)rowIndex +{ +#ifndef SP_CODA + if (tableView == filterTableView) { + return YES; + } + else +#endif + return tableView == tableContentView ? tableRowsSelectable : YES; +} + +/** + * Resize a column when it's double-clicked (10.6+ only). + */ +- (CGFloat)tableView:(NSTableView *)tableView sizeToFitWidthOfColumn:(NSInteger)columnIndex +{ + NSTableColumn *theColumn = [[tableView tableColumns] objectAtIndex:columnIndex]; + NSDictionary *columnDefinition = [dataColumns objectAtIndex:[[theColumn identifier] integerValue]]; + + // Get the column width + NSUInteger targetWidth = [tableContentView autodetectWidthForColumnDefinition:columnDefinition maxRows:500]; + +#ifndef SP_CODA + // Clear any saved widths for the column + NSString *dbKey = [NSString stringWithFormat:@"%@@%@", [tableDocumentInstance database], [tableDocumentInstance host]]; + NSString *tableKey = [tablesListInstance tableName]; + NSMutableDictionary *savedWidths = [NSMutableDictionary dictionaryWithDictionary:[prefs objectForKey:SPTableColumnWidths]]; + NSMutableDictionary *dbDict = [NSMutableDictionary dictionaryWithDictionary:[savedWidths objectForKey:dbKey]]; + NSMutableDictionary *tableDict = [NSMutableDictionary dictionaryWithDictionary:[dbDict objectForKey:tableKey]]; + + if ([tableDict objectForKey:[columnDefinition objectForKey:@"name"]]) { + [tableDict removeObjectForKey:[columnDefinition objectForKey:@"name"]]; + + if ([tableDict count]) { + [dbDict setObject:[NSDictionary dictionaryWithDictionary:tableDict] forKey:tableKey]; + } + else { + [dbDict removeObjectForKey:tableKey]; + } + + if ([dbDict count]) { + [savedWidths setObject:[NSDictionary dictionaryWithDictionary:dbDict] forKey:dbKey]; + } + else { + [savedWidths removeObjectForKey:dbKey]; + } + + [prefs setObject:[NSDictionary dictionaryWithDictionary:savedWidths] forKey:SPTableColumnWidths]; + } +#endif + + // Return the width, while the delegate is empty to prevent column resize notifications + [tableContentView setDelegate:nil]; + [tableContentView performSelector:@selector(setDelegate:) withObject:self afterDelay:0.1]; + + return targetWidth; +} + +/** + * This function changes the text color of text/blob fields which are null or not yet loaded to gray + */ +- (void)tableView:(SPCopyTable *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex +{ +#ifndef SP_CODA + if (tableView == filterTableView) { + if (filterTableIsSwapped && [[tableColumn identifier] integerValue] == 0) { + [cell setDrawsBackground:YES]; + [cell setBackgroundColor:lightGrayColor]; + } + else { + [cell setDrawsBackground:NO]; + } + + return; + } + else +#endif + if (tableView == tableContentView) { + + if (![cell respondsToSelector:@selector(setTextColor:)]) return; + + BOOL cellIsNullOrUnloaded = NO; + BOOL cellIsLinkCell = [cell isMemberOfClass:[SPTextAndLinkCell class]]; + + NSUInteger columnIndex = [[tableColumn identifier] integerValue]; + + // If user wants to edit 'cell' set text color to black and return to avoid + // writing in gray if value was NULL + if ([tableView editedColumn] != -1 + && [tableView editedRow] == rowIndex + && (NSUInteger)[[NSArrayObjectAtIndex([tableView tableColumns], [tableView editedColumn]) identifier] integerValue] == columnIndex) { + [cell setTextColor:blackColor]; + if (cellIsLinkCell) [cell setLinkActive:NO]; + return; + } + + // 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 indicate loading in these cases. + if (isWorking) { + pthread_mutex_lock(&tableValuesLock); + + if (rowIndex < (NSInteger)tableRowsCount && columnIndex < [tableValues columnCount]) { + cellIsNullOrUnloaded = [tableValues cellIsNullOrUnloadedAtRow:rowIndex column:columnIndex]; + } + + pthread_mutex_unlock(&tableValuesLock); + } + else { + cellIsNullOrUnloaded = [tableValues cellIsNullOrUnloadedAtRow:rowIndex column:columnIndex]; + } + + if (cellIsNullOrUnloaded) { + [cell setTextColor:rowIndex == [tableContentView selectedRow] ? whiteColor : lightGrayColor]; + } + else { + [cell setTextColor:blackColor]; + + if ([self cellValueIsDisplayedAsHexForColumn:[[tableColumn identifier] integerValue]]) { + [cell setTextColor:rowIndex == [tableContentView selectedRow] ? whiteColor : blueColor]; + } + } + + // Disable link arrows for the currently editing row and for any NULL or unloaded cells + if (cellIsLinkCell) { + if (cellIsNullOrUnloaded || [tableView editedRow] == rowIndex) { + [cell setLinkActive:NO]; + } + else { + [cell setLinkActive:YES]; + } + } + } +} + +#ifndef SP_CODA +/** + * Show the table cell content as tooltip + * + * - for text displays line breaks and tabs as well + * - if blob data can be interpret as image data display the image as transparent thumbnail + * (up to now using base64 encoded HTML data). + */ +- (NSString *)tableView:(NSTableView *)tableView toolTipForCell:(id)aCell rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row mouseLocation:(NSPoint)mouseLocation +{ + if (tableView == filterTableView) { + return nil; + } + else if (tableView == tableContentView) { + + if ([[aCell stringValue] length] < 2 || [tableDocumentInstance isWorking]) return nil; + + // Suppress tooltip if another toolip is already visible, mainly displayed by a Bundle command + // TODO has to be improved + for (id win in [NSApp orderedWindows]) + { + if ([[[[win contentView] class] description] isEqualToString:@"WebView"]) return nil; + } + + NSImage *image; + + NSPoint pos = [NSEvent mouseLocation]; + pos.y -= 20; + + 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(&tableValuesLock); + + if (row < (NSInteger)tableRowsCount && [[tableColumn identifier] integerValue] < (NSInteger)[tableValues columnCount]) { + theValue = [[SPDataStorageObjectAtRowAndColumn(tableValues, row, [[tableColumn identifier] integerValue]) copy] autorelease]; + } + + pthread_mutex_unlock(&tableValuesLock); + + if (!theValue) theValue = @"..."; + } + else { + theValue = SPDataStorageObjectAtRowAndColumn(tableValues, row, [[tableColumn identifier] integerValue]); + } + + if (theValue == nil) return nil; + + if ([theValue isKindOfClass:[NSData class]]) { + image = [[[NSImage alloc] initWithData:theValue] autorelease]; + + if (image) { + [SPTooltip showWithObject:image atLocation:pos ofType:@"image"]; + return nil; + } + } + else if ([theValue isKindOfClass:[SPMySQLGeometryData class]]) { + SPGeometryDataView *v = [[SPGeometryDataView alloc] initWithCoordinates:[theValue coordinates]]; + image = [v thumbnailImage]; + + if (image) { + [SPTooltip showWithObject:image atLocation:pos ofType:@"image"]; + [v release]; + return nil; + } + + [v release]; + } + + // Show the cell string value as tooltip (including line breaks and tabs) + // by using the cell's font + [SPTooltip showWithObject:[aCell stringValue] + atLocation:pos + ofType:@"text" + displayOptions:[NSDictionary dictionaryWithObjectsAndKeys: + [[aCell font] familyName], @"fontname", + [NSString stringWithFormat:@"%f",[[aCell font] pointSize]], @"fontsize", + nil]]; + + return nil; + } + + return nil; +} +#endif + +#ifndef SP_CODA /* SplitView delegate methods */ + +#pragma mark - +#pragma mark SplitView delegate methods + +- (BOOL)splitView:(NSSplitView *)sender canCollapseSubview:(NSView *)subview +{ + return NO; +} + +/** + * Set a minimum size for the filter text area. + */ +- (CGFloat)splitView:(NSSplitView *)sender constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)offset +{ + return proposedMax - 180; +} + +/** + * Set a minimum size for the field list and action area. + */ +- (CGFloat)splitView:(NSSplitView *)sender constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)offset +{ + return proposedMin + 225; +} + +/** + * Improve default resizing and resize only the filter text area by default. + */ +- (void)splitView:(NSSplitView *)sender resizeSubviewsWithOldSize:(NSSize)oldSize +{ + NSSize newSize = [sender frame].size; + NSView *leftView = [[sender subviews] objectAtIndex:0]; + NSView *rightView = [[sender subviews] objectAtIndex:1]; + float dividerThickness = [sender dividerThickness]; + NSRect leftFrame = [leftView frame]; + NSRect rightFrame = [rightView frame]; + + // Resize height of both views + leftFrame.size.height = newSize.height; + rightFrame.size.height = newSize.height; + + // Only resize the right view's width - unless the constraint has been reached + if (rightFrame.size.width > 180 || newSize.width > oldSize.width) { + rightFrame.size.width = newSize.width - leftFrame.size.width - dividerThickness; + } + else { + leftFrame.size.width = newSize.width - rightFrame.size.width - dividerThickness; + } + + rightFrame.origin.x = leftFrame.size.width + dividerThickness; + + [leftView setFrame:leftFrame]; + [rightView setFrame:rightFrame]; +} + +#endif + +#pragma mark - +#pragma mark Control delegate methods + +- (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)editor +{ + // Validate hex input + // We do this here because the textfield will still be selected with the pending changes if we bail out here + if(control == tableContentView) { + NSInteger columnIndex = [tableContentView editedColumn]; + if ([self cellValueIsDisplayedAsHexForColumn:columnIndex]) { + // special case: the "NULL" string + NSDictionary *column = NSArrayObjectAtIndex(dataColumns, columnIndex); + if ([[editor string] isEqualToString:[prefs objectForKey:SPNullValue]] && [[column objectForKey:@"null"] boolValue]) { + return YES; + } + // This is a binary object being edited as a hex string. + // Convert the string back to binary, checking for errors. + NSData *data = [NSData dataWithHexString:[editor string]]; + if (!data) { + SPOnewayAlertSheet( + NSLocalizedString(@"Invalid hexadecimal value", @"table content : editing : error message title when parsing as hex string failed"), + [tableDocumentInstance parentWindow], + NSLocalizedString(@"A valid hex string may only contain the numbers 0-9 and letters A-F (a-f). It can optionally begin with „0x“ and spaces will be ignored.\nAlternatively the syntax X'val' is supported, too.", @"table content : editing : error message description when parsing as hex string failed") + ); + return NO; + } + } + } + return YES; +} + +- (void)controlTextDidChange:(NSNotification *)notification +{ +#ifndef SP_CODA + if ([notification object] == filterTableView) { + + NSString *string = [[[[notification userInfo] objectForKey:@"NSFieldEditor"] textStorage] string]; + + if (string && [string length]) { + if (lastEditedFilterTableValue) [lastEditedFilterTableValue release]; + + lastEditedFilterTableValue = [[NSString stringWithString:string] retain]; + } + + [self updateFilterTableClause:string]; + } +#endif +} + +/** + * If the user selected a table cell which is a blob field and tried to edit it + * cancel the inline edit, display the field editor sheet instead for editing + * and re-enable inline editing after closing the sheet. + */ +- (BOOL)control:(NSControl *)control textShouldBeginEditing:(NSText *)aFieldEditor +{ + if (control != tableContentView) return YES; + + NSUInteger row, column; + BOOL shouldBeginEditing = YES; + + row = [tableContentView editedRow]; + column = [tableContentView editedColumn]; + + // If cell editing mode and editing request comes + // 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] 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; + + switch (numberOfPossibleUpdateRows) + { + case -1: + [SPTooltip showWithObject:kCellEditorErrorNoMultiTabDb + atLocation:pos + ofType:@"text"]; + shouldBeginEditing = NO; + break; + case 0: + [SPTooltip showWithObject:[NSString stringWithFormat:kCellEditorErrorNoMatch, selectedTable] + atLocation:pos + ofType:@"text"]; + shouldBeginEditing = NO; + break; + case 1: + shouldBeginEditing = YES; + break; + default: + [SPTooltip showWithObject:[NSString stringWithFormat:kCellEditorErrorTooManyMatches, (long)numberOfPossibleUpdateRows] + atLocation:pos + ofType:@"text"]; + shouldBeginEditing = NO; + } + + } + + // Open the field editor sheet if required + if ([tableContentView shouldUseFieldEditorForRow:row column:column checkWithLock:NULL]) + { + [tableContentView setFieldEditorSelectedRange:[aFieldEditor selectedRange]]; + + // Cancel editing + [control abortEditing]; + + NSAssert(fieldEditor == nil, @"Method should not to be called while a field editor sheet is open!"); + // Call the field editor sheet + [self tableView:tableContentView shouldEditTableColumn:NSArrayObjectAtIndex([tableContentView tableColumns], column) row:row]; + + // send current event to field editor sheet + if ([NSApp currentEvent]) { + [NSApp sendEvent:[NSApp currentEvent]]; + } + + return NO; + } + + return shouldBeginEditing; +} + +/** + * Trap the enter, escape, tab and arrow keys, overriding default behaviour and continuing/ending editing, + * only within the current row. + */ +- (BOOL)control:(NSControl<NSControlTextEditingDelegate> *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)command +{ + // Check firstly if SPCopyTable can handle command + if ([control control:control textView:textView doCommandBySelector:command]) + return YES; + + // Trap the escape key + if ([[control window] methodForSelector:command] == [[control window] methodForSelector:@selector(cancelOperation:)]) { + // Abort editing + [control abortEditing]; + + if ((SPCopyTable*)control == tableContentView) { + [self cancelRowEditing]; + } + + return YES; + } + + return NO; +} + +#pragma mark - +#pragma mark Database content view delegate methods + +- (NSString *)usedQuery +{ + return usedQuery; +} + +/** + * Retrieve the data column definitions + */ +- (NSArray *)dataColumnDefinitions +{ + return dataColumns; +} #pragma mark - diff --git a/Source/SPTableContentDataSource.h b/Source/SPTableContentDataSource.h deleted file mode 100644 index 19864a80..00000000 --- a/Source/SPTableContentDataSource.h +++ /dev/null @@ -1,37 +0,0 @@ -// -// SPTableContentDataSource.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on March 20, 2012. -// Copyright (c) 2012 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPTableContent.h" - -@interface SPTableContent (SPTableContentDataSource) - -- (BOOL)cellValueIsDisplayedAsHexForColumn:(NSUInteger)columnIndex; - -@end diff --git a/Source/SPTableContentDataSource.m b/Source/SPTableContentDataSource.m deleted file mode 100644 index a623f83b..00000000 --- a/Source/SPTableContentDataSource.m +++ /dev/null @@ -1,255 +0,0 @@ -// -// SPTableContentDataSource.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on March 20, 2012. -// Copyright (c) 2012 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPTableContentDataSource.h" -#import "SPTableContentFilter.h" -#import "SPDataStorage.h" -#import "SPCopyTable.h" -#import "SPTablesList.h" -#import "SPAlertSheets.h" - -#import <pthread.h> -#import <SPMySQL/SPMySQL.h> - -@interface SPTableContent (SPTableContentDataSource_Private_API) - -- (id)_contentValueForTableColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex asPreview:(BOOL)asPreview; - -@end - -@implementation SPTableContent (SPTableContentDataSource) - -#pragma mark - -#pragma mark TableView datasource methods - -- (NSInteger)numberOfRowsInTableView:(SPCopyTable *)tableView -{ -#ifndef SP_CODA - if (tableView == filterTableView) { - return filterTableIsSwapped ? [filterTableData count] : [[[filterTableData objectForKey:@"0"] objectForKey:SPTableContentFilterKey] count]; - } -#endif - if (tableView == tableContentView) { - return tableRowsCount; - } - - return 0; -} - -- (id)tableView:(SPCopyTable *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex -{ - NSUInteger columnIndex = [[tableColumn identifier] integerValue]; -#ifndef SP_CODA - if (tableView == filterTableView) { - if (filterTableIsSwapped) { - // First column shows the field names - if (columnIndex == 0) { - return [[[NSTableHeaderCell alloc] initTextCell:[[filterTableData objectForKey:[NSNumber numberWithInteger:rowIndex]] objectForKey:@"name"]] autorelease]; - } - - return NSArrayObjectAtIndex([[filterTableData objectForKey:[NSNumber numberWithInteger:rowIndex]] objectForKey:SPTableContentFilterKey], columnIndex - 1); - } - - return NSArrayObjectAtIndex([[filterTableData objectForKey:[tableColumn identifier]] objectForKey:SPTableContentFilterKey], rowIndex); - } -#endif - if (tableView == tableContentView) { - - 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(&tableValuesLock); - - if (rowIndex < (NSInteger)tableRowsCount && columnIndex < [tableValues columnCount]) { - value = [self _contentValueForTableColumn:columnIndex row:rowIndex asPreview:YES]; - } - - pthread_mutex_unlock(&tableValuesLock); - - if (!value) return @"..."; - } - else { - if ([tableView editedColumn] == (NSInteger)columnIndex && [tableView editedRow] == rowIndex) { - value = [self _contentValueForTableColumn:columnIndex row:rowIndex asPreview:NO]; - } - else { - value = [self _contentValueForTableColumn:columnIndex row:rowIndex asPreview:YES]; - } - } - - if ([value isKindOfClass:[SPMySQLGeometryData class]]) { - return [value wktString]; - } - - if ([value isNSNull]) { - return [prefs objectForKey:SPNullValue]; - } - - if ([value isKindOfClass:[NSData class]]) { - - if ([self cellValueIsDisplayedAsHexForColumn:columnIndex]) { - if ([(NSData *)value length] > 255) { - return [NSString stringWithFormat:@"0x%@…", [[(NSData *)value subdataWithRange:NSMakeRange(0, 255)] dataToHexString]]; - } - return [NSString stringWithFormat:@"0x%@", [(NSData *)value dataToHexString]]; - } - - pthread_mutex_t *fieldEditorCheckLock = NULL; - if (isWorking) { - fieldEditorCheckLock = &tableValuesLock; - } - - // Unless we're editing, always retrieve the short string representation, truncating the value where necessary - if ([tableView editedColumn] == (NSInteger)columnIndex || [tableView editedRow] == rowIndex) { - return [value stringRepresentationUsingEncoding:[mySQLConnection stringEncoding]]; - } else { - return [value shortStringRepresentationUsingEncoding:[mySQLConnection stringEncoding]]; - } - } - - if ([value isSPNotLoaded]) { - return NSLocalizedString(@"(not loaded)", @"value shown for hidden blob and text fields"); - } - - return value; - } - - return nil; -} - -- (void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex -{ -#ifndef SP_CODA - if(tableView == filterTableView) { - if (filterTableIsSwapped) { - [[[filterTableData objectForKey:[NSNumber numberWithInteger:rowIndex]] objectForKey:SPTableContentFilterKey] replaceObjectAtIndex:([[tableColumn identifier] integerValue] - 1) withObject:(NSString *)object]; - } - else { - [[[filterTableData objectForKey:[tableColumn identifier]] objectForKey:SPTableContentFilterKey] replaceObjectAtIndex:rowIndex withObject:(NSString *)object]; - } - - [self updateFilterTableClause:nil]; - - return; - } -#endif - if (tableView == tableContentView) { - NSInteger columnIndex = [[tableColumn identifier] integerValue]; - // 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:columnIndex checkWithLock:NULL]) { - return; - } - - // If table data comes from a view, save back to the view - if ([tablesListInstance tableType] == SPTableTypeView) { - [self saveViewCellValue:object forTableColumn:tableColumn row:rowIndex]; - return; - } - - // Catch editing events in the row and if the row isn't currently being edited, - // start an edit. This allows edits including enum changes to save correctly. - if (isEditingRow && [tableContentView selectedRow] != currentlyEditingRow) { - [self saveRowOnDeselect]; - } - - if (!isEditingRow) { - [oldRow setArray:[tableValues rowContentsAtIndex:rowIndex]]; - - isEditingRow = YES; - currentlyEditingRow = rowIndex; - } - - NSDictionary *column = NSArrayObjectAtIndex(dataColumns, columnIndex); - - if (object) { - // Restore NULLs if necessary - if ([object isEqualToString:[prefs objectForKey:SPNullValue]] && [[column objectForKey:@"null"] boolValue]) { - object = [NSNull null]; - } - else if ([self cellValueIsDisplayedAsHexForColumn:columnIndex]) { - // This is a binary object being edited as a hex string. - // Convert the string back to binary. - // Error checking is done in -control:textShouldEndEditing: - NSData *data = [NSData dataWithHexString:object]; - if (!data) { - NSBeep(); - return; - } - object = data; - } - - [tableValues replaceObjectInRow:rowIndex column:columnIndex withObject:object]; - } - else { - [tableValues replaceObjectInRow:rowIndex column:columnIndex withObject:@""]; - } - } -} - -- (BOOL)cellValueIsDisplayedAsHexForColumn:(NSUInteger)columnIndex -{ - if (![prefs boolForKey:SPDisplayBinaryDataAsHex]) { - return NO; - } - - NSDictionary *columnDefinition = [[(id <SPDatabaseContentViewDelegate>)[tableContentView delegate] dataColumnDefinitions] objectAtIndex:columnIndex]; - NSString *typeGrouping = columnDefinition[@"typegrouping"]; - - if ([typeGrouping isEqual:@"binary"]) { - return YES; - } - - if ([typeGrouping isEqual:@"blobdata"]) { - return YES; - } - - - return NO; -} - -@end - -@implementation SPTableContent (SPTableContentDataSource_Private_API) - -- (id)_contentValueForTableColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex asPreview:(BOOL)asPreview -{ - if (asPreview) { - return SPDataStoragePreviewAtRowAndColumn(tableValues, rowIndex, columnIndex, 150); - } - - return SPDataStorageObjectAtRowAndColumn(tableValues, rowIndex, columnIndex); -} - -@end diff --git a/Source/SPTableContentDelegate.h b/Source/SPTableContentDelegate.h deleted file mode 100644 index 2925fa36..00000000 --- a/Source/SPTableContentDelegate.h +++ /dev/null @@ -1,35 +0,0 @@ -// -// SPTableContentDelegate.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on March 20, 2012. -// Copyright (c) 2012 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPTableContent.h" - -@interface SPTableContent (SPTableContentDelegate) <SPDatabaseContentViewDelegate> - -@end diff --git a/Source/SPTableContentDelegate.m b/Source/SPTableContentDelegate.m deleted file mode 100644 index 0a80b602..00000000 --- a/Source/SPTableContentDelegate.m +++ /dev/null @@ -1,845 +0,0 @@ -// -// SPTableContentDelegate.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on March 20, 2012. -// Copyright (c) 2012 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPTableContentDelegate.h" -#import "SPTableContentFilter.h" -#import "SPTableContentDataSource.h" -#ifndef SP_CODA /* headers */ -#import "SPAppController.h" -#endif -#import "SPDatabaseDocument.h" -#import "SPDataStorage.h" -#import "SPGeometryDataView.h" -#import "SPTooltip.h" -#import "SPTablesList.h" -#ifndef SP_CODA /* headers */ -#import "SPBundleHTMLOutputController.h" -#endif -#import "SPCopyTable.h" -#import "SPAlertSheets.h" -#import "SPTableData.h" -#import "SPFieldEditorController.h" -#import "SPThreadAdditions.h" -#import "SPTextAndLinkCell.h" - -#import <pthread.h> -#import <SPMySQL/SPMySQL.h> - -@interface SPTableContent (SPDeclaredAPI) - -- (BOOL)cancelRowEditing; - -@end - -@implementation SPTableContent (SPTableContentDelegate) - -#pragma mark - -#pragma mark TableView delegate methods - -/** - * Sorts the tableView by the clicked column. If clicked twice, order is altered to descending. - * Performs the task in a new thread if necessary. - */ -- (void)tableView:(NSTableView*)tableView didClickTableColumn:(NSTableColumn *)tableColumn -{ - if ([selectedTable isEqualToString:@""] || !selectedTable || tableView != tableContentView) return; - - // Prevent sorting while the table is still loading - if ([tableDocumentInstance isWorking]) return; - - // Start the task - [tableDocumentInstance startTaskWithDescription:NSLocalizedString(@"Sorting table...", @"Sorting table task description")]; - - if ([NSThread isMainThread]) { - [NSThread detachNewThreadWithName:SPCtxt(@"SPTableContent table sort task", tableDocumentInstance) target:self selector:@selector(sortTableTaskWithColumn:) object:tableColumn]; - } - else { - [self sortTableTaskWithColumn:tableColumn]; - } -} - -- (void)tableViewSelectionDidChange:(NSNotification *)aNotification -{ - // Check our notification object is our table content view - if ([aNotification object] != tableContentView) return; - - 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. - if (isEditingRow && [tableContentView selectedRow] != currentlyEditingRow && ![self saveRowOnDeselect]) return; - - if (![tableDocumentInstance isWorking]) { - // Update the row selection count - // and update the status of the delete/duplicate buttons - if([tablesListInstance tableType] == SPTableTypeTable) { - if ([tableContentView numberOfSelectedRows] > 0) { - [duplicateButton setEnabled:([tableContentView numberOfSelectedRows] == 1)]; - [removeButton setEnabled:YES]; - } - else { - [duplicateButton setEnabled:NO]; - [removeButton setEnabled:NO]; - } - } - else { - [duplicateButton setEnabled:NO]; - [removeButton setEnabled:NO]; - } - } - - [self updateCountText]; - -#ifndef SP_CODA /* triggered commands */ - NSArray *triggeredCommands = [SPAppDelegate bundleCommandsForTrigger:SPBundleTriggerActionTableRowChanged]; - - for (NSString *cmdPath in triggeredCommands) - { - NSArray *data = [cmdPath componentsSeparatedByString:@"|"]; - NSMenuItem *aMenuItem = [[[NSMenuItem alloc] init] autorelease]; - - [aMenuItem setTag:0]; - [aMenuItem setToolTip:[data objectAtIndex:0]]; - - // For HTML output check if corresponding window already exists - BOOL stopTrigger = NO; - - if ([(NSString *)[data objectAtIndex:2] length]) { - BOOL correspondingWindowFound = NO; - NSString *uuid = [data objectAtIndex:2]; - - for (id win in [NSApp windows]) - { - if ([[[[win delegate] class] description] isEqualToString:@"SPBundleHTMLOutputController"]) { - if ([[[win delegate] windowUUID] isEqualToString:uuid]) { - correspondingWindowFound = YES; - break; - } - } - } - - if (!correspondingWindowFound) stopTrigger = YES; - } - if (!stopTrigger) { - id firstResponder = [[NSApp keyWindow] firstResponder]; - if ([[data objectAtIndex:1] isEqualToString:SPBundleScopeGeneral]) { - [[SPAppDelegate onMainThread] executeBundleItemForApp:aMenuItem]; - } - else if ([[data objectAtIndex:1] isEqualToString:SPBundleScopeDataTable]) { - if ([[[firstResponder class] description] isEqualToString:@"SPCopyTable"]) { - [[firstResponder onMainThread] executeBundleItemForDataTable:aMenuItem]; - } - } - else if ([[data objectAtIndex:1] isEqualToString:SPBundleScopeInputField]) { - if ([firstResponder isKindOfClass:[NSTextView class]]) { - [[firstResponder onMainThread] executeBundleItemForInputField:aMenuItem]; - } - } - } - } -#endif -} - -/** - * Saves the new column size in the preferences. - */ -- (void)tableViewColumnDidResize:(NSNotification *)notification -{ - // Check our notification object is our table content view - if ([notification object] != tableContentView) return; - - // Sometimes the column has no identifier. I can't figure out what is causing it, so we just skip over this item - if (![[[notification userInfo] objectForKey:@"NSTableColumn"] identifier]) return; - - NSMutableDictionary *tableColumnWidths; - NSString *database = [NSString stringWithFormat:@"%@@%@", [tableDocumentInstance database], [tableDocumentInstance host]]; - NSString *table = [tablesListInstance tableName]; - - // Get tableColumnWidths object -#ifndef SP_CODA - if ([prefs objectForKey:SPTableColumnWidths] != nil ) { - tableColumnWidths = [NSMutableDictionary dictionaryWithDictionary:[prefs objectForKey:SPTableColumnWidths]]; - } - else { -#endif - tableColumnWidths = [NSMutableDictionary dictionary]; -#ifndef SP_CODA - } -#endif - - // Get the database object - if ([tableColumnWidths objectForKey:database] == nil) { - [tableColumnWidths setObject:[NSMutableDictionary dictionary] forKey:database]; - } - else { - [tableColumnWidths setObject:[NSMutableDictionary dictionaryWithDictionary:[tableColumnWidths objectForKey:database]] forKey:database]; - } - - // Get the table object - if ([[tableColumnWidths objectForKey:database] objectForKey:table] == nil) { - [[tableColumnWidths objectForKey:database] setObject:[NSMutableDictionary dictionary] forKey:table]; - } - else { - [[tableColumnWidths objectForKey:database] setObject:[NSMutableDictionary dictionaryWithDictionary:[[tableColumnWidths objectForKey:database] objectForKey:table]] forKey:table]; - } - - // Save column size - [[[tableColumnWidths objectForKey:database] objectForKey:table] - setObject:[NSNumber numberWithDouble:[(NSTableColumn *)[[notification userInfo] objectForKey:@"NSTableColumn"] width]] - forKey:[[[[notification userInfo] objectForKey:@"NSTableColumn"] headerCell] stringValue]]; -#ifndef SP_CODA - [prefs setObject:tableColumnWidths forKey:SPTableColumnWidths]; -#endif -} - -/** - * Confirm whether to allow editing of a row. Returns YES by default, unless the multipleLineEditingButton is in - * the ON state, or for blob or text fields - in those cases opens a sheet for editing instead and returns NO. - */ -- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex -{ - if ([tableDocumentInstance isWorking]) return NO; - -#ifndef SP_CODA - if (tableView == filterTableView) { - return (filterTableIsSwapped && [[tableColumn identifier] integerValue] == 0) ? NO : YES; - } - else -#endif - if (tableView == tableContentView) { - - // Nothing is editable while the field editor is running. - // This guards against a special case where accessibility services might - // check if a table field is editable while the sheet is running. - if (fieldEditor) return NO; - - // Ensure that row is editable since it could contain "(not loaded)" columns together with - // issue that the table has no primary key - NSString *wherePart = [NSString stringWithString:[self argumentForRow:[tableContentView selectedRow]]]; - - if (![wherePart length]) return NO; - - // If the selected cell hasn't been loaded, load it. - if ([[tableValues cellDataAtRow:rowIndex column:[[tableColumn identifier] integerValue]] isSPNotLoaded]) { - - // Only get the data for the selected column, not all of them - NSString *query = [NSString stringWithFormat:@"SELECT %@ FROM %@ WHERE %@", [[[tableColumn headerCell] stringValue] backtickQuotedString], [selectedTable backtickQuotedString], wherePart]; - - SPMySQLResult *tempResult = [mySQLConnection queryString:query]; - - if (![tempResult numberOfRows]) { - SPOnewayAlertSheet( - NSLocalizedString(@"Error", @"error"), - [tableDocumentInstance parentWindow], - NSLocalizedString(@"Couldn't load the row. Reload the table to be sure that the row exists and use a primary key for your table.", @"message of panel when loading of row failed") - ); - return NO; - } - - NSArray *tempRow = [tempResult getRowAsArray]; - - [tableValues replaceObjectInRow:rowIndex column:[[tableContentView tableColumns] indexOfObject:tableColumn] withObject:[tempRow objectAtIndex:0]]; - [tableContentView reloadData]; - } - - // Retrieve the column definition - NSDictionary *columnDefinition = [cqColumnDefinition objectAtIndex:[[tableColumn identifier] integerValue]]; - - // Open the editing sheet if required - if ([tableContentView shouldUseFieldEditorForRow:rowIndex column:[[tableColumn identifier] integerValue] checkWithLock:NULL]) { - - BOOL isBlob = [tableDataInstance columnIsBlobOrText:[[tableColumn headerCell] stringValue]]; - - // 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:[[tableColumn identifier] integerValue]]; - isFieldEditable = [[editStatus objectAtIndex:0] integerValue] == 1; - } - - NSUInteger fieldLength = 0; - NSString *fieldEncoding = nil; - BOOL allowNULL = YES; - - NSString *fieldType = [columnDefinition objectForKey:@"type"]; - - if ([columnDefinition objectForKey:@"char_length"]) { - fieldLength = [[columnDefinition objectForKey:@"char_length"] integerValue]; - } - - if ([columnDefinition objectForKey:@"null"]) { - allowNULL = (![[columnDefinition objectForKey:@"null"] integerValue]); - } - - if ([columnDefinition objectForKey:@"charset_name"] && ![[columnDefinition objectForKey:@"charset_name"] isEqualToString:@"binary"]) { - fieldEncoding = [columnDefinition objectForKey:@"charset_name"]; - } - - fieldEditor = [[SPFieldEditorController alloc] init]; - - [fieldEditor setEditedFieldInfo:[NSDictionary dictionaryWithObjectsAndKeys: - [[tableColumn headerCell] stringValue], @"colName", - [self usedQuery], @"usedQuery", - @"content", @"tableSource", - nil]]; - - [fieldEditor setTextMaxLength:fieldLength]; - [fieldEditor setFieldType:fieldType == nil ? @"" : fieldType]; - [fieldEditor setFieldEncoding:fieldEncoding == nil ? @"" : fieldEncoding]; - [fieldEditor setAllowNULL:allowNULL]; - - id cellValue = [tableValues cellDataAtRow:rowIndex column:[[tableColumn identifier] integerValue]]; - - if ([cellValue isNSNull]) { - cellValue = [NSString stringWithString:[prefs objectForKey:SPNullValue]]; - } - - if ([self cellValueIsDisplayedAsHexForColumn:[[tableColumn identifier] integerValue]]) { - [fieldEditor setTextMaxLength:[[self tableView:tableContentView objectValueForTableColumn:tableColumn row:rowIndex] length]]; - isFieldEditable = NO; - } - - NSInteger editedColumn = 0; - - for (NSTableColumn* col in [tableContentView tableColumns]) - { - if ([[col identifier] isEqualToString:[tableColumn identifier]]) break; - - editedColumn++; - } - - [fieldEditor editWithObject:cellValue - fieldName:[[tableColumn headerCell] stringValue] - usingEncoding:[mySQLConnection stringEncoding] - isObjectBlob:isBlob - isEditable:isFieldEditable - withWindow:[tableDocumentInstance parentWindow] - sender:self - contextInfo:[NSDictionary dictionaryWithObjectsAndKeys: - [NSNumber numberWithInteger:rowIndex], @"rowIndex", - [NSNumber numberWithInteger:editedColumn], @"columnIndex", - [NSNumber numberWithBool:isFieldEditable], @"isFieldEditable", - nil]]; - - return NO; - } - - return YES; - } - - return YES; -} - -/** - * Enable drag from tableview - */ -- (BOOL)tableView:(NSTableView *)tableView writeRowsWithIndexes:(NSIndexSet *)rows toPasteboard:(NSPasteboard*)pboard -{ - if (tableView == tableContentView) { - NSString *tmp; - - // By holding ⌘, ⇧, or/and ⌥ copies selected rows as SQL INSERTS - // otherwise \t delimited lines - if ([[NSApp currentEvent] modifierFlags] & (NSCommandKeyMask|NSShiftKeyMask|NSAlternateKeyMask)) { - tmp = [tableContentView rowsAsSqlInsertsOnlySelectedRows:YES]; - } - else { - tmp = [tableContentView draggedRowsAsTabString]; - } - - if (tmp && [tmp length]) - { - [pboard declareTypes:@[NSTabularTextPboardType, NSStringPboardType] owner:nil]; - - [pboard setString:tmp forType:NSStringPboardType]; - [pboard setString:tmp forType:NSTabularTextPboardType]; - - return YES; - } - } - - return NO; -} - -/** - * Disable row selection while the document is working. - */ -- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)rowIndex -{ -#ifndef SP_CODA - if (tableView == filterTableView) { - return YES; - } - else -#endif - return tableView == tableContentView ? tableRowsSelectable : YES; -} - -/** - * Resize a column when it's double-clicked (10.6+ only). - */ -- (CGFloat)tableView:(NSTableView *)tableView sizeToFitWidthOfColumn:(NSInteger)columnIndex -{ - NSTableColumn *theColumn = [[tableView tableColumns] objectAtIndex:columnIndex]; - NSDictionary *columnDefinition = [dataColumns objectAtIndex:[[theColumn identifier] integerValue]]; - - // Get the column width - NSUInteger targetWidth = [tableContentView autodetectWidthForColumnDefinition:columnDefinition maxRows:500]; - -#ifndef SP_CODA - // Clear any saved widths for the column - NSString *dbKey = [NSString stringWithFormat:@"%@@%@", [tableDocumentInstance database], [tableDocumentInstance host]]; - NSString *tableKey = [tablesListInstance tableName]; - NSMutableDictionary *savedWidths = [NSMutableDictionary dictionaryWithDictionary:[prefs objectForKey:SPTableColumnWidths]]; - NSMutableDictionary *dbDict = [NSMutableDictionary dictionaryWithDictionary:[savedWidths objectForKey:dbKey]]; - NSMutableDictionary *tableDict = [NSMutableDictionary dictionaryWithDictionary:[dbDict objectForKey:tableKey]]; - - if ([tableDict objectForKey:[columnDefinition objectForKey:@"name"]]) { - [tableDict removeObjectForKey:[columnDefinition objectForKey:@"name"]]; - - if ([tableDict count]) { - [dbDict setObject:[NSDictionary dictionaryWithDictionary:tableDict] forKey:tableKey]; - } - else { - [dbDict removeObjectForKey:tableKey]; - } - - if ([dbDict count]) { - [savedWidths setObject:[NSDictionary dictionaryWithDictionary:dbDict] forKey:dbKey]; - } - else { - [savedWidths removeObjectForKey:dbKey]; - } - - [prefs setObject:[NSDictionary dictionaryWithDictionary:savedWidths] forKey:SPTableColumnWidths]; - } -#endif - - // Return the width, while the delegate is empty to prevent column resize notifications - [tableContentView setDelegate:nil]; - [tableContentView performSelector:@selector(setDelegate:) withObject:self afterDelay:0.1]; - - return targetWidth; -} - -/** - * This function changes the text color of text/blob fields which are null or not yet loaded to gray - */ -- (void)tableView:(SPCopyTable *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex -{ -#ifndef SP_CODA - if (tableView == filterTableView) { - if (filterTableIsSwapped && [[tableColumn identifier] integerValue] == 0) { - [cell setDrawsBackground:YES]; - [cell setBackgroundColor:lightGrayColor]; - } - else { - [cell setDrawsBackground:NO]; - } - - return; - } - else -#endif - if (tableView == tableContentView) { - - if (![cell respondsToSelector:@selector(setTextColor:)]) return; - - BOOL cellIsNullOrUnloaded = NO; - BOOL cellIsLinkCell = [cell isMemberOfClass:[SPTextAndLinkCell class]]; - - NSUInteger columnIndex = [[tableColumn identifier] integerValue]; - - // If user wants to edit 'cell' set text color to black and return to avoid - // writing in gray if value was NULL - if ([tableView editedColumn] != -1 - && [tableView editedRow] == rowIndex - && (NSUInteger)[[NSArrayObjectAtIndex([tableView tableColumns], [tableView editedColumn]) identifier] integerValue] == columnIndex) { - [cell setTextColor:blackColor]; - if (cellIsLinkCell) [cell setLinkActive:NO]; - return; - } - - // 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 indicate loading in these cases. - if (isWorking) { - pthread_mutex_lock(&tableValuesLock); - - if (rowIndex < (NSInteger)tableRowsCount && columnIndex < [tableValues columnCount]) { - cellIsNullOrUnloaded = [tableValues cellIsNullOrUnloadedAtRow:rowIndex column:columnIndex]; - } - - pthread_mutex_unlock(&tableValuesLock); - } - else { - cellIsNullOrUnloaded = [tableValues cellIsNullOrUnloadedAtRow:rowIndex column:columnIndex]; - } - - if (cellIsNullOrUnloaded) { - [cell setTextColor:rowIndex == [tableContentView selectedRow] ? whiteColor : lightGrayColor]; - } - else { - [cell setTextColor:blackColor]; - - if ([self cellValueIsDisplayedAsHexForColumn:[[tableColumn identifier] integerValue]]) { - [cell setTextColor:rowIndex == [tableContentView selectedRow] ? whiteColor : blueColor]; - } - } - - // Disable link arrows for the currently editing row and for any NULL or unloaded cells - if (cellIsLinkCell) { - if (cellIsNullOrUnloaded || [tableView editedRow] == rowIndex) { - [cell setLinkActive:NO]; - } - else { - [cell setLinkActive:YES]; - } - } - } -} - -#ifndef SP_CODA -/** - * Show the table cell content as tooltip - * - * - for text displays line breaks and tabs as well - * - if blob data can be interpret as image data display the image as transparent thumbnail - * (up to now using base64 encoded HTML data). - */ -- (NSString *)tableView:(NSTableView *)tableView toolTipForCell:(id)aCell rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row mouseLocation:(NSPoint)mouseLocation -{ - if (tableView == filterTableView) { - return nil; - } - else if (tableView == tableContentView) { - - if ([[aCell stringValue] length] < 2 || [tableDocumentInstance isWorking]) return nil; - - // Suppress tooltip if another toolip is already visible, mainly displayed by a Bundle command - // TODO has to be improved - for (id win in [NSApp orderedWindows]) - { - if ([[[[win contentView] class] description] isEqualToString:@"WebView"]) return nil; - } - - NSImage *image; - - NSPoint pos = [NSEvent mouseLocation]; - pos.y -= 20; - - 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(&tableValuesLock); - - if (row < (NSInteger)tableRowsCount && [[tableColumn identifier] integerValue] < (NSInteger)[tableValues columnCount]) { - theValue = [[SPDataStorageObjectAtRowAndColumn(tableValues, row, [[tableColumn identifier] integerValue]) copy] autorelease]; - } - - pthread_mutex_unlock(&tableValuesLock); - - if (!theValue) theValue = @"..."; - } - else { - theValue = SPDataStorageObjectAtRowAndColumn(tableValues, row, [[tableColumn identifier] integerValue]); - } - - if (theValue == nil) return nil; - - if ([theValue isKindOfClass:[NSData class]]) { - image = [[[NSImage alloc] initWithData:theValue] autorelease]; - - if (image) { - [SPTooltip showWithObject:image atLocation:pos ofType:@"image"]; - return nil; - } - } - else if ([theValue isKindOfClass:[SPMySQLGeometryData class]]) { - SPGeometryDataView *v = [[SPGeometryDataView alloc] initWithCoordinates:[theValue coordinates]]; - image = [v thumbnailImage]; - - if (image) { - [SPTooltip showWithObject:image atLocation:pos ofType:@"image"]; - [v release]; - return nil; - } - - [v release]; - } - - // Show the cell string value as tooltip (including line breaks and tabs) - // by using the cell's font - [SPTooltip showWithObject:[aCell stringValue] - atLocation:pos - ofType:@"text" - displayOptions:[NSDictionary dictionaryWithObjectsAndKeys: - [[aCell font] familyName], @"fontname", - [NSString stringWithFormat:@"%f",[[aCell font] pointSize]], @"fontsize", - nil]]; - - return nil; - } - - return nil; -} -#endif - -#ifndef SP_CODA /* SplitView delegate methods */ - -#pragma mark - -#pragma mark SplitView delegate methods - -- (BOOL)splitView:(NSSplitView *)sender canCollapseSubview:(NSView *)subview -{ - return NO; -} - -/** - * Set a minimum size for the filter text area. - */ -- (CGFloat)splitView:(NSSplitView *)sender constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)offset -{ - return proposedMax - 180; -} - -/** - * Set a minimum size for the field list and action area. - */ -- (CGFloat)splitView:(NSSplitView *)sender constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)offset -{ - return proposedMin + 225; -} - -/** - * Improve default resizing and resize only the filter text area by default. - */ -- (void)splitView:(NSSplitView *)sender resizeSubviewsWithOldSize:(NSSize)oldSize -{ - NSSize newSize = [sender frame].size; - NSView *leftView = [[sender subviews] objectAtIndex:0]; - NSView *rightView = [[sender subviews] objectAtIndex:1]; - float dividerThickness = [sender dividerThickness]; - NSRect leftFrame = [leftView frame]; - NSRect rightFrame = [rightView frame]; - - // Resize height of both views - leftFrame.size.height = newSize.height; - rightFrame.size.height = newSize.height; - - // Only resize the right view's width - unless the constraint has been reached - if (rightFrame.size.width > 180 || newSize.width > oldSize.width) { - rightFrame.size.width = newSize.width - leftFrame.size.width - dividerThickness; - } - else { - leftFrame.size.width = newSize.width - rightFrame.size.width - dividerThickness; - } - - rightFrame.origin.x = leftFrame.size.width + dividerThickness; - - [leftView setFrame:leftFrame]; - [rightView setFrame:rightFrame]; -} - -#endif - -#pragma mark - -#pragma mark Control delegate methods - -- (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)editor -{ - // Validate hex input - // We do this here because the textfield will still be selected with the pending changes if we bail out here - if(control == tableContentView) { - NSInteger columnIndex = [tableContentView editedColumn]; - if ([self cellValueIsDisplayedAsHexForColumn:columnIndex]) { - // special case: the "NULL" string - NSDictionary *column = NSArrayObjectAtIndex(dataColumns, columnIndex); - if ([[editor string] isEqualToString:[prefs objectForKey:SPNullValue]] && [[column objectForKey:@"null"] boolValue]) { - return YES; - } - // This is a binary object being edited as a hex string. - // Convert the string back to binary, checking for errors. - NSData *data = [NSData dataWithHexString:[editor string]]; - if (!data) { - SPOnewayAlertSheet( - NSLocalizedString(@"Invalid hexadecimal value", @"table content : editing : error message title when parsing as hex string failed"), - [tableDocumentInstance parentWindow], - NSLocalizedString(@"A valid hex string may only contain the numbers 0-9 and letters A-F (a-f). It can optionally begin with „0x“ and spaces will be ignored.\nAlternatively the syntax X'val' is supported, too.", @"table content : editing : error message description when parsing as hex string failed") - ); - return NO; - } - } - } - return YES; -} - -- (void)controlTextDidChange:(NSNotification *)notification -{ -#ifndef SP_CODA - if ([notification object] == filterTableView) { - - NSString *string = [[[[notification userInfo] objectForKey:@"NSFieldEditor"] textStorage] string]; - - if (string && [string length]) { - if (lastEditedFilterTableValue) [lastEditedFilterTableValue release]; - - lastEditedFilterTableValue = [[NSString stringWithString:string] retain]; - } - - [self updateFilterTableClause:string]; - } -#endif -} - -/** - * If the user selected a table cell which is a blob field and tried to edit it - * cancel the inline edit, display the field editor sheet instead for editing - * and re-enable inline editing after closing the sheet. - */ -- (BOOL)control:(NSControl *)control textShouldBeginEditing:(NSText *)aFieldEditor -{ - if (control != tableContentView) return YES; - - NSUInteger row, column; - BOOL shouldBeginEditing = YES; - - row = [tableContentView editedRow]; - column = [tableContentView editedColumn]; - - // If cell editing mode and editing request comes - // 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] 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; - - switch (numberOfPossibleUpdateRows) - { - case -1: - [SPTooltip showWithObject:kCellEditorErrorNoMultiTabDb - atLocation:pos - ofType:@"text"]; - shouldBeginEditing = NO; - break; - case 0: - [SPTooltip showWithObject:[NSString stringWithFormat:kCellEditorErrorNoMatch, selectedTable] - atLocation:pos - ofType:@"text"]; - shouldBeginEditing = NO; - break; - case 1: - shouldBeginEditing = YES; - break; - default: - [SPTooltip showWithObject:[NSString stringWithFormat:kCellEditorErrorTooManyMatches, (long)numberOfPossibleUpdateRows] - atLocation:pos - ofType:@"text"]; - shouldBeginEditing = NO; - } - - } - - // Open the field editor sheet if required - if ([tableContentView shouldUseFieldEditorForRow:row column:column checkWithLock:NULL]) - { - [tableContentView setFieldEditorSelectedRange:[aFieldEditor selectedRange]]; - - // Cancel editing - [control abortEditing]; - - NSAssert(fieldEditor == nil, @"Method should not to be called while a field editor sheet is open!"); - // Call the field editor sheet - [self tableView:tableContentView shouldEditTableColumn:NSArrayObjectAtIndex([tableContentView tableColumns], column) row:row]; - - // send current event to field editor sheet - if ([NSApp currentEvent]) { - [NSApp sendEvent:[NSApp currentEvent]]; - } - - return NO; - } - - return shouldBeginEditing; -} - -/** - * Trap the enter, escape, tab and arrow keys, overriding default behaviour and continuing/ending editing, - * only within the current row. - */ -- (BOOL)control:(NSControl<NSControlTextEditingDelegate> *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)command -{ - // Check firstly if SPCopyTable can handle command - if ([control control:control textView:textView doCommandBySelector:command]) - return YES; - - // Trap the escape key - if ([[control window] methodForSelector:command] == [[control window] methodForSelector:@selector(cancelOperation:)]) { - // Abort editing - [control abortEditing]; - - if ((SPCopyTable*)control == tableContentView) { - [self cancelRowEditing]; - } - - return YES; - } - - return NO; -} - -#pragma mark - -#pragma mark Database content view delegate methods - -- (NSString *)usedQuery -{ - return usedQuery; -} - -/** - * Retrieve the data column definitions - */ -- (NSArray *)dataColumnDefinitions -{ - return dataColumns; -} - -@end diff --git a/Source/SPTableContentFilter.h b/Source/SPTableContentFilter.h deleted file mode 100644 index 2380f34c..00000000 --- a/Source/SPTableContentFilter.h +++ /dev/null @@ -1,37 +0,0 @@ -// -// SPTableContentFilter.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on August 14, 2012. -// Copyright (c) 2012 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. - -#import "SPTableContent.h" - -@interface SPTableContent (SPTableContentFilter) - -- (void)makeContentFilterHaveFocus; -- (void)updateFilterTableClause:(id)currentValue; -- (NSString*)escapeFilterTableDefaultOperator:(NSString*)operator; - -@end diff --git a/Source/SPTableContentFilter.m b/Source/SPTableContentFilter.m deleted file mode 100644 index b4651e50..00000000 --- a/Source/SPTableContentFilter.m +++ /dev/null @@ -1,246 +0,0 @@ -// -// SPTableContentFilter.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on August 14, 2012. -// Copyright (c) 2012 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. - -#import "SPTableContentFilter.h" -#import "RegexKitLite.h" -#import "SPCopyTable.h" -#import "SPTextView.h" - -@implementation SPTableContent (SPTableContentFilter) - -#ifndef SP_CODA - -/** - * Escape passed operator for usage as filterTableDefaultOperator. - */ -- (NSString*)escapeFilterTableDefaultOperator:(NSString *)operator -{ - if (!operator) return @""; - - NSMutableString *newOp = [[[NSMutableString alloc] initWithCapacity:[operator length]] autorelease]; - - [newOp setString:operator]; - [newOp replaceOccurrencesOfRegex:@"%" withString:@"%%"]; - [newOp replaceOccurrencesOfRegex:@"(?<!`)@(?!=`)" withString:@"%@"]; - - return newOp; -} - -/** - * Update WHERE clause in filter table window. - * - * @param currentValue If currentValue == nil take the data from filterTableData, if currentValue == filterTableSearchAllFields - * generate a WHERE clause to search in all given fields, if currentValue == a string take this string as table cell data of the - * currently edited table cell - */ -- (void)updateFilterTableClause:(id)currentValue -{ - NSMutableString *clause = [NSMutableString string]; - NSInteger numberOfRows = [self numberOfRowsInTableView:filterTableView]; - NSInteger numberOfCols = [[filterTableView tableColumns] count]; - NSInteger numberOfValues = 0; - NSRange opRange, defopRange; - - BOOL lookInAllFields = NO; - - NSString *re1 = @"^\\s*(<[=>]?|>=?|!?=|≠|≤|≥)\\s*(.*?)\\s*$"; - NSString *re2 = @"^\\s*(.*)\\s+(.*?)\\s*$"; - - NSInteger editedRow = [filterTableView editedRow]; - - if (currentValue == filterTableSearchAllFields) { - numberOfRows = 1; - lookInAllFields = YES; - } - - [filterTableWhereClause setString:@""]; - - for (NSInteger i = 0; i < numberOfRows; i++) - { - numberOfValues = 0; - - for (NSInteger anIndex = 0; anIndex < numberOfCols; anIndex++) - { - NSString *filterCell = nil; - NSDictionary *filterCellData = [NSDictionary dictionaryWithDictionary:[filterTableData objectForKey:[NSString stringWithFormat:@"%ld", (long)anIndex]]]; - - // Take filterTableData - if (!currentValue) { - filterCell = NSArrayObjectAtIndex([filterCellData objectForKey:SPTableContentFilterKey], i); - } - // Take last edited value to create the OR clause - else if (lookInAllFields) { - if (lastEditedFilterTableValue && [lastEditedFilterTableValue length]) { - filterCell = lastEditedFilterTableValue; - } - else { - [filterTableWhereClause setString:@""]; - [filterTableWhereClause insertText:@""]; - [filterTableWhereClause scrollRangeToVisible:NSMakeRange(0, 0)]; - - // If live search is set perform filtering - if ([filterTableLiveSearchCheckbox state] == NSOnState) { - [self filterTable:filterTableFilterButton]; - } - } - } - // Take value from currently edited table cell - else if ([currentValue isKindOfClass:[NSString class]]) { - if (i == editedRow && anIndex == [[NSArrayObjectAtIndex([filterTableView tableColumns], [filterTableView editedColumn]) identifier] integerValue]) { - filterCell = (NSString*)currentValue; - } - else { - filterCell = NSArrayObjectAtIndex([filterCellData objectForKey:SPTableContentFilterKey], i); - } - } - - if ([filterCell length]) { - - // Recode special operators - filterCell = [filterCell stringByReplacingOccurrencesOfRegex:@"^\\s*≠" withString:@"!="]; - filterCell = [filterCell stringByReplacingOccurrencesOfRegex:@"^\\s*≤" withString:@"<="]; - filterCell = [filterCell stringByReplacingOccurrencesOfRegex:@"^\\s*≥" withString:@">="]; - - if (numberOfValues) { - [clause appendString:(lookInAllFields) ? @" OR " : @" AND "]; - } - - NSString *fieldName = [[filterCellData objectForKey:@"name"] backtickQuotedString]; - NSString *filterTableDefaultOperatorWithFieldName = [filterTableDefaultOperator stringByReplacingOccurrencesOfString:@"`@`" withString:fieldName]; - - opRange = [filterCell rangeOfString:@"`@`"]; - defopRange = [filterTableDefaultOperator rangeOfString:@"`@`"]; - - // if cell data begins with ' or " treat it as it is - // by checking if default operator by itself contains a ' or " - if so - // remove first and if given the last ' or " - if ([filterCell isMatchedByRegex:@"^\\s*['\"]"]) { - if ([filterTableDefaultOperator isMatchedByRegex:@"['\"]"]) { - NSArray *matches = [filterCell arrayOfCaptureComponentsMatchedByRegex:@"^\\s*(['\"])(.*)\\1\\s*$"]; - - if ([matches count] && [matches = NSArrayObjectAtIndex(matches, 0) count] == 3) { - [clause appendFormat:[NSString stringWithFormat:@"%%@ %@", filterTableDefaultOperatorWithFieldName], fieldName, NSArrayObjectAtIndex(matches, 2)]; - } - else { - matches = [filterCell arrayOfCaptureComponentsMatchedByRegex:@"^\\s*(['\"])(.*)\\s*$"]; - - if ([matches count] && [matches = NSArrayObjectAtIndex(matches, 0) count] == 3) { - [clause appendFormat:[NSString stringWithFormat:@"%%@ %@", filterTableDefaultOperatorWithFieldName], fieldName, NSArrayObjectAtIndex(matches, 2)]; - } - } - } - else { - [clause appendFormat:[NSString stringWithFormat:@"%%@ %@", filterTableDefaultOperatorWithFieldName], fieldName, filterCell]; - } - } - // If cell contains the field name placeholder - else if (opRange.length || defopRange.length) { - filterCell = [filterCell stringByReplacingOccurrencesOfString:@"`@`" withString:fieldName]; - - if (defopRange.length) { - [clause appendFormat:filterTableDefaultOperatorWithFieldName, [filterCell stringByReplacingOccurrencesOfString:@"`@`" withString:fieldName]]; - } - else { - [clause appendString:[filterCell stringByReplacingOccurrencesOfString:@"`@`" withString:fieldName]]; - } - } - // If cell is equal to NULL - else if ([filterCell isMatchedByRegex:@"(?i)^\\s*null\\s*$"]) { - [clause appendFormat:@"%@ IS NULL", fieldName]; - } - // If cell starts with an operator - else if ([filterCell isMatchedByRegex:re1]) { - NSArray *matches = [filterCell arrayOfCaptureComponentsMatchedByRegex:re1]; - - if ([matches count] && [matches = NSArrayObjectAtIndex(matches, 0) count] == 3) { - [clause appendFormat:@"%@ %@ %@", fieldName, NSArrayObjectAtIndex(matches, 1), NSArrayObjectAtIndex(matches, 2)]; - } - } - // If cell consists of at least two words treat the first as operator and the rest as argument - else if ([filterCell isMatchedByRegex:re2]) { - NSArray *matches = [filterCell arrayOfCaptureComponentsMatchedByRegex:re2]; - - if ([matches count] && [matches = NSArrayObjectAtIndex(matches,0) count] == 3) { - [clause appendFormat:@"%@ %@ %@", fieldName, [NSArrayObjectAtIndex(matches, 1) uppercaseString], NSArrayObjectAtIndex(matches, 2)]; - } - } - // Apply the default operator - else { - [clause appendFormat:[NSString stringWithFormat:@"%%@ %@", filterTableDefaultOperatorWithFieldName], fieldName, filterCell]; - } - - numberOfValues++; - } - } - - if (numberOfValues) { - [clause appendString:@"\nOR\n"]; - } - } - - // Remove last " OR " if any - [filterTableWhereClause setString:[clause length] > 3 ? [clause substringToIndex:([clause length] - 4)] : @""]; - - // Update syntax highlighting and uppercasing - [filterTableWhereClause insertText:@""]; - [filterTableWhereClause scrollRangeToVisible:NSMakeRange(0, 0)]; - - // If live search is set perform filtering - if ([filterTableLiveSearchCheckbox state] == NSOnState) { - [self filterTable:filterTableFilterButton]; - } -} - -/** - * Makes the content filter field have focus by making it the first responder. - */ -- (void)makeContentFilterHaveFocus -{ - NSDictionary *filter = [[contentFilters objectForKey:compareType] objectAtIndex:[[compareField selectedItem] tag]]; - - if ([filter objectForKey:@"NumberOfArguments"]) { - - NSUInteger numOfArgs = [[filter objectForKey:@"NumberOfArguments"] integerValue]; - - switch (numOfArgs) - { - case 2: - [[firstBetweenField window] makeFirstResponder:firstBetweenField]; - break; - case 1: - [[argumentField window] makeFirstResponder:argumentField]; - break; - default: - [[compareField window] makeFirstResponder:compareField]; - } - } -} - -#endif - -@end diff --git a/Source/SPTableData.m b/Source/SPTableData.m index a0bfa7ff..c065d4d1 100644 --- a/Source/SPTableData.m +++ b/Source/SPTableData.m @@ -39,7 +39,7 @@ #import <pthread.h> #import <SPMySQL/SPMySQL.h> -@interface SPTableData (PrivateAPI) +@interface SPTableData () - (void)_loopWhileWorking; - (NSDictionary *)parseCreateStatement:(NSString *)tableDef ofType:(NSString *)tableType; diff --git a/Source/SPTableFilterParser.m b/Source/SPTableFilterParser.m index f7c9ed62..93b976f8 100644 --- a/Source/SPTableFilterParser.m +++ b/Source/SPTableFilterParser.m @@ -31,7 +31,7 @@ #import "SPTableFilterParser.h" #import "RegexKitLite.h" -@interface SPTableFilterParser (Private) +@interface SPTableFilterParser () + (NSString *)escapeFilterArgument:(NSString *)argument againstClause:(NSString *)clause; @end diff --git a/Source/SPTableInfo.m b/Source/SPTableInfo.m index ce9546d9..6846639f 100644 --- a/Source/SPTableInfo.m +++ b/Source/SPTableInfo.m @@ -37,7 +37,7 @@ #import "SPTableTextFieldCell.h" #import "SPAppController.h" -@interface SPTableInfo (PrivateAPI) +@interface SPTableInfo () - (NSString *)_getUserDefinedDateStringFromMySQLDate:(NSString *)mysqlDate; diff --git a/Source/SPTableRelations.m b/Source/SPTableRelations.m index 20059fbb..6b959430 100644 --- a/Source/SPTableRelations.m +++ b/Source/SPTableRelations.m @@ -541,6 +541,57 @@ static NSString *SPRelationOnDeleteKey = @"on_delete"; } #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 delegate methods + +/** + * Called whenever the relations table view selection changes. + */ +- (void)tableViewSelectionDidChange:(NSNotification *)notification +{ + [removeRelationButton setEnabled:([relationsTableView numberOfSelectedRows] > 0)]; +} + +/* + * Double-click action on table cells - for the time being, return + * NO to disable editing. + */ +- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex +{ + if ([tableDocumentInstance isWorking]) return NO; + + return NO; +} + +/** + * Disable row selection while the document is working. + */ +- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)rowIndex +{ + return ![tableDocumentInstance isWorking]; +} + +#pragma mark - #pragma mark Private API /** diff --git a/Source/SPTableRelationsDelegate.h b/Source/SPTableRelationsDelegate.h deleted file mode 100644 index 79b1e914..00000000 --- a/Source/SPTableRelationsDelegate.h +++ /dev/null @@ -1,35 +0,0 @@ -// -// SPTableRelationsDelegate.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on March 28, 2017. -// Copyright (c) 2017 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPTableRelations.h" - -@interface SPTableRelations (SPTableRelationsDelegate) - -@end diff --git a/Source/SPTableRelationsDelegate.m b/Source/SPTableRelationsDelegate.m deleted file mode 100644 index bf083318..00000000 --- a/Source/SPTableRelationsDelegate.m +++ /dev/null @@ -1,87 +0,0 @@ -// -// SPTableRelationsDelegate.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on March 28, 2017. -// Copyright (c) 2017 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPTableRelationsDelegate.h" -#import "SPDatabaseDocument.h" - -@implementation SPTableRelations (SPTableRelationsDelegate) - -#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 delegate methods - -/** - * Called whenever the relations table view selection changes. - */ -- (void)tableViewSelectionDidChange:(NSNotification *)notification -{ - [removeRelationButton setEnabled:([relationsTableView numberOfSelectedRows] > 0)]; -} - -/* - * Double-click action on table cells - for the time being, return - * NO to disable editing. - */ -- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex -{ - if ([tableDocumentInstance isWorking]) return NO; - - return NO; -} - -/** - * Disable row selection while the document is working. - */ -- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)rowIndex -{ - return ![tableDocumentInstance isWorking]; -} - -@end diff --git a/Source/SPTableStructure.h b/Source/SPTableStructure.h index c8b0ec0a..ff71c0b6 100644 --- a/Source/SPTableStructure.h +++ b/Source/SPTableStructure.h @@ -175,4 +175,10 @@ + (SPFieldTypeHelp *)helpForFieldType:(NSString *)typeName; +#pragma mark - SPTableStructureLoading + +- (void)loadTable:(NSString *)aTable; +- (IBAction)reloadTable:(id)sender; +- (void)setTableDetails:(NSDictionary *)tableDetails; + @end diff --git a/Source/SPTableStructure.m b/Source/SPTableStructure.m index c1b007c3..d3723f86 100644 --- a/Source/SPTableStructure.m +++ b/Source/SPTableStructure.m @@ -32,7 +32,6 @@ #import "SPTableStructure.h" #import "SPDatabaseStructure.h" #import "SPDatabaseDocument.h" -#import "SPDatabaseViewController.h" #import "SPTableInfo.h" #import "SPTablesList.h" #import "SPTableData.h" @@ -43,10 +42,13 @@ #import "SPIndexesController.h" #import "RegexKitLite.h" #import "SPTableFieldValidation.h" -#import "SPTableStructureLoading.h" #import "SPThreadAdditions.h" #import "SPServerSupport.h" #import "SPExtendedTableInfo.h" +#import "SPFunctions.h" +#import "SPPillAttachmentCell.h" +#import "SPIdMenu.h" +#import "SPComboBoxCell.h" #import <SPMySQL/SPMySQL.h> @@ -89,11 +91,33 @@ static inline SPFieldTypeHelp *MakeFieldTypeHelp(NSString *typeName,NSString *ty return [obj autorelease]; } -@interface SPTableStructure (PrivateAPI) +struct _cmpMap { + NSString *title; // the title of the "pill" + NSString *tooltipPart; // the tooltip of the menuitem + NSString *cmpWith; // the string to match against +}; +/** + * This function will compare the representedObject of every item in menu against + * every map->cmpWith. If they match it will append a pill-like (similar to a TokenFieldCell's token) + * element labelled map->title to the menu item's title. If map->tooltipPart is set, + * it will also be added to the menu item's tooltip. + * + * This is used with the encoding/collation popup menus to add visual indicators for the + * table-level and default encoding/collation. + */ +static void _BuildMenuWithPills(NSMenu *menu,struct _cmpMap *map,size_t mapEntries); + +@interface SPTableStructure () + +- (void)sheetDidEnd:(id)sheet returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo; - (void)_removeFieldAndForeignKey:(NSNumber *)removeForeignKey; - (NSString *)_buildPartialColumnDefinitionString:(NSDictionary *)theRow; +#pragma mark - SPTableStructureDelegate + +- (void)_displayFieldTypeHelpIfPossible:(SPComboBoxCell *)cell; + @end @implementation SPTableStructure @@ -1514,6 +1538,1092 @@ static inline SPFieldTypeHelp *MakeFieldTypeHelp(NSString *typeName,NSString *ty } #pragma mark - +#pragma mark Table loading + +/** + * Loads aTable, puts it in an array, updates the tableViewColumns and reloads the tableView. + */ +- (void)loadTable:(NSString *)aTable +{ + NSMutableDictionary *theTableEnumLists = [NSMutableDictionary dictionary]; + + // Check whether a save of the current row is required. + if (![[self onMainThread] saveRowOnDeselect]) return; + + // If no table is selected, reset the interface and return + if (!aTable || ![aTable length]) { + [[self onMainThread] setTableDetails:nil]; + return; + } + + NSMutableArray *theTableFields = [[NSMutableArray alloc] init]; + + // Make a mutable copy out of the cached [tableDataInstance columns] since we're adding infos + for (id col in [tableDataInstance columns]) + { + [theTableFields addObject:[[col mutableCopy] autorelease]]; + } + + // Retrieve the indexes for the table + SPMySQLResult *indexResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW INDEX FROM %@", [aTable backtickQuotedString]]]; + + // If an error occurred, reset the interface and abort + if ([mySQLConnection queryErrored]) { + [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; + [[self onMainThread] setTableDetails:nil]; + + if ([mySQLConnection isConnected]) { + SPOnewayAlertSheet( + NSLocalizedString(@"Error", @"error"), + [NSApp mainWindow], + [NSString stringWithFormat:NSLocalizedString(@"An error occurred while retrieving information.\nMySQL said: %@", @"message of panel when retrieving information failed"), [mySQLConnection lastErrorMessage]] + ); + } + + return; + } + + // Process the indexes into a local array of dictionaries + NSArray *theTableIndexes = [self convertIndexResultToArray:indexResult]; + + // Set the Key column + for (NSDictionary* theIndex in theTableIndexes) + { + for (id field in theTableFields) + { + if ([[field objectForKey:@"name"] isEqualToString:[theIndex objectForKey:@"Column_name"]]) { + if ([[theIndex objectForKey:@"Key_name"] isEqualToString:@"PRIMARY"]) { + [field setObject:@"PRI" forKey:@"Key"]; + } + else { + if ([[field objectForKey:@"typegrouping"] isEqualToString:@"geometry"]) { + [field setObject:@"SPA" forKey:@"Key"]; + } + else { + [field setObject:(([[theIndex objectForKey:@"Non_unique"] isEqualToString:@"1"]) ? @"MUL" : @"UNI") forKey:@"Key"]; + } + } + + break; + } + } + } + + // Set up the encoding PopUpButtonCell + NSArray *encodings = [databaseDataInstance getDatabaseCharacterSetEncodings]; + + SPMainQSync(^{ + [encodingPopupCell removeAllItems]; + + if ([encodings count]) { + + [encodingPopupCell addItemWithTitle:@"dummy"]; + //copy the default attributes and add gray color + NSMutableDictionary *defaultAttrs = [NSMutableDictionary dictionaryWithDictionary:[[encodingPopupCell attributedTitle] attributesAtIndex:0 effectiveRange:NULL]]; + [defaultAttrs setObject:[NSColor lightGrayColor] forKey:NSForegroundColorAttributeName]; + [[encodingPopupCell lastItem] setTitle:@""]; + + for (NSDictionary *encoding in encodings) + { + NSString *encodingName = [encoding objectForKey:@"CHARACTER_SET_NAME"]; + NSString *title = (![encoding objectForKey:@"DESCRIPTION"]) ? encodingName : [NSString stringWithFormat:@"%@ (%@)", [encoding objectForKey:@"DESCRIPTION"], encodingName]; + + [encodingPopupCell addItemWithTitle:title]; + NSMenuItem *item = [encodingPopupCell lastItem]; + + [item setRepresentedObject:encodingName]; + + if ([encodingName isEqualToString:[tableDataInstance tableEncoding]]) { + + NSAttributedString *itemString = [[NSAttributedString alloc] initWithString:[item title] attributes:defaultAttrs]; + + [item setAttributedTitle:[itemString autorelease]]; + } + } + } + else { + [encodingPopupCell addItemWithTitle:NSLocalizedString(@"Not available", @"not available label")]; + } + }); + + // Process all the fields to normalise keys and add additional information + for (id theField in theTableFields) + { + NSString *type = [[[theField objectForKey:@"type"] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] uppercaseString]; + + if([type isEqualToString:@"JSON"]) { + // MySQL 5.7 manual: + // "MySQL handles strings used in JSON context using the utf8mb4 character set and utf8mb4_bin collation. + // Strings in other character set are converted to utf8mb4 as necessary." + [theField setObject:@"utf8mb4" forKey:@"encodingName"]; + [theField setObject:@"utf8mb4_bin" forKey:@"collationName"]; + [theField setObject:@1 forKey:@"binary"]; + } + else if ([fieldValidation isFieldTypeString:type]) { + // The MySQL 4.1 manual says: + // + // MySQL chooses the column character set and collation in the following manner: + // 1. If both CHARACTER SET X and COLLATE Y were specified, then character set X and collation Y are used. + // 2. If CHARACTER SET X was specified without COLLATE, then character set X and its default collation are used. + // 3. If COLLATE Y was specified without CHARACTER SET, then the character set associated with Y and collation Y. + // 4. Otherwise, the table character set and collation are used. + NSString *encoding = [theField objectForKey:@"encoding"]; + NSString *collation = [theField objectForKey:@"collation"]; + if(encoding) { + if(collation) { + // 1 + } + else { + collation = [databaseDataInstance getDefaultCollationForEncoding:encoding]; // 2 + } + } + else { + if(collation) { + encoding = [databaseDataInstance getEncodingFromCollation:collation]; // 3 + } + else { + encoding = [tableDataInstance tableEncoding]; //4 + collation = [tableDataInstance statusValueForKey:@"Collation"]; + if(!collation) { + // should not happen, as the TABLE STATUS output always(?) includes the collation + collation = [databaseDataInstance getDefaultCollationForEncoding:encoding]; + } + } + } + + // MySQL < 4.1 does not support collations (they are part of the charset), it will be nil there + + [theField setObject:encoding forKey:@"encodingName"]; + [theField setObject:collation forKey:@"collationName"]; + + // Set BINARY if collation ends with _bin for convenience + if ([collation hasSuffix:@"_bin"]) { + [theField setObject:@1 forKey:@"binary"]; + } + } + + // Get possible values if the field is an enum or a set + if (([type isEqualToString:@"ENUM"] || [type isEqualToString:@"SET"]) && [theField objectForKey:@"values"]) { + [theTableEnumLists setObject:[NSArray arrayWithArray:[theField objectForKey:@"values"]] forKey:[theField objectForKey:@"name"]]; + [theField setObject:[NSString stringWithFormat:@"'%@'", [[theField objectForKey:@"values"] componentsJoinedByString:@"','"]] forKey:@"length"]; + } + + // Join length and decimals if any + if ([theField objectForKey:@"decimals"]) + [theField setObject:[NSString stringWithFormat:@"%@,%@", [theField objectForKey:@"length"], [theField objectForKey:@"decimals"]] forKey:@"length"]; + + // Normalize default + if (![theField objectForKey:@"default"]) { + [theField setObject:@"" forKey:@"default"]; + } + else if ([[theField objectForKey:@"default"] isNSNull]) { + [theField setObject:[prefs stringForKey:SPNullValue] forKey:@"default"]; + } + + // Init Extra field + [theField setObject:@"None" forKey:@"Extra"]; + + // Check for auto_increment and set Extra accordingly + if ([[theField objectForKey:@"autoincrement"] integerValue]) { + [theField setObject:@"auto_increment" forKey:@"Extra"]; + } + + // For timestamps/datetime check to see whether "on update CURRENT_TIMESTAMP" and set Extra accordingly + else if ([type isInArray:@[@"TIMESTAMP",@"DATETIME"]] && [[theField objectForKey:@"onupdatetimestamp"] boolValue]) { + NSString *ouct = @"on update CURRENT_TIMESTAMP"; + // restore a length parameter if the field has fractional seconds. + // the parameter of current_timestamp MUST match the field's length in that case, so we can just 'guess' it. + NSString *fieldLen = [theField objectForKey:@"length"]; + if([fieldLen length] && ![fieldLen isEqualToString:@"0"]) { + ouct = [ouct stringByAppendingFormat:@"(%@)",fieldLen]; + } + [theField setObject:ouct forKey:@"Extra"]; + } + } + + // Set up the table details for the new table, and request an data/interface update + NSDictionary *tableDetails = [NSDictionary dictionaryWithObjectsAndKeys: + aTable, @"name", + theTableFields, @"tableFields", + theTableIndexes, @"tableIndexes", + theTableEnumLists, @"enumLists", + nil]; + + [[self onMainThread] setTableDetails:tableDetails]; + + isCurrentExtraAutoIncrement = [tableDataInstance tableHasAutoIncrementField]; + autoIncrementIndex = nil; + + // Send the query finished/work complete notification + [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; + + [theTableFields release]; +} + +/** + * Reloads the table (performing a new query). + */ +- (IBAction)reloadTable:(id)sender +{ + // Check whether a save of the current row is required + if (![[self onMainThread] saveRowOnDeselect]) return; + + [tableDataInstance resetAllData]; + [tableDocumentInstance setStatusRequiresReload:YES]; + + // Query the structure of all databases in the background (mainly for completion) + [[tableDocumentInstance databaseStructureRetrieval] queryDbStructureInBackgroundWithUserInfo:@{@"forceUpdate" : @YES}]; + + [self loadTable:selectedTable]; +} + +/** + * Updates the stored table details and updates the interface to match. + * + * Should be called on the main thread. + */ +- (void)setTableDetails:(NSDictionary *)tableDetails +{ + NSString *newTableName = [tableDetails objectForKey:@"name"]; + NSMutableDictionary *newDefaultValues; + + BOOL enableInteraction = +#ifndef SP_CODA /* patch */ + ![[tableDocumentInstance selectedToolbarItemIdentifier] isEqualToString:SPMainToolbarTableStructure] || +#endif + ![tableDocumentInstance isWorking]; + + // Update the selected table name + if (selectedTable) SPClear(selectedTable); + if (newTableName) selectedTable = [[NSString alloc] initWithString:newTableName]; + + [indexesController setTable:selectedTable]; + + // Reset the table store and display + [tableSourceView deselectAll:self]; + [tableFields removeAllObjects]; + [enumFields removeAllObjects]; + [indexesTableView deselectAll:self]; + [addFieldButton setEnabled:NO]; + [duplicateFieldButton setEnabled:NO]; + [removeFieldButton setEnabled:NO]; +#ifndef SP_CODA + [addIndexButton setEnabled:NO]; + [removeIndexButton setEnabled:NO]; + [editTableButton setEnabled:NO]; +#endif + + // If no table is selected, refresh the table/index display to blank and return + if (!selectedTable) { + [tableSourceView reloadData]; + // Empty indexesController's fields and indices explicitly before reloading + [indexesController setFields:@[]]; + [indexesController setIndexes:@[]]; + [indexesTableView reloadData]; + + return; + } + + // Update the fields and indexes stores + [tableFields setArray:[tableDetails objectForKey:@"tableFields"]]; + + [indexesController setFields:tableFields]; + [indexesController setIndexes:[tableDetails objectForKey:@"tableIndexes"]]; + + if (defaultValues) SPClear(defaultValues); + + newDefaultValues = [NSMutableDictionary dictionaryWithCapacity:[tableFields count]]; + + for (id theField in tableFields) + { + [newDefaultValues setObject:[theField objectForKey:@"default"] forKey:[theField objectForKey:@"name"]]; + } + + defaultValues = [[NSDictionary dictionaryWithDictionary:newDefaultValues] retain]; + +#ifndef SP_CODA + // Enable the edit table button + [editTableButton setEnabled:enableInteraction]; +#endif + + // If a view is selected, disable the buttons; otherwise enable. + BOOL editingEnabled = ([tablesListInstance tableType] == SPTableTypeTable) && enableInteraction; + + [addFieldButton setEnabled:editingEnabled]; +#ifndef SP_CODA + [addIndexButton setEnabled:editingEnabled && ![[[tableDataInstance statusValueForKey:@"Engine"] uppercaseString] isEqualToString:@"CSV"]]; +#endif + + // Reload the views + [indexesTableView reloadData]; + [tableSourceView reloadData]; +} + +#pragma mark - SPTableStructureDelegate + +#pragma mark Table view datasource methods + +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView +{ + return [tableFields count]; +} + +- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex +{ + // Return a placeholder if the table is reloading + if ((NSUInteger)rowIndex >= [tableFields count]) return @"..."; + + NSDictionary *rowData = NSArrayObjectAtIndex(tableFields, rowIndex); + + if ([[tableColumn identifier] isEqualToString:@"collation"]) { + NSString *tableEncoding = [tableDataInstance tableEncoding]; + NSString *columnEncoding = [rowData objectForKey:@"encodingName"]; + NSString *columnCollation = [rowData objectForKey:@"collationName"]; // loadTable: has already inferred it, if not set explicit + +#warning Building the collation menu here is a big performance hog. This should be done in menuNeedsUpdate: below! + NSPopUpButtonCell *collationCell = [tableColumn dataCell]; + [collationCell removeAllItems]; + [collationCell addItemWithTitle:@"dummy"]; + //copy the default style of menu items and add gray color for default item + NSMutableDictionary *menuAttrs = [NSMutableDictionary dictionaryWithDictionary:[[collationCell attributedTitle] attributesAtIndex:0 effectiveRange:NULL]]; + [menuAttrs setObject:[NSColor lightGrayColor] forKey:NSForegroundColorAttributeName]; + [[collationCell lastItem] setTitle:@""]; + + //if this is not set the column either has no encoding (numeric etc.) or retrieval failed. Either way we can't provide collations + if([columnEncoding length]) { + collations = [databaseDataInstance getDatabaseCollationsForEncoding:columnEncoding]; + + if ([collations count] > 0) { + NSString *tableCollation = [[tableDataInstance statusValues] objectForKey:@"Collation"]; + + if (![tableCollation length]) { + tableCollation = [databaseDataInstance getDefaultCollationForEncoding:tableEncoding]; + } + + BOOL columnUsesTableDefaultEncoding = ([columnEncoding isEqualToString:tableEncoding]); + // Populate collation popup button + for (NSDictionary *collation in collations) + { + NSString *collationName = [collation objectForKey:@"COLLATION_NAME"]; + + [collationCell addItemWithTitle:collationName]; + NSMenuItem *item = [collationCell lastItem]; + [item setRepresentedObject:collationName]; + + // If this matches the table's collation, draw in gray + if (columnUsesTableDefaultEncoding && [collationName isEqualToString:tableCollation]) { + NSAttributedString *itemString = [[NSAttributedString alloc] initWithString:[item title] attributes:menuAttrs]; + [item setAttributedTitle:[itemString autorelease]]; + } + } + + // the popup cell is subclassed to take the representedObject instead of the item index + return columnCollation; + } + } + + return nil; + } + else if ([[tableColumn identifier] isEqualToString:@"encoding"]) { + // the encoding menu was already configured during setTableDetails: + NSString *columnEncoding = [rowData objectForKey:@"encodingName"]; + + if([columnEncoding length]) { + NSInteger idx = [encodingPopupCell indexOfItemWithRepresentedObject:columnEncoding]; + if(idx > 0) return @(idx); + } + + return @0; + } + else if ([[tableColumn identifier] isEqualToString:@"Extra"]) { + id dataCell = [tableColumn dataCell]; + + [dataCell removeAllItems]; + + // Populate Extra suggestion popup button + for (id item in extraFieldSuggestions) + { + if (!(isCurrentExtraAutoIncrement && [item isEqualToString:@"auto_increment"])) { + [dataCell addItemWithObjectValue:item]; + } + } + } + + return [rowData objectForKey:[tableColumn identifier]]; +} + +- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex +{ + // Make sure that the operation is for the right table view + if (aTableView != tableSourceView) return; + + NSMutableDictionary *currentRow = NSArrayObjectAtIndex(tableFields,rowIndex); + + if (!isEditingRow) { + [oldRow setDictionary:currentRow]; + isEditingRow = YES; + currentlyEditingRow = rowIndex; + } + + // Reset collation if encoding was changed + if ([[aTableColumn identifier] isEqualToString:@"encoding"]) { + NSString *oldEncoding = [currentRow objectForKey:@"encodingName"]; + NSString *newEncoding = [[encodingPopupCell itemAtIndex:[anObject integerValue]] representedObject]; + if (![oldEncoding isEqualToString:newEncoding]) { + [currentRow removeObjectForKey:@"collationName"]; + [tableSourceView reloadData]; + } + if(!newEncoding) + [currentRow removeObjectForKey:@"encodingName"]; + else + [currentRow setObject:newEncoding forKey:@"encodingName"]; + return; + } + else if ([[aTableColumn identifier] isEqualToString:@"collation"]) { + //the popup button is subclassed to return the representedObject instead of the item index + NSString *newCollation = anObject; + + if(!newCollation) + [currentRow removeObjectForKey:@"collationName"]; + else + [currentRow setObject:newCollation forKey:@"collationName"]; + return; + } + // Reset collation if BINARY was changed, as enabling BINARY sets collation to *_bin + else if ([[aTableColumn identifier] isEqualToString:@"binary"]) { + if ([[currentRow objectForKey:@"binary"] integerValue] != [anObject integerValue]) { + [currentRow removeObjectForKey:@"collationName"]; + + [tableSourceView reloadData]; + } + } + // Set null field to "do not allow NULL" for auto_increment Extra and reset Extra suggestion list + else if ([[aTableColumn identifier] isEqualToString:@"Extra"]) { + if (![[currentRow objectForKey:@"Extra"] isEqualToString:anObject]) { + + isCurrentExtraAutoIncrement = [[[anObject stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] uppercaseString] isEqualToString:@"AUTO_INCREMENT"]; + + if (isCurrentExtraAutoIncrement) { + [currentRow setObject:@0 forKey:@"null"]; + + // Asks the user to add an index to query if AUTO_INCREMENT is set and field isn't indexed + if ((![currentRow objectForKey:@"Key"] || [[currentRow objectForKey:@"Key"] isEqualToString:@""])) { +#ifndef SP_CODA + [chooseKeyButton selectItemWithTag:SPPrimaryKeyMenuTag]; + + [NSApp beginSheet:keySheet + modalForWindow:[tableDocumentInstance parentWindow] modalDelegate:self + didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) + contextInfo:@"autoincrementindex" ]; +#endif + } + } + else { + autoIncrementIndex = nil; + } + + id dataCell = [aTableColumn dataCell]; + + [dataCell removeAllItems]; + [dataCell addItemsWithObjectValues:extraFieldSuggestions]; + [dataCell noteNumberOfItemsChanged]; + [dataCell reloadData]; + + [tableSourceView reloadData]; + + } + } + // 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]) { + if ([anObject integerValue] == 0) { + if ([[currentRow objectForKey:@"default"] isEqualToString:[prefs objectForKey:SPNullValue]]) { + [currentRow setObject:@"" forKey:@"default"]; + } + } + + [tableSourceView reloadData]; + } + } + // Store new value but not if user choose "---" for type and reset values if required + else 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"] || + [[currentRow objectForKey:@"type"] hasSuffix:@"BLOB"] || + [[currentRow objectForKey:@"type"] isEqualToString:@"JSON"] || + [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]; + } + return; + } + + [currentRow setObject:(anObject) ? anObject : @"" forKey:[aTableColumn identifier]]; +} + +/** + * Confirm whether to allow editing of a row. Returns YES by default, but NO for views. + */ +- (BOOL)tableView:(NSTableView *)aTableView shouldEditTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex +{ + if ([tableDocumentInstance isWorking]) return NO; + + // Return NO for views + if ([tablesListInstance tableType] == SPTableTypeView) return NO; + + return YES; +} + +/** + * 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 +{ + // Make sure that the drag operation is started from the right table view + if (aTableView != tableSourceView) return NO; + + // Check whether a save of the current field row is required. + if (![self saveRowOnDeselect]) return NO; + + if ([rows count] == 1) { + [pboard declareTypes:@[SPDefaultPasteboardDragType] owner:nil]; + [pboard setString:[NSString stringWithFormat:@"%lu",[rows firstIndex]] forType:SPDefaultPasteboardDragType]; + + return YES; + } + + return NO; +} + +/** + * 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 +{ + // Make sure that the drag operation is for the right table view + if (tableView!=tableSourceView) return NSDragOperationNone; + + NSArray *pboardTypes = [[info draggingPasteboard] types]; + NSInteger originalRow; + + // Ensure the drop is of the correct type + if (operation == NSTableViewDropAbove && row != -1 && [pboardTypes containsObject:SPDefaultPasteboardDragType]) { + + // Ensure the drag originated within this table + if ([info draggingSource] == tableView) { + originalRow = [[[info draggingPasteboard] stringForType:SPDefaultPasteboardDragType] integerValue]; + + if (row != originalRow && row != (originalRow+1)) { + return NSDragOperationMove; + } + } + } + + return NSDragOperationNone; +} + +/** + * Having validated a drop, perform the field/column reordering to match. + */ +- (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; + + // Extract the original row position from the pasteboard and retrieve the details + NSInteger originalRowIndex = [[[info draggingPasteboard] stringForType:SPDefaultPasteboardDragType] integerValue]; + NSDictionary *originalRow = [[NSDictionary alloc] initWithDictionary:[tableFields objectAtIndex:originalRowIndex]]; + + [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:tableDocumentInstance]; + + // Begin construction of the reordering query + NSMutableString *queryString = [NSMutableString stringWithFormat:@"ALTER TABLE %@ MODIFY COLUMN %@", + [selectedTable backtickQuotedString], + [self _buildPartialColumnDefinitionString:originalRow]]; + + [queryString appendString:@" "]; + // Add the new location + if (destinationRowIndex == 0) { + [queryString appendString:@"FIRST"]; + } + else { + [queryString appendFormat:@"AFTER %@", [[[tableFields objectAtIndex:destinationRowIndex - 1] objectForKey:@"name"] backtickQuotedString]]; + } + + // Run the query; report any errors, or reload the table on success + [mySQLConnection queryString:queryString]; + + if ([mySQLConnection queryErrored]) { + SPOnewayAlertSheet( + NSLocalizedString(@"Error moving field", @"error moving field message"), + [tableDocumentInstance parentWindow], + [NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to move the field.\n\nMySQL said: %@", @"error moving field informative message"), [mySQLConnection lastErrorMessage]] + ); + } + else { + [tableDataInstance resetAllData]; + [tableDocumentInstance setStatusRequiresReload:YES]; + + [self loadTable:selectedTable]; + + // Mark the content table cache for refresh + [tableDocumentInstance setContentRequiresReload:YES]; + + [tableSourceView selectRowIndexes:[NSIndexSet indexSetWithIndex:destinationRowIndex - ((originalRowIndex < destinationRowIndex) ? 1 : 0)] byExtendingSelection:NO]; + } + + [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; + + [originalRow release]; + + return YES; +} + +#pragma mark - +#pragma mark Table view delegate methods + +- (BOOL)tableView:(NSTableView *)aTableView shouldSelectRow:(NSInteger)rowIndex +{ + // If we are editing a row, attempt to save that row - if saving failed, do not select the new row. + if (isEditingRow && ![self addRowToDB]) return NO; + + return YES; +} + +/** + * Performs various interface validation + */ +- (void)tableViewSelectionDidChange:(NSNotification *)aNotification +{ + // Check for which table view the selection changed + if ([aNotification object] == tableSourceView) { + + // If we are editing a row, attempt to save that row - if saving failed, reselect the edit row. + if (isEditingRow && [tableSourceView selectedRow] != currentlyEditingRow && ![self saveRowOnDeselect]) return; + + [duplicateFieldButton setEnabled:YES]; + + // Check if there is currently a field selected and change button state accordingly + if ([tableSourceView numberOfSelectedRows] > 0 && [tablesListInstance tableType] == SPTableTypeTable) { + [removeFieldButton setEnabled:YES]; + } + else { + [removeFieldButton setEnabled:NO]; + [duplicateFieldButton setEnabled:NO]; + } + + // If the table only has one field, disable the remove button. This removes the need to check that the user + // is attempting to remove the last field in a table in removeField: above, but leave it in just in case. + if ([tableSourceView numberOfRows] == 1) { + [removeFieldButton setEnabled:NO]; + } + } +} + +/** + * Traps enter and esc and make/cancel editing without entering next row + */ +- (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)command +{ + NSInteger row, column; + + row = [tableSourceView editedRow]; + column = [tableSourceView editedColumn]; + + // Trap the tab key, selecting the next item in the line + if ([textView methodForSelector:command] == [textView methodForSelector:@selector(insertTab:)] && [tableSourceView numberOfColumns] - 1 == column) + { + //save current line + [[control window] makeFirstResponder:control]; + + if ([self addRowToDB] && [textView methodForSelector:command] == [textView methodForSelector:@selector(insertTab:)]) { + if (row < ([tableSourceView numberOfRows] - 1)) { + [tableSourceView selectRowIndexes:[NSIndexSet indexSetWithIndex:row + 1] byExtendingSelection:NO]; + [tableSourceView editColumn:0 row:row + 1 withEvent:nil select:YES]; + } + else { + [tableSourceView selectRowIndexes:[NSIndexSet indexSetWithIndex:0] byExtendingSelection:NO]; + [tableSourceView editColumn:0 row:0 withEvent:nil select:YES]; + } + } + + return YES; + } + // Trap shift-tab key + else if ([textView methodForSelector:command] == [textView methodForSelector:@selector(insertBacktab:)] && column < 1) + { + if ([self addRowToDB] && [textView methodForSelector:command] == [textView methodForSelector:@selector(insertBacktab:)]) { + [[control window] makeFirstResponder:control]; + + if (row > 0) { + [tableSourceView selectRowIndexes:[NSIndexSet indexSetWithIndex:row - 1] byExtendingSelection:NO]; + [tableSourceView editColumn:([tableSourceView numberOfColumns] - 1) row:row - 1 withEvent:nil select:YES]; + } + else { + [tableSourceView selectRowIndexes:[NSIndexSet indexSetWithIndex:([tableFields count] - 1)] byExtendingSelection:NO]; + [tableSourceView editColumn:([tableSourceView numberOfColumns] - 1) row:([tableSourceView numberOfRows] - 1) withEvent:nil select:YES]; + } + } + + return YES; + } + // Trap the enter key, triggering a save + 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"]) { + return YES; + } + + [[control window] makeFirstResponder:control]; + + [self addRowToDB]; + + [tableSourceView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO]; + + [[tableDocumentInstance parentWindow] makeFirstResponder:tableSourceView]; + + return YES; + } + // Trap escape, aborting the edit and reverting the row + else if ([[control window] methodForSelector:command] == [[control window] methodForSelector:@selector(cancelOperation:)]) + { + [control abortEditing]; + + [self cancelRowEditing]; + + return YES; + } + + return NO; +} + +/** + * 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. + */ +- (void)tableView:(NSTableView *)tableView willDisplayCell:(id)aCell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex +{ + // Make sure that the message is from the right table view + if (tableView != tableSourceView) return; + + if ([tablesListInstance tableType] == SPTableTypeView) { + [aCell setEnabled:NO]; + } + else { + // Validate cell against current field type + NSString *rowType; + NSDictionary *row = NSArrayObjectAtIndex(tableFields, rowIndex); + + if ((rowType = [row objectForKey:@"type"])) { + rowType = [[rowType stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] uppercaseString]; + } + + // Only string fields allow encoding settings, but JSON only uses utf8mb4 + if (([[tableColumn identifier] isEqualToString:@"encoding"])) { + [aCell setEnabled:(![rowType isEqualToString:@"JSON"] && [fieldValidation isFieldTypeString:rowType] && [[tableDocumentInstance serverSupport] supportsPost41CharacterSetHandling])]; + } + + // Only string fields allow collation settings and string field is not set to BINARY since BINARY sets the collation to *_bin + else if ([[tableColumn identifier] isEqualToString:@"collation"]) { + // JSON always uses utf8mb4_bin which is already covered by this logic + [aCell setEnabled:([fieldValidation isFieldTypeString:rowType] && [[row objectForKey:@"binary"] integerValue] == 0 && [[tableDocumentInstance serverSupport] supportsPost41CharacterSetHandling])]; + } + + // Check if UNSIGNED and ZEROFILL is allowed + else if ([[tableColumn identifier] isEqualToString:@"zerofill"] || [[tableColumn identifier] isEqualToString:@"unsigned"]) { + [aCell setEnabled:([fieldValidation isFieldTypeNumeric:rowType] && ![rowType isEqualToString:@"BIT"])]; + } + + // Check if BINARY is allowed + else if ([[tableColumn identifier] isEqualToString:@"binary"]) { + // JSON always uses utf8mb4_bin + [aCell setEnabled:(![rowType isEqualToString:@"JSON"] && [fieldValidation isFieldTypeAllowBinary:rowType])]; + } + + // TEXT, BLOB, GEOMETRY and JSON fields don't allow a DEFAULT + else if ([[tableColumn identifier] isEqualToString:@"default"]) { + [aCell setEnabled:([rowType hasSuffix:@"TEXT"] || [rowType hasSuffix:@"BLOB"] || [rowType isEqualToString:@"JSON"] || [fieldValidation isFieldTypeGeometry:rowType]) ? NO : YES]; + } + + // Check allow NULL + else if ([[tableColumn identifier] isEqualToString:@"null"]) { + [aCell setEnabled:([[row objectForKey:@"Key"] isEqualToString:@"PRI"] || + [[[row objectForKey:@"Extra"] uppercaseString] isEqualToString:@"AUTO_INCREMENT"] || + [[[tableDataInstance statusValueForKey:@"Engine"] uppercaseString] isEqualToString:@"CSV"]) ? NO : YES]; + } + + // TEXT, BLOB, date, GEOMETRY and JSON fields don't allow a length + else if ([[tableColumn identifier] isEqualToString:@"length"]) { + [aCell setEnabled:([rowType hasSuffix:@"TEXT"] || + [rowType hasSuffix:@"BLOB"] || + [rowType isEqualToString:@"JSON"] || + ([fieldValidation isFieldTypeDate:rowType] && ![[tableDocumentInstance serverSupport] supportsFractionalSeconds] && ![rowType isEqualToString:@"YEAR"]) || + [fieldValidation isFieldTypeGeometry:rowType]) ? NO : YES]; + } + else { + [aCell setEnabled:YES]; + } + } +} + +#pragma mark - +#pragma mark Split view delegate methods +#ifndef SP_CODA /* Split view delegate methods */ + +- (BOOL)splitView:(NSSplitView *)sender canCollapseSubview:(NSView *)subview +{ + return YES; +} + +- (CGFloat)splitView:(NSSplitView *)sender constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)offset +{ + return proposedMax - 130; +} + +- (CGFloat)splitView:(NSSplitView *)sender constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)offset +{ + return proposedMin + 130; +} + +- (NSRect)splitView:(NSSplitView *)splitView additionalEffectiveRectOfDividerAtIndex:(NSInteger)dividerIndex +{ + return [structureGrabber convertRect:[structureGrabber bounds] toView:splitView]; +} + +- (void)splitViewDidResizeSubviews:(NSNotification *)aNotification +{ + if ([aNotification object] == tablesIndexesSplitView) { + + NSView *indexesView = [[tablesIndexesSplitView subviews] objectAtIndex:1]; + + if ([tablesIndexesSplitView isSubviewCollapsed:indexesView]) { + [indexesShowButton setHidden:NO]; + } + else { + [indexesShowButton setHidden:YES]; + } + } +} +#endif + +#pragma mark - +#pragma mark Combo box delegate methods + +- (id)comboBoxCell:(NSComboBoxCell *)aComboBoxCell objectValueForItemAtIndex:(NSInteger)index +{ + return NSArrayObjectAtIndex(typeSuggestions, index); +} + +- (NSInteger)numberOfItemsInComboBoxCell:(NSComboBoxCell *)aComboBoxCell +{ + return [typeSuggestions count]; +} + +/** + * Allow completion of field data types of lowercased input. + */ +- (NSString *)comboBoxCell:(NSComboBoxCell *)aComboBoxCell completedString:(NSString *)uncompletedString +{ + if ([uncompletedString hasPrefix:@"-"]) return @""; + + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF BEGINSWITH[c] %@", [uncompletedString uppercaseString]]; + NSArray *result = [typeSuggestions filteredArrayUsingPredicate:predicate]; + + if ([result count]) return [result objectAtIndex:0]; + + return @""; +} + +- (void)comboBoxCell:(SPComboBoxCell *)cell willPopUpWindow:(NSWindow *)win +{ + // the selected item in the popup list is independent of the displayed text, we have to explicitly set it, too + NSInteger pos = [typeSuggestions indexOfObject:[cell stringValue]]; + if(pos != NSNotFound) { + [cell selectItemAtIndex:pos]; + [cell scrollItemAtIndexToTop:pos]; + } + + //set up the help window to the right position + NSRect listFrame = [win frame]; + NSRect helpFrame = [structureHelpPanel frame]; + helpFrame.origin.y = listFrame.origin.y; + helpFrame.size.height = listFrame.size.height; + [structureHelpPanel setFrame:helpFrame display:YES]; + + [self _displayFieldTypeHelpIfPossible:cell]; +} + +- (void)comboBoxCell:(SPComboBoxCell *)cell willDismissWindow:(NSWindow *)win +{ + //hide the window if it is still visible + [structureHelpPanel orderOut:nil]; +} + +- (void)comboBoxCellSelectionDidChange:(SPComboBoxCell *)cell +{ + [self _displayFieldTypeHelpIfPossible:cell]; +} + +- (void)_displayFieldTypeHelpIfPossible:(SPComboBoxCell *)cell +{ + NSString *selected = [typeSuggestions objectOrNilAtIndex:[cell indexOfSelectedItem]]; + + const SPFieldTypeHelp *help = [[self class] helpForFieldType:selected]; + + if(help) { + NSMutableAttributedString *as = [[NSMutableAttributedString alloc] init]; + + //title + { + NSDictionary *titleAttr = @{NSFontAttributeName: [NSFont boldSystemFontOfSize:[NSFont systemFontSize]]}; + NSAttributedString *title = [[NSAttributedString alloc] initWithString:[help typeDefinition] attributes:titleAttr]; + [as appendAttributedString:[title autorelease]]; + [[as mutableString] appendString:@"\n"]; + } + + //range + if([[help typeRange] length]) { + NSDictionary *rangeAttr = @{NSFontAttributeName: [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]}; + NSAttributedString *range = [[NSAttributedString alloc] initWithString:[help typeRange] attributes:rangeAttr]; + [as appendAttributedString:[range autorelease]]; + [[as mutableString] appendString:@"\n"]; + } + + [[as mutableString] appendString:@"\n"]; + + //description + { + NSDictionary *descAttr = @{NSFontAttributeName: [NSFont systemFontOfSize:[NSFont systemFontSize]]}; + NSAttributedString *desc = [[NSAttributedString alloc] initWithString:[help typeDescription] attributes:descAttr]; + [as appendAttributedString:[desc autorelease]]; + } + + [as addAttribute:NSParagraphStyleAttributeName value:[NSParagraphStyle defaultParagraphStyle] range:NSMakeRange(0, [as length])]; + + [[structureHelpText textStorage] setAttributedString:[as autorelease]]; + + NSRect rect = [as boundingRectWithSize:NSMakeSize([structureHelpText frame].size.width-2, CGFLOAT_MAX) options:NSStringDrawingUsesFontLeading|NSStringDrawingUsesLineFragmentOrigin]; + + NSRect winRect = [structureHelpPanel frame]; + + CGFloat winAddonSize = (winRect.size.height - [[structureHelpPanel contentView] frame].size.height) + (6*2); + + NSRect popUpFrame = [[cell spPopUpWindow] frame]; + + //determine the side on which to add our window based on the space left on screen + NSPoint topRightCorner = NSMakePoint(popUpFrame.origin.x, NSMaxY(popUpFrame)); + NSRect screenRect = [NSScreen rectOfScreenAtPoint:topRightCorner]; + + if(NSMaxX(popUpFrame)+10+winRect.size.width > NSMaxX(screenRect)-10) { + // exceeds right border, display on the left + winRect.origin.x = popUpFrame.origin.x - 10 - winRect.size.width; + } + else { + // display on the right + winRect.origin.x = NSMaxX(popUpFrame)+10; + } + + winRect.size.height = rect.size.height + winAddonSize; + winRect.origin.y = NSMaxY(popUpFrame) - winRect.size.height; + [structureHelpPanel setFrame:winRect display:YES]; + + [structureHelpPanel orderFront:nil]; + } + else { + [structureHelpPanel orderOut:nil]; + } +} + +#pragma mark - +#pragma mark Menu delegate methods (encoding/collation dropdown menu) + +- (void)menuNeedsUpdate:(SPIdMenu *)menu +{ + if(![menu isKindOfClass:[SPIdMenu class]]) return; + //NOTE: NSTableView will usually copy the menu and call this method on the copy. Matching with == won't work! + + //walk through the menu and clear the attributedTitle if set. This will remove the gray color from the default items + for(NSMenuItem *item in [menu itemArray]) { + if([item attributedTitle]) { + [item setAttributedTitle:nil]; + } + } + + NSDictionary *rowData = NSArrayObjectAtIndex(tableFields, [tableSourceView selectedRow]); + + if([[menu menuId] isEqualToString:@"encodingPopupMenu"]) { + NSString *tableEncoding = [tableDataInstance tableEncoding]; + //NSString *databaseEncoding = [databaseDataInstance getDatabaseDefaultCharacterSet]; + //NSString *serverEncoding = [databaseDataInstance getServerDefaultCharacterSet]; + + struct _cmpMap defaultCmp[] = { + { + NSLocalizedString(@"Table",@"Table Structure : Encoding dropdown : 'item is table default' marker"), + [NSString stringWithFormat:NSLocalizedString(@"This is the default encoding of table “%@”.", @"Table Structure : Encoding dropdown : table marker tooltip"),selectedTable], + tableEncoding + }, + /* //we could, but that might confuse users even more plus there is no inheritance between a columns charset and the db/server default + { + NSLocalizedString(@"Database",@"Table Structure : Encoding dropdown : 'item is database default' marker"), + [NSString stringWithFormat:NSLocalizedString(@"This is the default encoding of database “%@”.", @"Table Structure : Encoding dropdown : database marker tooltip"),[tableDocumentInstance database]], + databaseEncoding + }, + { + NSLocalizedString(@"Server",@"Table Structure : Encoding dropdown : 'item is server default' marker"), + NSLocalizedString(@"This is the default encoding of this server.", @"Table Structure : Encoding dropdown : server marker tooltip"), + serverEncoding + } */ + }; + + _BuildMenuWithPills(menu, defaultCmp, COUNT_OF(defaultCmp)); + } + else if([[menu menuId] isEqualToString:@"collationPopupMenu"]) { + NSString *encoding = [rowData objectForKey:@"encodingName"]; + NSString *encodingDefaultCollation = [databaseDataInstance getDefaultCollationForEncoding:encoding]; + NSString *tableCollation = [tableDataInstance statusValueForKey:@"Collation"]; + //NSString *databaseCollation = [databaseDataInstance getDatabaseDefaultCollation]; + //NSString *serverCollation = [databaseDataInstance getServerDefaultCollation]; + + struct _cmpMap defaultCmp[] = { + { + NSLocalizedString(@"Default",@"Table Structure : Collation dropdown : 'item is the same as the default collation of the row's charset' marker"), + [NSString stringWithFormat:NSLocalizedString(@"This is the default collation of encoding “%@”.", @"Table Structure : Collation dropdown : default marker tooltip"),encoding], + encodingDefaultCollation + }, + { + NSLocalizedString(@"Table",@"Table Structure : Collation dropdown : 'item is the same as the collation of table' marker"), + [NSString stringWithFormat:NSLocalizedString(@"This is the default collation of table “%@”.", @"Table Structure : Collation dropdown : table marker tooltip"),selectedTable], + tableCollation + }, + /* // see the comment for charset above + { + NSLocalizedString(@"Database",@"Table Structure : Collation dropdown : 'item is the same as the collation of database' marker"), + [NSString stringWithFormat:NSLocalizedString(@"This is the default collation of database “%@”.", @"Table Structure : Collation dropdown : database marker tooltip"),[tableDocumentInstance database]], + databaseCollation + }, + { + NSLocalizedString(@"Server",@"Table Structure : Collation dropdown : 'item is the same as the collation of server' marker"), + NSLocalizedString(@"This is the default collation of this server.", @"Table Structure : Collation dropdown : server marker tooltip"), + serverCollation + } */ + }; + + _BuildMenuWithPills(menu, defaultCmp, COUNT_OF(defaultCmp)); + } +} + +#pragma mark - - (void)dealloc { @@ -1835,3 +2945,35 @@ static inline SPFieldTypeHelp *MakeFieldTypeHelp(NSString *typeName,NSString *ty } @end + +#pragma mark - + +void _BuildMenuWithPills(NSMenu *menu,struct _cmpMap *map,size_t mapEntries) +{ + NSDictionary *baseAttrs = @{NSFontAttributeName:[menu font],NSParagraphStyleAttributeName: [NSParagraphStyle defaultParagraphStyle]}; + + for(NSMenuItem *item in [menu itemArray]) { + NSMutableAttributedString *itemStr = [[NSMutableAttributedString alloc] initWithString:[item title] attributes:baseAttrs]; + NSString *value = [item representedObject]; + + NSMutableArray *tooltipParts = [NSMutableArray array]; + for (unsigned int i = 0; i < mapEntries; ++i) { + struct _cmpMap *cmp = &map[i]; + if([cmp->cmpWith isEqualToString:value]) { + SPPillAttachmentCell *cell = [[SPPillAttachmentCell alloc] init]; + [cell setStringValue:cmp->title]; + NSTextAttachment *attachment = [[NSTextAttachment alloc] init]; + [attachment setAttachmentCell:[cell autorelease]]; + NSAttributedString *attachmentString = [NSAttributedString attributedStringWithAttachment:[attachment autorelease]]; + + [[itemStr mutableString] appendString:@" "]; + [itemStr appendAttributedString:attachmentString]; + + if(cmp->tooltipPart) [tooltipParts addObject:cmp->tooltipPart]; + } + } + if([tooltipParts count]) [item setToolTip:[tooltipParts componentsJoinedByString:@" "]]; + + [item setAttributedTitle:[itemStr autorelease]]; + } +} diff --git a/Source/SPTableStructureDelegate.h b/Source/SPTableStructureDelegate.h deleted file mode 100644 index 4e845d6e..00000000 --- a/Source/SPTableStructureDelegate.h +++ /dev/null @@ -1,44 +0,0 @@ -// -// SPConnectionDelegate.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on October 26, 2010. -// Copyright (c) 2010 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPTableStructure.h" - -/** - * @category SPTableStructureDelegate SPTableStructureDelegate.h - * - * @author Stuart Connolly http://stuconnolly.com/ - * - * This category is intended to contain all of SPTableStructure's delegate and datasource methods. It is - * defined as a category simply as a convenient way to separate these methods from SPTableStructure's main - * logic. - */ -@interface SPTableStructure (SPTableStructureDelegate) - -@end diff --git a/Source/SPTableStructureDelegate.m b/Source/SPTableStructureDelegate.m deleted file mode 100644 index 4c6e2db3..00000000 --- a/Source/SPTableStructureDelegate.m +++ /dev/null @@ -1,868 +0,0 @@ -// -// SPConnectionDelegate.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on October 26, 2010. -// Copyright (c) 2010 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPTableStructureDelegate.h" -#import "SPAlertSheets.h" -#import "SPDatabaseData.h" -#import "SPDatabaseViewController.h" -#import "SPTableData.h" -#import "SPTableView.h" -#import "SPTableFieldValidation.h" -#import "SPTableStructureLoading.h" -#import "SPServerSupport.h" -#import "SPTablesList.h" -#import "SPPillAttachmentCell.h" -#import "SPIdMenu.h" -#import "SPComboBoxCell.h" - -#import <SPMySQL/SPMySQL.h> - -struct _cmpMap { - NSString *title; // the title of the "pill" - NSString *tooltipPart; // the tooltip of the menuitem - NSString *cmpWith; // the string to match against -}; - -/** - * This function will compare the representedObject of every item in menu against - * every map->cmpWith. If they match it will append a pill-like (similar to a TokenFieldCell's token) - * element labelled map->title to the menu item's title. If map->tooltipPart is set, - * it will also be added to the menu item's tooltip. - * - * This is used with the encoding/collation popup menus to add visual indicators for the - * table-level and default encoding/collation. - */ -static void _BuildMenuWithPills(NSMenu *menu,struct _cmpMap *map,size_t mapEntries); - -@interface SPTableStructure (PrivateAPI) - -- (void)sheetDidEnd:(id)sheet returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo; -- (NSString *)_buildPartialColumnDefinitionString:(NSDictionary *)theRow; - -- (void)_displayFieldTypeHelpIfPossible:(SPComboBoxCell *)cell; - -@end - -@implementation SPTableStructure (SPTableStructureDelegate) - -#pragma mark - -#pragma mark Table view datasource methods - -- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView -{ - return [tableFields count]; -} - -- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex -{ - // Return a placeholder if the table is reloading - if ((NSUInteger)rowIndex >= [tableFields count]) return @"..."; - - NSDictionary *rowData = NSArrayObjectAtIndex(tableFields, rowIndex); - - if ([[tableColumn identifier] isEqualToString:@"collation"]) { - NSString *tableEncoding = [tableDataInstance tableEncoding]; - NSString *columnEncoding = [rowData objectForKey:@"encodingName"]; - NSString *columnCollation = [rowData objectForKey:@"collationName"]; // loadTable: has already inferred it, if not set explicit - -#warning Building the collation menu here is a big performance hog. This should be done in menuNeedsUpdate: below! - NSPopUpButtonCell *collationCell = [tableColumn dataCell]; - [collationCell removeAllItems]; - [collationCell addItemWithTitle:@"dummy"]; - //copy the default style of menu items and add gray color for default item - NSMutableDictionary *menuAttrs = [NSMutableDictionary dictionaryWithDictionary:[[collationCell attributedTitle] attributesAtIndex:0 effectiveRange:NULL]]; - [menuAttrs setObject:[NSColor lightGrayColor] forKey:NSForegroundColorAttributeName]; - [[collationCell lastItem] setTitle:@""]; - - //if this is not set the column either has no encoding (numeric etc.) or retrieval failed. Either way we can't provide collations - if([columnEncoding length]) { - collations = [databaseDataInstance getDatabaseCollationsForEncoding:columnEncoding]; - - if ([collations count] > 0) { - NSString *tableCollation = [[tableDataInstance statusValues] objectForKey:@"Collation"]; - - if (![tableCollation length]) { - tableCollation = [databaseDataInstance getDefaultCollationForEncoding:tableEncoding]; - } - - BOOL columnUsesTableDefaultEncoding = ([columnEncoding isEqualToString:tableEncoding]); - // Populate collation popup button - for (NSDictionary *collation in collations) - { - NSString *collationName = [collation objectForKey:@"COLLATION_NAME"]; - - [collationCell addItemWithTitle:collationName]; - NSMenuItem *item = [collationCell lastItem]; - [item setRepresentedObject:collationName]; - - // If this matches the table's collation, draw in gray - if (columnUsesTableDefaultEncoding && [collationName isEqualToString:tableCollation]) { - NSAttributedString *itemString = [[NSAttributedString alloc] initWithString:[item title] attributes:menuAttrs]; - [item setAttributedTitle:[itemString autorelease]]; - } - } - - // the popup cell is subclassed to take the representedObject instead of the item index - return columnCollation; - } - } - - return nil; - } - else if ([[tableColumn identifier] isEqualToString:@"encoding"]) { - // the encoding menu was already configured during setTableDetails: - NSString *columnEncoding = [rowData objectForKey:@"encodingName"]; - - if([columnEncoding length]) { - NSInteger idx = [encodingPopupCell indexOfItemWithRepresentedObject:columnEncoding]; - if(idx > 0) return @(idx); - } - - return @0; - } - else if ([[tableColumn identifier] isEqualToString:@"Extra"]) { - id dataCell = [tableColumn dataCell]; - - [dataCell removeAllItems]; - - // Populate Extra suggestion popup button - for (id item in extraFieldSuggestions) - { - if (!(isCurrentExtraAutoIncrement && [item isEqualToString:@"auto_increment"])) { - [dataCell addItemWithObjectValue:item]; - } - } - } - - return [rowData objectForKey:[tableColumn identifier]]; -} - -- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex -{ - // Make sure that the operation is for the right table view - if (aTableView != tableSourceView) return; - - NSMutableDictionary *currentRow = NSArrayObjectAtIndex(tableFields,rowIndex); - - if (!isEditingRow) { - [oldRow setDictionary:currentRow]; - isEditingRow = YES; - currentlyEditingRow = rowIndex; - } - - // Reset collation if encoding was changed - if ([[aTableColumn identifier] isEqualToString:@"encoding"]) { - NSString *oldEncoding = [currentRow objectForKey:@"encodingName"]; - NSString *newEncoding = [[encodingPopupCell itemAtIndex:[anObject integerValue]] representedObject]; - if (![oldEncoding isEqualToString:newEncoding]) { - [currentRow removeObjectForKey:@"collationName"]; - [tableSourceView reloadData]; - } - if(!newEncoding) - [currentRow removeObjectForKey:@"encodingName"]; - else - [currentRow setObject:newEncoding forKey:@"encodingName"]; - return; - } - else if ([[aTableColumn identifier] isEqualToString:@"collation"]) { - //the popup button is subclassed to return the representedObject instead of the item index - NSString *newCollation = anObject; - - if(!newCollation) - [currentRow removeObjectForKey:@"collationName"]; - else - [currentRow setObject:newCollation forKey:@"collationName"]; - return; - } - // Reset collation if BINARY was changed, as enabling BINARY sets collation to *_bin - else if ([[aTableColumn identifier] isEqualToString:@"binary"]) { - if ([[currentRow objectForKey:@"binary"] integerValue] != [anObject integerValue]) { - [currentRow removeObjectForKey:@"collationName"]; - - [tableSourceView reloadData]; - } - } - // Set null field to "do not allow NULL" for auto_increment Extra and reset Extra suggestion list - else if ([[aTableColumn identifier] isEqualToString:@"Extra"]) { - if (![[currentRow objectForKey:@"Extra"] isEqualToString:anObject]) { - - isCurrentExtraAutoIncrement = [[[anObject stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] uppercaseString] isEqualToString:@"AUTO_INCREMENT"]; - - if (isCurrentExtraAutoIncrement) { - [currentRow setObject:@0 forKey:@"null"]; - - // Asks the user to add an index to query if AUTO_INCREMENT is set and field isn't indexed - if ((![currentRow objectForKey:@"Key"] || [[currentRow objectForKey:@"Key"] isEqualToString:@""])) { -#ifndef SP_CODA - [chooseKeyButton selectItemWithTag:SPPrimaryKeyMenuTag]; - - [NSApp beginSheet:keySheet - modalForWindow:[tableDocumentInstance parentWindow] modalDelegate:self - didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) - contextInfo:@"autoincrementindex" ]; -#endif - } - } - else { - autoIncrementIndex = nil; - } - - id dataCell = [aTableColumn dataCell]; - - [dataCell removeAllItems]; - [dataCell addItemsWithObjectValues:extraFieldSuggestions]; - [dataCell noteNumberOfItemsChanged]; - [dataCell reloadData]; - - [tableSourceView reloadData]; - - } - } - // 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]) { - if ([anObject integerValue] == 0) { - if ([[currentRow objectForKey:@"default"] isEqualToString:[prefs objectForKey:SPNullValue]]) { - [currentRow setObject:@"" forKey:@"default"]; - } - } - - [tableSourceView reloadData]; - } - } - // Store new value but not if user choose "---" for type and reset values if required - else 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"] || - [[currentRow objectForKey:@"type"] hasSuffix:@"BLOB"] || - [[currentRow objectForKey:@"type"] isEqualToString:@"JSON"] || - [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]; - } - return; - } - - [currentRow setObject:(anObject) ? anObject : @"" forKey:[aTableColumn identifier]]; -} - -/** - * Confirm whether to allow editing of a row. Returns YES by default, but NO for views. - */ -- (BOOL)tableView:(NSTableView *)aTableView shouldEditTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex -{ - if ([tableDocumentInstance isWorking]) return NO; - - // Return NO for views - if ([tablesListInstance tableType] == SPTableTypeView) return NO; - - return YES; -} - -/** - * 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 -{ - // Make sure that the drag operation is started from the right table view - if (aTableView != tableSourceView) return NO; - - // Check whether a save of the current field row is required. - if (![self saveRowOnDeselect]) return NO; - - if ([rows count] == 1) { - [pboard declareTypes:@[SPDefaultPasteboardDragType] owner:nil]; - [pboard setString:[NSString stringWithFormat:@"%lu",[rows firstIndex]] forType:SPDefaultPasteboardDragType]; - - return YES; - } - - return NO; -} - -/** - * 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 -{ - // Make sure that the drag operation is for the right table view - if (tableView!=tableSourceView) return NSDragOperationNone; - - NSArray *pboardTypes = [[info draggingPasteboard] types]; - NSInteger originalRow; - - // Ensure the drop is of the correct type - if (operation == NSTableViewDropAbove && row != -1 && [pboardTypes containsObject:SPDefaultPasteboardDragType]) { - - // Ensure the drag originated within this table - if ([info draggingSource] == tableView) { - originalRow = [[[info draggingPasteboard] stringForType:SPDefaultPasteboardDragType] integerValue]; - - if (row != originalRow && row != (originalRow+1)) { - return NSDragOperationMove; - } - } - } - - return NSDragOperationNone; -} - -/** - * Having validated a drop, perform the field/column reordering to match. - */ -- (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; - - // Extract the original row position from the pasteboard and retrieve the details - NSInteger originalRowIndex = [[[info draggingPasteboard] stringForType:SPDefaultPasteboardDragType] integerValue]; - NSDictionary *originalRow = [[NSDictionary alloc] initWithDictionary:[tableFields objectAtIndex:originalRowIndex]]; - - [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:tableDocumentInstance]; - - // Begin construction of the reordering query - NSMutableString *queryString = [NSMutableString stringWithFormat:@"ALTER TABLE %@ MODIFY COLUMN %@", - [selectedTable backtickQuotedString], - [self _buildPartialColumnDefinitionString:originalRow]]; - - [queryString appendString:@" "]; - // Add the new location - if (destinationRowIndex == 0) { - [queryString appendString:@"FIRST"]; - } - else { - [queryString appendFormat:@"AFTER %@", [[[tableFields objectAtIndex:destinationRowIndex - 1] objectForKey:@"name"] backtickQuotedString]]; - } - - // Run the query; report any errors, or reload the table on success - [mySQLConnection queryString:queryString]; - - if ([mySQLConnection queryErrored]) { - SPOnewayAlertSheet( - NSLocalizedString(@"Error moving field", @"error moving field message"), - [tableDocumentInstance parentWindow], - [NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to move the field.\n\nMySQL said: %@", @"error moving field informative message"), [mySQLConnection lastErrorMessage]] - ); - } - else { - [tableDataInstance resetAllData]; - [tableDocumentInstance setStatusRequiresReload:YES]; - - [self loadTable:selectedTable]; - - // Mark the content table cache for refresh - [tableDocumentInstance setContentRequiresReload:YES]; - - [tableSourceView selectRowIndexes:[NSIndexSet indexSetWithIndex:destinationRowIndex - ((originalRowIndex < destinationRowIndex) ? 1 : 0)] byExtendingSelection:NO]; - } - - [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; - - [originalRow release]; - - return YES; -} - -#pragma mark - -#pragma mark Table view delegate methods - -- (BOOL)tableView:(NSTableView *)aTableView shouldSelectRow:(NSInteger)rowIndex -{ - // If we are editing a row, attempt to save that row - if saving failed, do not select the new row. - if (isEditingRow && ![self addRowToDB]) return NO; - - return YES; -} - -/** - * Performs various interface validation - */ -- (void)tableViewSelectionDidChange:(NSNotification *)aNotification -{ - // Check for which table view the selection changed - if ([aNotification object] == tableSourceView) { - - // If we are editing a row, attempt to save that row - if saving failed, reselect the edit row. - if (isEditingRow && [tableSourceView selectedRow] != currentlyEditingRow && ![self saveRowOnDeselect]) return; - - [duplicateFieldButton setEnabled:YES]; - - // Check if there is currently a field selected and change button state accordingly - if ([tableSourceView numberOfSelectedRows] > 0 && [tablesListInstance tableType] == SPTableTypeTable) { - [removeFieldButton setEnabled:YES]; - } - else { - [removeFieldButton setEnabled:NO]; - [duplicateFieldButton setEnabled:NO]; - } - - // If the table only has one field, disable the remove button. This removes the need to check that the user - // is attempting to remove the last field in a table in removeField: above, but leave it in just in case. - if ([tableSourceView numberOfRows] == 1) { - [removeFieldButton setEnabled:NO]; - } - } -} - -/** - * Traps enter and esc and make/cancel editing without entering next row - */ -- (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)command -{ - NSInteger row, column; - - row = [tableSourceView editedRow]; - column = [tableSourceView editedColumn]; - - // Trap the tab key, selecting the next item in the line - if ([textView methodForSelector:command] == [textView methodForSelector:@selector(insertTab:)] && [tableSourceView numberOfColumns] - 1 == column) - { - //save current line - [[control window] makeFirstResponder:control]; - - if ([self addRowToDB] && [textView methodForSelector:command] == [textView methodForSelector:@selector(insertTab:)]) { - if (row < ([tableSourceView numberOfRows] - 1)) { - [tableSourceView selectRowIndexes:[NSIndexSet indexSetWithIndex:row + 1] byExtendingSelection:NO]; - [tableSourceView editColumn:0 row:row + 1 withEvent:nil select:YES]; - } - else { - [tableSourceView selectRowIndexes:[NSIndexSet indexSetWithIndex:0] byExtendingSelection:NO]; - [tableSourceView editColumn:0 row:0 withEvent:nil select:YES]; - } - } - - return YES; - } - // Trap shift-tab key - else if ([textView methodForSelector:command] == [textView methodForSelector:@selector(insertBacktab:)] && column < 1) - { - if ([self addRowToDB] && [textView methodForSelector:command] == [textView methodForSelector:@selector(insertBacktab:)]) { - [[control window] makeFirstResponder:control]; - - if (row > 0) { - [tableSourceView selectRowIndexes:[NSIndexSet indexSetWithIndex:row - 1] byExtendingSelection:NO]; - [tableSourceView editColumn:([tableSourceView numberOfColumns] - 1) row:row - 1 withEvent:nil select:YES]; - } - else { - [tableSourceView selectRowIndexes:[NSIndexSet indexSetWithIndex:([tableFields count] - 1)] byExtendingSelection:NO]; - [tableSourceView editColumn:([tableSourceView numberOfColumns] - 1) row:([tableSourceView numberOfRows] - 1) withEvent:nil select:YES]; - } - } - - return YES; - } - // Trap the enter key, triggering a save - 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"]) { - return YES; - } - - [[control window] makeFirstResponder:control]; - - [self addRowToDB]; - - [tableSourceView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO]; - - [[tableDocumentInstance parentWindow] makeFirstResponder:tableSourceView]; - - return YES; - } - // Trap escape, aborting the edit and reverting the row - else if ([[control window] methodForSelector:command] == [[control window] methodForSelector:@selector(cancelOperation:)]) - { - [control abortEditing]; - - [self cancelRowEditing]; - - return YES; - } - - return NO; -} - -/** - * 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. - */ -- (void)tableView:(NSTableView *)tableView willDisplayCell:(id)aCell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex -{ - // Make sure that the message is from the right table view - if (tableView != tableSourceView) return; - - if ([tablesListInstance tableType] == SPTableTypeView) { - [aCell setEnabled:NO]; - } - else { - // Validate cell against current field type - NSString *rowType; - NSDictionary *row = NSArrayObjectAtIndex(tableFields, rowIndex); - - if ((rowType = [row objectForKey:@"type"])) { - rowType = [[rowType stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] uppercaseString]; - } - - // Only string fields allow encoding settings, but JSON only uses utf8mb4 - if (([[tableColumn identifier] isEqualToString:@"encoding"])) { - [aCell setEnabled:(![rowType isEqualToString:@"JSON"] && [fieldValidation isFieldTypeString:rowType] && [[tableDocumentInstance serverSupport] supportsPost41CharacterSetHandling])]; - } - - // Only string fields allow collation settings and string field is not set to BINARY since BINARY sets the collation to *_bin - else if ([[tableColumn identifier] isEqualToString:@"collation"]) { - // JSON always uses utf8mb4_bin which is already covered by this logic - [aCell setEnabled:([fieldValidation isFieldTypeString:rowType] && [[row objectForKey:@"binary"] integerValue] == 0 && [[tableDocumentInstance serverSupport] supportsPost41CharacterSetHandling])]; - } - - // Check if UNSIGNED and ZEROFILL is allowed - else if ([[tableColumn identifier] isEqualToString:@"zerofill"] || [[tableColumn identifier] isEqualToString:@"unsigned"]) { - [aCell setEnabled:([fieldValidation isFieldTypeNumeric:rowType] && ![rowType isEqualToString:@"BIT"])]; - } - - // Check if BINARY is allowed - else if ([[tableColumn identifier] isEqualToString:@"binary"]) { - // JSON always uses utf8mb4_bin - [aCell setEnabled:(![rowType isEqualToString:@"JSON"] && [fieldValidation isFieldTypeAllowBinary:rowType])]; - } - - // TEXT, BLOB, GEOMETRY and JSON fields don't allow a DEFAULT - else if ([[tableColumn identifier] isEqualToString:@"default"]) { - [aCell setEnabled:([rowType hasSuffix:@"TEXT"] || [rowType hasSuffix:@"BLOB"] || [rowType isEqualToString:@"JSON"] || [fieldValidation isFieldTypeGeometry:rowType]) ? NO : YES]; - } - - // Check allow NULL - else if ([[tableColumn identifier] isEqualToString:@"null"]) { - [aCell setEnabled:([[row objectForKey:@"Key"] isEqualToString:@"PRI"] || - [[[row objectForKey:@"Extra"] uppercaseString] isEqualToString:@"AUTO_INCREMENT"] || - [[[tableDataInstance statusValueForKey:@"Engine"] uppercaseString] isEqualToString:@"CSV"]) ? NO : YES]; - } - - // TEXT, BLOB, date, GEOMETRY and JSON fields don't allow a length - else if ([[tableColumn identifier] isEqualToString:@"length"]) { - [aCell setEnabled:([rowType hasSuffix:@"TEXT"] || - [rowType hasSuffix:@"BLOB"] || - [rowType isEqualToString:@"JSON"] || - ([fieldValidation isFieldTypeDate:rowType] && ![[tableDocumentInstance serverSupport] supportsFractionalSeconds] && ![rowType isEqualToString:@"YEAR"]) || - [fieldValidation isFieldTypeGeometry:rowType]) ? NO : YES]; - } - else { - [aCell setEnabled:YES]; - } - } -} - -#pragma mark - -#pragma mark Split view delegate methods -#ifndef SP_CODA /* Split view delegate methods */ - -- (BOOL)splitView:(NSSplitView *)sender canCollapseSubview:(NSView *)subview -{ - return YES; -} - -- (CGFloat)splitView:(NSSplitView *)sender constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)offset -{ - return proposedMax - 130; -} - -- (CGFloat)splitView:(NSSplitView *)sender constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)offset -{ - return proposedMin + 130; -} - -- (NSRect)splitView:(NSSplitView *)splitView additionalEffectiveRectOfDividerAtIndex:(NSInteger)dividerIndex -{ - return [structureGrabber convertRect:[structureGrabber bounds] toView:splitView]; -} - -- (void)splitViewDidResizeSubviews:(NSNotification *)aNotification -{ - if ([aNotification object] == tablesIndexesSplitView) { - - NSView *indexesView = [[tablesIndexesSplitView subviews] objectAtIndex:1]; - - if ([tablesIndexesSplitView isSubviewCollapsed:indexesView]) { - [indexesShowButton setHidden:NO]; - } - else { - [indexesShowButton setHidden:YES]; - } - } -} -#endif - -#pragma mark - -#pragma mark Combo box delegate methods - -- (id)comboBoxCell:(NSComboBoxCell *)aComboBoxCell objectValueForItemAtIndex:(NSInteger)index -{ - return NSArrayObjectAtIndex(typeSuggestions, index); -} - -- (NSInteger)numberOfItemsInComboBoxCell:(NSComboBoxCell *)aComboBoxCell -{ - return [typeSuggestions count]; -} - -/** - * Allow completion of field data types of lowercased input. - */ -- (NSString *)comboBoxCell:(NSComboBoxCell *)aComboBoxCell completedString:(NSString *)uncompletedString -{ - if ([uncompletedString hasPrefix:@"-"]) return @""; - - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF BEGINSWITH[c] %@", [uncompletedString uppercaseString]]; - NSArray *result = [typeSuggestions filteredArrayUsingPredicate:predicate]; - - if ([result count]) return [result objectAtIndex:0]; - - return @""; -} - -- (void)comboBoxCell:(SPComboBoxCell *)cell willPopUpWindow:(NSWindow *)win -{ - // the selected item in the popup list is independent of the displayed text, we have to explicitly set it, too - NSInteger pos = [typeSuggestions indexOfObject:[cell stringValue]]; - if(pos != NSNotFound) { - [cell selectItemAtIndex:pos]; - [cell scrollItemAtIndexToTop:pos]; - } - - //set up the help window to the right position - NSRect listFrame = [win frame]; - NSRect helpFrame = [structureHelpPanel frame]; - helpFrame.origin.y = listFrame.origin.y; - helpFrame.size.height = listFrame.size.height; - [structureHelpPanel setFrame:helpFrame display:YES]; - - [self _displayFieldTypeHelpIfPossible:cell]; -} - -- (void)comboBoxCell:(SPComboBoxCell *)cell willDismissWindow:(NSWindow *)win -{ - //hide the window if it is still visible - [structureHelpPanel orderOut:nil]; -} - -- (void)comboBoxCellSelectionDidChange:(SPComboBoxCell *)cell -{ - [self _displayFieldTypeHelpIfPossible:cell]; -} - -- (void)_displayFieldTypeHelpIfPossible:(SPComboBoxCell *)cell -{ - NSString *selected = [typeSuggestions objectOrNilAtIndex:[cell indexOfSelectedItem]]; - - const SPFieldTypeHelp *help = [[self class] helpForFieldType:selected]; - - if(help) { - NSMutableAttributedString *as = [[NSMutableAttributedString alloc] init]; - - //title - { - NSDictionary *titleAttr = @{NSFontAttributeName: [NSFont boldSystemFontOfSize:[NSFont systemFontSize]]}; - NSAttributedString *title = [[NSAttributedString alloc] initWithString:[help typeDefinition] attributes:titleAttr]; - [as appendAttributedString:[title autorelease]]; - [[as mutableString] appendString:@"\n"]; - } - - //range - if([[help typeRange] length]) { - NSDictionary *rangeAttr = @{NSFontAttributeName: [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]}; - NSAttributedString *range = [[NSAttributedString alloc] initWithString:[help typeRange] attributes:rangeAttr]; - [as appendAttributedString:[range autorelease]]; - [[as mutableString] appendString:@"\n"]; - } - - [[as mutableString] appendString:@"\n"]; - - //description - { - NSDictionary *descAttr = @{NSFontAttributeName: [NSFont systemFontOfSize:[NSFont systemFontSize]]}; - NSAttributedString *desc = [[NSAttributedString alloc] initWithString:[help typeDescription] attributes:descAttr]; - [as appendAttributedString:[desc autorelease]]; - } - - [as addAttribute:NSParagraphStyleAttributeName value:[NSParagraphStyle defaultParagraphStyle] range:NSMakeRange(0, [as length])]; - - [[structureHelpText textStorage] setAttributedString:[as autorelease]]; - - NSRect rect = [as boundingRectWithSize:NSMakeSize([structureHelpText frame].size.width-2, CGFLOAT_MAX) options:NSStringDrawingUsesFontLeading|NSStringDrawingUsesLineFragmentOrigin]; - - NSRect winRect = [structureHelpPanel frame]; - - CGFloat winAddonSize = (winRect.size.height - [[structureHelpPanel contentView] frame].size.height) + (6*2); - - NSRect popUpFrame = [[cell spPopUpWindow] frame]; - - //determine the side on which to add our window based on the space left on screen - NSPoint topRightCorner = NSMakePoint(popUpFrame.origin.x, NSMaxY(popUpFrame)); - NSRect screenRect = [NSScreen rectOfScreenAtPoint:topRightCorner]; - - if(NSMaxX(popUpFrame)+10+winRect.size.width > NSMaxX(screenRect)-10) { - // exceeds right border, display on the left - winRect.origin.x = popUpFrame.origin.x - 10 - winRect.size.width; - } - else { - // display on the right - winRect.origin.x = NSMaxX(popUpFrame)+10; - } - - winRect.size.height = rect.size.height + winAddonSize; - winRect.origin.y = NSMaxY(popUpFrame) - winRect.size.height; - [structureHelpPanel setFrame:winRect display:YES]; - - [structureHelpPanel orderFront:nil]; - } - else { - [structureHelpPanel orderOut:nil]; - } -} - -#pragma mark - -#pragma mark Menu delegate methods (encoding/collation dropdown menu) - -- (void)menuNeedsUpdate:(SPIdMenu *)menu -{ - if(![menu isKindOfClass:[SPIdMenu class]]) return; - //NOTE: NSTableView will usually copy the menu and call this method on the copy. Matching with == won't work! - - //walk through the menu and clear the attributedTitle if set. This will remove the gray color from the default items - for(NSMenuItem *item in [menu itemArray]) { - if([item attributedTitle]) { - [item setAttributedTitle:nil]; - } - } - - NSDictionary *rowData = NSArrayObjectAtIndex(tableFields, [tableSourceView selectedRow]); - - if([[menu menuId] isEqualToString:@"encodingPopupMenu"]) { - NSString *tableEncoding = [tableDataInstance tableEncoding]; - //NSString *databaseEncoding = [databaseDataInstance getDatabaseDefaultCharacterSet]; - //NSString *serverEncoding = [databaseDataInstance getServerDefaultCharacterSet]; - - struct _cmpMap defaultCmp[] = { - { - NSLocalizedString(@"Table",@"Table Structure : Encoding dropdown : 'item is table default' marker"), - [NSString stringWithFormat:NSLocalizedString(@"This is the default encoding of table “%@”.", @"Table Structure : Encoding dropdown : table marker tooltip"),selectedTable], - tableEncoding - }, - /* //we could, but that might confuse users even more plus there is no inheritance between a columns charset and the db/server default - { - NSLocalizedString(@"Database",@"Table Structure : Encoding dropdown : 'item is database default' marker"), - [NSString stringWithFormat:NSLocalizedString(@"This is the default encoding of database “%@”.", @"Table Structure : Encoding dropdown : database marker tooltip"),[tableDocumentInstance database]], - databaseEncoding - }, - { - NSLocalizedString(@"Server",@"Table Structure : Encoding dropdown : 'item is server default' marker"), - NSLocalizedString(@"This is the default encoding of this server.", @"Table Structure : Encoding dropdown : server marker tooltip"), - serverEncoding - } */ - }; - - _BuildMenuWithPills(menu, defaultCmp, COUNT_OF(defaultCmp)); - } - else if([[menu menuId] isEqualToString:@"collationPopupMenu"]) { - NSString *encoding = [rowData objectForKey:@"encodingName"]; - NSString *encodingDefaultCollation = [databaseDataInstance getDefaultCollationForEncoding:encoding]; - NSString *tableCollation = [tableDataInstance statusValueForKey:@"Collation"]; - //NSString *databaseCollation = [databaseDataInstance getDatabaseDefaultCollation]; - //NSString *serverCollation = [databaseDataInstance getServerDefaultCollation]; - - struct _cmpMap defaultCmp[] = { - { - NSLocalizedString(@"Default",@"Table Structure : Collation dropdown : 'item is the same as the default collation of the row's charset' marker"), - [NSString stringWithFormat:NSLocalizedString(@"This is the default collation of encoding “%@”.", @"Table Structure : Collation dropdown : default marker tooltip"),encoding], - encodingDefaultCollation - }, - { - NSLocalizedString(@"Table",@"Table Structure : Collation dropdown : 'item is the same as the collation of table' marker"), - [NSString stringWithFormat:NSLocalizedString(@"This is the default collation of table “%@”.", @"Table Structure : Collation dropdown : table marker tooltip"),selectedTable], - tableCollation - }, - /* // see the comment for charset above - { - NSLocalizedString(@"Database",@"Table Structure : Collation dropdown : 'item is the same as the collation of database' marker"), - [NSString stringWithFormat:NSLocalizedString(@"This is the default collation of database “%@”.", @"Table Structure : Collation dropdown : database marker tooltip"),[tableDocumentInstance database]], - databaseCollation - }, - { - NSLocalizedString(@"Server",@"Table Structure : Collation dropdown : 'item is the same as the collation of server' marker"), - NSLocalizedString(@"This is the default collation of this server.", @"Table Structure : Collation dropdown : server marker tooltip"), - serverCollation - } */ - }; - - _BuildMenuWithPills(menu, defaultCmp, COUNT_OF(defaultCmp)); - } -} - -@end - -void _BuildMenuWithPills(NSMenu *menu,struct _cmpMap *map,size_t mapEntries) -{ - NSDictionary *baseAttrs = @{NSFontAttributeName:[menu font],NSParagraphStyleAttributeName: [NSParagraphStyle defaultParagraphStyle]}; - - for(NSMenuItem *item in [menu itemArray]) { - NSMutableAttributedString *itemStr = [[NSMutableAttributedString alloc] initWithString:[item title] attributes:baseAttrs]; - NSString *value = [item representedObject]; - - NSMutableArray *tooltipParts = [NSMutableArray array]; - for (unsigned int i = 0; i < mapEntries; ++i) { - struct _cmpMap *cmp = &map[i]; - if([cmp->cmpWith isEqualToString:value]) { - SPPillAttachmentCell *cell = [[SPPillAttachmentCell alloc] init]; - [cell setStringValue:cmp->title]; - NSTextAttachment *attachment = [[NSTextAttachment alloc] init]; - [attachment setAttachmentCell:[cell autorelease]]; - NSAttributedString *attachmentString = [NSAttributedString attributedStringWithAttachment:[attachment autorelease]]; - - [[itemStr mutableString] appendString:@" "]; - [itemStr appendAttributedString:attachmentString]; - - if(cmp->tooltipPart) [tooltipParts addObject:cmp->tooltipPart]; - } - } - if([tooltipParts count]) [item setToolTip:[tooltipParts componentsJoinedByString:@" "]]; - - [item setAttributedTitle:[itemStr autorelease]]; - } -} diff --git a/Source/SPTableStructureLoading.h b/Source/SPTableStructureLoading.h deleted file mode 100644 index 502c37ae..00000000 --- a/Source/SPTableStructureLoading.h +++ /dev/null @@ -1,44 +0,0 @@ -// -// SPTableStructureLoading.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on July 4, 2012. -// Copyright (c) 2012 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. - -#import "SPTableStructure.h" - -/** - * @category SPTableStructureLoading SPTableStructureLoading.h - * - * @author Stuart Connolly http://stuconnolly.com/ - * - * Contains all functionality related to loading a table's structure. - */ -@interface SPTableStructure (SPTableStructureLoading) - -- (void)loadTable:(NSString *)aTable; -- (IBAction)reloadTable:(id)sender; -- (void)setTableDetails:(NSDictionary *)tableDetails; - -@end diff --git a/Source/SPTableStructureLoading.m b/Source/SPTableStructureLoading.m deleted file mode 100644 index b7f8e748..00000000 --- a/Source/SPTableStructureLoading.m +++ /dev/null @@ -1,368 +0,0 @@ -// -// SPTableStructureLoading.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on July 4, 2012. -// Copyright (c) 2012 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. - -#import "SPTableStructureLoading.h" -#import "SPTableData.h" -#import "SPAlertSheets.h" -#import "SPDatabaseData.h" -#import "SPDatabaseStructure.h" -#import "SPTableFieldValidation.h" -#import "SPDatabaseViewController.h" -#import "SPIndexesController.h" -#import "SPTablesList.h" -#import "SPThreadAdditions.h" -#import "SPTableView.h" -#import "SPFunctions.h" - -#import <SPMySQL/SPMySQL.h> - -@implementation SPTableStructure (SPTableStructureLoading) - -#pragma mark - -#pragma mark Table loading - -/** - * Loads aTable, puts it in an array, updates the tableViewColumns and reloads the tableView. - */ -- (void)loadTable:(NSString *)aTable -{ - NSMutableDictionary *theTableEnumLists = [NSMutableDictionary dictionary]; - - // Check whether a save of the current row is required. - if (![[self onMainThread] saveRowOnDeselect]) return; - - // If no table is selected, reset the interface and return - if (!aTable || ![aTable length]) { - [[self onMainThread] setTableDetails:nil]; - return; - } - - NSMutableArray *theTableFields = [[NSMutableArray alloc] init]; - - // Make a mutable copy out of the cached [tableDataInstance columns] since we're adding infos - for (id col in [tableDataInstance columns]) - { - [theTableFields addObject:[[col mutableCopy] autorelease]]; - } - - // Retrieve the indexes for the table - SPMySQLResult *indexResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW INDEX FROM %@", [aTable backtickQuotedString]]]; - - // If an error occurred, reset the interface and abort - if ([mySQLConnection queryErrored]) { - [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; - [[self onMainThread] setTableDetails:nil]; - - if ([mySQLConnection isConnected]) { - SPOnewayAlertSheet( - NSLocalizedString(@"Error", @"error"), - [NSApp mainWindow], - [NSString stringWithFormat:NSLocalizedString(@"An error occurred while retrieving information.\nMySQL said: %@", @"message of panel when retrieving information failed"), [mySQLConnection lastErrorMessage]] - ); - } - - return; - } - - // Process the indexes into a local array of dictionaries - NSArray *theTableIndexes = [self convertIndexResultToArray:indexResult]; - - // Set the Key column - for (NSDictionary* theIndex in theTableIndexes) - { - for (id field in theTableFields) - { - if ([[field objectForKey:@"name"] isEqualToString:[theIndex objectForKey:@"Column_name"]]) { - if ([[theIndex objectForKey:@"Key_name"] isEqualToString:@"PRIMARY"]) { - [field setObject:@"PRI" forKey:@"Key"]; - } - else { - if ([[field objectForKey:@"typegrouping"] isEqualToString:@"geometry"]) { - [field setObject:@"SPA" forKey:@"Key"]; - } - else { - [field setObject:(([[theIndex objectForKey:@"Non_unique"] isEqualToString:@"1"]) ? @"MUL" : @"UNI") forKey:@"Key"]; - } - } - - break; - } - } - } - - // Set up the encoding PopUpButtonCell - NSArray *encodings = [databaseDataInstance getDatabaseCharacterSetEncodings]; - - SPMainQSync(^{ - [encodingPopupCell removeAllItems]; - - if ([encodings count]) { - - [encodingPopupCell addItemWithTitle:@"dummy"]; - //copy the default attributes and add gray color - NSMutableDictionary *defaultAttrs = [NSMutableDictionary dictionaryWithDictionary:[[encodingPopupCell attributedTitle] attributesAtIndex:0 effectiveRange:NULL]]; - [defaultAttrs setObject:[NSColor lightGrayColor] forKey:NSForegroundColorAttributeName]; - [[encodingPopupCell lastItem] setTitle:@""]; - - for (NSDictionary *encoding in encodings) - { - NSString *encodingName = [encoding objectForKey:@"CHARACTER_SET_NAME"]; - NSString *title = (![encoding objectForKey:@"DESCRIPTION"]) ? encodingName : [NSString stringWithFormat:@"%@ (%@)", [encoding objectForKey:@"DESCRIPTION"], encodingName]; - - [encodingPopupCell addItemWithTitle:title]; - NSMenuItem *item = [encodingPopupCell lastItem]; - - [item setRepresentedObject:encodingName]; - - if ([encodingName isEqualToString:[tableDataInstance tableEncoding]]) { - - NSAttributedString *itemString = [[NSAttributedString alloc] initWithString:[item title] attributes:defaultAttrs]; - - [item setAttributedTitle:[itemString autorelease]]; - } - } - } - else { - [encodingPopupCell addItemWithTitle:NSLocalizedString(@"Not available", @"not available label")]; - } - }); - - // Process all the fields to normalise keys and add additional information - for (id theField in theTableFields) - { - NSString *type = [[[theField objectForKey:@"type"] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] uppercaseString]; - - if([type isEqualToString:@"JSON"]) { - // MySQL 5.7 manual: - // "MySQL handles strings used in JSON context using the utf8mb4 character set and utf8mb4_bin collation. - // Strings in other character set are converted to utf8mb4 as necessary." - [theField setObject:@"utf8mb4" forKey:@"encodingName"]; - [theField setObject:@"utf8mb4_bin" forKey:@"collationName"]; - [theField setObject:@1 forKey:@"binary"]; - } - else if ([fieldValidation isFieldTypeString:type]) { - // The MySQL 4.1 manual says: - // - // MySQL chooses the column character set and collation in the following manner: - // 1. If both CHARACTER SET X and COLLATE Y were specified, then character set X and collation Y are used. - // 2. If CHARACTER SET X was specified without COLLATE, then character set X and its default collation are used. - // 3. If COLLATE Y was specified without CHARACTER SET, then the character set associated with Y and collation Y. - // 4. Otherwise, the table character set and collation are used. - NSString *encoding = [theField objectForKey:@"encoding"]; - NSString *collation = [theField objectForKey:@"collation"]; - if(encoding) { - if(collation) { - // 1 - } - else { - collation = [databaseDataInstance getDefaultCollationForEncoding:encoding]; // 2 - } - } - else { - if(collation) { - encoding = [databaseDataInstance getEncodingFromCollation:collation]; // 3 - } - else { - encoding = [tableDataInstance tableEncoding]; //4 - collation = [tableDataInstance statusValueForKey:@"Collation"]; - if(!collation) { - // should not happen, as the TABLE STATUS output always(?) includes the collation - collation = [databaseDataInstance getDefaultCollationForEncoding:encoding]; - } - } - } - - // MySQL < 4.1 does not support collations (they are part of the charset), it will be nil there - - [theField setObject:encoding forKey:@"encodingName"]; - [theField setObject:collation forKey:@"collationName"]; - - // Set BINARY if collation ends with _bin for convenience - if ([collation hasSuffix:@"_bin"]) { - [theField setObject:@1 forKey:@"binary"]; - } - } - - // Get possible values if the field is an enum or a set - if (([type isEqualToString:@"ENUM"] || [type isEqualToString:@"SET"]) && [theField objectForKey:@"values"]) { - [theTableEnumLists setObject:[NSArray arrayWithArray:[theField objectForKey:@"values"]] forKey:[theField objectForKey:@"name"]]; - [theField setObject:[NSString stringWithFormat:@"'%@'", [[theField objectForKey:@"values"] componentsJoinedByString:@"','"]] forKey:@"length"]; - } - - // Join length and decimals if any - if ([theField objectForKey:@"decimals"]) - [theField setObject:[NSString stringWithFormat:@"%@,%@", [theField objectForKey:@"length"], [theField objectForKey:@"decimals"]] forKey:@"length"]; - - // Normalize default - if (![theField objectForKey:@"default"]) { - [theField setObject:@"" forKey:@"default"]; - } - else if ([[theField objectForKey:@"default"] isNSNull]) { - [theField setObject:[prefs stringForKey:SPNullValue] forKey:@"default"]; - } - - // Init Extra field - [theField setObject:@"None" forKey:@"Extra"]; - - // Check for auto_increment and set Extra accordingly - if ([[theField objectForKey:@"autoincrement"] integerValue]) { - [theField setObject:@"auto_increment" forKey:@"Extra"]; - } - - // For timestamps/datetime check to see whether "on update CURRENT_TIMESTAMP" and set Extra accordingly - else if ([type isInArray:@[@"TIMESTAMP",@"DATETIME"]] && [[theField objectForKey:@"onupdatetimestamp"] boolValue]) { - NSString *ouct = @"on update CURRENT_TIMESTAMP"; - // restore a length parameter if the field has fractional seconds. - // the parameter of current_timestamp MUST match the field's length in that case, so we can just 'guess' it. - NSString *fieldLen = [theField objectForKey:@"length"]; - if([fieldLen length] && ![fieldLen isEqualToString:@"0"]) { - ouct = [ouct stringByAppendingFormat:@"(%@)",fieldLen]; - } - [theField setObject:ouct forKey:@"Extra"]; - } - } - - // Set up the table details for the new table, and request an data/interface update - NSDictionary *tableDetails = [NSDictionary dictionaryWithObjectsAndKeys: - aTable, @"name", - theTableFields, @"tableFields", - theTableIndexes, @"tableIndexes", - theTableEnumLists, @"enumLists", - nil]; - - [[self onMainThread] setTableDetails:tableDetails]; - - isCurrentExtraAutoIncrement = [tableDataInstance tableHasAutoIncrementField]; - autoIncrementIndex = nil; - - // Send the query finished/work complete notification - [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; - - [theTableFields release]; -} - -/** - * Reloads the table (performing a new query). - */ -- (IBAction)reloadTable:(id)sender -{ - // Check whether a save of the current row is required - if (![[self onMainThread] saveRowOnDeselect]) return; - - [tableDataInstance resetAllData]; - [tableDocumentInstance setStatusRequiresReload:YES]; - - // Query the structure of all databases in the background (mainly for completion) - [[tableDocumentInstance databaseStructureRetrieval] queryDbStructureInBackgroundWithUserInfo:@{@"forceUpdate" : @YES}]; - - [self loadTable:selectedTable]; -} - -/** - * Updates the stored table details and updates the interface to match. - * - * Should be called on the main thread. - */ -- (void)setTableDetails:(NSDictionary *)tableDetails -{ - NSString *newTableName = [tableDetails objectForKey:@"name"]; - NSMutableDictionary *newDefaultValues; - - BOOL enableInteraction = -#ifndef SP_CODA /* patch */ - ![[tableDocumentInstance selectedToolbarItemIdentifier] isEqualToString:SPMainToolbarTableStructure] || -#endif - ![tableDocumentInstance isWorking]; - - // Update the selected table name - if (selectedTable) SPClear(selectedTable); - if (newTableName) selectedTable = [[NSString alloc] initWithString:newTableName]; - - [indexesController setTable:selectedTable]; - - // Reset the table store and display - [tableSourceView deselectAll:self]; - [tableFields removeAllObjects]; - [enumFields removeAllObjects]; - [indexesTableView deselectAll:self]; - [addFieldButton setEnabled:NO]; - [duplicateFieldButton setEnabled:NO]; - [removeFieldButton setEnabled:NO]; -#ifndef SP_CODA - [addIndexButton setEnabled:NO]; - [removeIndexButton setEnabled:NO]; - [editTableButton setEnabled:NO]; -#endif - - // If no table is selected, refresh the table/index display to blank and return - if (!selectedTable) { - [tableSourceView reloadData]; - // Empty indexesController's fields and indices explicitly before reloading - [indexesController setFields:@[]]; - [indexesController setIndexes:@[]]; - [indexesTableView reloadData]; - - return; - } - - // Update the fields and indexes stores - [tableFields setArray:[tableDetails objectForKey:@"tableFields"]]; - - [indexesController setFields:tableFields]; - [indexesController setIndexes:[tableDetails objectForKey:@"tableIndexes"]]; - - if (defaultValues) SPClear(defaultValues); - - newDefaultValues = [NSMutableDictionary dictionaryWithCapacity:[tableFields count]]; - - for (id theField in tableFields) - { - [newDefaultValues setObject:[theField objectForKey:@"default"] forKey:[theField objectForKey:@"name"]]; - } - - defaultValues = [[NSDictionary dictionaryWithDictionary:newDefaultValues] retain]; - -#ifndef SP_CODA - // Enable the edit table button - [editTableButton setEnabled:enableInteraction]; -#endif - - // If a view is selected, disable the buttons; otherwise enable. - BOOL editingEnabled = ([tablesListInstance tableType] == SPTableTypeTable) && enableInteraction; - - [addFieldButton setEnabled:editingEnabled]; -#ifndef SP_CODA - [addIndexButton setEnabled:editingEnabled && ![[[tableDataInstance statusValueForKey:@"Engine"] uppercaseString] isEqualToString:@"CSV"]]; -#endif - - // Reload the views - [indexesTableView reloadData]; - [tableSourceView reloadData]; -} - -@end diff --git a/Source/SPTableTriggers.m b/Source/SPTableTriggers.m index 23911279..97b9e459 100644 --- a/Source/SPTableTriggers.m +++ b/Source/SPTableTriggers.m @@ -657,6 +657,65 @@ static SPTriggerEventTag TagForEvent(NSString *mysql); } #pragma mark - +#pragma mark Tableview delegate methods + +/** + * Called whenever the triggers table view selection changes. + */ +- (void)tableViewSelectionDidChange:(NSNotification *)notification +{ + [removeTriggerButton setEnabled:([triggersTableView numberOfSelectedRows] > 0)]; +} + +/** + * Alter the colour of cells displaying NULL values + */ +- (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex +{ + if (![cell respondsToSelector:@selector(setTextColor:)]) { + return; + } + + id value = [[triggerData objectAtIndex:rowIndex] objectForKey:[tableColumn identifier]]; + + [cell setTextColor:[value isNSNull] ? [NSColor lightGrayColor] : [NSColor blackColor]]; +} + +/** + * Double-click action on table cells - for the time being, return NO to disable editing. + */ +- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex +{ + if ([tableDocumentInstance isWorking]) return NO; + + // Start Edit panel + if (((NSInteger)[triggerData count] > rowIndex) && [triggerData objectAtIndex:rowIndex]) { + [self _editTriggerAtIndex:rowIndex]; + } + + return NO; +} + +/** + * Disable row selection while the document is working. + */ +- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)rowIndex +{ + return (![tableDocumentInstance isWorking]); +} + +#pragma mark - +#pragma mark Textfield delegate methods + +/** + * Toggles the enabled state of confirm add trigger button based on the editing of the trigger's name. + */ +- (void)controlTextDidChange:(NSNotification *)notification +{ + [self _toggleConfirmAddTriggerButtonEnabled]; +} + +#pragma mark - - (void)dealloc { diff --git a/Source/SPTableTriggersDelegate.h b/Source/SPTableTriggersDelegate.h deleted file mode 100644 index 9ba3ae2d..00000000 --- a/Source/SPTableTriggersDelegate.h +++ /dev/null @@ -1,33 +0,0 @@ -// -// SPTableTriggersDelegate.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on February 21, 2013. -// Copyright (c) 2013 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. - -#import "SPTableTriggers.h" - -@interface SPTableTriggers (SPTableTriggersDelegate) - -@end diff --git a/Source/SPTableTriggersDelegate.m b/Source/SPTableTriggersDelegate.m deleted file mode 100644 index 87308551..00000000 --- a/Source/SPTableTriggersDelegate.m +++ /dev/null @@ -1,100 +0,0 @@ -// -// SPTableTriggersDelegate.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on February 21, 2013. -// Copyright (c) 2013 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. - -#import "SPTableTriggersDelegate.h" -#import "SPDatabaseDocument.h" - -@interface SPTableTriggers () - -- (void)_editTriggerAtIndex:(NSInteger)index; -- (void)_toggleConfirmAddTriggerButtonEnabled; - -@end - -@implementation SPTableTriggers (SPTableTriggersDelegate) - -#pragma mark - -#pragma mark Tableview delegate methods - -/** - * Called whenever the triggers table view selection changes. - */ -- (void)tableViewSelectionDidChange:(NSNotification *)notification -{ - [removeTriggerButton setEnabled:([triggersTableView numberOfSelectedRows] > 0)]; -} - -/** - * Alter the colour of cells displaying NULL values - */ -- (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex -{ - if (![cell respondsToSelector:@selector(setTextColor:)]) { - return; - } - - id value = [[triggerData objectAtIndex:rowIndex] objectForKey:[tableColumn identifier]]; - - [cell setTextColor:[value isNSNull] ? [NSColor lightGrayColor] : [NSColor blackColor]]; -} - -/** - * Double-click action on table cells - for the time being, return NO to disable editing. - */ -- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex -{ - if ([tableDocumentInstance isWorking]) return NO; - - // Start Edit panel - if (((NSInteger)[triggerData count] > rowIndex) && [triggerData objectAtIndex:rowIndex]) { - [self _editTriggerAtIndex:rowIndex]; - } - - return NO; -} - -/** - * Disable row selection while the document is working. - */ -- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)rowIndex -{ - return (![tableDocumentInstance isWorking]); -} - -#pragma mark - -#pragma mark Textfield delegate methods - -/** - * Toggles the enabled state of confirm add trigger button based on the editing of the trigger's name. - */ -- (void)controlTextDidChange:(NSNotification *)notification -{ - [self _toggleConfirmAddTriggerButtonEnabled]; -} - -@end diff --git a/Source/SPTableView.m b/Source/SPTableView.m index df48a407..1c2bef25 100644 --- a/Source/SPTableView.m +++ b/Source/SPTableView.m @@ -34,8 +34,8 @@ #import "SPWindowController.h" #import "SPFieldMapperController.h" -@interface SPTableView (SPTableViewDelegate) - +@protocol SPTableViewDelegate <NSObject> +@optional - (BOOL)cancelRowEditing; @end @@ -212,7 +212,7 @@ // Check if ESCAPE is hit and use it to cancel row editing if supported else if ([theEvent keyCode] == 53 && [[self delegate] respondsToSelector:@selector(cancelRowEditing)]) { - if ([[self delegate] performSelector:@selector(cancelRowEditing)]) return; + if ([(id<SPTableViewDelegate>)[self delegate] cancelRowEditing]) return; } // If the Tab key is used, but tab editing is disabled, change focus rather than entering edit mode. diff --git a/Source/SPTablesList.m b/Source/SPTablesList.m index 34d1dfcb..eceb57f2 100644 --- a/Source/SPTablesList.m +++ b/Source/SPTablesList.m @@ -33,7 +33,6 @@ #import "SPDatabaseDocument.h" #import "SPTableStructure.h" #import "SPDatabaseStructure.h" -#import "SPDatabaseViewController.h" #import "SPTableContent.h" #import "SPTableData.h" #import "SPTableInfo.h" @@ -52,7 +51,6 @@ #import "SPThreadAdditions.h" #import "SPFunctions.h" #import "SPCharsetCollationHelper.h" -#import "SPWindowManagement.h" #import <SPMySQL/SPMySQL.h> diff --git a/Source/SPTextView.m b/Source/SPTextView.m index f6ee4efc..de1ddc04 100644 --- a/Source/SPTextView.m +++ b/Source/SPTextView.m @@ -33,7 +33,6 @@ #import "SPDatabaseDocument.h" #import "SPNarrowDownCompletion.h" #import "SPQueryController.h" -#import "SPQueryDocumentsController.h" #import "SPTooltip.h" #import "SPTablesList.h" #import "SPNavigatorController.h" @@ -42,7 +41,6 @@ #ifndef SP_CODA /* headers */ #import "SPBundleHTMLOutputController.h" #endif -#import "SPDatabaseViewController.h" #ifndef SP_CODA /* headers */ #import "SPAppController.h" #endif @@ -81,7 +79,7 @@ #pragma mark - -@interface SPTextView (Private_API) +@interface SPTextView () NSInteger _alphabeticSort(id string1, id string2, void *reverse); #ifndef SP_CODA @@ -1062,7 +1060,7 @@ retry: - (IBAction)printDocument:(id)sender { - // If Extended Table Info tab is active delegate the print call to the SPPrintController + // If Extended Table Info tab is active delegate the print call to the SPDatabaseDocument // if the user doesn't select anything in self if([[[[self delegate] class] description] isEqualToString:@"SPExtendedTableInfo"] && ![self selectedRange].length) { [[(NSObject*)[self delegate] valueForKeyPath:@"tableDocumentInstance"] printDocument:sender]; @@ -3727,13 +3725,9 @@ retry: [super dealloc]; } -@end - #pragma mark - #pragma mark Private API -@implementation SPTextView (Private_API) - /** * Sort function (mainly used to sort the words in the textView) */ diff --git a/Source/SPTextViewAdditions.m b/Source/SPTextViewAdditions.m index c20959de..2a322748 100644 --- a/Source/SPTextViewAdditions.m +++ b/Source/SPTextViewAdditions.m @@ -36,7 +36,6 @@ #import "SPCustomQuery.h" #ifndef SP_CODA /* headers */ #import "SPAppController.h" -#import "SPWindowManagement.h" #endif #import "SPFieldEditorController.h" #import "SPTextView.h" diff --git a/Source/SPTooltip.m b/Source/SPTooltip.m index ff68fdbe..af899129 100644 --- a/Source/SPTooltip.m +++ b/Source/SPTooltip.m @@ -71,7 +71,7 @@ static CGFloat slow_in_out (CGFloat t) return t; } -@interface SPTooltip (Private) +@interface SPTooltip () - (void)setContent:(NSString *)content withOptions:(NSDictionary *)displayOptions; - (void)runUntilUserActivity; diff --git a/Source/SPUserManager.m b/Source/SPUserManager.m index c06025d2..397275cb 100644 --- a/Source/SPUserManager.m +++ b/Source/SPUserManager.m @@ -44,6 +44,12 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; +static NSString *SPGeneralTabIdentifier = @"General"; +static NSString *SPGlobalPrivilegesTabIdentifier = @"Global Privileges"; +static NSString *SPResourcesTabIdentifier = @"Resources"; +static NSString *SPSchemaPrivilegesTabIdentifier = @"Schema Privileges"; + + @interface SPUserManager () - (void)_initializeTree:(NSArray *)items; @@ -64,6 +70,7 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; - (BOOL)_renameUserFrom:(NSString *)originalUser host:(NSString *)originalHost to:(NSString *)newUser host:(NSString *)newHost; - (void)sheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void*)context; - (void)contextWillSave:(NSNotification *)notice; +- (void)_selectFirstChildOfParentNode; @end @@ -1520,6 +1527,306 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; return YES; } +#pragma mark - SPUserManagerDelegate + +#pragma mark TableView Delegate Methods + +- (void)tableViewSelectionDidChange:(NSNotification *)notification +{ + id object = [notification object]; + + if (object == schemasTableView) { + [grantedSchemaPrivs removeAllObjects]; + [grantedTableView reloadData]; + + [self _initializeAvailablePrivs]; + + if ([[treeController selectedObjects] count] > 0 && [[schemasTableView selectedRowIndexes] count] > 0) { + SPUserMO *user = [[treeController selectedObjects] objectAtIndex:0]; + + // Check to see if the user host node was selected + if ([user valueForKey:@"host"]) { + NSString *selectedSchema = [schemas objectAtIndex:[schemasTableView selectedRow]]; + + NSArray *results = [self _fetchPrivsWithUser:[[user parent] valueForKey:@"user"] + schema:[selectedSchema stringByReplacingOccurrencesOfString:@"_" withString:@"\\_"] + host:[user valueForKey:@"host"]]; + + if ([results count] > 0) { + NSManagedObject *priv = [results objectAtIndex:0]; + + for (NSPropertyDescription *property in [priv entity]) + { + if ([[property name] hasSuffix:@"_priv"] && [[priv valueForKey:[property name]] boolValue]) + { + NSString *displayName = [[[property name] stringByReplacingOccurrencesOfString:@"_priv" withString:@""] replaceUnderscoreWithSpace]; + NSDictionary *newDict = [NSDictionary dictionaryWithObjectsAndKeys:displayName, @"displayName", [property name], @"name", nil]; + [grantedController addObject:newDict]; + + // Remove items from available so they can't be added twice. + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"displayName like[cd] %@", displayName]; + NSArray *previousObjects = [[availableController arrangedObjects] filteredArrayUsingPredicate:predicate]; + + for (NSDictionary *dict in previousObjects) + { + [availableController removeObject:dict]; + } + } + } + } + + [availableTableView setEnabled:YES]; + } + } + else { + [availableTableView setEnabled:NO]; + } + } + else if (object == grantedTableView) { + [removeSchemaPrivButton setEnabled:[[grantedController selectedObjects] count] > 0]; + } + else if (object == availableTableView) { + [addSchemaPrivButton setEnabled:[[availableController selectedObjects] count] > 0]; + } +} + +- (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex +{ + if (tableView == schemasTableView) { + NSString *schemaName = [schemas objectAtIndex:rowIndex]; + + // Gray out the "all database" entries + if ([schemaName isEqualToString:@""] || [schemaName isEqualToString:@"%"]) { + [cell setTextColor:[NSColor lightGrayColor]]; + } else { + [cell setTextColor:[NSColor blackColor]]; + } + + // If the schema has permissions set, highlight with a yellow background + BOOL enabledPermissions = NO; + SPUserMO *user = [[treeController selectedObjects] objectAtIndex:0]; + NSArray *results = [self _fetchPrivsWithUser:[[user parent] valueForKey:@"user"] + schema:[schemaName stringByReplacingOccurrencesOfString:@"_" withString:@"\\_"] + host:[user valueForKey:@"host"]]; + if ([results count]) { + NSManagedObject *schemaPrivs = [results objectAtIndex:0]; + for (NSString *itemKey in [[[schemaPrivs entity] attributesByName] allKeys]) { + if ([itemKey hasSuffix:@"_priv"] && [[schemaPrivs valueForKey:itemKey] boolValue]) { + enabledPermissions = YES; + break; + } + } + } + + if (enabledPermissions) { + [cell setDrawsBackground:YES]; + [cell setBackgroundColor:[NSColor colorWithDeviceRed:1.f green:1.f blue:0.f alpha:0.2]]; + } else { + [cell setDrawsBackground:NO]; + } + } +} + +#pragma mark - +#pragma mark Tab View Delegate methods + +- (BOOL)tabView:(NSTabView *)tabView shouldSelectTabViewItem:(NSTabViewItem *)tabViewItem +{ + BOOL retVal = YES; + + if ([[treeController selectedObjects] count] == 0) return NO; + + if (![treeController commitEditing]) { + return NO; + } + + // Currently selected object in tree + id selectedObject = [[treeController selectedObjects] objectAtIndex:0]; + + // If we are selecting a tab view that requires there be a child, + // make sure there is a child to select. If not, don't allow it. + if ([[tabViewItem identifier] isEqualToString:SPGlobalPrivilegesTabIdentifier] || + [[tabViewItem identifier] isEqualToString:SPResourcesTabIdentifier] || + [[tabViewItem identifier] isEqualToString:SPSchemaPrivilegesTabIdentifier]) { + + id parent = [selectedObject parent]; + + retVal = parent ? ([[parent children] count] > 0) : ([[selectedObject children] count] > 0); + + if (!retVal) { + NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"User has no hosts", @"user has no hosts message") + defaultButton:NSLocalizedString(@"Add Host", @"Add Host") + alternateButton:NSLocalizedString(@"Cancel", @"cancel button") + otherButton:nil + informativeTextWithFormat:NSLocalizedString(@"This user doesn't have any hosts associated with it. It will be deleted unless one is added", @"user has no hosts informative message")]; + + if ([alert runModal] == NSAlertDefaultReturn) { + [self addHost:nil]; + } + } + + // If this is the resources tab, enable or disable the controls based on the server's support for them + if ([[tabViewItem identifier] isEqualToString:SPResourcesTabIdentifier]) { + + BOOL serverSupportsUserMaxVars = [serverSupport supportsUserMaxVars]; + + // Disable the fields according to the version + [maxUpdatesTextField setEnabled:serverSupportsUserMaxVars]; + [maxConnectionsTextField setEnabled:serverSupportsUserMaxVars]; + [maxQuestionsTextField setEnabled:serverSupportsUserMaxVars]; + } + } + + return retVal; +} + +- (void)tabView:(NSTabView *)tabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem +{ + if ([[treeController selectedObjects] count] == 0) return; + + id selectedObject = [[treeController selectedObjects] objectAtIndex:0]; + + // If the selected tab is General and a child is selected, select the + // parent (user info). + if ([[tabViewItem identifier] isEqualToString:SPGeneralTabIdentifier]) { + if ([selectedObject parent]) { + [self _selectParentFromSelection]; + } + } + else if ([[tabViewItem identifier] isEqualToString:SPGlobalPrivilegesTabIdentifier] || + [[tabViewItem identifier] isEqualToString:SPResourcesTabIdentifier] || + [[tabViewItem identifier] isEqualToString:SPSchemaPrivilegesTabIdentifier]) { + // If the tab is either Global Privs or Resources and we have a user + // selected, then open tree and select first child node. + [self _selectFirstChildOfParentNode]; + } +} + +#pragma mark - +#pragma mark Outline view Delegate Methods + +- (void)outlineView:(NSOutlineView *)olv willDisplayCell:(NSCell *)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item +{ + if ([cell isKindOfClass:[ImageAndTextCell class]]) + { + // Determines which Image to display depending on parent or child object + NSImage *image = [[NSImage imageNamed:[(SPUserMO *)[item representedObject] parent] ? NSImageNameNetwork : NSImageNameUser] retain]; + + [image setSize:(NSSize){16, 16}]; + [(ImageAndTextCell *)cell setImage:image]; + [image release]; + } +} + +- (BOOL)outlineView:(NSOutlineView *)olv isGroupItem:(id)item +{ + return NO; +} + +- (BOOL)outlineView:(NSOutlineView *)olv shouldSelectItem:(id)item +{ + return YES; +} + +- (BOOL)outlineView:(NSOutlineView *)olv shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item +{ + return ([[[item representedObject] children] count] == 0); +} + +- (void)outlineViewSelectionDidChange:(NSNotification *)notification +{ + if ([[treeController selectedObjects] count] == 0) return; + + id selectedObject = [[treeController selectedObjects] objectAtIndex:0]; + + if ([selectedObject parent] == nil && !([[[tabView selectedTabViewItem] identifier] isEqualToString:@"General"])) { + [tabView selectTabViewItemWithIdentifier:SPGeneralTabIdentifier]; + } + else { + if ([selectedObject parent] != nil && [[[tabView selectedTabViewItem] identifier] isEqualToString:@"General"]) { + [tabView selectTabViewItemWithIdentifier:SPGlobalPrivilegesTabIdentifier]; + } + } + + if ([selectedObject parent] != nil && [selectedObject host] == nil) + { + [selectedObject setValue:@"%" forKey:@"host"]; + [outlineView reloadItem:selectedObject]; + } + + [schemasTableView deselectAll:nil]; + [schemasTableView setNeedsDisplay:YES]; + [grantedTableView deselectAll:nil]; + [availableTableView deselectAll:nil]; +} + +- (BOOL)selectionShouldChangeInOutlineView:(NSOutlineView *)olv +{ + if ([[treeController selectedObjects] count] > 0) + { + id selectedObject = [[treeController selectedObjects] objectAtIndex:0]; + + // Check parents + if ([selectedObject valueForKey:@"parent"] == nil) + { + NSString *name = [selectedObject valueForKey:@"user"]; + NSArray *results = [self _fetchUserWithUserName:name]; + + if ([results count] > 1) { + NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Duplicate User", @"duplicate user message") + defaultButton:NSLocalizedString(@"OK", @"OK button") + alternateButton:nil + otherButton:nil + informativeTextWithFormat:NSLocalizedString(@"A user with the name '%@' already exists", @"duplicate user informative message"), name]; + [alert runModal]; + + return NO; + } + } + else + { + NSArray *children = [selectedObject valueForKeyPath:@"parent.children"]; + NSString *host = [selectedObject valueForKey:@"host"]; + + for (NSManagedObject *child in children) + { + if (![selectedObject isEqual:child] && [[child valueForKey:@"host"] isEqualToString:host]) + { + NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Duplicate Host", @"duplicate host message") + defaultButton:NSLocalizedString(@"OK", @"OK button") + alternateButton:nil + otherButton:nil + informativeTextWithFormat:NSLocalizedString(@"A user with the host '%@' already exists", @"duplicate host informative message"), host]; + + [alert runModal]; + + return NO; + } + } + } + } + + return YES; +} + +#pragma mark - SPUserManagerDataSource + +- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView +{ + return [schemas count]; +} + +- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex +{ + NSString *databaseName = [schemas objectAtIndex:rowIndex]; + if ([databaseName isEqualToString:@""]) { + databaseName = NSLocalizedString(@"All Databases", @"All databases placeholder"); + } else if ([databaseName isEqualToString:@"%"]) { + databaseName = NSLocalizedString(@"All Databases (%)", @"All databases (%) placeholder"); + } + return databaseName; +} + #pragma mark - - (void)dealloc diff --git a/Source/SPUserManagerDataSource.h b/Source/SPUserManagerDataSource.h deleted file mode 100644 index f441c156..00000000 --- a/Source/SPUserManagerDataSource.h +++ /dev/null @@ -1,35 +0,0 @@ -// -// SPUserManagerDataSource.h -// sequel-pro -// -// Created by Rowan Beentje on March 8, 2013. -// Copyright (c) 2013 Rowan Beentje. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPUserManager.h" - -@interface SPUserManager (SPUserManagerDataSource) - -@end diff --git a/Source/SPUserManagerDataSource.m b/Source/SPUserManagerDataSource.m deleted file mode 100644 index 95ec6b68..00000000 --- a/Source/SPUserManagerDataSource.m +++ /dev/null @@ -1,51 +0,0 @@ -// -// SPUserManagerDataSource.m -// sequel-pro -// -// Created by Rowan Beentje on March 8, 2013. -// Copyright (c) 2013 Rowan Beentje. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPUserManagerDataSource.h" - -@implementation SPUserManager (SPUserManagerDataSource) - -- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView -{ - return [schemas count]; -} - -- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex -{ - NSString *databaseName = [schemas objectAtIndex:rowIndex]; - if ([databaseName isEqualToString:@""]) { - databaseName = NSLocalizedString(@"All Databases", @"All databases placeholder"); - } else if ([databaseName isEqualToString:@"%"]) { - databaseName = NSLocalizedString(@"All Databases (%)", @"All databases (%) placeholder"); - } - return databaseName; -} - -@end diff --git a/Source/SPUserManagerDelegate.h b/Source/SPUserManagerDelegate.h deleted file mode 100644 index 14581148..00000000 --- a/Source/SPUserManagerDelegate.h +++ /dev/null @@ -1,35 +0,0 @@ -// -// SPUserManagerDelegate.h -// sequel-pro -// -// Created by Mark Townsend on Jan 1, 2009. -// Copyright (c) 2009 Mark Townsend. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPUserManager.h" - -@interface SPUserManager (SPUserManagerDelegate) - -@end diff --git a/Source/SPUserManagerDelegate.m b/Source/SPUserManagerDelegate.m deleted file mode 100644 index cefe7f2e..00000000 --- a/Source/SPUserManagerDelegate.m +++ /dev/null @@ -1,335 +0,0 @@ -// -// SPUserManagerDelegate.m -// sequel-pro -// -// Created by Mark Townsend on Jan 1, 2009. -// Copyright (c) 2009 Mark Townsend. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPUserManagerDelegate.h" -#import "SPUserMO.h" -#import "SPServerSupport.h" -#import "ImageAndTextCell.h" - -static NSString *SPGeneralTabIdentifier = @"General"; -static NSString *SPGlobalPrivilegesTabIdentifier = @"Global Privileges"; -static NSString *SPResourcesTabIdentifier = @"Resources"; -static NSString *SPSchemaPrivilegesTabIdentifier = @"Schema Privileges"; - -@interface SPUserManager (DeclaredAPI) - -- (void)_initializeAvailablePrivs; -- (void)_selectParentFromSelection; -- (void)_selectFirstChildOfParentNode; -- (NSArray *)_fetchUserWithUserName:(NSString *)username; - -- (NSArray *)_fetchPrivsWithUser:(NSString *)username schema:(NSString *)selectedSchema host:(NSString *)host; - -@end - -@implementation SPUserManager (SPUserManagerDelegate) - -#pragma mark - -#pragma mark TableView Delegate Methods - -- (void)tableViewSelectionDidChange:(NSNotification *)notification -{ - id object = [notification object]; - - if (object == schemasTableView) { - [grantedSchemaPrivs removeAllObjects]; - [grantedTableView reloadData]; - - [self _initializeAvailablePrivs]; - - if ([[treeController selectedObjects] count] > 0 && [[schemasTableView selectedRowIndexes] count] > 0) { - SPUserMO *user = [[treeController selectedObjects] objectAtIndex:0]; - - // Check to see if the user host node was selected - if ([user valueForKey:@"host"]) { - NSString *selectedSchema = [schemas objectAtIndex:[schemasTableView selectedRow]]; - - NSArray *results = [self _fetchPrivsWithUser:[[user parent] valueForKey:@"user"] - schema:[selectedSchema stringByReplacingOccurrencesOfString:@"_" withString:@"\\_"] - host:[user valueForKey:@"host"]]; - - if ([results count] > 0) { - NSManagedObject *priv = [results objectAtIndex:0]; - - for (NSPropertyDescription *property in [priv entity]) - { - if ([[property name] hasSuffix:@"_priv"] && [[priv valueForKey:[property name]] boolValue]) - { - NSString *displayName = [[[property name] stringByReplacingOccurrencesOfString:@"_priv" withString:@""] replaceUnderscoreWithSpace]; - NSDictionary *newDict = [NSDictionary dictionaryWithObjectsAndKeys:displayName, @"displayName", [property name], @"name", nil]; - [grantedController addObject:newDict]; - - // Remove items from available so they can't be added twice. - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"displayName like[cd] %@", displayName]; - NSArray *previousObjects = [[availableController arrangedObjects] filteredArrayUsingPredicate:predicate]; - - for (NSDictionary *dict in previousObjects) - { - [availableController removeObject:dict]; - } - } - } - } - - [availableTableView setEnabled:YES]; - } - } - else { - [availableTableView setEnabled:NO]; - } - } - else if (object == grantedTableView) { - [removeSchemaPrivButton setEnabled:[[grantedController selectedObjects] count] > 0]; - } - else if (object == availableTableView) { - [addSchemaPrivButton setEnabled:[[availableController selectedObjects] count] > 0]; - } -} - -- (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex -{ - if (tableView == schemasTableView) { - NSString *schemaName = [schemas objectAtIndex:rowIndex]; - - // Gray out the "all database" entries - if ([schemaName isEqualToString:@""] || [schemaName isEqualToString:@"%"]) { - [cell setTextColor:[NSColor lightGrayColor]]; - } else { - [cell setTextColor:[NSColor blackColor]]; - } - - // If the schema has permissions set, highlight with a yellow background - BOOL enabledPermissions = NO; - SPUserMO *user = [[treeController selectedObjects] objectAtIndex:0]; - NSArray *results = [self _fetchPrivsWithUser:[[user parent] valueForKey:@"user"] - schema:[schemaName stringByReplacingOccurrencesOfString:@"_" withString:@"\\_"] - host:[user valueForKey:@"host"]]; - if ([results count]) { - NSManagedObject *schemaPrivs = [results objectAtIndex:0]; - for (NSString *itemKey in [[[schemaPrivs entity] attributesByName] allKeys]) { - if ([itemKey hasSuffix:@"_priv"] && [[schemaPrivs valueForKey:itemKey] boolValue]) { - enabledPermissions = YES; - break; - } - } - } - - if (enabledPermissions) { - [cell setDrawsBackground:YES]; - [cell setBackgroundColor:[NSColor colorWithDeviceRed:1.f green:1.f blue:0.f alpha:0.2]]; - } else { - [cell setDrawsBackground:NO]; - } - } -} - -#pragma mark - -#pragma mark Tab View Delegate methods - -- (BOOL)tabView:(NSTabView *)tabView shouldSelectTabViewItem:(NSTabViewItem *)tabViewItem -{ - BOOL retVal = YES; - - if ([[treeController selectedObjects] count] == 0) return NO; - - if (![treeController commitEditing]) { - return NO; - } - - // Currently selected object in tree - id selectedObject = [[treeController selectedObjects] objectAtIndex:0]; - - // If we are selecting a tab view that requires there be a child, - // make sure there is a child to select. If not, don't allow it. - if ([[tabViewItem identifier] isEqualToString:SPGlobalPrivilegesTabIdentifier] || - [[tabViewItem identifier] isEqualToString:SPResourcesTabIdentifier] || - [[tabViewItem identifier] isEqualToString:SPSchemaPrivilegesTabIdentifier]) { - - id parent = [selectedObject parent]; - - retVal = parent ? ([[parent children] count] > 0) : ([[selectedObject children] count] > 0); - - if (!retVal) { - NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"User has no hosts", @"user has no hosts message") - defaultButton:NSLocalizedString(@"Add Host", @"Add Host") - alternateButton:NSLocalizedString(@"Cancel", @"cancel button") - otherButton:nil - informativeTextWithFormat:NSLocalizedString(@"This user doesn't have any hosts associated with it. It will be deleted unless one is added", @"user has no hosts informative message")]; - - if ([alert runModal] == NSAlertDefaultReturn) { - [self addHost:nil]; - } - } - - // If this is the resources tab, enable or disable the controls based on the server's support for them - if ([[tabViewItem identifier] isEqualToString:SPResourcesTabIdentifier]) { - - BOOL serverSupportsUserMaxVars = [serverSupport supportsUserMaxVars]; - - // Disable the fields according to the version - [maxUpdatesTextField setEnabled:serverSupportsUserMaxVars]; - [maxConnectionsTextField setEnabled:serverSupportsUserMaxVars]; - [maxQuestionsTextField setEnabled:serverSupportsUserMaxVars]; - } - } - - return retVal; -} - -- (void)tabView:(NSTabView *)tabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem -{ - if ([[treeController selectedObjects] count] == 0) return; - - id selectedObject = [[treeController selectedObjects] objectAtIndex:0]; - - // If the selected tab is General and a child is selected, select the - // parent (user info). - if ([[tabViewItem identifier] isEqualToString:SPGeneralTabIdentifier]) { - if ([selectedObject parent]) { - [self _selectParentFromSelection]; - } - } - else if ([[tabViewItem identifier] isEqualToString:SPGlobalPrivilegesTabIdentifier] || - [[tabViewItem identifier] isEqualToString:SPResourcesTabIdentifier] || - [[tabViewItem identifier] isEqualToString:SPSchemaPrivilegesTabIdentifier]) { - // If the tab is either Global Privs or Resources and we have a user - // selected, then open tree and select first child node. - [self _selectFirstChildOfParentNode]; - } -} - -#pragma mark - -#pragma mark Outline view Delegate Methods - -- (void)outlineView:(NSOutlineView *)olv willDisplayCell:(NSCell *)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item -{ - if ([cell isKindOfClass:[ImageAndTextCell class]]) - { - // Determines which Image to display depending on parent or child object - NSImage *image = [[NSImage imageNamed:[(SPUserMO *)[item representedObject] parent] ? NSImageNameNetwork : NSImageNameUser] retain]; - - [image setSize:(NSSize){16, 16}]; - [(ImageAndTextCell *)cell setImage:image]; - [image release]; - } -} - -- (BOOL)outlineView:(NSOutlineView *)olv isGroupItem:(id)item -{ - return NO; -} - -- (BOOL)outlineView:(NSOutlineView *)olv shouldSelectItem:(id)item -{ - return YES; -} - -- (BOOL)outlineView:(NSOutlineView *)olv shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item -{ - return ([[[item representedObject] children] count] == 0); -} - -- (void)outlineViewSelectionDidChange:(NSNotification *)notification -{ - if ([[treeController selectedObjects] count] == 0) return; - - id selectedObject = [[treeController selectedObjects] objectAtIndex:0]; - - if ([selectedObject parent] == nil && !([[[tabView selectedTabViewItem] identifier] isEqualToString:@"General"])) { - [tabView selectTabViewItemWithIdentifier:SPGeneralTabIdentifier]; - } - else { - if ([selectedObject parent] != nil && [[[tabView selectedTabViewItem] identifier] isEqualToString:@"General"]) { - [tabView selectTabViewItemWithIdentifier:SPGlobalPrivilegesTabIdentifier]; - } - } - - if ([selectedObject parent] != nil && [selectedObject host] == nil) - { - [selectedObject setValue:@"%" forKey:@"host"]; - [outlineView reloadItem:selectedObject]; - } - - [schemasTableView deselectAll:nil]; - [schemasTableView setNeedsDisplay:YES]; - [grantedTableView deselectAll:nil]; - [availableTableView deselectAll:nil]; -} - -- (BOOL)selectionShouldChangeInOutlineView:(NSOutlineView *)olv -{ - if ([[treeController selectedObjects] count] > 0) - { - id selectedObject = [[treeController selectedObjects] objectAtIndex:0]; - - // Check parents - if ([selectedObject valueForKey:@"parent"] == nil) - { - NSString *name = [selectedObject valueForKey:@"user"]; - NSArray *results = [self _fetchUserWithUserName:name]; - - if ([results count] > 1) { - NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Duplicate User", @"duplicate user message") - defaultButton:NSLocalizedString(@"OK", @"OK button") - alternateButton:nil - otherButton:nil - informativeTextWithFormat:NSLocalizedString(@"A user with the name '%@' already exists", @"duplicate user informative message"), name]; - [alert runModal]; - - return NO; - } - } - else - { - NSArray *children = [selectedObject valueForKeyPath:@"parent.children"]; - NSString *host = [selectedObject valueForKey:@"host"]; - - for (NSManagedObject *child in children) - { - if (![selectedObject isEqual:child] && [[child valueForKey:@"host"] isEqualToString:host]) - { - NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Duplicate Host", @"duplicate host message") - defaultButton:NSLocalizedString(@"OK", @"OK button") - alternateButton:nil - otherButton:nil - informativeTextWithFormat:NSLocalizedString(@"A user with the host '%@' already exists", @"duplicate host informative message"), host]; - - [alert runModal]; - - return NO; - } - } - } - } - - return YES; -} - -@end diff --git a/Source/SPWindowController.m b/Source/SPWindowController.m index 9755cd0d..330d792f 100644 --- a/Source/SPWindowController.m +++ b/Source/SPWindowController.m @@ -29,12 +29,11 @@ // More info at <https://github.com/sequelpro/sequelpro> #import "SPWindowController.h" -#import "SPWindowControllerDelegate.h" #import "SPDatabaseDocument.h" -#import "SPDatabaseViewController.h" #import "SPAppController.h" #import "PSMTabDragAssistant.h" #import "SPConnectionController.h" +#import "SPFavoritesOutlineView.h" #import <PSMTabBar/PSMTabBarControl.h> #import <PSMTabBar/PSMTabStyle.h> @@ -45,6 +44,12 @@ - (void)_updateProgressIndicatorForItem:(NSTabViewItem *)theItem; - (void)_switchOutSelectedTableDocument:(SPDatabaseDocument *)newDoc; - (void)_selectedTableDocumentDeallocd:(NSNotification *)notification; + +#pragma mark - SPWindowControllerDelegate + +- (void)tabDragStarted:(id)sender; +- (void)tabDragStopped:(id)sender; + @end @implementation SPWindowController @@ -520,6 +525,411 @@ [self _switchOutSelectedTableDocument:nil]; } +#pragma mark - SPWindowControllerDelegate + +/** + * Determine whether the window is permitted to close. + * Go through the tabs in this window, and ask the database connection view + * in each one if it can be closed, returning YES only if all can be closed. + */ +- (BOOL)windowShouldClose:(id)sender +{ + for (NSTabViewItem *eachItem in [tabView tabViewItems]) + { + SPDatabaseDocument *eachDocument = [eachItem identifier]; + + if (![eachDocument parentTabShouldClose]) return NO; + } + + // Remove global session data if the last window of a session will be closed + if ([SPAppDelegate sessionURL] && [[SPAppDelegate orderedDatabaseConnectionWindows] count] == 1) { + [SPAppDelegate setSessionURL:nil]; + [SPAppDelegate setSpfSessionDocData:nil]; + } + + return YES; +} + +/** + * When the window does close, close all tabs. + */ +- (void)windowWillClose:(NSNotification *)notification +{ + for (NSTabViewItem *eachItem in [tabView tabViewItems]) + { + [tabView removeTabViewItem:eachItem]; + } + + [self autorelease]; +} + +/** + * When the window becomes key, inform the selected tab and + * update menu items. + */ +- (void)windowDidBecomeKey:(NSNotification *)notification +{ + [selectedTableDocument tabDidBecomeKey]; + + // Update the "Close window" item + [closeWindowMenuItem setTitle:NSLocalizedString(@"Close Window", @"Close Window menu item")]; + [closeWindowMenuItem setKeyEquivalentModifierMask:(NSCommandKeyMask | NSShiftKeyMask)]; + + // Ensure the "Close tab" item is enabled and has the standard shortcut + [closeTabMenuItem setEnabled:YES]; + [closeTabMenuItem setKeyEquivalent:@"w"]; + [closeTabMenuItem setKeyEquivalentModifierMask:NSCommandKeyMask]; +} + +/** + * When the window resigns key, update menu items. + */ +- (void)windowDidResignKey:(NSNotification *)notification +{ + + // Disable the "Close tab" menu item + [closeTabMenuItem setEnabled:NO]; + [closeTabMenuItem setKeyEquivalent:@""]; + + // Update the "Close window" item to show only "Close" + [closeWindowMenuItem setTitle:NSLocalizedString(@"Close", @"Close menu item")]; + [closeWindowMenuItem setKeyEquivalentModifierMask:NSCommandKeyMask]; +} + +/** + * Observe changes in main window status to update drawing state to match + */ +- (void)windowDidBecomeMain:(NSNotification *)notification +{ + +} +- (void)windowDidResignMain:(NSNotification *)notification +{ +} + +/** + * If the window is resized, notify all the tabs. + */ +- (void)windowDidResize:(NSNotification *)notification +{ + for (NSTabViewItem *eachItem in [tabView tabViewItems]) + { + SPDatabaseDocument *eachDocument = [eachItem identifier]; + + [eachDocument tabDidResize]; + } +} + +/** + * 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 Tab view delegate methods + +/** + * Called when a tab item is about to be selected. + */ +- (void)tabView:(NSTabView *)tabView willSelectTabViewItem:(NSTabViewItem *)tabViewItem +{ + [selectedTableDocument willResignActiveTabInWindow]; +} + +/** + * Called when a tab item was selected. + */ +- (void)tabView:(NSTabView *)tabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem +{ + if ([[PSMTabDragAssistant sharedDragAssistant] isDragging]) return; + + [self _switchOutSelectedTableDocument:[tabViewItem identifier]]; + [selectedTableDocument didBecomeActiveTabInWindow]; + + if ([[self window] isKeyWindow]) [selectedTableDocument tabDidBecomeKey]; + + [self updateAllTabTitles:self]; +} + +/** + * Called to determine whether a tab view item can be closed + * + * Note: This is ONLY called when using the "X" button on the tab itself. + */ +- (BOOL)tabView:(NSTabView *)aTabView shouldCloseTabViewItem:(NSTabViewItem *)tabViewItem +{ + SPDatabaseDocument *theDocument = [tabViewItem identifier]; + + if (![theDocument parentTabShouldClose]) return NO; + + return YES; +} + +/** + * Called after a tab view item is closed. + */ +- (void)tabView:(NSTabView *)aTabView didCloseTabViewItem:(NSTabViewItem *)tabViewItem +{ + SPDatabaseDocument *theDocument = [tabViewItem identifier]; + + [theDocument removeObserver:self forKeyPath:@"isProcessing"]; + [theDocument parentTabDidClose]; +} + +/** + * Called to allow dragging of tab view items + */ +- (BOOL)tabView:(NSTabView *)aTabView shouldDragTabViewItem:(NSTabViewItem *)tabViewItem fromTabBar:(PSMTabBarControl *)tabBarControl +{ + return YES; +} + +/** + * Called when a tab finishes a drop. This is called with the new tabView. + */ +- (void)tabView:(NSTabView*)aTabView didDropTabViewItem:(NSTabViewItem *)tabViewItem inTabBar:(PSMTabBarControl *)tabBarControl +{ + SPDatabaseDocument *draggedDocument = [tabViewItem identifier]; + + // Grab a reference to the old window + NSWindow *draggedFromWindow = [draggedDocument parentWindow]; + + // If the window changed, perform additional processing. + if (draggedFromWindow != [tabBarControl window]) { + + // Update the old window, ensuring the toolbar is cleared to prevent issues with toolbars in multiple windows + [draggedFromWindow setToolbar:nil]; + [[draggedFromWindow windowController] updateSelectedTableDocument]; + + // Update the item's document's window and controller + [draggedDocument willResignActiveTabInWindow]; + [draggedDocument setParentWindowController:[[tabBarControl window] windowController]]; + [draggedDocument setParentWindow:[tabBarControl window]]; + [draggedDocument didBecomeActiveTabInWindow]; + + // Update window controller's active tab, and update the document's isProcessing observation + [[[tabBarControl window] windowController] updateSelectedTableDocument]; + [draggedDocument removeObserver:[draggedFromWindow windowController] forKeyPath:@"isProcessing"]; + [[[tabBarControl window] windowController] _updateProgressIndicatorForItem:tabViewItem]; + } + + // Check the window and move it to front if it's key (eg for new window creation) + if ([[tabBarControl window] isKeyWindow]) [[tabBarControl window] orderFront:self]; + + // workaround bug where "source list" table views are broken in the new window. See https://github.com/sequelpro/sequelpro/issues/2863 + SPWindowController *newWindowController = tabBarControl.window.windowController; + newWindowController.selectedTableDocument.connectionController.favoritesOutlineView.selectionHighlightStyle = NSTableViewSelectionHighlightStyleRegular; + newWindowController.selectedTableDocument.dbTablesTableView.selectionHighlightStyle = NSTableViewSelectionHighlightStyleRegular; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0)), dispatch_get_main_queue(), ^{ + newWindowController.selectedTableDocument.dbTablesTableView.selectionHighlightStyle = NSTableViewSelectionHighlightStyleSourceList; + newWindowController.selectedTableDocument.connectionController.favoritesOutlineView.selectionHighlightStyle = NSTableViewSelectionHighlightStyleSourceList; + }); +} + +/** + * Respond to dragging events entering the tab in the tab bar. + * Allows custom behaviours - for example, if dragging text, switch to the custom + * query view. + */ +- (void)draggingEvent:(id <NSDraggingInfo>)dragEvent enteredTabBar:(PSMTabBarControl *)tabBarControl tabView:(NSTabViewItem *)tabViewItem +{ + SPDatabaseDocument *theDocument = [tabViewItem identifier]; + + if (![theDocument isCustomQuerySelected] && [[[dragEvent draggingPasteboard] types] indexOfObject:NSStringPboardType] != NSNotFound) + { + [theDocument viewQuery:self]; + } +} + +/** + * Show tooltip for a tab view item. + */ +- (NSString *)tabView:(NSTabView *)aTabView toolTipForTabViewItem:(NSTabViewItem *)tabViewItem +{ + NSInteger tabIndex = [tabView indexOfTabViewItem:tabViewItem]; + + if ([[tabBar cells] count] < (NSUInteger)tabIndex) return @""; + + PSMTabBarCell *theCell = [[tabBar cells] objectAtIndex:tabIndex]; + + // If cell is selected show tooltip if truncated only + if ([theCell tabState] & PSMTab_SelectedMask) { + + CGFloat cellWidth = [theCell width]; + CGFloat titleWidth = [theCell stringSize].width; + CGFloat closeButtonWidth = 0; + + if ([theCell hasCloseButton]) + closeButtonWidth = [theCell closeButtonRectForFrame:[theCell frame]].size.width; + + if (titleWidth > cellWidth - closeButtonWidth) { + return [theCell title]; + } + + return @""; + } + // if cell is not selected show full title plus MySQL version is enabled as tooltip + else { + if ([[tabViewItem identifier] respondsToSelector:@selector(tabTitleForTooltip)]) { + return [[tabViewItem identifier] tabTitleForTooltip]; + } + + return @""; + } +} + +/** + * Allow window closing of the last tab item. + */ +- (void)tabView:(NSTabView *)aTabView closeWindowForLastTabViewItem:(NSTabViewItem *)tabViewItem +{ + [[aTabView window] close]; +} + +/** + * When dragging a tab off a tab bar, add a shadow to the drag window. + */ +- (void)tabViewDragWindowCreated:(NSWindow *)dragWindow +{ + [dragWindow setHasShadow:YES]; +} + +/** + * Allow dragging and dropping of tabs to any position, including out of a tab bar + * to create a new window. + */ +- (BOOL)tabView:(NSTabView*)aTabView shouldDropTabViewItem:(NSTabViewItem *)tabViewItem inTabBar:(PSMTabBarControl *)tabBarControl +{ + return YES; +} + +/** + * When a tab is dragged off a tab bar, create a new window containing a new + * (empty) tab bar to hold it. + */ +- (PSMTabBarControl *)tabView:(NSTabView *)aTabView newTabBarForDraggedTabViewItem:(NSTabViewItem *)tabViewItem atPoint:(NSPoint)point +{ + // Create the new window controller, with no tabs + SPWindowController *newWindowController = [[SPWindowController alloc] initWithWindowNibName:@"MainWindow"]; + NSWindow *newWindow = [newWindowController window]; + + CGFloat toolbarHeight = 0; + + if ([[[self window] toolbar] isVisible]) { + NSRect innerFrame = [NSWindow contentRectForFrameRect:[[self window] frame] styleMask:[[self window] styleMask]]; + toolbarHeight = innerFrame.size.height - [[[self window] contentView] frame].size.height; + } + + // Adjust the positioning as appropriate + point.y += toolbarHeight; + + if ([[NSUserDefaults standardUserDefaults] boolForKey:SPAlwaysShowWindowTabBar]) point.y += kPSMTabBarControlHeight; + + // Set the new window position and size + NSRect targetWindowFrame = [[self window] frame]; + targetWindowFrame.size.height -= toolbarHeight; + [newWindow setFrame:targetWindowFrame display:NO]; + [newWindow setFrameTopLeftPoint:point]; + + // Set the window controller as the window's delegate + [newWindow setDelegate:newWindowController]; + + // Set window title + [newWindow setTitle:[[[tabViewItem identifier] parentWindow] title]]; + + // Return the window's tab bar + return [newWindowController valueForKey:@"tabBar"]; +} + +/** + * When dragging a tab off the tab bar, return an image so that a + * drag placeholder can be displayed. + */ +- (NSImage *)tabView:(NSTabView *)aTabView imageForTabViewItem:(NSTabViewItem *)tabViewItem offset:(NSSize *)offset styleMask:(unsigned int *)styleMask +{ + NSImage *viewImage = [[NSImage alloc] init]; + + // Capture an image of the entire window + CGImageRef windowImage = CGWindowListCreateImage(CGRectNull, kCGWindowListOptionIncludingWindow, (unsigned int)[[self window] windowNumber], kCGWindowImageBoundsIgnoreFraming); + NSBitmapImageRep *viewRep = [[NSBitmapImageRep alloc] initWithCGImage:windowImage]; + [viewRep setSize:[[self window] frame].size]; + [viewImage addRepresentation:viewRep]; + [viewRep release]; + + // Calculate the titlebar+toolbar height + CGFloat contentViewOffsetY = [[self window] frame].size.height - [[[self window] contentView] frame].size.height; + offset->height = contentViewOffsetY + [tabBar frame].size.height; + + // Draw over the tab bar area + [viewImage lockFocus]; + [[NSColor windowBackgroundColor] set]; + NSRectFill([tabBar frame]); + [viewImage unlockFocus]; + + // Draw the tab bar background in the tab bar area + [viewImage lockFocus]; + NSRect tabFrame = [tabBar frame]; + [[NSColor windowBackgroundColor] set]; + NSRectFill(tabFrame); + + // Draw the background flipped, which is actually the right way up + NSAffineTransform *transform = [NSAffineTransform transform]; + + [transform translateXBy:0.0f yBy:[[[self window] contentView] frame].size.height]; + [transform scaleXBy:1.0f yBy:-1.0f]; + + [transform concat]; + [(id <PSMTabStyle>)[(PSMTabBarControl *)[aTabView delegate] style] drawBackgroundInRect:tabFrame]; + + [viewImage unlockFocus]; + + return [viewImage autorelease]; +} + +/** + * Displays the current tab's context menu. + */ +- (NSMenu *)tabView:(NSTabView *)aTabView menuForTabViewItem:(NSTabViewItem *)tabViewItem +{ + NSMenu *menu = [[NSMenu alloc] init]; + + [menu addItemWithTitle:NSLocalizedString(@"Close Tab", @"close tab context menu item") action:@selector(closeTab:) keyEquivalent:@""]; + [menu insertItem:[NSMenuItem separatorItem] atIndex:1]; + [menu addItemWithTitle:NSLocalizedString(@"Open in New Tab", @"open connection in new tab context menu item") action:@selector(openDatabaseInNewTab:) keyEquivalent:@""]; + + return [menu autorelease]; +} + +/** + * When tab drags start, show all the tab bars. This allows adding tabs to windows + * containing only one tab - where the bar is normally hidden. + */ +- (void)tabDragStarted:(id)sender +{ + [tabBar setHideForSingleTab:NO]; +} + +/** + * When tab drags stop, set tab bars to automatically hide again for only one tab. + */ +- (void)tabDragStopped:(id)sender +{ + if (![[NSUserDefaults standardUserDefaults] boolForKey:SPAlwaysShowWindowTabBar]) { + [tabBar setHideForSingleTab:YES]; + } +} + #pragma mark - - (void)dealloc diff --git a/Source/SPWindowControllerDelegate.h b/Source/SPWindowControllerDelegate.h deleted file mode 100644 index 12a2c7ea..00000000 --- a/Source/SPWindowControllerDelegate.h +++ /dev/null @@ -1,39 +0,0 @@ -// -// SPWindowControllerDelegate.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on April 9, 2012. -// Copyright (c) 2012 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPWindowController.h" - -@interface SPWindowController (SPWindowControllerDelegate) - -- (void)tabDragStarted:(id)sender; -- (void)tabDragStopped:(id)sender; - - -@end diff --git a/Source/SPWindowControllerDelegate.m b/Source/SPWindowControllerDelegate.m deleted file mode 100644 index ba7f6303..00000000 --- a/Source/SPWindowControllerDelegate.m +++ /dev/null @@ -1,457 +0,0 @@ -// -// SPWindowControllerDelegate.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on April 9, 2012. -// Copyright (c) 2012 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPWindowControllerDelegate.h" -#import "PSMTabDragAssistant.h" -#import "SPDatabaseDocument.h" -#import "SPDatabaseViewController.h" -#import "SPAppController.h" -#import "SPConnectionController.h" -#import "SPFavoritesOutlineView.h" - -#import <PSMTabBar/PSMTabBarControl.h> -#import <PSMTabBar/PSMTabStyle.h> - -@interface SPWindowController (SPDeclaredAPI) - -- (void)_updateProgressIndicatorForItem:(NSTabViewItem *)theItem; -- (void)_switchOutSelectedTableDocument:(SPDatabaseDocument *)newDoc; - -@end - -@implementation SPWindowController (SPWindowControllerDelegate) - -#pragma mark - -#pragma mark Window delegate methods - -/** - * Determine whether the window is permitted to close. - * Go through the tabs in this window, and ask the database connection view - * in each one if it can be closed, returning YES only if all can be closed. - */ -- (BOOL)windowShouldClose:(id)sender -{ - for (NSTabViewItem *eachItem in [tabView tabViewItems]) - { - SPDatabaseDocument *eachDocument = [eachItem identifier]; - - if (![eachDocument parentTabShouldClose]) return NO; - } - - // Remove global session data if the last window of a session will be closed - if ([SPAppDelegate sessionURL] && [[SPAppDelegate orderedDatabaseConnectionWindows] count] == 1) { - [SPAppDelegate setSessionURL:nil]; - [SPAppDelegate setSpfSessionDocData:nil]; - } - - return YES; -} - -/** - * When the window does close, close all tabs. - */ -- (void)windowWillClose:(NSNotification *)notification -{ - for (NSTabViewItem *eachItem in [tabView tabViewItems]) - { - [tabView removeTabViewItem:eachItem]; - } - - [self autorelease]; -} - -/** - * When the window becomes key, inform the selected tab and - * update menu items. - */ -- (void)windowDidBecomeKey:(NSNotification *)notification -{ - [selectedTableDocument tabDidBecomeKey]; - - // Update the "Close window" item - [closeWindowMenuItem setTitle:NSLocalizedString(@"Close Window", @"Close Window menu item")]; - [closeWindowMenuItem setKeyEquivalentModifierMask:(NSCommandKeyMask | NSShiftKeyMask)]; - - // Ensure the "Close tab" item is enabled and has the standard shortcut - [closeTabMenuItem setEnabled:YES]; - [closeTabMenuItem setKeyEquivalent:@"w"]; - [closeTabMenuItem setKeyEquivalentModifierMask:NSCommandKeyMask]; -} - -/** - * When the window resigns key, update menu items. - */ -- (void)windowDidResignKey:(NSNotification *)notification -{ - - // Disable the "Close tab" menu item - [closeTabMenuItem setEnabled:NO]; - [closeTabMenuItem setKeyEquivalent:@""]; - - // Update the "Close window" item to show only "Close" - [closeWindowMenuItem setTitle:NSLocalizedString(@"Close", @"Close menu item")]; - [closeWindowMenuItem setKeyEquivalentModifierMask:NSCommandKeyMask]; -} - -/** - * Observe changes in main window status to update drawing state to match - */ -- (void)windowDidBecomeMain:(NSNotification *)notification -{ - -} -- (void)windowDidResignMain:(NSNotification *)notification -{ -} - -/** - * If the window is resized, notify all the tabs. - */ -- (void)windowDidResize:(NSNotification *)notification -{ - for (NSTabViewItem *eachItem in [tabView tabViewItems]) - { - SPDatabaseDocument *eachDocument = [eachItem identifier]; - - [eachDocument tabDidResize]; - } -} - -/** - * 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 Tab view delegate methods - -/** - * Called when a tab item is about to be selected. - */ -- (void)tabView:(NSTabView *)tabView willSelectTabViewItem:(NSTabViewItem *)tabViewItem -{ - [selectedTableDocument willResignActiveTabInWindow]; -} - -/** - * Called when a tab item was selected. - */ -- (void)tabView:(NSTabView *)tabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem -{ - if ([[PSMTabDragAssistant sharedDragAssistant] isDragging]) return; - - [self _switchOutSelectedTableDocument:[tabViewItem identifier]]; - [selectedTableDocument didBecomeActiveTabInWindow]; - - if ([[self window] isKeyWindow]) [selectedTableDocument tabDidBecomeKey]; - - [self updateAllTabTitles:self]; -} - -/** - * Called to determine whether a tab view item can be closed - * - * Note: This is ONLY called when using the "X" button on the tab itself. - */ -- (BOOL)tabView:(NSTabView *)aTabView shouldCloseTabViewItem:(NSTabViewItem *)tabViewItem -{ - SPDatabaseDocument *theDocument = [tabViewItem identifier]; - - if (![theDocument parentTabShouldClose]) return NO; - - return YES; -} - -/** - * Called after a tab view item is closed. - */ -- (void)tabView:(NSTabView *)aTabView didCloseTabViewItem:(NSTabViewItem *)tabViewItem -{ - SPDatabaseDocument *theDocument = [tabViewItem identifier]; - - [theDocument removeObserver:self forKeyPath:@"isProcessing"]; - [theDocument parentTabDidClose]; -} - -/** - * Called to allow dragging of tab view items - */ -- (BOOL)tabView:(NSTabView *)aTabView shouldDragTabViewItem:(NSTabViewItem *)tabViewItem fromTabBar:(PSMTabBarControl *)tabBarControl -{ - return YES; -} - -/** - * Called when a tab finishes a drop. This is called with the new tabView. - */ -- (void)tabView:(NSTabView*)aTabView didDropTabViewItem:(NSTabViewItem *)tabViewItem inTabBar:(PSMTabBarControl *)tabBarControl -{ - SPDatabaseDocument *draggedDocument = [tabViewItem identifier]; - - // Grab a reference to the old window - NSWindow *draggedFromWindow = [draggedDocument parentWindow]; - - // If the window changed, perform additional processing. - if (draggedFromWindow != [tabBarControl window]) { - - // Update the old window, ensuring the toolbar is cleared to prevent issues with toolbars in multiple windows - [draggedFromWindow setToolbar:nil]; - [[draggedFromWindow windowController] updateSelectedTableDocument]; - - // Update the item's document's window and controller - [draggedDocument willResignActiveTabInWindow]; - [draggedDocument setParentWindowController:[[tabBarControl window] windowController]]; - [draggedDocument setParentWindow:[tabBarControl window]]; - [draggedDocument didBecomeActiveTabInWindow]; - - // Update window controller's active tab, and update the document's isProcessing observation - [[[tabBarControl window] windowController] updateSelectedTableDocument]; - [draggedDocument removeObserver:[draggedFromWindow windowController] forKeyPath:@"isProcessing"]; - [[[tabBarControl window] windowController] _updateProgressIndicatorForItem:tabViewItem]; - } - - // Check the window and move it to front if it's key (eg for new window creation) - if ([[tabBarControl window] isKeyWindow]) [[tabBarControl window] orderFront:self]; - - // workaround bug where "source list" table views are broken in the new window. See https://github.com/sequelpro/sequelpro/issues/2863 - SPWindowController *newWindowController = tabBarControl.window.windowController; - newWindowController.selectedTableDocument.connectionController.favoritesOutlineView.selectionHighlightStyle = NSTableViewSelectionHighlightStyleRegular; - newWindowController.selectedTableDocument.dbTablesTableView.selectionHighlightStyle = NSTableViewSelectionHighlightStyleRegular; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0)), dispatch_get_main_queue(), ^{ - newWindowController.selectedTableDocument.dbTablesTableView.selectionHighlightStyle = NSTableViewSelectionHighlightStyleSourceList; - newWindowController.selectedTableDocument.connectionController.favoritesOutlineView.selectionHighlightStyle = NSTableViewSelectionHighlightStyleSourceList; - }); -} - -/** - * Respond to dragging events entering the tab in the tab bar. - * Allows custom behaviours - for example, if dragging text, switch to the custom - * query view. - */ -- (void)draggingEvent:(id <NSDraggingInfo>)dragEvent enteredTabBar:(PSMTabBarControl *)tabBarControl tabView:(NSTabViewItem *)tabViewItem -{ - SPDatabaseDocument *theDocument = [tabViewItem identifier]; - - if (![theDocument isCustomQuerySelected] && [[[dragEvent draggingPasteboard] types] indexOfObject:NSStringPboardType] != NSNotFound) - { - [theDocument viewQuery:self]; - } -} - -/** - * Show tooltip for a tab view item. - */ -- (NSString *)tabView:(NSTabView *)aTabView toolTipForTabViewItem:(NSTabViewItem *)tabViewItem -{ - NSInteger tabIndex = [tabView indexOfTabViewItem:tabViewItem]; - - if ([[tabBar cells] count] < (NSUInteger)tabIndex) return @""; - - PSMTabBarCell *theCell = [[tabBar cells] objectAtIndex:tabIndex]; - - // If cell is selected show tooltip if truncated only - if ([theCell tabState] & PSMTab_SelectedMask) { - - CGFloat cellWidth = [theCell width]; - CGFloat titleWidth = [theCell stringSize].width; - CGFloat closeButtonWidth = 0; - - if ([theCell hasCloseButton]) - closeButtonWidth = [theCell closeButtonRectForFrame:[theCell frame]].size.width; - - if (titleWidth > cellWidth - closeButtonWidth) { - return [theCell title]; - } - - return @""; - } - // if cell is not selected show full title plus MySQL version is enabled as tooltip - else { - if ([[tabViewItem identifier] respondsToSelector:@selector(tabTitleForTooltip)]) { - return [[tabViewItem identifier] tabTitleForTooltip]; - } - - return @""; - } -} - -/** - * Allow window closing of the last tab item. - */ -- (void)tabView:(NSTabView *)aTabView closeWindowForLastTabViewItem:(NSTabViewItem *)tabViewItem -{ - [[aTabView window] close]; -} - -/** - * When dragging a tab off a tab bar, add a shadow to the drag window. - */ -- (void)tabViewDragWindowCreated:(NSWindow *)dragWindow -{ - [dragWindow setHasShadow:YES]; -} - -/** - * Allow dragging and dropping of tabs to any position, including out of a tab bar - * to create a new window. - */ -- (BOOL)tabView:(NSTabView*)aTabView shouldDropTabViewItem:(NSTabViewItem *)tabViewItem inTabBar:(PSMTabBarControl *)tabBarControl -{ - return YES; -} - -/** - * When a tab is dragged off a tab bar, create a new window containing a new - * (empty) tab bar to hold it. - */ -- (PSMTabBarControl *)tabView:(NSTabView *)aTabView newTabBarForDraggedTabViewItem:(NSTabViewItem *)tabViewItem atPoint:(NSPoint)point -{ - // Create the new window controller, with no tabs - SPWindowController *newWindowController = [[SPWindowController alloc] initWithWindowNibName:@"MainWindow"]; - NSWindow *newWindow = [newWindowController window]; - - CGFloat toolbarHeight = 0; - - if ([[[self window] toolbar] isVisible]) { - NSRect innerFrame = [NSWindow contentRectForFrameRect:[[self window] frame] styleMask:[[self window] styleMask]]; - toolbarHeight = innerFrame.size.height - [[[self window] contentView] frame].size.height; - } - - // Adjust the positioning as appropriate - point.y += toolbarHeight; - - if ([[NSUserDefaults standardUserDefaults] boolForKey:SPAlwaysShowWindowTabBar]) point.y += kPSMTabBarControlHeight; - - // Set the new window position and size - NSRect targetWindowFrame = [[self window] frame]; - targetWindowFrame.size.height -= toolbarHeight; - [newWindow setFrame:targetWindowFrame display:NO]; - [newWindow setFrameTopLeftPoint:point]; - - // Set the window controller as the window's delegate - [newWindow setDelegate:newWindowController]; - - // Set window title - [newWindow setTitle:[[[tabViewItem identifier] parentWindow] title]]; - - // Return the window's tab bar - return [newWindowController valueForKey:@"tabBar"]; -} - -/** - * When dragging a tab off the tab bar, return an image so that a - * drag placeholder can be displayed. - */ -- (NSImage *)tabView:(NSTabView *)aTabView imageForTabViewItem:(NSTabViewItem *)tabViewItem offset:(NSSize *)offset styleMask:(unsigned int *)styleMask -{ - NSImage *viewImage = [[NSImage alloc] init]; - - // Capture an image of the entire window - CGImageRef windowImage = CGWindowListCreateImage(CGRectNull, kCGWindowListOptionIncludingWindow, (unsigned int)[[self window] windowNumber], kCGWindowImageBoundsIgnoreFraming); - NSBitmapImageRep *viewRep = [[NSBitmapImageRep alloc] initWithCGImage:windowImage]; - [viewRep setSize:[[self window] frame].size]; - [viewImage addRepresentation:viewRep]; - [viewRep release]; - - // Calculate the titlebar+toolbar height - CGFloat contentViewOffsetY = [[self window] frame].size.height - [[[self window] contentView] frame].size.height; - offset->height = contentViewOffsetY + [tabBar frame].size.height; - - // Draw over the tab bar area - [viewImage lockFocus]; - [[NSColor windowBackgroundColor] set]; - NSRectFill([tabBar frame]); - [viewImage unlockFocus]; - - // Draw the tab bar background in the tab bar area - [viewImage lockFocus]; - NSRect tabFrame = [tabBar frame]; - [[NSColor windowBackgroundColor] set]; - NSRectFill(tabFrame); - - // Draw the background flipped, which is actually the right way up - NSAffineTransform *transform = [NSAffineTransform transform]; - - [transform translateXBy:0.0f yBy:[[[self window] contentView] frame].size.height]; - [transform scaleXBy:1.0f yBy:-1.0f]; - - [transform concat]; - [(id <PSMTabStyle>)[(PSMTabBarControl *)[aTabView delegate] style] drawBackgroundInRect:tabFrame]; - - [viewImage unlockFocus]; - - return [viewImage autorelease]; -} - -/** - * Displays the current tab's context menu. - */ -- (NSMenu *)tabView:(NSTabView *)aTabView menuForTabViewItem:(NSTabViewItem *)tabViewItem -{ - NSMenu *menu = [[NSMenu alloc] init]; - - [menu addItemWithTitle:NSLocalizedString(@"Close Tab", @"close tab context menu item") action:@selector(closeTab:) keyEquivalent:@""]; - [menu insertItem:[NSMenuItem separatorItem] atIndex:1]; - [menu addItemWithTitle:NSLocalizedString(@"Open in New Tab", @"open connection in new tab context menu item") action:@selector(openDatabaseInNewTab:) keyEquivalent:@""]; - - return [menu autorelease]; -} - -/** - * When tab drags start, show all the tab bars. This allows adding tabs to windows - * containing only one tab - where the bar is normally hidden. - */ -- (void)tabDragStarted:(id)sender -{ - [tabBar setHideForSingleTab:NO]; -} - -/** - * When tab drags stop, set tab bars to automatically hide again for only one tab. - */ -- (void)tabDragStopped:(id)sender -{ - if (![[NSUserDefaults standardUserDefaults] boolForKey:SPAlwaysShowWindowTabBar]) { - [tabBar setHideForSingleTab:YES]; - } -} - -@end diff --git a/Source/SPWindowManagement.h b/Source/SPWindowManagement.h deleted file mode 100644 index 4d32d6d4..00000000 --- a/Source/SPWindowManagement.h +++ /dev/null @@ -1,55 +0,0 @@ -// -// SPWindowManagement.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on July 7, 2012. -// Copyright (c) 2012 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPAppController.h" - -@class SPWindowController; - -/** - * @category SPWindowManagement SPWindowManagement.h - * - * @author Stuart Connolly http://stuconnolly.com/ - * - * Contains all of the main window management methods. - */ -@interface SPAppController (SPWindowManagement) - -- (IBAction)newWindow:(id)sender; -- (IBAction)newTab:(id)sender; -- (IBAction)duplicateTab:(id)sender; - -- (SPWindowController *)newWindow; -- (SPDatabaseDocument *)makeNewConnectionTabOrWindow; -- (SPWindowController *)frontController; - -- (NSWindow *)frontDocumentWindow; -- (void)tabDragStarted:(id)sender; - -@end diff --git a/Source/SPWindowManagement.m b/Source/SPWindowManagement.m deleted file mode 100644 index 6225c52f..00000000 --- a/Source/SPWindowManagement.m +++ /dev/null @@ -1,182 +0,0 @@ -// -// SPWindowManagement.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on July 7, 2012. -// Copyright (c) 2012 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. - -#import "SPWindowManagement.h" -#import "SPWindowController.h" -#import "SPDatabaseDocument.h" - -@implementation SPAppController (SPWindowManagement) - - -- (IBAction)newWindow:(id)sender -{ - [self newWindow]; -} - -/** - * Create a new window, containing a single tab. - */ -- (SPWindowController *)newWindow -{ - static NSPoint cascadeLocation = {.x = 0, .y = 0}; - - // Create a new window controller, and set up a new connection view within it. - SPWindowController *newWindowController = [[SPWindowController alloc] initWithWindowNibName:@"MainWindow"]; - NSWindow *newWindow = [newWindowController window]; - - // Cascading defaults to on - retrieve the window origin automatically assigned by cascading, - // and convert to a top left point. - NSPoint topLeftPoint = [newWindow frame].origin; - topLeftPoint.y += [newWindow frame].size.height; - - // The first window should use autosaving; subsequent windows should cascade. - // So attempt to set the frame autosave name; this will succeed for the very - // first window, and fail for others. - BOOL usedAutosave = [newWindow setFrameAutosaveName:@"DBView"]; - - if (!usedAutosave) { - [newWindow setFrameUsingName:@"DBView"]; - } - - // Add the connection view - [newWindowController addNewConnection]; - - // Cascade according to the statically stored cascade location. - cascadeLocation = [newWindow cascadeTopLeftFromPoint:cascadeLocation]; - - // Set the window controller as the window's delegate - [newWindow setDelegate:newWindowController]; - - // Show the window, and perform frontmost tasks again once the window has drawn - [newWindowController showWindow:self]; - [[newWindowController selectedTableDocument] didBecomeActiveTabInWindow]; - - return newWindowController; -} - -/** - * Create a new tab in the frontmost window. - */ -- (IBAction)newTab:(id)sender -{ - SPWindowController *frontController = [self frontController]; - - // If no window was found, create a new one - if (!frontController) { - [self newWindow:self]; - } - else { - if ([[frontController window] isMiniaturized]) { - [[frontController window] deminiaturize:self]; - } - - [frontController addNewConnection:self]; - } -} - -- (SPDatabaseDocument *)makeNewConnectionTabOrWindow -{ - SPWindowController *frontController = [self frontController]; - - SPDatabaseDocument *frontDocument; - // If no window was found or the front most window has no tabs, create a new one - if (!frontController || [[frontController valueForKeyPath:@"tabView"] numberOfTabViewItems] == 1) { - frontController = [self newWindow]; - frontDocument = [frontController selectedTableDocument]; - } - // Open the spf file in a new tab if the tab bar is visible - else { - if ([[frontController window] isMiniaturized]) [[frontController window] deminiaturize:self]; - frontDocument = [frontController addNewConnection]; - } - - return frontDocument; -} - -/** - * Duplicate the current connection tab - */ -- (IBAction)duplicateTab:(id)sender -{ - SPDatabaseDocument *theFrontDocument = [self frontDocument]; - - if (!theFrontDocument) return [self newTab:sender]; - - // Add a new tab to the window - if ([[self frontDocumentWindow] isMiniaturized]) { - [[self frontDocumentWindow] deminiaturize:self]; - } - - SPDatabaseDocument *newConnection = [[self frontController] addNewConnection]; - - // Get the state of the previously-frontmost document - NSDictionary *allStateDetails = @{ - @"connection" : @YES, - @"history" : @YES, - @"session" : @YES, - @"query" : @YES, - @"password" : @YES - }; - - NSMutableDictionary *frontState = [NSMutableDictionary dictionaryWithDictionary:[theFrontDocument stateIncludingDetails:allStateDetails]]; - - // Ensure it's set to autoconnect - [frontState setObject:@YES forKey:@"auto_connect"]; - - // Set the connection on the new tab - [newConnection setState:frontState]; -} - -/** - * Retrieve the frontmost document window; returns nil if not found. - */ -- (NSWindow *)frontDocumentWindow -{ - return [[self frontController] window]; -} - -- (SPWindowController *)frontController -{ - for (NSWindow *aWindow in [NSApp orderedWindows]) { - id ctr = [aWindow windowController]; - if ([ctr isMemberOfClass:[SPWindowController class]]) { - return ctr; - } - } - return nil; -} - -/** - * When tab drags start, bring all the windows in front of other applications. - */ -- (void)tabDragStarted:(id)sender -{ - [NSApp arrangeInFront:self]; -} - -@end diff --git a/Source/SPXMLExporterDelegate.h b/Source/SPXMLExporterDelegate.h deleted file mode 100644 index 606235e1..00000000 --- a/Source/SPXMLExporterDelegate.h +++ /dev/null @@ -1,43 +0,0 @@ -// -// SPXMLExporterDelegate.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on April 6, 2010. -// Copyright (c) 2010 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPExportController.h" -#import "SPXMLExporterProtocol.h" - -/** - * @category SPXMLExporterDelegate SPXMLExporterDelegate.h - * - * @author Stuart Connolly http://stuconnolly.com/ - * - * XML exporter delegate category. - */ -@interface SPExportController (SPXMLExporterDelegate) <SPXMLExporterProtocol> - -@end diff --git a/Source/SPXMLExporterDelegate.m b/Source/SPXMLExporterDelegate.m deleted file mode 100644 index 80d5d0ca..00000000 --- a/Source/SPXMLExporterDelegate.m +++ /dev/null @@ -1,134 +0,0 @@ -// -// SPXMLExporterDelegate.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on April 6, 2010. -// Copyright (c) 2010 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPXMLExporterDelegate.h" -#import "SPXMLExporter.h" -#import "SPDatabaseDocument.h" -#import "SPExportFile.h" -#import "SPExportInitializer.h" - -#import <SPMySQL/SPMySQL.h> - -@implementation SPExportController (SPXMLExporterDelegate) - -- (void)xmlExportProcessWillBegin:(SPXMLExporter *)exporter -{ - [exportProgressText displayIfNeeded]; - - [exportProgressIndicator setIndeterminate:YES]; - [exportProgressIndicator setUsesThreadedAnimation:YES]; - [exportProgressIndicator startAnimation:self]; - - // Only update the progress text if this is a table export - if (exportSource == SPTableExport) { - - // Update the current table export index - currentTableExportIndex = (exportTableCount - [exporters count]); - - [exportProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %lu (%@): Fetching data...", @"export label showing that the app is fetching data for a specific table"), currentTableExportIndex, exportTableCount, [exporter xmlTableName]]]; - } - else { - [exportProgressText setStringValue:NSLocalizedString(@"Fetching data...", @"export label showing that the app is fetching data")]; - } - - [exportProgressText displayIfNeeded]; -} - -- (void)xmlExportProcessComplete:(SPXMLExporter *)exporter -{ - NSUInteger exportCount = [exporters count]; - - // If required add the next exporter to the operation queue - if ((exportCount > 0) && (exportSource == SPTableExport)) { - - // If we're exporting to multiple files then close the file handle of the exporter - // that just finished, ensuring its data is written to disk. - if (exportToMultipleFiles) { - NSString *string = @""; - - if ([exporter xmlFormat] == SPXMLExportMySQLFormat) { - string = (exportSource == SPTableExport) ? @"</database>\n</mysqldump>\n" : @"</resultset>\n";; - } - else if ([exporter xmlFormat] == SPXMLExportPlainFormat) { - string = [NSString stringWithFormat:@"</%@>\n", [[tableDocumentInstance database] HTMLEscapeString]]; - } - - [[exporter exportOutputFile] writeData:[string dataUsingEncoding:[connection stringEncoding]]]; - [[exporter exportOutputFile] close]; - } - - [operationQueue addOperation:[exporters objectAtIndex:0]]; - - // Remove the exporter we just added to the operation queue from our list of exporters - // so we know it's already been done. - [exporters removeObjectAtIndex:0]; - } - // Otherwise if the exporter list is empty, close the progress sheet - else { - NSString *string = @""; - - if ([exporter xmlFormat] == SPXMLExportMySQLFormat) { - string = (exportSource == SPTableExport) ? @"</database>\n</mysqldump>\n" : @"</resultset>\n";; - } - else if ([exporter xmlFormat] == SPXMLExportPlainFormat) { - string = [NSString stringWithFormat:@"</%@>\n", [[tableDocumentInstance database] HTMLEscapeString]]; - } - - [[exporter exportOutputFile] writeData:[string dataUsingEncoding:[connection stringEncoding]]]; - [[exporter exportOutputFile] close]; - - [self exportEnded]; - } -} - -- (void)xmlExportProcessProgressUpdated:(SPXMLExporter *)exporter -{ - [[exportProgressIndicator onMainThread] setDoubleValue:[exporter exportProgressValue]]; -} - -- (void)xmlExportProcessWillBeginWritingData:(SPXMLExporter *)exporter -{ - // Only update the progress text if this is a table export - if (exportSource == SPTableExport) { - [exportProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %lu (%@): Writing data...", @"export label showing app if writing data for a specific table"), currentTableExportIndex, exportTableCount, [exporter xmlTableName]]]; - } - else { - [exportProgressText setStringValue:NSLocalizedString(@"Writing data...", @"export label showing app is writing data")]; - } - - [exportProgressText displayIfNeeded]; - - [exportProgressIndicator stopAnimation:self]; - [exportProgressIndicator setUsesThreadedAnimation:NO]; - [exportProgressIndicator setIndeterminate:NO]; - [exportProgressIndicator setDoubleValue:0]; -} - -@end diff --git a/Source/YRKSpinningProgressIndicator.m b/Source/YRKSpinningProgressIndicator.m index c706e17a..4128acf7 100644 --- a/Source/YRKSpinningProgressIndicator.m +++ b/Source/YRKSpinningProgressIndicator.m @@ -31,7 +31,7 @@ #import "YRKSpinningProgressIndicator.h" -@interface YRKSpinningProgressIndicator (YRKSpinningProgressIndicatorPrivate) +@interface YRKSpinningProgressIndicator () - (void)updateFrame:(NSTimer *)timer; - (void) animateInBackgroundThread; diff --git a/sequel-pro.xcodeproj/project.pbxproj b/sequel-pro.xcodeproj/project.pbxproj index 4866e18f..409f85b4 100644 --- a/sequel-pro.xcodeproj/project.pbxproj +++ b/sequel-pro.xcodeproj/project.pbxproj @@ -27,68 +27,42 @@ 1198F5B31174EDD500670590 /* SPDatabaseCopy.m in Sources */ = {isa = PBXBuildFile; fileRef = 1198F5B21174EDD500670590 /* SPDatabaseCopy.m */; }; 11B55BFE1189E3B2009EF465 /* SPDatabaseAction.m in Sources */ = {isa = PBXBuildFile; fileRef = 11B55BFD1189E3B2009EF465 /* SPDatabaseAction.m */; }; 11C211301180EC9A00758039 /* SPDatabaseRename.m in Sources */ = {isa = PBXBuildFile; fileRef = 11C2109D1180E70800758039 /* SPDatabaseRename.m */; }; - 17005CB316D6CF0000AF81F4 /* SPTableTriggersDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 17005CB216D6CF0000AF81F4 /* SPTableTriggersDelegate.m */; }; - 171156551E8B0F96002E6363 /* SPTableRelationsDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 171156541E8B0F96002E6363 /* SPTableRelationsDelegate.m */; }; 171312CE109D23C700FB465F /* SPTableTextFieldCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 171312CD109D23C700FB465F /* SPTableTextFieldCell.m */; }; - 1713C740140D8AEF00CFD461 /* SPQueryDocumentsController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1713C73F140D8AEF00CFD461 /* SPQueryDocumentsController.m */; }; - 1713C75F140D8D5900CFD461 /* SPQueryConsoleDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 1713C75E140D8D5900CFD461 /* SPQueryConsoleDataSource.m */; }; 17148565125F5FF500321285 /* SPDatabaseCharacterSets.m in Sources */ = {isa = PBXBuildFile; fileRef = 17148564125F5FF500321285 /* SPDatabaseCharacterSets.m */; }; 1717F9661557E0450065C036 /* SPStringAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 1789343B0F30C1DD0097539A /* SPStringAdditions.m */; }; 1717F9DB1558114D0065C036 /* OCMock.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 11D44DEF118F5887002AA43C /* OCMock.framework */; }; 1717FA401558313A0065C036 /* RegexKitLite.m in Sources */ = {isa = PBXBuildFile; fileRef = 296DC8AB0F909194002A3258 /* RegexKitLite.m */; }; 1717FA43155831600065C036 /* libicucore.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 296DC8BE0F9091DF002A3258 /* libicucore.dylib */; }; - 171B374115DA654300EBC7AB /* SPTableContentFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = 171B374015DA654300EBC7AB /* SPTableContentFilter.m */; }; 17292443107AC41000B21980 /* SPXMLExporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 17292442107AC41000B21980 /* SPXMLExporter.m */; }; 172A65110F7BED7A001E861A /* SPConsoleMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 172A65100F7BED7A001E861A /* SPConsoleMessage.m */; }; 173284EA1088FEDE0062E892 /* SPConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 173284E91088FEDE0062E892 /* SPConstants.m */; }; 1734696B11C1167000AB3D16 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A7FEA54F5311CA2CBB /* Cocoa.framework */; }; - 17381856151FB34E0078FFE2 /* SPUserManagerDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 17381855151FB34E0078FFE2 /* SPUserManagerDelegate.m */; }; - 17386E0B15192526002DC206 /* SPTableContentDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 17386E0A15192526002DC206 /* SPTableContentDataSource.m */; }; - 17386E0E1519257E002DC206 /* SPTableContentDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 17386E0D1519257E002DC206 /* SPTableContentDelegate.m */; }; 173C4362104455CA001F3A30 /* QueryFavoriteManager.xib in Resources */ = {isa = PBXBuildFile; fileRef = 173C4360104455CA001F3A30 /* QueryFavoriteManager.xib */; }; 173C4366104455E0001F3A30 /* SPQueryFavoriteManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 173C4365104455E0001F3A30 /* SPQueryFavoriteManager.m */; }; 173C44D81044A6B0001F3A30 /* SPOutlineView.m in Sources */ = {isa = PBXBuildFile; fileRef = 173C44D71044A6B0001F3A30 /* SPOutlineView.m */; }; - 173C837111AAD26E00B8B084 /* SPExportInitializer.m in Sources */ = {isa = PBXBuildFile; fileRef = 173C836E11AAD26E00B8B084 /* SPExportInitializer.m */; }; 173C837211AAD26E00B8B084 /* SPExportUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 173C837011AAD26E00B8B084 /* SPExportUtilities.m */; }; 173C837911AAD2AE00B8B084 /* SPDotExporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 173C837411AAD2AE00B8B084 /* SPDotExporter.m */; }; 173C837A11AAD2AE00B8B084 /* SPHTMLExporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 173C837611AAD2AE00B8B084 /* SPHTMLExporter.m */; }; 173C837B11AAD2AE00B8B084 /* SPPDFExporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 173C837811AAD2AE00B8B084 /* SPPDFExporter.m */; }; - 173C839011AAD32A00B8B084 /* SPCSVExporterDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 173C838511AAD32A00B8B084 /* SPCSVExporterDelegate.m */; }; - 173C839111AAD32A00B8B084 /* SPDotExporterDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 173C838711AAD32A00B8B084 /* SPDotExporterDelegate.m */; }; - 173C839211AAD32A00B8B084 /* SPHTMLExporterDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 173C838911AAD32A00B8B084 /* SPHTMLExporterDelegate.m */; }; - 173C839311AAD32A00B8B084 /* SPPDFExporterDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 173C838B11AAD32A00B8B084 /* SPPDFExporterDelegate.m */; }; - 173C839411AAD32A00B8B084 /* SPSQLExporterDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 173C838D11AAD32A00B8B084 /* SPSQLExporterDelegate.m */; }; - 173C839511AAD32A00B8B084 /* SPXMLExporterDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 173C838F11AAD32A00B8B084 /* SPXMLExporterDelegate.m */; }; 1740FABB0FC4372F00CF3699 /* SPDatabaseData.m in Sources */ = {isa = PBXBuildFile; fileRef = 1740FABA0FC4372F00CF3699 /* SPDatabaseData.m */; }; - 1748D50C15A4444F003562F2 /* SPTableStructureLoading.m in Sources */ = {isa = PBXBuildFile; fileRef = 1748D50B15A4444F003562F2 /* SPTableStructureLoading.m */; }; - 1748D58615A83E54003562F2 /* SPWindowManagement.m in Sources */ = {isa = PBXBuildFile; fileRef = 1748D58515A83E54003562F2 /* SPWindowManagement.m */; }; 174CE11E10AB80B5008F892B /* DatabaseProcessList.xib in Resources */ = {isa = PBXBuildFile; fileRef = 174CE11C10AB80B5008F892B /* DatabaseProcessList.xib */; }; 174CE14210AB9281008F892B /* SPProcessListController.m in Sources */ = {isa = PBXBuildFile; fileRef = 174CE14110AB9281008F892B /* SPProcessListController.m */; }; - 175EC63512733B36009A7C0F /* SPExportControllerDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 175EC63412733B36009A7C0F /* SPExportControllerDelegate.m */; }; 1761FD480EF03A6F00331368 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1761FD460EF03A6F00331368 /* MainMenu.xib */; }; 176E14D115570FE300FAF326 /* SPBundleCommandRunner.m in Sources */ = {isa = PBXBuildFile; fileRef = 176E14D015570FE300FAF326 /* SPBundleCommandRunner.m */; }; 177E792E0FCB54EC00E9E122 /* database-small.png in Resources */ = {isa = PBXBuildFile; fileRef = 177E792B0FCB54EC00E9E122 /* database-small.png */; }; 177E792F0FCB54EC00E9E122 /* dummy-small.png in Resources */ = {isa = PBXBuildFile; fileRef = 177E792C0FCB54EC00E9E122 /* dummy-small.png */; }; 177E7A230FCB6A2E00E9E122 /* SPExtendedTableInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 177E7A220FCB6A2E00E9E122 /* SPExtendedTableInfo.m */; }; - 17846BA4170C962E00414499 /* SPProcessListControllerDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 17846BA3170C962E00414499 /* SPProcessListControllerDataSource.m */; }; 1785E9F7127D8C7500F468C8 /* SPPreferencePane.m in Sources */ = {isa = PBXBuildFile; fileRef = 1785E9F6127D8C7500F468C8 /* SPPreferencePane.m */; }; - 1785EA16127DAE3A00F468C8 /* SPPreferenceControllerDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1785EA15127DAE3A00F468C8 /* SPPreferenceControllerDelegate.m */; }; 1785EA23127DAF3300F468C8 /* SPTablesPreferencePane.m in Sources */ = {isa = PBXBuildFile; fileRef = 1785EA22127DAF3300F468C8 /* SPTablesPreferencePane.m */; }; 1785EB60127DD5A800F468C8 /* SPNotificationsPreferencePane.m in Sources */ = {isa = PBXBuildFile; fileRef = 1785EB5F127DD5A800F468C8 /* SPNotificationsPreferencePane.m */; }; 1785EB63127DD5DE00F468C8 /* SPAutoUpdatePreferencePane.m in Sources */ = {isa = PBXBuildFile; fileRef = 1785EB62127DD5DE00F468C8 /* SPAutoUpdatePreferencePane.m */; }; 1785EB66127DD5EA00F468C8 /* SPNetworkPreferencePane.m in Sources */ = {isa = PBXBuildFile; fileRef = 1785EB65127DD5EA00F468C8 /* SPNetworkPreferencePane.m */; }; 1785EB6A127DD79300F468C8 /* SPEditorPreferencePane.m in Sources */ = {isa = PBXBuildFile; fileRef = 1785EB69127DD79300F468C8 /* SPEditorPreferencePane.m */; }; 1789343C0F30C1DD0097539A /* SPStringAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 1789343B0F30C1DD0097539A /* SPStringAdditions.m */; }; - 17902612141025BB005F677F /* SPQueryControllerInitializer.m in Sources */ = {isa = PBXBuildFile; fileRef = 17902611141025BB005F677F /* SPQueryControllerInitializer.m */; }; 1792C13210AD752100ABE758 /* DatabaseServerVariables.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1792C13010AD752100ABE758 /* DatabaseServerVariables.xib */; }; 1792C13710AD75C800ABE758 /* SPServerVariablesController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1792C13610AD75C800ABE758 /* SPServerVariablesController.m */; }; - 1792C26110AE1A2D00ABE758 /* SPConnectionDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1792C26010AE1A2D00ABE758 /* SPConnectionDelegate.m */; }; - 1798AB911267924D000D946A /* SPAppleScriptSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 1798AB901267924D000D946A /* SPAppleScriptSupport.m */; }; 1798F1871550175B004B0AB8 /* SPFavoritesExporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 1798F1821550175B004B0AB8 /* SPFavoritesExporter.m */; }; 1798F1881550175B004B0AB8 /* SPFavoritesImporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 1798F1851550175B004B0AB8 /* SPFavoritesImporter.m */; }; - 1798F18F1550178E004B0AB8 /* SPConnectionControllerDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 1798F18A1550178E004B0AB8 /* SPConnectionControllerDataSource.m */; }; - 1798F1901550178E004B0AB8 /* SPConnectionControllerInitializer.m in Sources */ = {isa = PBXBuildFile; fileRef = 1798F18C1550178E004B0AB8 /* SPConnectionControllerInitializer.m */; }; - 1798F1911550178E004B0AB8 /* SPConnectionHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 1798F18E1550178E004B0AB8 /* SPConnectionHandler.m */; }; 1798F1951550181B004B0AB8 /* SPGroupNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 1798F1941550181B004B0AB8 /* SPGroupNode.m */; }; 1798F19815501838004B0AB8 /* SPMutableArrayAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 1798F19715501838004B0AB8 /* SPMutableArrayAdditions.m */; }; 1798F19B1550185B004B0AB8 /* SPTreeNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 1798F19A1550185B004B0AB8 /* SPTreeNode.m */; }; @@ -101,7 +75,6 @@ 17A7773811C52E61001E27B4 /* IndexesView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 17A7773611C52E61001E27B4 /* IndexesView.xib */; }; 17AD35F51E79888D000F213E /* button_clear@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 17AD35F41E79888D000F213E /* button_clear@2x.png */; }; 17AED4161888BD67008E380F /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5EAC0FC0EC87FF900CC579C /* Security.framework */; }; - 17AF787B11FC41C00073D043 /* SPExportFilenameUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 17AF787A11FC41C00073D043 /* SPExportFilenameUtilities.m */; }; 17B548631E81FFA600175D5A /* SPCreateDatabaseInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 17B548621E81FFA600175D5A /* SPCreateDatabaseInfo.m */; }; 17B548711E82B02100175D5A /* button_bar_handle@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 17B548661E82B02100175D5A /* button_bar_handle@2x.png */; }; 17B548721E82B02100175D5A /* button_duplicate@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 17B548671E82B02100175D5A /* button_duplicate@2x.png */; }; @@ -114,28 +87,22 @@ 17B548791E82B02100175D5A /* button_right@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 17B5486E1E82B02100175D5A /* button_right@2x.png */; }; 17B5487A1E82B02100175D5A /* button_select_all@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 17B5486F1E82B02100175D5A /* button_select_all@2x.png */; }; 17B5487B1E82B02100175D5A /* button_select_none@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 17B548701E82B02100175D5A /* button_select_none@2x.png */; }; - 17BA2A3215275D8600389803 /* SPExportInterfaceController.m in Sources */ = {isa = PBXBuildFile; fileRef = 17BA2A3115275D8600389803 /* SPExportInterfaceController.m */; }; 17C058880FC9FC390077E9CF /* SPNarrowDownCompletion.m in Sources */ = {isa = PBXBuildFile; fileRef = 17C058870FC9FC390077E9CF /* SPNarrowDownCompletion.m */; }; 17CC97F310B4ABE90034CD7A /* SPAboutController.m in Sources */ = {isa = PBXBuildFile; fileRef = 17CC97F210B4ABE90034CD7A /* SPAboutController.m */; }; 17CC97F710B4AC6C0034CD7A /* AboutPanel.xib in Resources */ = {isa = PBXBuildFile; fileRef = 17CC97F510B4AC6C0034CD7A /* AboutPanel.xib */; }; 17CC993B10B4C9C80034CD7A /* License.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 17CC993A10B4C9C80034CD7A /* License.rtf */; }; - 17D3583F1533768E00A654D7 /* SPWindowControllerDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D3583E1533768E00A654D7 /* SPWindowControllerDelegate.m */; }; - 17D38EBC12771A1C00672B13 /* SPTableStructureDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D38EBB12771A1C00672B13 /* SPTableStructureDelegate.m */; }; 17D38F701279E23A00672B13 /* SPTableFieldValidation.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D38F6F1279E23A00672B13 /* SPTableFieldValidation.m */; }; 17D390C8127B65AF00672B13 /* SPGeneralPreferencePane.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D390C7127B65AF00672B13 /* SPGeneralPreferencePane.m */; }; 17D390CB127B6BF800672B13 /* SPPreferencesUpgrade.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D390CA127B6BF800672B13 /* SPPreferencesUpgrade.m */; }; 17D3C22212859E070047709F /* SPFavoriteNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D3C22112859E070047709F /* SPFavoriteNode.m */; }; - 17D3C6041289BF350047709F /* SPConnectionControllerDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D3C6031289BF350047709F /* SPConnectionControllerDelegate.m */; }; 17D3C66E128AD4710047709F /* SPFavoritesController.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D3C66D128AD4710047709F /* SPFavoritesController.m */; }; 17D3C671128AD8160047709F /* SPSingleton.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D3C670128AD8160047709F /* SPSingleton.m */; }; 17D3C6D3128B1C900047709F /* SPFavoritesOutlineView.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D3C6D2128B1C900047709F /* SPFavoritesOutlineView.m */; }; - 17D3DC201281816E002A163A /* SPDatabaseViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D3DC1F1281816E002A163A /* SPDatabaseViewController.m */; }; 17D5B49E1553059F00EF3BB3 /* SPViewCopy.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D5B49D1553059F00EF3BB3 /* SPViewCopy.m */; }; 17DB5F441555CA300046834B /* SPMutableArrayAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 1798F19715501838004B0AB8 /* SPMutableArrayAdditions.m */; }; 17DD52B7115071D0007D8950 /* SPPrintTemplate.html in Resources */ = {isa = PBXBuildFile; fileRef = 17DD52B6115071D0007D8950 /* SPPrintTemplate.html */; }; 17DD52C3115074B3007D8950 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 17DD52C1115074B3007D8950 /* InfoPlist.strings */; }; 17DD52C6115074CB007D8950 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 17DD52C4115074CB007D8950 /* Localizable.strings */; }; - 17E090E811498FC9007FC1B4 /* SPPrintController.m in Sources */ = {isa = PBXBuildFile; fileRef = 17E090E711498FC9007FC1B4 /* SPPrintController.m */; }; 17E0937E114AE154007FC1B4 /* SPTableInfoPrintTemplate.html in Resources */ = {isa = PBXBuildFile; fileRef = 17E0937D114AE154007FC1B4 /* SPTableInfoPrintTemplate.html */; }; 17E20E0012D660C3007F75A6 /* OCMock.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 11D44DEF118F5887002AA43C /* OCMock.framework */; }; 17E641460EF01EB5001BC333 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 17E641440EF01EB5001BC333 /* main.m */; }; @@ -165,7 +132,6 @@ 17F5B39C1049B96A00FC794F /* SPSQLExporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 17F5B39B1049B96A00FC794F /* SPSQLExporter.m */; }; 17F90E2C1210B34900274C98 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 17F90E2B1210B34900274C98 /* Credits.rtf */; }; 17F90E481210B42700274C98 /* SPExportFile.m in Sources */ = {isa = PBXBuildFile; fileRef = 17F90E471210B42700274C98 /* SPExportFile.m */; }; - 17F90E4B1210B43A00274C98 /* SPExportFileUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 17F90E4A1210B43A00274C98 /* SPExportFileUtilities.m */; }; 17FDB04C1280778B00DBBBC2 /* SPFontPreviewTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = 17FDB04B1280778B00DBBBC2 /* SPFontPreviewTextField.m */; }; 1A564F74237E2E4958CA593A /* SPPillAttachmentCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A56463D14569A0B56EE8BAC /* SPPillAttachmentCell.m */; }; 296DC89F0F8FD336002A3258 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 296DC89E0F8FD336002A3258 /* WebKit.framework */; }; @@ -223,7 +189,6 @@ 507FF1121BBCC57600104523 /* SPFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = 507FF1111BBCC57600104523 /* SPFunctions.m */; }; 507FF1621BBF0D5000104523 /* SPTableCopyTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 112730551180788A000737FD /* SPTableCopyTest.m */; }; 507FF2421BC33BBC00104523 /* SPOSInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 50EAB5B71A8FBB08008F627A /* SPOSInfo.m */; }; - 507FF26A1BC8450100104523 /* SPExportSettingsPersistence.m in Sources */ = {isa = PBXBuildFile; fileRef = 507FF2691BC8450100104523 /* SPExportSettingsPersistence.m */; }; 507FF2A11BCD27A700104523 /* SPFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = 507FF1111BBCC57600104523 /* SPFunctions.m */; }; 507FF2A21BCD27AE00104523 /* SPOSInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 50EAB5B71A8FBB08008F627A /* SPOSInfo.m */; }; 508022951BF7BA470052A9B2 /* SPQLPluginExportSettingsTemplate.html in Resources */ = {isa = PBXBuildFile; fileRef = 508022931BF7BA470052A9B2 /* SPQLPluginExportSettingsTemplate.html */; }; @@ -404,7 +369,6 @@ 58CDB3400FCE13EF00F8ACA3 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5EAC0FC0EC87FF900CC579C /* Security.framework */; }; 58CDB3410FCE141900F8ACA3 /* SequelProTunnelAssistant.m in Sources */ = {isa = PBXBuildFile; fileRef = 58CDB3310FCE139C00F8ACA3 /* SequelProTunnelAssistant.m */; }; 58CDB3420FCE142500F8ACA3 /* SPKeychain.m in Sources */ = {isa = PBXBuildFile; fileRef = 17E641740EF01F80001BC333 /* SPKeychain.m */; }; - 58D29FE016E96306002EB401 /* SPUserManagerDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 58D29FDF16E96306002EB401 /* SPUserManagerDataSource.m */; }; 58D2A6A716FBDEFF002EB401 /* SPComboPopupButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 58D2A6A616FBDEFF002EB401 /* SPComboPopupButton.m */; }; 58D2E229101222670063EF1D /* SPTextAndLinkCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 58D2E227101222670063EF1D /* SPTextAndLinkCell.m */; }; 58D2E22E101222870063EF1D /* link-arrow-clicked.png in Resources */ = {isa = PBXBuildFile; fileRef = 58D2E22B101222870063EF1D /* link-arrow-clicked.png */; }; @@ -669,21 +633,11 @@ 11C2109D1180E70800758039 /* SPDatabaseRename.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDatabaseRename.m; sourceTree = "<group>"; }; 11C210DE1180E9B800758039 /* SPDatabaseRenameTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDatabaseRenameTest.m; sourceTree = "<group>"; }; 11D44DEF118F5887002AA43C /* OCMock.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OCMock.framework; path = Frameworks/OCMock.framework; sourceTree = "<group>"; }; - 17005CB116D6CF0000AF81F4 /* SPTableTriggersDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTableTriggersDelegate.h; sourceTree = "<group>"; }; - 17005CB216D6CF0000AF81F4 /* SPTableTriggersDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTableTriggersDelegate.m; sourceTree = "<group>"; }; - 171156531E8B0F96002E6363 /* SPTableRelationsDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTableRelationsDelegate.h; sourceTree = "<group>"; }; - 171156541E8B0F96002E6363 /* SPTableRelationsDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTableRelationsDelegate.m; sourceTree = "<group>"; }; 1713122F109C7DF600FB465F /* build.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = build.sh; sourceTree = "<group>"; }; 171312CC109D23C700FB465F /* SPTableTextFieldCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTableTextFieldCell.h; sourceTree = "<group>"; }; 171312CD109D23C700FB465F /* SPTableTextFieldCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTableTextFieldCell.m; sourceTree = "<group>"; }; - 1713C73E140D8AEF00CFD461 /* SPQueryDocumentsController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPQueryDocumentsController.h; sourceTree = "<group>"; }; - 1713C73F140D8AEF00CFD461 /* SPQueryDocumentsController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPQueryDocumentsController.m; sourceTree = "<group>"; }; - 1713C75D140D8D5900CFD461 /* SPQueryConsoleDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPQueryConsoleDataSource.h; sourceTree = "<group>"; }; - 1713C75E140D8D5900CFD461 /* SPQueryConsoleDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPQueryConsoleDataSource.m; sourceTree = "<group>"; }; 17148563125F5FF500321285 /* SPDatabaseCharacterSets.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPDatabaseCharacterSets.h; sourceTree = "<group>"; }; 17148564125F5FF500321285 /* SPDatabaseCharacterSets.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDatabaseCharacterSets.m; sourceTree = "<group>"; }; - 171B373F15DA654300EBC7AB /* SPTableContentFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTableContentFilter.h; sourceTree = "<group>"; }; - 171B374015DA654300EBC7AB /* SPTableContentFilter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTableContentFilter.m; sourceTree = "<group>"; }; 171C398D16BD634600209EC6 /* SPDatabaseContentViewDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPDatabaseContentViewDelegate.h; sourceTree = "<group>"; }; 17292441107AC41000B21980 /* SPXMLExporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPXMLExporter.h; sourceTree = "<group>"; }; 17292442107AC41000B21980 /* SPXMLExporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPXMLExporter.m; sourceTree = "<group>"; }; @@ -691,19 +645,11 @@ 172A65100F7BED7A001E861A /* SPConsoleMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPConsoleMessage.m; sourceTree = "<group>"; }; 173284E81088FEDE0062E892 /* SPConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPConstants.h; sourceTree = "<group>"; }; 173284E91088FEDE0062E892 /* SPConstants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPConstants.m; sourceTree = "<group>"; }; - 17381854151FB34E0078FFE2 /* SPUserManagerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPUserManagerDelegate.h; sourceTree = "<group>"; }; - 17381855151FB34E0078FFE2 /* SPUserManagerDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPUserManagerDelegate.m; sourceTree = "<group>"; }; - 17386E0915192526002DC206 /* SPTableContentDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTableContentDataSource.h; sourceTree = "<group>"; }; - 17386E0A15192526002DC206 /* SPTableContentDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTableContentDataSource.m; sourceTree = "<group>"; }; - 17386E0C1519257E002DC206 /* SPTableContentDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTableContentDelegate.h; sourceTree = "<group>"; }; - 17386E0D1519257E002DC206 /* SPTableContentDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTableContentDelegate.m; sourceTree = "<group>"; }; 173C4361104455CA001F3A30 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/QueryFavoriteManager.xib; sourceTree = "<group>"; }; 173C4364104455E0001F3A30 /* SPQueryFavoriteManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPQueryFavoriteManager.h; sourceTree = "<group>"; }; 173C4365104455E0001F3A30 /* SPQueryFavoriteManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPQueryFavoriteManager.m; sourceTree = "<group>"; }; 173C44D61044A6AF001F3A30 /* SPOutlineView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPOutlineView.h; sourceTree = "<group>"; }; 173C44D71044A6B0001F3A30 /* SPOutlineView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPOutlineView.m; sourceTree = "<group>"; }; - 173C836D11AAD26E00B8B084 /* SPExportInitializer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPExportInitializer.h; sourceTree = "<group>"; }; - 173C836E11AAD26E00B8B084 /* SPExportInitializer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPExportInitializer.m; sourceTree = "<group>"; }; 173C836F11AAD26E00B8B084 /* SPExportUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPExportUtilities.h; sourceTree = "<group>"; }; 173C837011AAD26E00B8B084 /* SPExportUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPExportUtilities.m; sourceTree = "<group>"; }; 173C837311AAD2AE00B8B084 /* SPDotExporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPDotExporter.h; sourceTree = "<group>"; }; @@ -718,31 +664,13 @@ 173C838111AAD2FF00B8B084 /* SPPDFExporterProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPPDFExporterProtocol.h; sourceTree = "<group>"; }; 173C838211AAD2FF00B8B084 /* SPSQLExporterProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPSQLExporterProtocol.h; sourceTree = "<group>"; }; 173C838311AAD2FF00B8B084 /* SPXMLExporterProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPXMLExporterProtocol.h; sourceTree = "<group>"; }; - 173C838411AAD32A00B8B084 /* SPCSVExporterDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPCSVExporterDelegate.h; sourceTree = "<group>"; }; - 173C838511AAD32A00B8B084 /* SPCSVExporterDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPCSVExporterDelegate.m; sourceTree = "<group>"; }; - 173C838611AAD32A00B8B084 /* SPDotExporterDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPDotExporterDelegate.h; sourceTree = "<group>"; }; - 173C838711AAD32A00B8B084 /* SPDotExporterDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDotExporterDelegate.m; sourceTree = "<group>"; }; - 173C838811AAD32A00B8B084 /* SPHTMLExporterDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPHTMLExporterDelegate.h; sourceTree = "<group>"; }; - 173C838911AAD32A00B8B084 /* SPHTMLExporterDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPHTMLExporterDelegate.m; sourceTree = "<group>"; }; - 173C838A11AAD32A00B8B084 /* SPPDFExporterDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPPDFExporterDelegate.h; sourceTree = "<group>"; }; - 173C838B11AAD32A00B8B084 /* SPPDFExporterDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPPDFExporterDelegate.m; sourceTree = "<group>"; }; - 173C838C11AAD32A00B8B084 /* SPSQLExporterDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPSQLExporterDelegate.h; sourceTree = "<group>"; }; - 173C838D11AAD32A00B8B084 /* SPSQLExporterDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPSQLExporterDelegate.m; sourceTree = "<group>"; }; - 173C838E11AAD32A00B8B084 /* SPXMLExporterDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPXMLExporterDelegate.h; sourceTree = "<group>"; }; - 173C838F11AAD32A00B8B084 /* SPXMLExporterDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPXMLExporterDelegate.m; sourceTree = "<group>"; }; 1740FAB90FC4372F00CF3699 /* SPDatabaseData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPDatabaseData.h; sourceTree = "<group>"; }; 1740FABA0FC4372F00CF3699 /* SPDatabaseData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDatabaseData.m; sourceTree = "<group>"; }; - 1748D50A15A4444F003562F2 /* SPTableStructureLoading.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTableStructureLoading.h; sourceTree = "<group>"; }; - 1748D50B15A4444F003562F2 /* SPTableStructureLoading.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTableStructureLoading.m; sourceTree = "<group>"; }; - 1748D58415A83E54003562F2 /* SPWindowManagement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPWindowManagement.h; sourceTree = "<group>"; }; - 1748D58515A83E54003562F2 /* SPWindowManagement.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPWindowManagement.m; sourceTree = "<group>"; }; 174A345112DA4ED000DB0ADE /* create-test-stubs.pl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.perl; path = "create-test-stubs.pl"; sourceTree = "<group>"; }; 174CE11D10AB80B5008F892B /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = Interfaces/English.lproj/DatabaseProcessList.xib; sourceTree = "<group>"; }; 174CE14010AB9281008F892B /* SPProcessListController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPProcessListController.h; sourceTree = "<group>"; }; 174CE14110AB9281008F892B /* SPProcessListController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPProcessListController.m; sourceTree = "<group>"; }; 1755A25C16B33BEA00B35787 /* SPSyntaxParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPSyntaxParser.h; sourceTree = "<group>"; }; - 175EC63312733B36009A7C0F /* SPExportControllerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPExportControllerDelegate.h; sourceTree = "<group>"; }; - 175EC63412733B36009A7C0F /* SPExportControllerDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPExportControllerDelegate.m; sourceTree = "<group>"; }; 175EC64C12733CDF009A7C0F /* SPCategoryAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPCategoryAdditions.h; sourceTree = "<group>"; }; 1761FD470EF03A6F00331368 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/MainMenu.xib; sourceTree = "<group>"; }; 1761FD9D0EF0488900331368 /* build-version.pl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.perl; path = "build-version.pl"; sourceTree = "<group>"; }; @@ -752,12 +680,8 @@ 177E792C0FCB54EC00E9E122 /* dummy-small.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "dummy-small.png"; sourceTree = "<group>"; }; 177E7A210FCB6A2E00E9E122 /* SPExtendedTableInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPExtendedTableInfo.h; sourceTree = "<group>"; }; 177E7A220FCB6A2E00E9E122 /* SPExtendedTableInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPExtendedTableInfo.m; sourceTree = "<group>"; }; - 17846BA2170C962E00414499 /* SPProcessListControllerDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPProcessListControllerDataSource.h; sourceTree = "<group>"; }; - 17846BA3170C962E00414499 /* SPProcessListControllerDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPProcessListControllerDataSource.m; sourceTree = "<group>"; }; 1785E9F5127D8C7500F468C8 /* SPPreferencePane.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPPreferencePane.h; sourceTree = "<group>"; }; 1785E9F6127D8C7500F468C8 /* SPPreferencePane.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPPreferencePane.m; sourceTree = "<group>"; }; - 1785EA14127DAE3A00F468C8 /* SPPreferenceControllerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPPreferenceControllerDelegate.h; sourceTree = "<group>"; }; - 1785EA15127DAE3A00F468C8 /* SPPreferenceControllerDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPPreferenceControllerDelegate.m; sourceTree = "<group>"; }; 1785EA21127DAF3300F468C8 /* SPTablesPreferencePane.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTablesPreferencePane.h; sourceTree = "<group>"; }; 1785EA22127DAF3300F468C8 /* SPTablesPreferencePane.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTablesPreferencePane.m; sourceTree = "<group>"; }; 1785EB5E127DD5A800F468C8 /* SPNotificationsPreferencePane.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPNotificationsPreferencePane.h; sourceTree = "<group>"; }; @@ -771,16 +695,10 @@ 1789343A0F30C1DD0097539A /* SPStringAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPStringAdditions.h; sourceTree = "<group>"; }; 1789343B0F30C1DD0097539A /* SPStringAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPStringAdditions.m; sourceTree = "<group>"; }; 178934980F30CDA10097539A /* trim-application.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = "trim-application.sh"; sourceTree = "<group>"; }; - 17902610141025BB005F677F /* SPQueryControllerInitializer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPQueryControllerInitializer.h; sourceTree = "<group>"; }; - 17902611141025BB005F677F /* SPQueryControllerInitializer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPQueryControllerInitializer.m; sourceTree = "<group>"; }; 1792C13110AD752100ABE758 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = Interfaces/English.lproj/DatabaseServerVariables.xib; sourceTree = "<group>"; }; 1792C13510AD75C800ABE758 /* SPServerVariablesController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPServerVariablesController.h; sourceTree = "<group>"; }; 1792C13610AD75C800ABE758 /* SPServerVariablesController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPServerVariablesController.m; sourceTree = "<group>"; }; - 1792C25F10AE1A2D00ABE758 /* SPConnectionDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPConnectionDelegate.h; sourceTree = "<group>"; }; - 1792C26010AE1A2D00ABE758 /* SPConnectionDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPConnectionDelegate.m; sourceTree = "<group>"; }; 1798AB0C12676CD9000D946A /* localize.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = localize.sh; sourceTree = "<group>"; }; - 1798AB8F1267924D000D946A /* SPAppleScriptSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPAppleScriptSupport.h; sourceTree = "<group>"; }; - 1798AB901267924D000D946A /* SPAppleScriptSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPAppleScriptSupport.m; sourceTree = "<group>"; }; 1798F17E1550171B004B0AB8 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; }; 1798F1811550175B004B0AB8 /* SPFavoritesExporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPFavoritesExporter.h; sourceTree = "<group>"; }; 1798F1821550175B004B0AB8 /* SPFavoritesExporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPFavoritesExporter.m; sourceTree = "<group>"; }; @@ -788,12 +706,6 @@ 1798F1841550175B004B0AB8 /* SPFavoritesImporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPFavoritesImporter.h; sourceTree = "<group>"; }; 1798F1851550175B004B0AB8 /* SPFavoritesImporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPFavoritesImporter.m; sourceTree = "<group>"; }; 1798F1861550175B004B0AB8 /* SPFavoritesImportProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPFavoritesImportProtocol.h; sourceTree = "<group>"; }; - 1798F1891550178E004B0AB8 /* SPConnectionControllerDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPConnectionControllerDataSource.h; sourceTree = "<group>"; }; - 1798F18A1550178E004B0AB8 /* SPConnectionControllerDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPConnectionControllerDataSource.m; sourceTree = "<group>"; }; - 1798F18B1550178E004B0AB8 /* SPConnectionControllerInitializer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPConnectionControllerInitializer.h; sourceTree = "<group>"; }; - 1798F18C1550178E004B0AB8 /* SPConnectionControllerInitializer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPConnectionControllerInitializer.m; sourceTree = "<group>"; }; - 1798F18D1550178E004B0AB8 /* SPConnectionHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPConnectionHandler.h; sourceTree = "<group>"; }; - 1798F18E1550178E004B0AB8 /* SPConnectionHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPConnectionHandler.m; sourceTree = "<group>"; }; 1798F1931550181B004B0AB8 /* SPGroupNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPGroupNode.h; sourceTree = "<group>"; }; 1798F1941550181B004B0AB8 /* SPGroupNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPGroupNode.m; sourceTree = "<group>"; }; 1798F19615501838004B0AB8 /* SPMutableArrayAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPMutableArrayAdditions.h; sourceTree = "<group>"; }; @@ -812,8 +724,6 @@ 17A7773311C52D8E001E27B4 /* SPIndexesController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPIndexesController.m; sourceTree = "<group>"; }; 17A7773711C52E61001E27B4 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = Interfaces/English.lproj/IndexesView.xib; sourceTree = "<group>"; }; 17AD35F41E79888D000F213E /* button_clear@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "button_clear@2x.png"; sourceTree = "<group>"; }; - 17AF787911FC41C00073D043 /* SPExportFilenameUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPExportFilenameUtilities.h; sourceTree = "<group>"; }; - 17AF787A11FC41C00073D043 /* SPExportFilenameUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPExportFilenameUtilities.m; sourceTree = "<group>"; }; 17B548611E81FFA600175D5A /* SPCreateDatabaseInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPCreateDatabaseInfo.h; sourceTree = "<group>"; }; 17B548621E81FFA600175D5A /* SPCreateDatabaseInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPCreateDatabaseInfo.m; sourceTree = "<group>"; }; 17B548661E82B02100175D5A /* button_bar_handle@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "button_bar_handle@2x.png"; sourceTree = "<group>"; }; @@ -828,18 +738,12 @@ 17B5486F1E82B02100175D5A /* button_select_all@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "button_select_all@2x.png"; sourceTree = "<group>"; }; 17B548701E82B02100175D5A /* button_select_none@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "button_select_none@2x.png"; sourceTree = "<group>"; }; 17B7B591101602AE00F057DE /* libssl.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libssl.dylib; path = /usr/lib/libssl.dylib; sourceTree = "<absolute>"; }; - 17BA2A3015275D8600389803 /* SPExportInterfaceController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPExportInterfaceController.h; sourceTree = "<group>"; }; - 17BA2A3115275D8600389803 /* SPExportInterfaceController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPExportInterfaceController.m; sourceTree = "<group>"; }; 17C058860FC9FC390077E9CF /* SPNarrowDownCompletion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPNarrowDownCompletion.h; sourceTree = "<group>"; }; 17C058870FC9FC390077E9CF /* SPNarrowDownCompletion.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPNarrowDownCompletion.m; sourceTree = "<group>"; }; 17CC97F110B4ABE90034CD7A /* SPAboutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPAboutController.h; sourceTree = "<group>"; }; 17CC97F210B4ABE90034CD7A /* SPAboutController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPAboutController.m; sourceTree = "<group>"; }; 17CC97F610B4AC6C0034CD7A /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = Interfaces/English.lproj/AboutPanel.xib; sourceTree = "<group>"; }; 17CC993A10B4C9C80034CD7A /* License.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = License.rtf; sourceTree = "<group>"; }; - 17D3583D1533768E00A654D7 /* SPWindowControllerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPWindowControllerDelegate.h; sourceTree = "<group>"; }; - 17D3583E1533768E00A654D7 /* SPWindowControllerDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPWindowControllerDelegate.m; sourceTree = "<group>"; }; - 17D38EBA12771A1C00672B13 /* SPTableStructureDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTableStructureDelegate.h; sourceTree = "<group>"; }; - 17D38EBB12771A1C00672B13 /* SPTableStructureDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTableStructureDelegate.m; sourceTree = "<group>"; }; 17D38F6E1279E23A00672B13 /* SPTableFieldValidation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTableFieldValidation.h; sourceTree = "<group>"; }; 17D38F6F1279E23A00672B13 /* SPTableFieldValidation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTableFieldValidation.m; sourceTree = "<group>"; }; 17D38FC3127B0CFC00672B13 /* SPConnectionControllerDelegateProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPConnectionControllerDelegateProtocol.h; sourceTree = "<group>"; }; @@ -850,16 +754,12 @@ 17D390CA127B6BF800672B13 /* SPPreferencesUpgrade.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPPreferencesUpgrade.m; sourceTree = "<group>"; }; 17D3C22012859E070047709F /* SPFavoriteNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPFavoriteNode.h; sourceTree = "<group>"; }; 17D3C22112859E070047709F /* SPFavoriteNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPFavoriteNode.m; sourceTree = "<group>"; }; - 17D3C6021289BF350047709F /* SPConnectionControllerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPConnectionControllerDelegate.h; sourceTree = "<group>"; }; - 17D3C6031289BF350047709F /* SPConnectionControllerDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPConnectionControllerDelegate.m; sourceTree = "<group>"; }; 17D3C66C128AD4710047709F /* SPFavoritesController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPFavoritesController.h; sourceTree = "<group>"; }; 17D3C66D128AD4710047709F /* SPFavoritesController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPFavoritesController.m; sourceTree = "<group>"; }; 17D3C66F128AD8160047709F /* SPSingleton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPSingleton.h; sourceTree = "<group>"; }; 17D3C670128AD8160047709F /* SPSingleton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPSingleton.m; sourceTree = "<group>"; }; 17D3C6D1128B1C900047709F /* SPFavoritesOutlineView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPFavoritesOutlineView.h; sourceTree = "<group>"; }; 17D3C6D2128B1C900047709F /* SPFavoritesOutlineView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPFavoritesOutlineView.m; sourceTree = "<group>"; }; - 17D3DC1E1281816E002A163A /* SPDatabaseViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPDatabaseViewController.h; sourceTree = "<group>"; }; - 17D3DC1F1281816E002A163A /* SPDatabaseViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDatabaseViewController.m; sourceTree = "<group>"; }; 17D5B49C1553059F00EF3BB3 /* SPViewCopy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPViewCopy.h; sourceTree = "<group>"; }; 17D5B49D1553059F00EF3BB3 /* SPViewCopy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPViewCopy.m; sourceTree = "<group>"; }; 17DA04EA0FC1A7DA00D66140 /* Unit Tests-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "Unit Tests-Info.plist"; path = "Plists/Unit Tests-Info.plist"; sourceTree = "<group>"; }; @@ -867,8 +767,6 @@ 17DD52B811507217007D8950 /* English */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = English; path = English.lproj/Credits.rtf; sourceTree = "<group>"; }; 17DD52C2115074B3007D8950 /* English */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = "<group>"; }; 17DD52C5115074CB007D8950 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/Localizable.strings; sourceTree = "<group>"; }; - 17E090E611498FC9007FC1B4 /* SPPrintController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPPrintController.h; sourceTree = "<group>"; }; - 17E090E711498FC9007FC1B4 /* SPPrintController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPPrintController.m; sourceTree = "<group>"; }; 17E0937D114AE154007FC1B4 /* SPTableInfoPrintTemplate.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = SPTableInfoPrintTemplate.html; path = Templates/SPTableInfoPrintTemplate.html; sourceTree = "<group>"; }; 17E5954E14F304000054EE08 /* QueryKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = QueryKit.xcodeproj; path = Frameworks/QueryKit/QueryKit.xcodeproj; sourceTree = "<group>"; }; 17E641440EF01EB5001BC333 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; }; @@ -916,8 +814,6 @@ 17F5B39B1049B96A00FC794F /* SPSQLExporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPSQLExporter.m; sourceTree = "<group>"; }; 17F90E461210B42700274C98 /* SPExportFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPExportFile.h; sourceTree = "<group>"; }; 17F90E471210B42700274C98 /* SPExportFile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPExportFile.m; sourceTree = "<group>"; }; - 17F90E491210B43A00274C98 /* SPExportFileUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPExportFileUtilities.h; sourceTree = "<group>"; }; - 17F90E4A1210B43A00274C98 /* SPExportFileUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPExportFileUtilities.m; sourceTree = "<group>"; }; 17FDB04A1280778B00DBBBC2 /* SPFontPreviewTextField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPFontPreviewTextField.h; sourceTree = "<group>"; }; 17FDB04B1280778B00DBBBC2 /* SPFontPreviewTextField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPFontPreviewTextField.m; sourceTree = "<group>"; }; 1A56463D14569A0B56EE8BAC /* SPPillAttachmentCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPPillAttachmentCell.m; sourceTree = "<group>"; }; @@ -987,8 +883,6 @@ 506CE9301A311C6C0039F736 /* SPTableContentFilterController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTableContentFilterController.m; sourceTree = "<group>"; }; 507FF1101BBCC4C400104523 /* SPFunctions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPFunctions.h; sourceTree = "<group>"; }; 507FF1111BBCC57600104523 /* SPFunctions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPFunctions.m; sourceTree = "<group>"; }; - 507FF2681BC8450100104523 /* SPExportSettingsPersistence.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPExportSettingsPersistence.h; sourceTree = "<group>"; }; - 507FF2691BC8450100104523 /* SPExportSettingsPersistence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPExportSettingsPersistence.m; sourceTree = "<group>"; }; 508022941BF7BA470052A9B2 /* English */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = English; path = English.lproj/SPQLPluginExportSettingsTemplate.html; sourceTree = "<group>"; }; 50805B0B1BF2A068005F7A99 /* SPPopUpButtonCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPPopUpButtonCell.h; sourceTree = "<group>"; }; 50805B0C1BF2A068005F7A99 /* SPPopUpButtonCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPPopUpButtonCell.m; sourceTree = "<group>"; }; @@ -1002,7 +896,6 @@ 50D3C3501A77135F00B5429C /* SPParserUtils.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SPParserUtils.c; sourceTree = "<group>"; }; 50D3C3511A77135F00B5429C /* SPParserUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPParserUtils.h; sourceTree = "<group>"; }; 50D3C35B1A771C4C00B5429C /* SPParserUtilsTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPParserUtilsTest.m; sourceTree = "<group>"; }; - 50D3C3831A8177D900B5429C /* SPExportController+SharedPrivateAPI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SPExportController+SharedPrivateAPI.h"; sourceTree = "<group>"; }; 50E217B118174246009D3580 /* SPColorSelectorView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPColorSelectorView.h; sourceTree = "<group>"; }; 50E217B218174246009D3580 /* SPColorSelectorView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPColorSelectorView.m; sourceTree = "<group>"; }; 50E217B418174280009D3580 /* SPFavoriteColorSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPFavoriteColorSupport.h; sourceTree = "<group>"; }; @@ -1204,8 +1097,6 @@ 58CDB32F0FCE138D00F8ACA3 /* SPSSHTunnel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPSSHTunnel.m; sourceTree = "<group>"; }; 58CDB3310FCE139C00F8ACA3 /* SequelProTunnelAssistant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SequelProTunnelAssistant.m; sourceTree = "<group>"; }; 58CDB3360FCE13C900F8ACA3 /* SequelProTunnelAssistant */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = SequelProTunnelAssistant; sourceTree = BUILT_PRODUCTS_DIR; }; - 58D29FDE16E96306002EB401 /* SPUserManagerDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPUserManagerDataSource.h; sourceTree = "<group>"; }; - 58D29FDF16E96306002EB401 /* SPUserManagerDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPUserManagerDataSource.m; sourceTree = "<group>"; }; 58D2A6A516FBDEFF002EB401 /* SPComboPopupButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPComboPopupButton.h; sourceTree = "<group>"; }; 58D2A6A616FBDEFF002EB401 /* SPComboPopupButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPComboPopupButton.m; sourceTree = "<group>"; }; 58D2E227101222670063EF1D /* SPTextAndLinkCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTextAndLinkCell.m; sourceTree = "<group>"; }; @@ -1487,8 +1378,6 @@ children = ( 29FA88211114619E00D1AF3D /* SPTableTriggers.h */, 29FA88221114619E00D1AF3D /* SPTableTriggers.m */, - 17005CB116D6CF0000AF81F4 /* SPTableTriggersDelegate.h */, - 17005CB216D6CF0000AF81F4 /* SPTableTriggersDelegate.m */, ); name = "Table Triggers"; sourceTree = "<group>"; @@ -1498,8 +1387,6 @@ children = ( 387BBBA60FBCB6CB00B31746 /* SPTableRelations.h */, 387BBBA70FBCB6CB00B31746 /* SPTableRelations.m */, - 171156531E8B0F96002E6363 /* SPTableRelationsDelegate.h */, - 171156541E8B0F96002E6363 /* SPTableRelationsDelegate.m */, ); name = "Table Relations"; sourceTree = "<group>"; @@ -1532,12 +1419,6 @@ children = ( BC29C37D10501EFD00DD6C6E /* SPQueryController.h */, BC29C37E10501EFD00DD6C6E /* SPQueryController.m */, - 17902610141025BB005F677F /* SPQueryControllerInitializer.h */, - 17902611141025BB005F677F /* SPQueryControllerInitializer.m */, - 1713C75D140D8D5900CFD461 /* SPQueryConsoleDataSource.h */, - 1713C75E140D8D5900CFD461 /* SPQueryConsoleDataSource.m */, - 1713C73E140D8AEF00CFD461 /* SPQueryDocumentsController.h */, - 1713C73F140D8AEF00CFD461 /* SPQueryDocumentsController.m */, ); name = "Query Controller"; sourceTree = "<group>"; @@ -1569,10 +1450,6 @@ children = ( 4D90B798101E0CDF00D116A1 /* SPUserManager.h */, 4D90B799101E0CDF00D116A1 /* SPUserManager.m */, - 17381854151FB34E0078FFE2 /* SPUserManagerDelegate.h */, - 17381855151FB34E0078FFE2 /* SPUserManagerDelegate.m */, - 58D29FDE16E96306002EB401 /* SPUserManagerDataSource.h */, - 58D29FDF16E96306002EB401 /* SPUserManagerDataSource.m */, ); name = "User Manager"; sourceTree = "<group>"; @@ -1582,12 +1459,6 @@ children = ( 17E6414E0EF01EF6001BC333 /* SPTableContent.h */, 17E6414F0EF01EF6001BC333 /* SPTableContent.m */, - 171B373F15DA654300EBC7AB /* SPTableContentFilter.h */, - 171B374015DA654300EBC7AB /* SPTableContentFilter.m */, - 17386E0C1519257E002DC206 /* SPTableContentDelegate.h */, - 17386E0D1519257E002DC206 /* SPTableContentDelegate.m */, - 17386E0915192526002DC206 /* SPTableContentDataSource.h */, - 17386E0A15192526002DC206 /* SPTableContentDataSource.m */, ); name = "Table Content"; sourceTree = "<group>"; @@ -1626,25 +1497,6 @@ name = "Delegate Protocols"; sourceTree = "<group>"; }; - 173C837D11AAD2D300B8B084 /* Delegate Categories */ = { - isa = PBXGroup; - children = ( - 173C838411AAD32A00B8B084 /* SPCSVExporterDelegate.h */, - 173C838511AAD32A00B8B084 /* SPCSVExporterDelegate.m */, - 173C838C11AAD32A00B8B084 /* SPSQLExporterDelegate.h */, - 173C838D11AAD32A00B8B084 /* SPSQLExporterDelegate.m */, - 173C838E11AAD32A00B8B084 /* SPXMLExporterDelegate.h */, - 173C838F11AAD32A00B8B084 /* SPXMLExporterDelegate.m */, - 173C838611AAD32A00B8B084 /* SPDotExporterDelegate.h */, - 173C838711AAD32A00B8B084 /* SPDotExporterDelegate.m */, - 173C838A11AAD32A00B8B084 /* SPPDFExporterDelegate.h */, - 173C838B11AAD32A00B8B084 /* SPPDFExporterDelegate.m */, - 173C838811AAD32A00B8B084 /* SPHTMLExporterDelegate.h */, - 173C838911AAD32A00B8B084 /* SPHTMLExporterDelegate.m */, - ); - name = "Delegate Categories"; - sourceTree = "<group>"; - }; 173E70A1107FF495008733C9 /* Core Data */ = { isa = PBXGroup; children = ( @@ -1666,14 +1518,11 @@ 177E7A220FCB6A2E00E9E122 /* SPExtendedTableInfo.m */, 17E641500EF01EF6001BC333 /* SPDatabaseDocument.h */, 17E641510EF01EF6001BC333 /* SPDatabaseDocument.m */, - 17D3DC1E1281816E002A163A /* SPDatabaseViewController.h */, - 17D3DC1F1281816E002A163A /* SPDatabaseViewController.m */, 17D38FC2127B0C9500672B13 /* Connection View */, 17386E08151924E9002DC206 /* Table Content */, 17D38F691279E17D00672B13 /* Table Structure */, 17005CB016D6CEA400AF81F4 /* Table Triggers */, 171156501E8B0F40002E6363 /* Table Relations */, - 1792C28910AE1C7200ABE758 /* Controller Categories */, ); name = "Main View Controllers"; sourceTree = "<group>"; @@ -1808,23 +1657,10 @@ children = ( 174CE14010AB9281008F892B /* SPProcessListController.h */, 174CE14110AB9281008F892B /* SPProcessListController.m */, - 17846BA2170C962E00414499 /* SPProcessListControllerDataSource.h */, - 17846BA3170C962E00414499 /* SPProcessListControllerDataSource.m */, ); name = "Process List"; sourceTree = "<group>"; }; - 1792C28910AE1C7200ABE758 /* Controller Categories */ = { - isa = PBXGroup; - children = ( - 17E090E611498FC9007FC1B4 /* SPPrintController.h */, - 17E090E711498FC9007FC1B4 /* SPPrintController.m */, - 1792C25F10AE1A2D00ABE758 /* SPConnectionDelegate.h */, - 1792C26010AE1A2D00ABE758 /* SPConnectionDelegate.m */, - ); - name = "Controller Categories"; - sourceTree = "<group>"; - }; 1798D17215BE9B8E008690E6 /* Protocols */ = { isa = PBXGroup; children = ( @@ -1876,8 +1712,6 @@ children = ( 58A8A78F11A036C000B95749 /* SPWindowController.h */, 58A8A79011A036C000B95749 /* SPWindowController.m */, - 17D3583D1533768E00A654D7 /* SPWindowControllerDelegate.h */, - 17D3583E1533768E00A654D7 /* SPWindowControllerDelegate.m */, ); name = Window; sourceTree = "<group>"; @@ -1898,10 +1732,6 @@ children = ( 17E641540EF01EF6001BC333 /* SPTableStructure.h */, 17E641550EF01EF6001BC333 /* SPTableStructure.m */, - 1748D50A15A4444F003562F2 /* SPTableStructureLoading.h */, - 1748D50B15A4444F003562F2 /* SPTableStructureLoading.m */, - 17D38EBA12771A1C00672B13 /* SPTableStructureDelegate.h */, - 17D38EBB12771A1C00672B13 /* SPTableStructureDelegate.m */, 17D38F6E1279E23A00672B13 /* SPTableFieldValidation.h */, 17D38F6F1279E23A00672B13 /* SPTableFieldValidation.m */, ); @@ -1911,16 +1741,8 @@ 17D38FC2127B0C9500672B13 /* Connection View */ = { isa = PBXGroup; children = ( - 1798F18D1550178E004B0AB8 /* SPConnectionHandler.h */, - 1798F18E1550178E004B0AB8 /* SPConnectionHandler.m */, 5822C9B31000DB2400DCC3D6 /* SPConnectionController.h */, 5822C9B41000DB2400DCC3D6 /* SPConnectionController.m */, - 17D3C6021289BF350047709F /* SPConnectionControllerDelegate.h */, - 17D3C6031289BF350047709F /* SPConnectionControllerDelegate.m */, - 1798F18B1550178E004B0AB8 /* SPConnectionControllerInitializer.h */, - 1798F18C1550178E004B0AB8 /* SPConnectionControllerInitializer.m */, - 1798F1891550178E004B0AB8 /* SPConnectionControllerDataSource.h */, - 1798F18A1550178E004B0AB8 /* SPConnectionControllerDataSource.m */, 17D38FC3127B0CFC00672B13 /* SPConnectionControllerDelegateProtocol.h */, 1798F1801550172A004B0AB8 /* Import & Export */, ); @@ -1935,8 +1757,6 @@ B57747D50F7A8978003B34F9 /* SPPreferenceController.h */, B57747D30F7A8974003B34F9 /* SPPreferenceController.m */, 17D390A8127B556F00672B13 /* SPPreferencePaneProtocol.h */, - 1785EA14127DAE3A00F468C8 /* SPPreferenceControllerDelegate.h */, - 1785EA15127DAE3A00F468C8 /* SPPreferenceControllerDelegate.m */, 17D390A7127B551400672B13 /* Panes */, ); name = Preferences; @@ -2096,10 +1916,6 @@ children = ( 17E6414A0EF01EF6001BC333 /* SPAppController.h */, 17E6414B0EF01EF6001BC333 /* SPAppController.m */, - 1798AB8F1267924D000D946A /* SPAppleScriptSupport.h */, - 1798AB901267924D000D946A /* SPAppleScriptSupport.m */, - 1748D58415A83E54003562F2 /* SPWindowManagement.h */, - 1748D58515A83E54003562F2 /* SPWindowManagement.m */, 17D3583C1533766800A654D7 /* Window */, 173567BA12AC1306000DCCEF /* Bundle Support */, 173E70A6107FF61D008733C9 /* Main View Controllers */, @@ -2351,27 +2167,13 @@ children = ( B5E92F1A0F75B2E800012500 /* SPExportController.h */, B5E92F1B0F75B2E800012500 /* SPExportController.m */, - 173C836D11AAD26E00B8B084 /* SPExportInitializer.h */, - 173C836E11AAD26E00B8B084 /* SPExportInitializer.m */, 173C836F11AAD26E00B8B084 /* SPExportUtilities.h */, 173C837011AAD26E00B8B084 /* SPExportUtilities.m */, - 17F90E491210B43A00274C98 /* SPExportFileUtilities.h */, - 17F90E4A1210B43A00274C98 /* SPExportFileUtilities.m */, - 17AF787911FC41C00073D043 /* SPExportFilenameUtilities.h */, - 17AF787A11FC41C00073D043 /* SPExportFilenameUtilities.m */, - 17BA2A3015275D8600389803 /* SPExportInterfaceController.h */, - 17BA2A3115275D8600389803 /* SPExportInterfaceController.m */, - 175EC63312733B36009A7C0F /* SPExportControllerDelegate.h */, - 175EC63412733B36009A7C0F /* SPExportControllerDelegate.m */, 582F022F1370B52600B30621 /* SPExportFileNameTokenObject.h */, 582F02301370B52600B30621 /* SPExportFileNameTokenObject.m */, 17F90E451210B41100274C98 /* Model */, 173C836C11AAD24300B8B084 /* Exporters */, 173C837C11AAD2C500B8B084 /* Delegate Protocols */, - 173C837D11AAD2D300B8B084 /* Delegate Categories */, - 50D3C3831A8177D900B5429C /* SPExportController+SharedPrivateAPI.h */, - 507FF2681BC8450100104523 /* SPExportSettingsPersistence.h */, - 507FF2691BC8450100104523 /* SPExportSettingsPersistence.m */, ); name = "Data Export"; sourceTree = "<group>"; @@ -3384,7 +3186,6 @@ BC29C37F10501EFD00DD6C6E /* SPQueryController.m in Sources */, 5822D3091061833C00CE2157 /* SPCSVParser.m in Sources */, BC675A141072039C00C5ACD4 /* SPContentFilterManager.m in Sources */, - 507FF26A1BC8450100104523 /* SPExportSettingsPersistence.m in Sources */, 17292443107AC41000B21980 /* SPXMLExporter.m in Sources */, 582A01E9107C0C170027D42B /* SPNotLoaded.m in Sources */, 173284EA1088FEDE0062E892 /* SPConstants.m in Sources */, @@ -3392,13 +3193,11 @@ 174CE14210AB9281008F892B /* SPProcessListController.m in Sources */, 1792C13710AD75C800ABE758 /* SPServerVariablesController.m in Sources */, 5089B0271BE714E300E226CD /* SPIdMenu.m in Sources */, - 1792C26110AE1A2D00ABE758 /* SPConnectionDelegate.m in Sources */, 17CC97F310B4ABE90034CD7A /* SPAboutController.m in Sources */, 5870868410FA3E9C00D58E1C /* SPDataStorage.m in Sources */, 584095191107CB6600260CFD /* SPAlertSheets.m in Sources */, 29FA88231114619E00D1AF3D /* SPTableTriggers.m in Sources */, BCE0025D11173D2A009DA533 /* SPFieldMapperController.m in Sources */, - 17E090E811498FC9007FC1B4 /* SPPrintController.m in Sources */, BC2777A011514B940034DF6A /* SPNavigatorController.m in Sources */, 589582151154F8F400EDCC28 /* SPMainThreadTrampoline.m in Sources */, BC4DF1981158FB280059FABD /* SPNavigatorOutlineView.m in Sources */, @@ -3408,22 +3207,13 @@ 11B55BFE1189E3B2009EF465 /* SPDatabaseAction.m in Sources */, 58A8A79111A036C000B95749 /* SPWindowController.m in Sources */, 5806B76411A991EC00813A88 /* SPDocumentController.m in Sources */, - 173C837111AAD26E00B8B084 /* SPExportInitializer.m in Sources */, 173C837211AAD26E00B8B084 /* SPExportUtilities.m in Sources */, 173C837911AAD2AE00B8B084 /* SPDotExporter.m in Sources */, 173C837A11AAD2AE00B8B084 /* SPHTMLExporter.m in Sources */, 173C837B11AAD2AE00B8B084 /* SPPDFExporter.m in Sources */, - 173C839011AAD32A00B8B084 /* SPCSVExporterDelegate.m in Sources */, 50805B0D1BF2A068005F7A99 /* SPPopUpButtonCell.m in Sources */, - 173C839111AAD32A00B8B084 /* SPDotExporterDelegate.m in Sources */, - 173C839211AAD32A00B8B084 /* SPHTMLExporterDelegate.m in Sources */, - 173C839311AAD32A00B8B084 /* SPPDFExporterDelegate.m in Sources */, - 173C839411AAD32A00B8B084 /* SPSQLExporterDelegate.m in Sources */, - 173C839511AAD32A00B8B084 /* SPXMLExporterDelegate.m in Sources */, 17A7773411C52D8E001E27B4 /* SPIndexesController.m in Sources */, - 17AF787B11FC41C00073D043 /* SPExportFilenameUtilities.m in Sources */, 17F90E481210B42700274C98 /* SPExportFile.m in Sources */, - 17F90E4B1210B43A00274C98 /* SPExportFileUtilities.m in Sources */, BC85F5D012193B7D00E255B5 /* SPColorAdditions.m in Sources */, BC878A71121A836F00AE5066 /* SPColorWellCell.m in Sources */, BC398A2D121D526200BE3EF4 /* SPCopyTable.m in Sources */, @@ -3431,14 +3221,10 @@ 17A20AC6124F9B110095CEFB /* SPServerSupport.m in Sources */, BC2898F3125F4488001B50E1 /* SPGeometryDataView.m in Sources */, 17148565125F5FF500321285 /* SPDatabaseCharacterSets.m in Sources */, - 1798AB911267924D000D946A /* SPAppleScriptSupport.m in Sources */, - 175EC63512733B36009A7C0F /* SPExportControllerDelegate.m in Sources */, - 17D38EBC12771A1C00672B13 /* SPTableStructureDelegate.m in Sources */, 17D38F701279E23A00672B13 /* SPTableFieldValidation.m in Sources */, 17D390C8127B65AF00672B13 /* SPGeneralPreferencePane.m in Sources */, 17D390CB127B6BF800672B13 /* SPPreferencesUpgrade.m in Sources */, 1785E9F7127D8C7500F468C8 /* SPPreferencePane.m in Sources */, - 1785EA16127DAE3A00F468C8 /* SPPreferenceControllerDelegate.m in Sources */, 1785EA23127DAF3300F468C8 /* SPTablesPreferencePane.m in Sources */, 1785EB60127DD5A800F468C8 /* SPNotificationsPreferencePane.m in Sources */, 1785EB63127DD5DE00F468C8 /* SPAutoUpdatePreferencePane.m in Sources */, @@ -3446,15 +3232,12 @@ 500DA4B71BEFF877000773FE /* SPComboBoxCell.m in Sources */, 1785EB6A127DD79300F468C8 /* SPEditorPreferencePane.m in Sources */, 17FDB04C1280778B00DBBBC2 /* SPFontPreviewTextField.m in Sources */, - 17D3DC201281816E002A163A /* SPDatabaseViewController.m in Sources */, 17D3C22212859E070047709F /* SPFavoriteNode.m in Sources */, 506CE9311A311C6C0039F736 /* SPTableContentFilterController.m in Sources */, - 17D3C6041289BF350047709F /* SPConnectionControllerDelegate.m in Sources */, 17D3C66E128AD4710047709F /* SPFavoritesController.m in Sources */, 17D3C671128AD8160047709F /* SPSingleton.m in Sources */, 17D3C6D3128B1C900047709F /* SPFavoritesOutlineView.m in Sources */, 50D3C3521A77135F00B5429C /* SPParserUtils.c in Sources */, - 171156551E8B0F96002E6363 /* SPTableRelationsDelegate.m in Sources */, BC68BFC7128D4EAE004907D9 /* SPBundleEditorController.m in Sources */, BC1944D01297291800A236CD /* SPBundleCommandTextView.m in Sources */, BC77C5E4129AA69E009AD832 /* SPBundleHTMLOutputController.m in Sources */, @@ -3462,41 +3245,24 @@ BC0ED3DA12A9196C00088461 /* SPChooseMenuItemDialog.m in Sources */, 583CA21512EC8B2200C9E763 /* SPWindow.m in Sources */, 582F02311370B52600B30621 /* SPExportFileNameTokenObject.m in Sources */, - 1713C740140D8AEF00CFD461 /* SPQueryDocumentsController.m in Sources */, 500DA4BC1BF0CD57000773FE /* SPScreenAdditions.m in Sources */, - 1713C75F140D8D5900CFD461 /* SPQueryConsoleDataSource.m in Sources */, - 17902612141025BB005F677F /* SPQueryControllerInitializer.m in Sources */, 584D878B15140FEB00F24774 /* SPObjectAdditions.m in Sources */, 584D87921514101E00F24774 /* SPDatabaseStructure.m in Sources */, 584D88A91515034200F24774 /* NSNotificationCenterThreadingAdditions.m in Sources */, 584D899D15162CBE00F24774 /* SPDataBase64EncodingAdditions.m in Sources */, - 17386E0B15192526002DC206 /* SPTableContentDataSource.m in Sources */, - 17386E0E1519257E002DC206 /* SPTableContentDelegate.m in Sources */, - 17381856151FB34E0078FFE2 /* SPUserManagerDelegate.m in Sources */, - 17BA2A3215275D8600389803 /* SPExportInterfaceController.m in Sources */, - 17D3583F1533768E00A654D7 /* SPWindowControllerDelegate.m in Sources */, 1798F1871550175B004B0AB8 /* SPFavoritesExporter.m in Sources */, 1798F1881550175B004B0AB8 /* SPFavoritesImporter.m in Sources */, - 1798F18F1550178E004B0AB8 /* SPConnectionControllerDataSource.m in Sources */, - 1798F1901550178E004B0AB8 /* SPConnectionControllerInitializer.m in Sources */, - 1798F1911550178E004B0AB8 /* SPConnectionHandler.m in Sources */, 1798F1951550181B004B0AB8 /* SPGroupNode.m in Sources */, 1798F19815501838004B0AB8 /* SPMutableArrayAdditions.m in Sources */, 1798F19B1550185B004B0AB8 /* SPTreeNode.m in Sources */, 1798F19E15501892004B0AB8 /* SPFlippedView.m in Sources */, 17D5B49E1553059F00EF3BB3 /* SPViewCopy.m in Sources */, 176E14D115570FE300FAF326 /* SPBundleCommandRunner.m in Sources */, - 1748D50C15A4444F003562F2 /* SPTableStructureLoading.m in Sources */, - 1748D58615A83E54003562F2 /* SPWindowManagement.m in Sources */, 58DF9F3315AB26C2003B4330 /* SPDateAdditions.m in Sources */, 58DF9F7315AB8509003B4330 /* SPSplitView.m in Sources */, 58DFC91615CB3501003B4330 /* BGHUDButtonCell.m in Sources */, - 171B374115DA654300EBC7AB /* SPTableContentFilter.m in Sources */, 5843E247162B555B00EAA6D1 /* SPThreadAdditions.m in Sources */, - 17005CB316D6CF0000AF81F4 /* SPTableTriggersDelegate.m in Sources */, - 58D29FE016E96306002EB401 /* SPUserManagerDataSource.m in Sources */, 58D2A6A716FBDEFF002EB401 /* SPComboPopupButton.m in Sources */, - 17846BA4170C962E00414499 /* SPProcessListControllerDataSource.m in Sources */, 501B1D181728A3DA0017C92E /* SPCharsetCollationHelper.m in Sources */, 50E217B318174246009D3580 /* SPColorSelectorView.m in Sources */, 50E217B618174280009D3580 /* SPFavoriteColorSupport.m in Sources */, |