diff options
author | stuconnolly <stuart02@gmail.com> | 2012-04-16 20:16:52 +0000 |
---|---|---|
committer | stuconnolly <stuart02@gmail.com> | 2012-04-16 20:16:52 +0000 |
commit | 4cad6f0e6e4fb497b480256c2abe3de34ebf225c (patch) | |
tree | b66d6a72a1537cf98624acf3c685f1a4d916fd86 /Source | |
parent | 0d3b69f964a8d9d93ca794d457b461463f1ec95d (diff) | |
download | sequelpro-4cad6f0e6e4fb497b480256c2abe3de34ebf225c.tar.gz sequelpro-4cad6f0e6e4fb497b480256c2abe3de34ebf225c.tar.bz2 sequelpro-4cad6f0e6e4fb497b480256c2abe3de34ebf225c.zip |
Bring outline view branch up to date with trunk.
Diffstat (limited to 'Source')
92 files changed, 3619 insertions, 2547 deletions
diff --git a/Source/DMLocalizedNibBundle.m b/Source/DMLocalizedNibBundle.m index b9cbec28..86d6e671 100644 --- a/Source/DMLocalizedNibBundle.m +++ b/Source/DMLocalizedNibBundle.m @@ -179,6 +179,8 @@ static NSMutableArray *deliciousBindingKeys = nil; } else if ([view isKindOfClass:[NSTableView class]]) { for (NSTableColumn *column in [(NSTableView*)view tableColumns]) { [self _localizeStringValueOfObject:[column headerCell] table:table]; + NSString *localizedHeaderTip = [self _localizedStringForString:[column headerToolTip] table:table]; + if (localizedHeaderTip) [column setHeaderToolTip:localizedHeaderTip]; } } else if ([view isKindOfClass:[NSTextField class]]) { diff --git a/Source/DeepMutableCopy.h b/Source/DeepMutableCopy.h deleted file mode 100644 index a1c92e21..00000000 --- a/Source/DeepMutableCopy.h +++ /dev/null @@ -1,10 +0,0 @@ -/* - * DeepMutableCopy.h - * - * Created by Matt Gemmell on 02/05/2008. - * Copyright 2008 Instinctive Code. All rights reserved. - * - */ - -#import "NSArray_DeepMutableCopy.h" -#import "NSDictionary_DeepMutableCopy.h" diff --git a/Source/MGTemplateEngine.m b/Source/MGTemplateEngine.m index 1cf483f8..b735e939 100644 --- a/Source/MGTemplateEngine.m +++ b/Source/MGTemplateEngine.m @@ -8,7 +8,8 @@ #import "MGTemplateEngine.h" #import "MGTemplateStandardMarkers.h" #import "MGTemplateStandardFilters.h" -#import "DeepMutableCopy.h" +#import "NSArray_DeepMutableCopy.h" +#import "NSDictionary_DeepMutableCopy.h" #define DEFAULT_MARKER_START @"{%" diff --git a/Source/SPAppController.h b/Source/SPAppController.h index 5d12be48..87ae8e9e 100644 --- a/Source/SPAppController.h +++ b/Source/SPAppController.h @@ -24,7 +24,9 @@ // More info at <http://code.google.com/p/sequel-pro/> #import <Cocoa/Cocoa.h> +#ifndef SP_REFACTOR #import <FeedbackReporter/FRFeedbackReporter.h> +#endif @class SPPreferenceController, SPAboutController, SPDatabaseDocument, SPBundleEditorController; @@ -115,5 +117,6 @@ - (NSDictionary*)shellEnvironmentForDocument:(NSString*)docUUID; - (void)addHTMLOutputController:(id)controller; +- (void)removeHTMLOutputController:(id)controller; @end diff --git a/Source/SPAppController.m b/Source/SPAppController.m index 7bdef1a2..da25b07f 100644 --- a/Source/SPAppController.m +++ b/Source/SPAppController.m @@ -1515,13 +1515,19 @@ YY_BUFFER_STATE yy_scan_string (const char *); [bundleHTMLOutputController addObject:controller]; } +- (void)removeHTMLOutputController:(id)controller +{ + [bundleHTMLOutputController removeObject:controller]; +} + - (IBAction)reloadBundles:(id)sender { - // Force releasing of any HTML output windows - for(id c in bundleHTMLOutputController) { - if(![[c window] isVisible]) { - [c release]; + // Force releasing of any hidden HTML output windows, which will automatically remove them from the array. + // Keep the visible windows. + for (id c in bundleHTMLOutputController) { + if (![[c window] isVisible]) { + [[c window] performClose:self]; } } @@ -1529,7 +1535,6 @@ YY_BUFFER_STATE yy_scan_string (const char *); [bundleItems removeAllObjects]; [bundleUsedScopes removeAllObjects]; - [bundleHTMLOutputController removeAllObjects]; [bundleCategories removeAllObjects]; [bundleTriggers removeAllObjects]; [bundleKeyEquivalents removeAllObjects]; diff --git a/Source/SPBundleHTMLOutputController.m b/Source/SPBundleHTMLOutputController.m index 9243741c..cff150c0 100644 --- a/Source/SPBundleHTMLOutputController.m +++ b/Source/SPBundleHTMLOutputController.m @@ -59,9 +59,6 @@ { if ((self = [super initWithWindowNibName:@"BundleHTMLOutput"])) { - - [[self window] setReleasedWhenClosed:YES]; - [webView setContinuousSpellCheckingEnabled:NO]; [webView setGroupName:@"SequelProBundleHTMLOutput"]; [webView setDrawsBackground:YES]; @@ -286,6 +283,7 @@ [self setInitHTMLSourceString:@""]; windowUUID = @""; docUUID = @""; + [[NSApp delegate] removeHTMLOutputController:self]; [self release]; } diff --git a/Source/SPCSVExporter.m b/Source/SPCSVExporter.m index a552db78..7850e7ec 100644 --- a/Source/SPCSVExporter.m +++ b/Source/SPCSVExporter.m @@ -28,7 +28,7 @@ #import "SPTableData.h" #import "SPExportUtilities.h" #import "SPExportFile.h" -#import "SPMySQL.h" +#import <SPMySQL/SPMySQL.h> @implementation SPCSVExporter diff --git a/Source/SPCSVParser.m b/Source/SPCSVParser.m index 431c89a8..be71b13e 100644 --- a/Source/SPCSVParser.m +++ b/Source/SPCSVParser.m @@ -281,7 +281,7 @@ // Update the total parsed length (differs from parserPosition following trims) totalLengthParsed += parserPosition - startingParserPosition; - // Skip empty rows + // Skip empty rows. Note the NSNull pointer comparison; as [NSNull null] is a singleton this works correctly. if ([csvRowArray count] == 0 || ([csvRowArray count] == 1 && ([csvRowArray objectAtIndex:0] == [NSNull null] diff --git a/Source/SPConnectionController.h b/Source/SPConnectionController.h index dcf1c6d7..d6599b55 100644 --- a/Source/SPConnectionController.h +++ b/Source/SPConnectionController.h @@ -24,6 +24,7 @@ // More info at <http://code.google.com/p/sequel-pro/> #import "SPConnectionControllerDelegateProtocol.h" +#import <SPMySQL/SPMySQLConnectionDelegate.h> #ifndef SP_REFACTOR /* headers */ #endif @@ -51,7 +52,7 @@ #endif -@interface SPConnectionController : NSViewController +@interface SPConnectionController : NSViewController <SPMySQLConnectionDelegate> { id <SPConnectionControllerDelegateProtocol, NSObject> delegate; diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index 75752c1d..1a7668fd 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -40,7 +40,8 @@ #import "SPTreeNode.h" #import "SPFavoritesExporter.h" #import "SPFavoritesImporter.h" -#import "SPMySQL.h" + +#import <SPMySQL/SPMySQL.h> // Constants static NSString *SPRemoveNode = @"RemoveNode"; @@ -1440,6 +1441,7 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, if (connectionSSHKeychainItemName) [connectionSSHKeychainItemName release]; if (connectionSSHKeychainItemAccount) [connectionSSHKeychainItemAccount release]; if (currentFavorite) [currentFavorite release], currentFavorite = nil; + if (favoritesRoot) [favoritesRoot release], favoritesRoot = nil; [super dealloc]; } diff --git a/Source/SPConnectionDelegate.h b/Source/SPConnectionDelegate.h index 04bee877..923f5a97 100644 --- a/Source/SPConnectionDelegate.h +++ b/Source/SPConnectionDelegate.h @@ -25,7 +25,7 @@ #import "SPDatabaseDocument.h" -#import "SPMySQLConnectionDelegate.h" +#import <SPMySQL/SPMySQLConnectionDelegate.h> @interface SPDatabaseDocument (SPConnectionDelegate) <SPMySQLConnectionDelegate> diff --git a/Source/SPConnectionDelegate.m b/Source/SPConnectionDelegate.m index 9480a99e..74f1d407 100644 --- a/Source/SPConnectionDelegate.m +++ b/Source/SPConnectionDelegate.m @@ -28,7 +28,7 @@ #import "SPQueryController.h" #import "SPKeychain.h" #import "SPAlertSheets.h" -#import "SPMySQLConstants.h" +#import <SPMySQL/SPMySQLConstants.h> @implementation SPDatabaseDocument (SPConnectionDelegate) @@ -40,6 +40,7 @@ */ - (void)willQueryString:(NSString *)query connection:(id)connection { +#ifndef SP_REFACTOR if ([prefs boolForKey:SPConsoleEnableLogging]) { if ((_queryMode == SPInterfaceQueryMode && [prefs boolForKey:SPConsoleEnableInterfaceLogging]) || (_queryMode == SPCustomQueryQueryMode && [prefs boolForKey:SPConsoleEnableCustomQueryLogging]) @@ -48,6 +49,7 @@ [[SPQueryController sharedQueryController] showMessageInConsole:query connection:[self name]]; } } +#endif } /** @@ -55,9 +57,11 @@ */ - (void)queryGaveError:(NSString *)error connection:(id)connection { +#ifndef SP_REFACTOR if ([prefs boolForKey:SPConsoleEnableLogging] && [prefs boolForKey:SPConsoleEnableErrorLogging]) { [[SPQueryController sharedQueryController] showErrorInConsole:error connection:[self name]]; } +#endif } /** @@ -127,8 +131,10 @@ // Ensure the window isn't miniaturized if ([[self parentWindow] isMiniaturized]) [[self parentWindow] deminiaturize:self]; +#ifndef SP_REFACTOR // 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]; @@ -169,6 +175,7 @@ */ - (void) closeAndDisconnect { +#ifndef SP_REFACTOR NSWindow *theParentWindow = [self parentWindow]; _isConnected = NO; if ([[[self parentTabViewItem] tabView] numberOfTabViewItems] == 1) { @@ -180,6 +187,7 @@ [theParentWindow performSelector:@selector(makeKeyAndOrderFront:) withObject:nil afterDelay:0.6]; } [self parentTabDidClose]; +#endif } @end diff --git a/Source/SPConnectionHandler.m b/Source/SPConnectionHandler.m index 8c9f911c..bd03a8e0 100644 --- a/Source/SPConnectionHandler.m +++ b/Source/SPConnectionHandler.m @@ -29,7 +29,8 @@ #import "SPSSHTunnel.h" #import "SPKeychain.h" #import "RegexKitLite.h" -#import "SPMySQL.h" + +#import <SPMySQL/SPMySQL.h> static NSString *SPLocalhostAddress = @"127.0.0.1"; diff --git a/Source/SPConstants.h b/Source/SPConstants.h index 01750e95..c2775716 100644 --- a/Source/SPConstants.h +++ b/Source/SPConstants.h @@ -396,6 +396,8 @@ extern NSString *SPLastImportIntoNewTableType; extern NSString *SPGlobalValueHistory; extern NSString *SPBundleDeletedDefaultBundlesKey; extern NSString *SPHiddenKeyFileVisibilityKey; +extern NSString *SPSelectionDetailTypeIndexed; +extern NSString *SPSelectionDetailTypePrimaryKeyed; // URLs extern NSString *SPDonationsURL; diff --git a/Source/SPConstants.m b/Source/SPConstants.m index 01adcab2..4f41531c 100644 --- a/Source/SPConstants.m +++ b/Source/SPConstants.m @@ -201,6 +201,8 @@ NSString *SPLastImportIntoNewTableType = @"LastImportIntoNewTableType" NSString *SPGlobalValueHistory = @"GlobalValueHistory"; NSString *SPBundleDeletedDefaultBundlesKey = @"deletedDefaultBundles"; NSString *SPHiddenKeyFileVisibilityKey = @"KeySelectionHiddenFilesVisibility"; +NSString *SPSelectionDetailTypeIndexed = @"SelectionDetailTypeNSIndexSet"; +NSString *SPSelectionDetailTypePrimaryKeyed = @"SelectionDetailTypePrimaryKeyedDetails"; // URLs NSString *SPDonationsURL = @"http://www.sequelpro.com/donate/"; diff --git a/Source/SPContentFilterManager.m b/Source/SPContentFilterManager.m index 7c6213b2..e5eec285 100644 --- a/Source/SPContentFilterManager.m +++ b/Source/SPContentFilterManager.m @@ -307,6 +307,7 @@ */ - (IBAction)exportContentFilter:(id)sender { +#ifndef SP_REFACTOR NSSavePanel *panel = [NSSavePanel savePanel]; [panel setAllowedFileTypes:[NSArray arrayWithObject:SPFileExtensionDefault]]; @@ -317,6 +318,7 @@ [panel setCanCreateDirectories:YES]; [panel beginSheetForDirectory:nil file:nil modalForWindow:[self window] modalDelegate:self didEndSelector:@selector(savePanelDidEnd:returnCode:contextInfo:) contextInfo:@"exportFilter"]; +#endif } /** @@ -324,6 +326,7 @@ */ - (IBAction)importContentFilterByAdding:(id)sender { +#ifndef SP_REFACTOR NSOpenPanel *panel = [NSOpenPanel openPanel]; [panel setCanSelectHiddenExtension:YES]; [panel setDelegate:self]; @@ -338,6 +341,7 @@ modalDelegate:self didEndSelector:@selector(importPanelDidEnd:returnCode:contextInfo:) contextInfo:NULL]; +#endif } /** @@ -828,6 +832,7 @@ */ - (void)importPanelDidEnd:(NSOpenPanel *)panel returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo { +#ifndef SP_REFACTOR if (returnCode == NSOKButton) { NSString *filename = [[panel filenames] objectAtIndex:0]; @@ -885,6 +890,7 @@ } } } +#endif } @@ -894,6 +900,7 @@ - (void)savePanelDidEnd:(NSSavePanel *)panel returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo { +#ifndef SP_REFACTOR if([contextInfo isEqualToString:@"exportFilter"]) { if (returnCode == NSOKButton) { @@ -941,6 +948,7 @@ } } +#endif } @end diff --git a/Source/SPCopyTable.m b/Source/SPCopyTable.m index 3cdf4213..4460630c 100644 --- a/Source/SPCopyTable.m +++ b/Source/SPCopyTable.m @@ -31,12 +31,16 @@ #import "SPTextAndLinkCell.h" #import "SPTooltip.h" #import "SPAlertSheets.h" +#ifndef SP_REFACTOR /* headers */ #import "SPBundleHTMLOutputController.h" +#endif #import "SPGeometryDataView.h" +#ifndef SP_REFACTOR /* headers */ #import "SPBundleEditorController.h" #import "SPAppController.h" +#endif #import "SPTablesList.h" -#import "SPMySQL.h" +#import <SPMySQL/SPMySQL.h> NSInteger MENU_EDIT_COPY = 2001; NSInteger MENU_EDIT_COPY_WITH_COLUMN = 2002; @@ -121,6 +125,15 @@ NSInteger kBlobAsImageFile = 4; #endif } +#ifdef SP_REFACTOR + +- (void)delete:(id)sender +{ + [tableInstance removeRow:self]; +} + +#endif + /** * Get selected rows a string of newline separated lines of tab separated fields * the value in each field is from the objects description method @@ -425,7 +438,6 @@ NSInteger kBlobAsImageFile = 4; NSUInteger rowCounter = 0; NSUInteger penultimateRowIndex = [selectedRows count]; NSUInteger c; - NSUInteger valueLength = 0; NSMutableString *result = [NSMutableString stringWithCapacity:2000]; @@ -472,7 +484,10 @@ NSInteger kBlobAsImageFile = 4; [value appendString:@"\t("]; cellData = nil; rowCounter++; - for ( c = 0; c < numColumns; c++ ) + + NSMutableArray *rowValues = [[NSMutableArray alloc] initWithCapacity:numColumns]; + + for (c = 0; c < numColumns; c++) { cellData = SPDataStorageObjectAtRowAndColumn(tableStorage, rowIndex, columnMappings[c]); @@ -498,7 +513,7 @@ NSInteger kBlobAsImageFile = 4; // Check for NULL value if ([cellData isNSNull]) { - [value appendString:@"NULL, "]; + [rowValues addObject:@"NULL"]; continue; } else if (cellData) { @@ -508,28 +523,29 @@ NSInteger kBlobAsImageFile = 4; // Convert numeric types to unquoted strings case 0: - [value appendFormat:@"%@, ", [cellData description]]; + [rowValues addObject:[cellData description]]; break; // Quote string, text and blob types appropriately case 1: case 2: if ([cellData isKindOfClass:nsDataClass]) { - [value appendString:[mySQLConnection escapeAndQuoteData:cellData]]; + [rowValues addObject:[mySQLConnection escapeAndQuoteData:cellData]]; } else { - [value appendString:[mySQLConnection escapeAndQuoteString:[cellData description]]]; + [rowValues addObject:[mySQLConnection escapeAndQuoteString:[cellData description]]]; } break; // GEOMETRY case 3: - [value appendString:[mySQLConnection escapeAndQuoteData:[cellData data]]]; + [rowValues addObject:[mySQLConnection escapeAndQuoteData:[cellData data]]]; break; // Unhandled cases - abort default: NSBeep(); free(columnMappings); free(columnTypes); + [rowValues release]; return nil; } @@ -538,27 +554,27 @@ NSInteger kBlobAsImageFile = 4; NSBeep(); free(columnMappings); free(columnTypes); + + [rowValues release]; + return nil; } } - // Remove the trailing ', ' from the query - if ( [value length] > 2 ) - [value deleteCharactersInRange:NSMakeRange([value length]-2, 2)]; - - valueLength += [value length]; + // Add to the string in comma-separated form, and increment the string length + [value appendString:[rowValues componentsJoinedByString:@", "]]; + [rowValues release]; // Close this VALUES group and set up the next one if appropriate if ( rowCounter != penultimateRowIndex ) { // Add a new INSERT starter command every ~250k of data. - if ( valueLength > 250000 ) { + if ([value length] > 250000) { [result appendFormat:@"%@);\n\nINSERT INTO %@ (%@)\nVALUES\n", value, [(selectedTable == nil) ? @"<table>" : selectedTable backtickQuotedString], [tbHeader componentsJoinedAndBacktickQuoted]]; [value setString:@""]; - valueLength = 0; } else { [value appendString:@"),\n"]; } @@ -989,6 +1005,16 @@ NSInteger kBlobAsImageFile = 4; return (columnDefinitions != nil && [self numberOfSelectedRows] > 0); } #endif +#ifdef SP_REFACTOR + if ( [anItem action] == @selector(selectAll:) ) + return YES; + + if ( [anItem action] == @selector(delete:) ) + { + if ( [self numberOfSelectedRows] > 0 ) + return YES; + } +#endif return NO; } @@ -1170,7 +1196,18 @@ NSInteger kBlobAsImageFile = 4; { // Retrieve the column definition +#if SP_REFACTOR + NSDictionary *columnDefinition; + + if ( [[self delegate] isKindOfClass:[SPTableContent class]] ) + columnDefinition = [[(SPTableContent*)[self delegate] dataColumnDefinitions] objectAtIndex:colIndex]; + + else if ( [[self delegate] isKindOfClass:[SPCustomQuery class]] ) + columnDefinition = [[(SPCustomQuery*)[self delegate] dataColumnDefinitions] objectAtIndex:colIndex]; +#else NSDictionary *columnDefinition = [[[self delegate] dataColumnDefinitions] objectAtIndex:colIndex]; +#endif + NSString *columnType = [columnDefinition objectForKey:@"typegrouping"]; // Return YES if the multiple line editing button is enabled - triggers sheet editing on all cells. diff --git a/Source/SPCustomQuery.h b/Source/SPCustomQuery.h index 1e330682..943c307d 100644 --- a/Source/SPCustomQuery.h +++ b/Source/SPCustomQuery.h @@ -237,7 +237,7 @@ // Accessors - (NSArray *)currentResult; -- (NSArray *)currentDataResultWithNULLs:(BOOL)includeNULLs; +- (NSArray *)currentDataResultWithNULLs:(BOOL)includeNULLs truncateDataFields:(BOOL)truncate; - (void)processResultIntoDataStorage:(SPMySQLFastStreamingResult *)theResult; // Retrieving and setting table state diff --git a/Source/SPCustomQuery.m b/Source/SPCustomQuery.m index f69db3b7..f7f31088 100644 --- a/Source/SPCustomQuery.m +++ b/Source/SPCustomQuery.m @@ -25,10 +25,10 @@ #import "SPCustomQuery.h" #import "SPSQLParser.h" -#import "SPMySQL.h" #ifndef SP_REFACTOR /* headers */ #import "SPGrowlController.h" #endif +#import <SPMySQL/SPMySQL.h> #import "SPDataCellFormatter.h" #import "SPDatabaseDocument.h" #import "SPTablesList.h" @@ -44,8 +44,10 @@ #import "SPAlertSheets.h" #import "SPCopyTable.h" #import "SPGeometryDataView.h" +#ifndef SP_REFACTOR /* headers */ #import "SPAppController.h" #import "SPBundleHTMLOutputController.h" +#endif #include <pthread.h> #ifndef SP_REFACTOR /* headers */ @@ -55,7 +57,7 @@ @interface SPCustomQuery (PrivateAPI) - (id)_resultDataItemAtRow:(NSInteger)row columnIndex:(NSUInteger)column; -- (id)_convertResultDataValueToDisplayableRepresentation:(id)value whilePreservingNULLs:(BOOL)preserveNULLs; +- (id)_convertResultDataValueToDisplayableRepresentation:(id)value whilePreservingNULLs:(BOOL)preserveNULLs truncateDataFields:(BOOL)truncate; @end @@ -793,6 +795,7 @@ // if(!queriesSeparatedByDelimiter) // TODO: How to combine queries delimited by DELIMITER? usedQuery = [[NSString stringWithString:[tempQueries componentsJoinedByString:@";\n"]] retain]; + if (lastExecutedQuery) [lastExecutedQuery release]; lastExecutedQuery = [[tempQueries lastObject] retain]; // Perform empty query if no query is given @@ -858,7 +861,9 @@ ]; } } +#ifndef SP_REFACTOR [[affectedRowsText onMainThread] setStringValue:statusString]; +#endif // Restore automatic query retries [mySQLConnection setRetryQueriesOnConnectionFailure:YES]; @@ -1289,9 +1294,11 @@ // If errors occur, display them if ( [mySQLConnection lastQueryWasCancelled] || ([errorsString length] && !queryIsTableSorter)) { +#ifndef SP_REFACTOR // set the error text [errorText setString:[errorsString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]]; [[errorTextScrollView verticalScroller] setFloatValue:1.0f]; +#endif // try to select the line x of the first error if error message with ID 1064 contains "at line x" // by capturing the last number of the error string @@ -1365,6 +1372,9 @@ [queryInfoButton setState:NSOffState]; [self toggleQueryInfoPaneCollapse:queryInfoButton]; } +#else + if ( [errorsString length] > 0 ) + NSRunAlertPanel(LOCAL(@"Query Error"), @"%@", LOCAL(@"OK"), nil, nil, errorsString); #endif } @@ -1452,7 +1462,7 @@ */ - (NSArray *)currentResult { - return [self currentDataResultWithNULLs:NO]; + return [self currentDataResultWithNULLs:NO truncateDataFields:YES]; } /** @@ -1461,8 +1471,9 @@ * * @param includeNULLs Indicates whether to include NULLs as a native type * or use the user's NULL string representation preference. + * @param truncate Indicates whether to truncate data fields for display purposes. */ -- (NSArray *)currentDataResultWithNULLs:(BOOL)includeNULLs +- (NSArray *)currentDataResultWithNULLs:(BOOL)includeNULLs truncateDataFields:(BOOL)truncate { NSInteger i; id tableColumn; @@ -1485,7 +1496,7 @@ while ( (tableColumn = [enumerator nextObject]) ) { id value = [self _resultDataItemAtRow:i columnIndex:[[tableColumn identifier] integerValue]]; - [tempRow addObject:[self _convertResultDataValueToDisplayableRepresentation:value whilePreservingNULLs:YES]]; + [tempRow addObject:[self _convertResultDataValueToDisplayableRepresentation:value whilePreservingNULLs:includeNULLs truncateDataFields:truncate]]; } [currentResult addObject:[NSArray arrayWithArray:tempRow]]; } @@ -1520,11 +1531,11 @@ [autouppercaseKeywordsMenuItem setState:(YES?NSOnState:NSOffState)]; #endif +#ifndef SP_REFACTOR if ( [[SPQueryController sharedQueryController] historyForFileURL:[tableDocumentInstance fileURL]] ) [self performSelectorOnMainThread:@selector(historyItemsHaveBeenUpdated:) withObject:self waitUntilDone:YES]; // Populate query favorites -#ifndef SP_REFACTOR [self queryFavoritesHaveBeenUpdated:nil]; #endif @@ -2044,7 +2055,7 @@ { if (aTableView == customQueryView) { - return [self _convertResultDataValueToDisplayableRepresentation:[self _resultDataItemAtRow:rowIndex columnIndex:[[tableColumn identifier] integerValue]] whilePreservingNULLs:NO]; + return [self _convertResultDataValueToDisplayableRepresentation:[self _resultDataItemAtRow:rowIndex columnIndex:[[tableColumn identifier] integerValue]] whilePreservingNULLs:NO truncateDataFields:YES]; } return @""; @@ -2514,15 +2525,15 @@ // Retrieve the original index of the column from the identifier NSInteger columnIndex = [[[[aNotification userInfo] objectForKey:@"NSTableColumn"] identifier] integerValue]; NSDictionary *columnDefinition = NSArrayObjectAtIndex(cqColumnDefinition, columnIndex); + NSString *table = [columnDefinition objectForKey:@"org_table"]; + NSString *col = [columnDefinition objectForKey:@"org_name"]; // Don't save if the column doesn't map to an underlying SQL field - if (![columnDefinition objectForKey:@"org_name"] || ![(NSString *)[columnDefinition objectForKey:@"org_name"] length]) + if (!table || ![table length] || !col || ![col length]) return; NSMutableDictionary *tableColumnWidths; NSString *host_db = [NSString stringWithFormat:@"%@@%@", [columnDefinition objectForKey:@"db"], [tableDocumentInstance host]]; - NSString *table = [columnDefinition objectForKey:@"org_table"]; - NSString *col = [columnDefinition objectForKey:@"org_name"]; // Retrieve or instantiate the tableColumnWidths object #ifndef SP_REFACTOR @@ -3689,6 +3700,7 @@ if ((self = [super init])) { usedQuery = [[NSString stringWithString:@""] retain]; + lastExecutedQuery = nil; fieldIDQueryString = nil; sortField = nil; isDesc = NO; @@ -3988,13 +4000,14 @@ * @param value The value to convert * @param preserveNULLs Whether or not NULLs should be preserved or converted to the * user's NULL placeholder preference. + * @param truncate Whether or not data fields should be truncates for display purposes. * * @return The converted value */ -- (id)_convertResultDataValueToDisplayableRepresentation:(id)value whilePreservingNULLs:(BOOL)preserveNULLs +- (id)_convertResultDataValueToDisplayableRepresentation:(id)value whilePreservingNULLs:(BOOL)preserveNULLs truncateDataFields:(BOOL)truncate { if ([value isKindOfClass:[NSData class]]) { - value = [value shortStringRepresentationUsingEncoding:[mySQLConnection stringEncoding]]; + value = truncate ? [value shortStringRepresentationUsingEncoding:[mySQLConnection stringEncoding]] : [value stringRepresentationUsingEncoding:[mySQLConnection stringEncoding]]; } if ([value isNSNull] && !preserveNULLs) { @@ -4023,6 +4036,7 @@ [self clearQueryLoadTimer]; [usedQuery release]; + [lastExecutedQuery release]; [resultData release]; [favoritesManager release]; diff --git a/Source/SPDataAdditions.h b/Source/SPDataAdditions.h index 956e5b64..5ebbc356 100644 --- a/Source/SPDataAdditions.h +++ b/Source/SPDataAdditions.h @@ -24,11 +24,13 @@ @interface NSData (SPDataAdditions) -- (NSString *)dataToFormattedHexString; -- (NSString *)shortStringRepresentationUsingEncoding:(NSStringEncoding)encoding; - (NSData *)dataEncryptedWithPassword:(NSString *)password; - (NSData *)dataDecryptedWithPassword:(NSString *)password; - (NSData *)compress; - (NSData *)decompress; +- (NSString *)dataToFormattedHexString; +- (NSString *)stringRepresentationUsingEncoding:(NSStringEncoding)encoding; +- (NSString *)shortStringRepresentationUsingEncoding:(NSStringEncoding)encoding; + @end diff --git a/Source/SPDataAdditions.m b/Source/SPDataAdditions.m index 0329395c..b1aad389 100644 --- a/Source/SPDataAdditions.m +++ b/Source/SPDataAdditions.m @@ -76,7 +76,6 @@ - (NSData *)dataDecryptedWithPassword:(NSString *)password { - // Create the key from the password hash unsigned char passwordDigest[20]; SHA1((const unsigned char *)[password UTF8String], strlen([password UTF8String]), passwordDigest); @@ -190,10 +189,10 @@ return [NSData dataWithData: zipData]; } -- (NSString *)dataToFormattedHexString -/* - returns the hex representation of the given data +/** + * Returns the hex representation of the given data. */ +- (NSString *)dataToFormattedHexString { NSUInteger i, j; NSUInteger totalLength = [self length]; @@ -258,26 +257,33 @@ return retVal; } +/** + * Converts data instances to their string representation. + */ +- (NSString *)stringRepresentationUsingEncoding:(NSStringEncoding)encoding +{ + NSString *string = [[[NSString alloc] initWithData:self encoding:encoding] autorelease]; + + return !string ? [[[NSString alloc] initWithData:self encoding:NSASCIIStringEncoding] autorelease] : string; +} + /* * Convert data objects to their string representation (max 255 chars) * in the current encoding, falling back to ascii. (Mainly used for displaying * large blob data in a tableView) */ -- (NSString *) shortStringRepresentationUsingEncoding:(NSStringEncoding)encoding +- (NSString *)shortStringRepresentationUsingEncoding:(NSStringEncoding)encoding { - NSString *tmp = [[[NSString alloc] initWithData:self encoding:encoding] autorelease]; - - if (tmp == nil) - tmp = [[[NSString alloc] initWithData:self encoding:NSASCIIStringEncoding] autorelease]; - if (tmp == nil) - return @"- cannot be displayed -"; - else { - if([tmp length] > 255) - return [tmp substringToIndex:255]; - else - return tmp; + NSString *string = [self stringRepresentationUsingEncoding:encoding]; + + if (!string) { + string = @"-- cannot display --"; + } + else if ([string length] > 255) { + string = [string substringToIndex:255]; } - return @"- cannot be displayed -"; + + return string; } @end diff --git a/Source/SPDataImport.m b/Source/SPDataImport.m index 97c6d0f6..6d8f701d 100644 --- a/Source/SPDataImport.m +++ b/Source/SPDataImport.m @@ -39,8 +39,8 @@ #import "SPFieldMapperController.h" #import "SPFileHandle.h" #import "SPEncodingPopupAccessory.h" -#import "SPMySQL.h" +#import <SPMySQL/SPMySQL.h> #import <UniversalDetector/UniversalDetector.h> #define SP_FILE_READ_ERROR_STRING NSLocalizedString(@"File read error", @"File read error title (Import Dialog)") @@ -1394,7 +1394,7 @@ NSInteger colIndex = [[globalVar substringWithRange:[globalVar rangeOfRegex:re capture:1L]] integerValue]; if (colIndex > 0 && colIndex <= (NSInteger)[csvRowArray count]) { id colStr = NSArrayObjectAtIndex(csvRowArray, colIndex-1); - if(colStr == [NSNull null]) + if([colStr isNSNull]) [globalVar replaceCharactersInRange:aRange withString:@"NULL"]; else if([colStr isSPNotLoaded]) [globalVar replaceCharactersInRange:aRange withString:@""]; @@ -1414,7 +1414,7 @@ if ([cellData isSPNotLoaded]) cellData = NSArrayObjectAtIndex(fieldMappingTableDefaultValues, i); - if (cellData == [NSNull null]) { + if ([cellData isNSNull]) { [setString appendString:@"NULL"]; } else { [setString appendString:[mySQLConnection escapeAndQuoteString:cellData]]; @@ -1446,7 +1446,7 @@ NSInteger colIndex = [[globalVar substringWithRange:[globalVar rangeOfRegex:re capture:1L]] integerValue]; if(colIndex > 0 && colIndex <= (NSInteger)[csvRowArray count]) { id colStr = NSArrayObjectAtIndex(csvRowArray, colIndex-1); - if(colStr == [NSNull null]) + if([colStr isNSNull]) [globalVar replaceCharactersInRange:aRange withString:@"NULL"]; else if([colStr isSPNotLoaded]) [globalVar replaceCharactersInRange:aRange withString:@""]; @@ -1466,7 +1466,7 @@ if ([cellData isSPNotLoaded]) cellData = NSArrayObjectAtIndex(fieldMappingTableDefaultValues, i); - if (cellData == [NSNull null]) { + if ([cellData isNSNull]) { [whereString appendString:@" IS NULL"]; } else { [whereString appendString:@"="]; @@ -1521,7 +1521,7 @@ NSInteger colIndex = [[globalVar substringWithRange:[globalVar rangeOfRegex:re capture:1L]] integerValue]; if(colIndex > 0 && colIndex <= (NSInteger)[csvRowArray count]) { id colStr = NSArrayObjectAtIndex(csvRowArray, colIndex-1); - if(colStr == [NSNull null]) + if([colStr isNSNull]) [globalVar replaceCharactersInRange:aRange withString:@"NULL"]; else if([colStr isSPNotLoaded]) [globalVar replaceCharactersInRange:aRange withString:@""]; @@ -1542,7 +1542,7 @@ cellData = NSArrayObjectAtIndex(fieldMappingTableDefaultValues, i); // Insert a NULL if the cell is an NSNull, or is a nullable numeric field and empty - if (cellData == [NSNull null] || ([nullableNumericFieldsMapIndex containsIndex:i] && [[cellData description] isEqualToString:@""])) { + if ([cellData isNSNull] || ([nullableNumericFieldsMapIndex containsIndex:i] && [[cellData description] isEqualToString:@""])) { [valueString appendString:@"NULL"]; } else { diff --git a/Source/SPDatabaseCopy.m b/Source/SPDatabaseCopy.m index 2c9e11ad..4dcb4966 100644 --- a/Source/SPDatabaseCopy.m +++ b/Source/SPDatabaseCopy.m @@ -25,7 +25,7 @@ #import "SPDBActionCommons.h" #import "SPDatabaseCopy.h" #import "SPTableCopy.h" -#import "SPMySQL.h" +#import <SPMySQL/SPMySQL.h> @implementation SPDatabaseCopy diff --git a/Source/SPDatabaseData.m b/Source/SPDatabaseData.m index 2317902e..cc782b5e 100644 --- a/Source/SPDatabaseData.m +++ b/Source/SPDatabaseData.m @@ -26,13 +26,13 @@ #import "SPDatabaseData.h" #import "SPServerSupport.h" #import "SPDatabaseCharacterSets.h" -#import "SPMySQL.h" +#import <SPMySQL/SPMySQL.h> @interface SPDatabaseData (PrivateAPI) - (NSArray *)_getDatabaseDataForQuery:(NSString *)query; - NSInteger _sortMySQL4CharsetEntry(NSDictionary *itemOne, NSDictionary *itemTwo, void *context); +NSInteger _sortStorageEngineEntry(NSDictionary *itemOne, NSDictionary *itemTwo, void *context); @end @@ -228,7 +228,7 @@ NSInteger _sortMySQL4CharsetEntry(NSDictionary *itemOne, NSDictionary *itemTwo, } } - return storageEngines; + return [storageEngines sortedArrayUsingFunction:_sortStorageEngineEntry context:nil]; } /** @@ -327,4 +327,12 @@ NSInteger _sortMySQL4CharsetEntry(NSDictionary *itemOne, NSDictionary *itemTwo, return [[itemOne objectForKey:@"Charset"] compare:[itemTwo objectForKey:@"Charset"]]; } +/** + * Sorts a storage engine array by the Engine key. + */ +NSInteger _sortStorageEngineEntry(NSDictionary *itemOne, NSDictionary *itemTwo, void *context) +{ + return [[itemOne objectForKey:@"Engine"] compare:[itemTwo objectForKey:@"Engine"]]; +} + @end diff --git a/Source/SPDatabaseDocument.h b/Source/SPDatabaseDocument.h index 1c6fe296..b1d19600 100644 --- a/Source/SPDatabaseDocument.h +++ b/Source/SPDatabaseDocument.h @@ -247,6 +247,7 @@ SPDatabaseData, SPTablesList, SPTableStructure, SPTableContent, SPTableData, SPS @property (assign) id databaseNameField; @property (assign) id databaseEncodingButton; @property (assign) id addDatabaseButton; +@property (assign) id chooseDatabaseButton; @property (assign) id databaseRenameNameField; @property (assign) id renameDatabaseButton; @@ -293,8 +294,8 @@ SPDatabaseData, SPTablesList, SPTableStructure, SPTableContent, SPTableData, SPS - (void)selectDatabase:(NSString *)aDatabase item:(NSString *)anItem; - (IBAction)addDatabase:(id)sender; - (IBAction)removeDatabase:(id)sender; -#ifndef SP_REFACTOR /* method decls */ - (IBAction)refreshTables:(id)sender; +#ifndef SP_REFACTOR /* method decls */ - (IBAction)copyDatabase:(id)sender; #endif - (IBAction)renameDatabase:(id)sender; @@ -349,11 +350,10 @@ SPDatabaseData, SPTablesList, SPTableStructure, SPTableContent, SPTableData, SPS - (IBAction)focusOnTableContentFilter:(id)sender; - (IBAction)focusOnTableListFilter:(id)sender; - (IBAction)export:(id)sender; - - (IBAction)exportSelectedTablesAs:(id)sender; // Other methods -- (void) setQueryMode:(NSInteger)theQueryMode; +- (void)setQueryMode:(NSInteger)theQueryMode; - (IBAction)closeSheet:(id)sender; - (IBAction)closePanelSheet:(id)sender; - (void)doPerformQueryService:(NSString *)query; @@ -375,7 +375,6 @@ SPDatabaseData, SPTablesList, SPTableStructure, SPTableContent, SPTableData, SPS - (void)setIsSavedInBundle:(BOOL)savedInBundle; - (void)setFileURL:(NSURL *)fileURL; - (void)connect; - - (void)showConsole:(id)sender; - (IBAction)showNavigator:(id)sender; - (IBAction)toggleNavigator:(id)sender; @@ -396,7 +395,11 @@ SPDatabaseData, SPTablesList, SPTableStructure, SPTableContent, SPTableData, SPS - (NSString *)displayName; #ifndef SP_REFACTOR /* method decls */ - (NSUndoManager *)undoManager; +#endif +- (NSArray *)allTableNames; +- (SPTablesList *)tablesListInstance; +#ifndef SP_REFACTOR /* method decls */ // Notification center methods - (void)willPerformQuery:(NSNotification *)notification; - (void)hasPerformedQuery:(NSNotification *)notification; @@ -451,7 +454,7 @@ SPDatabaseData, SPTablesList, SPTableStructure, SPTableContent, SPTableData, SPS - (NSDictionary*)shellVariables; // State saving and setting -- (NSDictionary *) stateIncludingDetails:(NSDictionary *)detailsToReturn; +- (NSDictionary *)stateIncludingDetails:(NSDictionary *)detailsToReturn; - (BOOL)setState:(NSDictionary *)stateDetails; - (void)setStateFromConnectionFile:(NSString *)path; - (void)restoreSession; @@ -460,8 +463,6 @@ SPDatabaseData, SPTablesList, SPTableStructure, SPTableContent, SPTableData, SPS #ifdef SP_REFACTOR /* method decls */ - (SPConnectionController*)createConnectionController; - (void)connect; -- (NSArray*)allTableNames; -- (SPTablesList*)tablesListInstance; - (void)setTableSourceInstance:(SPTableStructure*)source; - (void)setTableContentInstance:(SPTableContent*)content; diff --git a/Source/SPDatabaseDocument.m b/Source/SPDatabaseDocument.m index 38027ac8..d43be841 100644 --- a/Source/SPDatabaseDocument.m +++ b/Source/SPDatabaseDocument.m @@ -36,7 +36,7 @@ enum { #import "SPConnectionController.h" #import "SPConnectionControllerInitializer.h" -#import "SPMySQL.h" +#import <SPMySQL/SPMySQL.h> #import "SPTablesList.h" #import "SPTableStructure.h" @@ -92,7 +92,7 @@ enum { #ifdef SP_REFACTOR /* headers */ #import "SPAlertSheets.h" -#import "NSNotificationAdditions.h" +#import "NSNotificationCenterThreadingAdditions.h" #import "SPCustomQuery.h" #import "SPDatabaseRename.h" #endif @@ -101,6 +101,7 @@ enum { #ifndef SP_REFACTOR static NSString *SPCreateSyntx = @"SPCreateSyntax"; #endif +static NSString *SPRenameDatabaseAction = @"SPRenameDatabase"; @interface SPDatabaseDocument () @@ -111,6 +112,9 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; - (void)_renameDatabase; - (void)_removeDatabase; - (void)_selectDatabaseAndItem:(NSDictionary *)selectionDetails; +#ifndef SP_REFACTOR /* method decls */ +- (void)_processDatabaseChangedBundleTriggerActions; +#endif @end @@ -141,6 +145,7 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; @synthesize databaseRenameSheet; @synthesize databaseRenameNameField; @synthesize renameDatabaseButton; +@synthesize chooseDatabaseButton; #endif - (id)init @@ -568,7 +573,7 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; * * @return The document's connection */ -- (SPMySQLConnection *) getConnection +- (SPMySQLConnection *)getConnection { return mySQLConnection; } @@ -659,10 +664,11 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; return; } - if ( [chooseDatabaseButton indexOfSelectedItem] == 0 ) { + if ([chooseDatabaseButton indexOfSelectedItem] == 0) { if ([self database]) { [chooseDatabaseButton selectItemWithTitle:[self database]]; } + return; } @@ -677,30 +683,32 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; /** * Select the specified database and, optionally, table. */ -- (void)selectDatabase:(NSString *)aDatabase item:(NSString *)anItem +- (void)selectDatabase:(NSString *)database item:(NSString *)item { #ifndef SP_REFACTOR /* update navigator controller */ // Do not update the navigator since nothing is changed [[SPNavigatorController sharedNavigatorController] setIgnoreUpdate:NO]; // If Navigator runs in syncMode let it follow the selection - if([[SPNavigatorController sharedNavigatorController] syncMode]) { + if ([[SPNavigatorController sharedNavigatorController] syncMode]) { NSMutableString *schemaPath = [NSMutableString string]; + [schemaPath setString:[self connectionID]]; - if([chooseDatabaseButton titleOfSelectedItem] && [[chooseDatabaseButton titleOfSelectedItem] length]) { + + if ([chooseDatabaseButton titleOfSelectedItem] && [[chooseDatabaseButton titleOfSelectedItem] length]) { [schemaPath appendString:SPUniqueSchemaDelimiter]; [schemaPath appendString:[chooseDatabaseButton titleOfSelectedItem]]; } + [[SPNavigatorController sharedNavigatorController] selectPath:schemaPath]; } #endif // Start a task [self startTaskWithDescription:[NSString stringWithFormat:NSLocalizedString(@"Loading database '%@'...", @"Loading database task string"), [chooseDatabaseButton titleOfSelectedItem]]]; - NSDictionary *selectionDetails = [NSDictionary dictionaryWithObjectsAndKeys: - aDatabase, @"database", - anItem, @"item", - nil]; + + NSDictionary *selectionDetails = [NSDictionary dictionaryWithObjectsAndKeys:database, @"database", item, @"item", nil]; + if ([NSThread isMainThread]) { [NSThread detachNewThreadSelector:@selector(_selectDatabaseAndItem:) toTarget:self withObject:selectionDetails]; } @@ -793,7 +801,7 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; modalForWindow:parentWindow modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) - contextInfo:@"renameDatabase"]; + contextInfo:SPRenameDatabaseAction]; } /** @@ -816,17 +824,21 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; NSArray *buttons = [alert buttons]; +#ifndef SP_REFACTOR // Change the alert's cancel button to have the key equivalent of return [[buttons objectAtIndex:0] setKeyEquivalent:@"d"]; [[buttons objectAtIndex:0] setKeyEquivalentModifierMask:NSCommandKeyMask]; [[buttons objectAtIndex:1] setKeyEquivalent:@"\r"]; +#else + [[buttons objectAtIndex:1] setKeyEquivalent:@"\e"]; // Esc = Cancel + [[buttons objectAtIndex:0] setKeyEquivalent:@"\r"]; // Return = OK +#endif [alert setAlertStyle:NSCriticalAlertStyle]; [alert beginSheetModalForWindow:parentWindow modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:@"removeDatabase"]; } -#ifndef SP_REFACTOR /** * Refreshes the tables list by calling SPTablesList's updateTables. */ @@ -835,6 +847,7 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; [tablesListInstance updateTables:self]; } +#ifndef SP_REFACTOR /** * Displays the database server variables sheet. */ @@ -897,7 +910,7 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; - (void)sheetDidEnd:(id)sheet returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo { #ifndef SP_REFACTOR - if([contextInfo isEqualToString:@"saveDocPrefSheetStatus"]) { + if ([contextInfo isEqualToString:@"saveDocPrefSheetStatus"]) { saveDocPrefSheetStatus = returnCode; return; } @@ -914,6 +927,17 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; if (returnCode == NSAlertDefaultReturn) { [self _removeDatabase]; } +#ifdef SP_REFACTOR + else { + // Reset chooseDatabaseButton + if ([[self database] length]) { + [chooseDatabaseButton selectItemWithTitle:[self database]]; + } + else { + [chooseDatabaseButton selectItemAtIndex:0]; + } + } +#endif } // Add a new database else if ([contextInfo isEqualToString:@"addDatabase"]) { @@ -923,12 +947,15 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; // Query the structure of all databases in the background (mainly for completion) [NSThread detachNewThreadSelector:@selector(queryDbStructureWithUserInfo:) toTarget:databaseStructureRetrieval withObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], @"forceUpdate", nil]]; - } else { - // reset chooseDatabaseButton - if([[self database] length]) + } + else { + // Reset chooseDatabaseButton + if ([[self database] length]) { [chooseDatabaseButton selectItemWithTitle:[self database]]; - else + } + else { [chooseDatabaseButton selectItemAtIndex:0]; + } } } #ifndef SP_REFACTOR @@ -938,10 +965,21 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; } } #endif - else if ([contextInfo isEqualToString:@"renameDatabase"]) { + else if ([contextInfo isEqualToString:SPRenameDatabaseAction]) { if (returnCode == NSOKButton) { [self _renameDatabase]; } +#ifdef SP_REFACTOR + else { + // Reset chooseDatabaseButton + if ([[self database] length]) { + [chooseDatabaseButton selectItemWithTitle:[self database]]; + } + else { + [chooseDatabaseButton selectItemAtIndex:0]; + } + } +#endif } #ifndef SP_REFACTOR // Close error status sheet for OPTIMIZE, CHECK, REPAIR etc. @@ -1265,7 +1303,6 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; */ - (void) endTask { - // Ensure a call on the main thread if (![NSThread isMainThread]) return [[self onMainThread] endTask]; @@ -2285,7 +2322,7 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; { userManagerInstance = [[SPUserManager alloc] init]; - [userManagerInstance setMySqlConnection:mySQLConnection]; + [userManagerInstance setConnection:mySQLConnection]; [userManagerInstance setServerSupport:serverSupport]; } @@ -2581,6 +2618,16 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; return _isSavedInBundle; } +- (NSArray *)allTableNames +{ + return [tablesListInstance allTableNames]; +} + +- (SPTablesList *)tablesListInstance +{ + return tablesListInstance; +} + #pragma mark - #pragma mark Notification center methods @@ -4297,16 +4344,9 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; if ([tableContentInstance filterSettings]) [sessionState setObject:[tableContentInstance filterSettings] forKey:@"contentFilter"]; - NSIndexSet *contentSelectedIndexSet = [tableContentInstance selectedRowIndexes]; - if (contentSelectedIndexSet && [contentSelectedIndexSet count]) { - NSMutableArray *indices = [NSMutableArray array]; - NSUInteger indexBuffer[[contentSelectedIndexSet count]]; - NSUInteger limit = [contentSelectedIndexSet getIndexes:indexBuffer maxCount:[contentSelectedIndexSet count] inIndexRange:NULL]; - NSUInteger idx; - for (idx = 0; idx < limit; idx++) { - [indices addObject:[NSNumber numberWithInteger:indexBuffer[idx]]]; - } - [sessionState setObject:indices forKey:@"contentSelectedIndexSet"]; + NSDictionary *contentSelectedRows = [tableContentInstance selectionDetailsAllowingIndexSelection:YES]; + if (contentSelectedRows) { + [sessionState setObject:contentSelectedRows forKey:@"contentSelection"]; } } @@ -4734,14 +4774,8 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; [tablesListInstance selectTableAtIndex:[NSNumber numberWithInteger:[tables indexOfObject:[spfSession objectForKey:@"table"]]]]; // Restore table selection indexes - if([spfSession objectForKey:@"contentSelectedIndexSet"]) { - NSMutableIndexSet *anIndexSet = [NSMutableIndexSet indexSet]; - NSArray *items = [spfSession objectForKey:@"contentSelectedIndexSet"]; - NSUInteger i; - for(i=0; i<[items count]; i++) - [anIndexSet addIndex:[NSArrayObjectAtIndex(items, i) integerValue]]; - - [tableContentInstance setSelectedRowIndexesToRestore:anIndexSet]; + if([spfSession objectForKey:@"contentSelection"]) { + [tableContentInstance setSelectionToRestore:[spfSession objectForKey:@"contentSelection"]]; } [[tablesListInstance valueForKeyPath:@"tablesListView"] scrollRowToVisible:[tables indexOfObject:[spfSession objectForKey:@"selectedTable"]]]; @@ -5578,7 +5612,7 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; #pragma mark - -#pragma mark status accessory view +#pragma mark Status accessory view - (IBAction)copyChecksumFromSheet:(id)sender { @@ -5611,7 +5645,6 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; #endif - #pragma mark - /** @@ -5693,15 +5726,7 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; [super dealloc]; } -- (NSArray*)allTableNames -{ - return [tablesListInstance allTableNames]; -} - -- (SPTablesList*)tablesListInstance -{ - return tablesListInstance; -} +#pragma mark - #ifndef SP_REFACTOR /* whole database operations */ @@ -5737,7 +5762,9 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; - (void)_renameDatabase { - if ([[databaseRenameNameField stringValue] isEqualToString:@""]) { + NSString *newDatabaseName = [databaseRenameNameField stringValue]; + + if ([newDatabaseName isEqualToString:@""]) { SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, parentWindow, self, nil, nil, NSLocalizedString(@"Database must have a name.", @"message of panel when no db name is given")); return; } @@ -5747,30 +5774,26 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; [dbActionRename setConnection:[self getConnection]]; [dbActionRename setMessageWindow:parentWindow]; - if ([dbActionRename renameDatabaseFrom:[self database] to:[databaseRenameNameField stringValue]]) { - [self selectDatabase:[databaseRenameNameField stringValue] item:nil]; + if ([dbActionRename renameDatabaseFrom:[self database] to:newDatabaseName]) { + [self setDatabases:self]; + [self selectDatabase:newDatabaseName item:nil]; } else { SPBeginAlertSheet(NSLocalizedString(@"Unable to rename database", @"unable to rename database message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, parentWindow, self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"An error occured while trying to rename the database '%@' to '%@'.", @"unable to rename database message informative message"), [self database], [databaseRenameNameField stringValue]]); + [NSString stringWithFormat:NSLocalizedString(@"An error occured while trying to rename the database '%@' to '%@'.", @"unable to rename database message informative message"), [self database], newDatabaseName]); } [dbActionRename release]; - - // Update DB list - [self setDatabases:self]; #ifdef SP_REFACTOR - if ( delegate && [delegate respondsToSelector:@selector(refreshDatabasePopup)] ) + if (delegate && [delegate respondsToSelector:@selector(refreshDatabasePopup)]) { [delegate performSelector:@selector(refreshDatabasePopup) withObject:nil]; + } - if ( delegate && [delegate respondsToSelector:@selector(selectDatabaseInPopup:)] ) - { - if ( [allDatabases count] > 0 ) - { - NSString* db = [databaseRenameNameField stringValue]; - [delegate performSelector:@selector(selectDatabaseInPopup:) withObject:db]; + if (delegate && [delegate respondsToSelector:@selector(selectDatabaseInPopup:)]) { + if ([allDatabases count] > 0 ) { + [delegate performSelector:@selector(selectDatabaseInPopup:) withObject:newDatabaseName]; } } #endif @@ -5873,7 +5896,6 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; // that's why we can run this on main thread [databaseStructureRetrieval queryDbStructureWithUserInfo:nil]; - // Delete was successful if (selectedDatabase) [selectedDatabase release], selectedDatabase = nil; [self setDatabases:self]; @@ -5881,9 +5903,10 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; [tablesListInstance setConnection:mySQLConnection]; [tableDumpInstance setConnection:mySQLConnection]; -#ifndef SP_REFACTOR +#ifndef SP_REFACTOR /* ui */ [self updateWindowTitle:self]; #endif + #ifdef SP_REFACTOR /* glue */ if ( delegate && [delegate respondsToSelector:@selector(refreshDatabasePopup)] ) [delegate performSelector:@selector(refreshDatabasePopup) withObject:nil]; @@ -5911,6 +5934,7 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; // Save existing scroll position and details, and ensure no duplicate entries are created as table list changes BOOL historyStateChanging = [spHistoryControllerInstance modifyingState]; + if (!historyStateChanging) { [spHistoryControllerInstance updateHistoryEntries]; [spHistoryControllerInstance setModifyingState:YES]; @@ -5921,13 +5945,11 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; // Attempt to select the specified database, and abort on failure #ifndef SP_REFACTOR /* patch */ - if ([chooseDatabaseButton indexOfItemWithTitle:targetDatabaseName] == NSNotFound - || ![mySQLConnection selectDatabase:targetDatabaseName]) + if ([chooseDatabaseButton indexOfItemWithTitle:targetDatabaseName] == NSNotFound || ![mySQLConnection selectDatabase:targetDatabaseName]) #else - if ( ![mySQLConnection selectDB:targetDatabaseName] ) + if (![mySQLConnection selectDatabase:targetDatabaseName]) #endif { - // End the task first to ensure the database dropdown can be reselected [self endTask]; @@ -5996,7 +6018,8 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; if (![targetItemName isEqualToString:[self table]]) { if (targetItemName) { [tablesListInstance selectItemWithName:targetItemName]; - } else { + } + else { [[tablesListInstance onMainThread] setTableListSelectability:YES]; [[[tablesListInstance valueForKey:@"tablesListView"] onMainThread] deselectAll:self]; [[tablesListInstance onMainThread] setTableListSelectability:NO]; @@ -6005,52 +6028,67 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; #endif [self endTask]; #ifndef SP_REFACTOR /* triggered commands */ + [self _processDatabaseChangedBundleTriggerActions]; +#endif + +#ifdef SP_REFACTOR /* glue */ + if (delegate && [delegate respondsToSelector:@selector(databaseDidChange:)]) { + [delegate performSelectorOnMainThread:@selector(databaseDidChange:) withObject:self waitUntilDone:NO]; + } +#endif + [taskPool drain]; +} + +#ifndef SP_REFACTOR +- (void)_processDatabaseChangedBundleTriggerActions +{ NSArray *triggeredCommands = [[NSApp delegate] bundleCommandsForTrigger:SPBundleTriggerActionDatabaseChanged]; - for(NSString* cmdPath in triggeredCommands) { + + 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]) { + + 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 (!correspondingWindowFound) stopTrigger = YES; } - if(!stopTrigger) { - if([[data objectAtIndex:1] isEqualToString:SPBundleScopeGeneral]) { + if (!stopTrigger) { + if ([[data objectAtIndex:1] isEqualToString:SPBundleScopeGeneral]) { [[[NSApp delegate] onMainThread] executeBundleItemForApp:aMenuItem]; } - else if([[data objectAtIndex:1] isEqualToString:SPBundleScopeDataTable]) { - if([[[[[NSApp mainWindow] firstResponder] class] description] isEqualToString:@"SPCopyTable"]) + else if ([[data objectAtIndex:1] isEqualToString:SPBundleScopeDataTable]) { + if ([[[[[NSApp mainWindow] firstResponder] class] description] isEqualToString:@"SPCopyTable"]) { [[[[NSApp mainWindow] firstResponder] onMainThread] executeBundleItemForDataTable:aMenuItem]; + } } - else if([[data objectAtIndex:1] isEqualToString:SPBundleScopeInputField]) { - if([[[NSApp mainWindow] firstResponder] isKindOfClass:[NSTextView class]]) + else if ([[data objectAtIndex:1] isEqualToString:SPBundleScopeInputField]) { + if ([[[NSApp mainWindow] firstResponder] isKindOfClass:[NSTextView class]]) { [[[[NSApp mainWindow] firstResponder] onMainThread] executeBundleItemForInputField:aMenuItem]; + } } } } -#endif - -#ifdef SP_REFACTOR /* glue */ - if (delegate && [delegate respondsToSelector:@selector(databaseDidChange:)]) { - [delegate performSelectorOnMainThread:@selector(databaseDidChange:) withObject:self waitUntilDone:NO]; - } -#endif - - [taskPool drain]; } +#endif @end diff --git a/Source/SPDatabaseInfo.m b/Source/SPDatabaseInfo.m index fc487718..ea43f403 100644 --- a/Source/SPDatabaseInfo.m +++ b/Source/SPDatabaseInfo.m @@ -24,7 +24,7 @@ #import "SPDBActionCommons.h" #import "SPDatabaseInfo.h" -#import "SPMySQL.h" +#import <SPMySQL/SPMySQL.h> @implementation SPDatabaseInfo diff --git a/Source/SPDatabaseRename.m b/Source/SPDatabaseRename.m index 1877e955..61186678 100644 --- a/Source/SPDatabaseRename.m +++ b/Source/SPDatabaseRename.m @@ -26,7 +26,7 @@ #import "SPDatabaseRename.h" #import "SPTableCopy.h" #import "SPDatabaseInfo.h" -#import "SPMySQL.h" +#import <SPMySQL/SPMySQL.h> @implementation SPDatabaseRename diff --git a/Source/SPDatabaseStructure.h b/Source/SPDatabaseStructure.h index 52e43ec8..32435a01 100644 --- a/Source/SPDatabaseStructure.h +++ b/Source/SPDatabaseStructure.h @@ -23,9 +23,12 @@ // // More info at <http://code.google.com/p/sequel-pro/> + +#import <SPMySQL/SPMySQLConnectionDelegate.h> + @class SPMySQLConnection, SPDatabaseDocument; -@interface SPDatabaseStructure : NSObject { +@interface SPDatabaseStructure : NSObject <SPMySQLConnectionDelegate> { SPDatabaseDocument *delegate; SPMySQLConnection *mySQLConnection; diff --git a/Source/SPDatabaseStructure.m b/Source/SPDatabaseStructure.m index 3579e269..79e2cc5d 100644 --- a/Source/SPDatabaseStructure.m +++ b/Source/SPDatabaseStructure.m @@ -28,7 +28,7 @@ #import "SPConnectionDelegate.h" #import "SPTablesList.h" #import "RegexKitLite.h" -#import "SPMySQL.h" +#import <SPMySQL/SPMySQL.h> #import <pthread.h> @interface SPDatabaseStructure (Private_API) diff --git a/Source/SPDatabaseViewController.m b/Source/SPDatabaseViewController.m index dcea5cdc..4d0548d5 100644 --- a/Source/SPDatabaseViewController.m +++ b/Source/SPDatabaseViewController.m @@ -23,8 +23,10 @@ // // More info at <http://code.google.com/p/sequel-pro/> +#ifndef SP_REFACTOR /* headers */ #import "SPAppController.h" #import "SPBundleHTMLOutputController.h" +#endif #import "SPCopyTable.h" #import "SPDatabaseViewController.h" #import "SPHistoryController.h" @@ -32,7 +34,7 @@ #import "SPTableData.h" #import "SPTablesList.h" #import "SPTableTriggers.h" -#import "SPMySQL.h" +#import <SPMySQL/SPMySQL.h> #ifdef SP_REFACTOR /* headers */ #import "SPTableStructure.h" #endif diff --git a/Source/SPEditorPreferencePane.m b/Source/SPEditorPreferencePane.m index 3c948e74..b84e41ba 100644 --- a/Source/SPEditorPreferencePane.m +++ b/Source/SPEditorPreferencePane.m @@ -329,7 +329,6 @@ static NSString *SPCustomColorSchemeNameLC = @"user-defined"; NSFont *font = [NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPCustomQueryEditorFont]]; [editorFontName setFont:font]; - [editorFontName setStringValue:[NSString stringWithFormat:@"%@, %.1f pt", [font displayName], [font pointSize]]]; [colorSettingTableView reloadData]; } diff --git a/Source/SPExportController.h b/Source/SPExportController.h index 9c084334..72a12a85 100644 --- a/Source/SPExportController.h +++ b/Source/SPExportController.h @@ -23,7 +23,13 @@ // // More info at <http://code.google.com/p/sequel-pro/> -@class SPMySQLConnection, BWAnchoredButtonBar; +@class SPDatabaseDocument, + SPTableContent, + SPCustomQuery, + SPTablesList, + SPTableData, + SPMySQLConnection, + BWAnchoredButtonBar; /** * @class SPExportController SPExportController.h @@ -35,11 +41,11 @@ @interface SPExportController : NSWindowController { // Controllers - IBOutlet id tableDocumentInstance; - IBOutlet id tableContentInstance; - IBOutlet id customQueryInstance; - IBOutlet id tablesListInstance; - IBOutlet id tableDataInstance; + IBOutlet SPDatabaseDocument *tableDocumentInstance; + IBOutlet SPTableContent *tableContentInstance; + IBOutlet SPCustomQuery *customQueryInstance; + IBOutlet SPTablesList *tablesListInstance; + IBOutlet SPTableData *tableDataInstance; // Export window IBOutlet NSView *exporterView; @@ -94,9 +100,6 @@ IBOutlet NSPopUpButton *exportSQLInsertDividerPopUpButton; IBOutlet NSButton *exportSQLIncludeAutoIncrementValueButton; - // Excel - IBOutlet NSMatrix *exportExcelSheetOrFilePerTableMatrix; - // CSV IBOutlet NSButton *exportCSVIncludeFieldNamesCheck; IBOutlet NSComboBox *exportCSVFieldsTerminatedField; @@ -263,6 +266,4 @@ - (IBAction)toggleSQLIncludeDropSyntax:(NSButton *)sender; - (IBAction)toggleNewFilePerTable:(NSButton *)sender; -- (void)savePanelDidEnd:(NSSavePanel *)panel returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo; - @end diff --git a/Source/SPExportController.m b/Source/SPExportController.m index 17720928..ace630b6 100644 --- a/Source/SPExportController.m +++ b/Source/SPExportController.m @@ -34,7 +34,7 @@ #import "SPExportFilenameUtilities.h" #import "SPExportFileNameTokenObject.h" #import "SPDatabaseDocument.h" -#import "SPMySQL.h" +#import <SPMySQL/SPMySQL.h> // Constants static const NSUInteger SPExportUIPadding = 20; @@ -615,6 +615,7 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; - (IBAction)toggleNewFilePerTable:(NSButton *)sender { [self _updateExportFormatInformation]; + [self updateAvailableExportFilenameTokens]; } /** @@ -835,7 +836,7 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; } if (j > i) { - NSUInteger diff = (j - i); + NSUInteger diff = j - i; SPBeginAlertSheet(NSLocalizedString(@"The list of tables has changed", @"table list change alert message"), NSLocalizedString(@"Continue", @"continue button"), @@ -975,9 +976,9 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; BOOL isHTML = (exportType == SPHTMLExport); BOOL isPDF = (exportType == SPPDFExport); - BOOL structureEnabled = [[uiStateDict objectForKey:SPSQLExportStructureEnabled] integerValue]; - BOOL contentEnabled = [[uiStateDict objectForKey:SPSQLExportContentEnabled] integerValue]; - BOOL dropEnabled = [[uiStateDict objectForKey:SPSQLExportDropEnabled] integerValue]; + BOOL structureEnabled = [[uiStateDict objectForKey:SPSQLExportStructureEnabled] boolValue]; + BOOL contentEnabled = [[uiStateDict objectForKey:SPSQLExportContentEnabled] boolValue]; + BOOL dropEnabled = [[uiStateDict objectForKey:SPSQLExportDropEnabled] boolValue]; if (isCSV || isXML || isHTML || isPDF || (isSQL && ((!structureEnabled) || (!dropEnabled)))) { enable = NO; @@ -1012,8 +1013,8 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; } } } - // Disable if structure is unchecked, but content and drop are as dropping a table then trying to insert - // into it is obviously an error. + // Disable if structure is unchecked, but content and drop are as dropping a + // table then trying to insert into it is obviously an error. else if (contentEnabled && (!structureEnabled) && (dropEnabled)) { enable = NO; } @@ -1053,119 +1054,4 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; [exportButton setEnabled:[enable boolValue]]; } -/** - * 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/SPExportControllerDelegate.m b/Source/SPExportControllerDelegate.m index e8e9f1c6..23821d12 100644 --- a/Source/SPExportControllerDelegate.m +++ b/Source/SPExportControllerDelegate.m @@ -31,6 +31,7 @@ @interface SPExportController (SPExportControllerPrivateAPI) - (void)_toggleExportButtonOnBackgroundThread; +- (void)_toggleSQLExportTableNameTokenAvailability; - (void)_updateExportFormatInformation; - (void)_switchTab; @@ -55,6 +56,7 @@ { [[tables objectAtIndex:rowIndex] replaceObjectAtIndex:[exportTableList columnWithIdentifier:[tableColumn identifier]] withObject:anObject]; + [self updateAvailableExportFilenameTokens]; [self _toggleExportButtonOnBackgroundThread]; [self _updateExportFormatInformation]; } @@ -67,9 +69,9 @@ return (tableView == exportTableList); } -- (void)tableView:(NSTableView *)tableView willDisplayCell:(id)aCell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex +- (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex { - [aCell setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; + [cell setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; } #pragma mark - @@ -102,23 +104,29 @@ */ - (NSArray *)tokenField:(NSTokenField *)tokenField shouldAddObjects:(NSArray *)tokens atIndex:(NSUInteger)index { - NSMutableArray *processedTokens = [NSMutableArray array]; NSUInteger i, j; + NSMutableArray *processedTokens = [NSMutableArray array]; NSCharacterSet *alphanumericSet = [NSCharacterSet alphanumericCharacterSet]; - for (NSString *inputToken in tokens) { + for (NSString *inputToken in tokens) + { j = 0; - for (i = 0; i < [inputToken length]; i++) { + + for (i = 0; i < [inputToken length]; i++) + { if (![alphanumericSet characterIsMember:[inputToken characterAtIndex:i]]) { if (i > j) { - [processedTokens addObject:[self tokenObjectForString:[inputToken substringWithRange:NSMakeRange(j, i-j)]]]; + [processedTokens addObject:[self tokenObjectForString:[inputToken substringWithRange:NSMakeRange(j, i - j)]]]; } + [processedTokens addObject:[inputToken substringWithRange:NSMakeRange(i, 1)]]; - j = i+1; + + j = i + 1; } } + if (j < i) { - [processedTokens addObject:[self tokenObjectForString:[inputToken substringWithRange:NSMakeRange(j, i-j)]]]; + [processedTokens addObject:[self tokenObjectForString:[inputToken substringWithRange:NSMakeRange(j, i - j)]]]; } } @@ -146,9 +154,9 @@ * During text entry into the token field, update the displayed filename and also * trigger tokenization after a short delay. */ -- (void)controlTextDidChange:(NSNotification *)aNotification +- (void)controlTextDidChange:(NSNotification *)notification { - if ([aNotification object] == exportCustomFilenameTokenField) { + if ([notification object] == exportCustomFilenameTokenField) { [self updateDisplayedExportFilename]; [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(tokenizeCustomFilenameTokenField) object:nil]; [self performSelector:@selector(tokenizeCustomFilenameTokenField) withObject:nil afterDelay:0.5]; diff --git a/Source/SPExportFile.m b/Source/SPExportFile.m index 10570a71..09d3b6af 100644 --- a/Source/SPExportFile.m +++ b/Source/SPExportFile.m @@ -26,7 +26,7 @@ #import "SPExportFile.h" #import "SPFileHandle.h" -@interface SPExportFile (PrivateAPI) +@interface SPExportFile () - (SPExportFileHandleStatus)_createFileHandle; @@ -104,6 +104,7 @@ if ([fileManager fileExistsAtPath:[self exportFilePath]]) { return [[NSFileManager defaultManager] removeItemAtPath:[self exportFilePath] error:nil]; } + return NO; } diff --git a/Source/SPExportFileUtilities.m b/Source/SPExportFileUtilities.m index 325ddb60..561c5340 100644 --- a/Source/SPExportFileUtilities.m +++ b/Source/SPExportFileUtilities.m @@ -29,7 +29,7 @@ #import "SPExportFile.h" #import "SPDatabaseDocument.h" #import "SPCustomQuery.h" -#import "SPMySQL.h" +#import <SPMySQL/SPMySQL.h> typedef enum { diff --git a/Source/SPExportFilenameUtilities.m b/Source/SPExportFilenameUtilities.m index 31625a4c..ad5ab36b 100644 --- a/Source/SPExportFilenameUtilities.m +++ b/Source/SPExportFilenameUtilities.m @@ -54,11 +54,61 @@ } /** - * Updates the available export filename tokens. + * Updates the available export filename tokens based on the currently selected options. */ - (void)updateAvailableExportFilenameTokens { - [exportCustomFilenameTokensField setStringValue:((exportSource == SPQueryExport) || (exportType == SPDotExport)) ? NSLocalizedString(@"host,database,date,year,month,day,time", @"custom export filename tokens without table") : NSLocalizedString(@"host,database,table,date,year,month,day,time", @"default custom export filename tokens")]; + NSUInteger i = 0; + BOOL removeTable = NO; + + BOOL isSQL = exportType == SPSQLExport; + BOOL isCSV = exportType == SPCSVExport; + BOOL isDot = exportType == SPDotExport; + BOOL isXML = exportType == SPXMLExport; + + NSString *tokens = NSLocalizedString(@"host,database,table,date,year,month,day,time", @"default custom export filename tokens");; + NSString *tokensWithoutTable = NSLocalizedString(@"host,database,date,year,month,day,time", @"custom export filename tokens without table"); + + if (exportSource == SPQueryExport || isDot) { + tokens = tokensWithoutTable; + } + else if (isSQL || isCSV || isXML) { + for (NSArray *table in tables) + { + if ([NSArrayObjectAtIndex(table, 2) boolValue]) { + i++; + removeTable = YES; + + if (i == 2) break; + } + } + + if (i > 1) { + removeTable = isSQL ? YES : ![exportFilePerTableCheck state]; + + tokens = isSQL ? tokensWithoutTable : ([exportFilePerTableCheck state] ? tokens : tokensWithoutTable); + } + } + + if (removeTable) { + NSArray *tokenParts = [exportCustomFilenameTokenField objectValue]; + + for (id token in [exportCustomFilenameTokenField objectValue]) + { + if ([token isKindOfClass:[SPExportFileNameTokenObject class]]) { + if ([[token tokenContent] isEqualToString:NSLocalizedString(@"table", @"table")]) { + NSMutableArray *newTokens = [NSMutableArray arrayWithArray:tokenParts]; + + [newTokens removeObjectAtIndex:[tokenParts indexOfObject:token]]; + + [exportCustomFilenameTokenField setObjectValue:newTokens]; + break; + } + } + } + } + + [exportCustomFilenameTokensField setStringValue:tokens]; } /** @@ -69,7 +119,9 @@ { if ([[exportCustomFilenameTokensField objectValue] containsObject:stringToTokenize]) { SPExportFileNameTokenObject *newToken = [[SPExportFileNameTokenObject alloc] init]; + [newToken setTokenContent:stringToTokenize]; + return [newToken autorelease]; } @@ -78,6 +130,7 @@ /** * Tokenize the filename field. + * * This is called on a delay after text entry to update the tokens during text entry. * There's no API to perform tokenizing, but the same result can be achieved by using the return key; * however, this only works if the cursor is after text, not after a token. @@ -90,6 +143,7 @@ if ([exportCustomFilenameTokenField currentEditor] == nil) return; NSRange selectedRange = [[exportCustomFilenameTokenField currentEditor] selectedRange]; + if (selectedRange.location == NSNotFound) return; if (selectedRange.length > 0) return; @@ -98,10 +152,15 @@ // Walk through the strings - not the tokens - and determine whether any need tokenizing BOOL tokenizingRequired = NO; - for (id representedObject in representedObjects) { + + for (id representedObject in representedObjects) + { if ([representedObject isKindOfClass:[SPExportFileNameTokenObject class]]) continue; + NSArray *tokenParts = [representedObject componentsSeparatedByCharactersInSet:nonAlphanumericSet]; - for (NSString *tokenPart in tokenParts) { + + for (NSString *tokenPart in tokenParts) + { if ([validTokens containsObject:tokenPart]) { tokenizingRequired = YES; break; @@ -115,12 +174,16 @@ // Detect where the cursor is currently located. If it's at the end of a token, also return - // or the enter key would result in closing the sheet. NSUInteger stringPosition = 0; - for (id representedObject in representedObjects) { + + for (id representedObject in representedObjects) + { if ([representedObject isKindOfClass:[SPExportFileNameTokenObject class]]) { stringPosition++; - } else { + } + else { stringPosition += [(NSString *)representedObject length]; } + if (selectedRange.location <= stringPosition) { if ([representedObject isKindOfClass:[SPExportFileNameTokenObject class]]) return; break; @@ -128,7 +191,17 @@ } // 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:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:0x24]; + NSEvent *tokenizingEvent = [NSEvent keyEventWithType:NSKeyDown + location:NSMakePoint(0,0) + modifierFlags:0 + timestamp:0 + windowNumber:[[exportCustomFilenameTokenField window] windowNumber] + context:[NSGraphicsContext currentContext] + characters:nil + charactersIgnoringModifiers:nil + isARepeat:NO + keyCode:0x24]; + [[NSApplication sharedApplication] postEvent:tokenizingEvent atStart:NO]; // Update the filename preview @@ -235,7 +308,6 @@ } else if ([tokenContent isEqualToString:NSLocalizedString(@"table", @"table")]) { [string appendString:(table) ? table : @""]; - } else if ([tokenContent isEqualToString:NSLocalizedString(@"date", @"export filename date token")]) { [dateFormatter setDateStyle:NSDateFormatterShortStyle]; @@ -259,7 +331,6 @@ [dateFormatter setDateStyle:NSDateFormatterNoStyle]; [dateFormatter setTimeStyle:NSDateFormatterShortStyle]; [string appendString:[dateFormatter stringFromDate:[NSDate date]]]; - } } else { diff --git a/Source/SPExportInitializer.m b/Source/SPExportInitializer.m index 69a282b4..a0c0ef41 100644 --- a/Source/SPExportInitializer.m +++ b/Source/SPExportInitializer.m @@ -25,13 +25,13 @@ #import "SPExportInitializer.h" #import "SPTableData.h" +#import "SPTableContent.h" #import "SPDatabaseDocument.h" #import "SPTablesList.h" #import "SPGrowlController.h" #import "SPDatabaseDocument.h" #import "SPCustomQuery.h" #import "SPAlertSheets.h" - #import "SPCSVExporter.h" #import "SPSQLExporter.h" #import "SPXMLExporter.h" @@ -41,7 +41,8 @@ #import "SPExportFileUtilities.h" #import "SPExportFilenameUtilities.h" #import "SPExportFileNameTokenObject.h" -#import "SPMySQL.h" + +#import <SPMySQL/SPMySQL.h> @implementation SPExportController (SPExportInitializer) @@ -97,10 +98,10 @@ switch (exportSource) { case SPFilteredExport: - dataArray = [tableContentInstance currentResult]; + dataArray = [tableContentInstance currentDataResultWithNULLs:YES hideBLOBs:NO]; break; case SPQueryExport: - dataArray = [customQueryInstance currentResult]; + dataArray = [customQueryInstance currentDataResultWithNULLs:YES truncateDataFields:NO]; break; case SPTableExport: // Create an array of tables to export @@ -210,9 +211,10 @@ // 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]]; + [exportFilename setString:createCustomFilename ? [self expandCustomFilenameFormatUsingTableName:selectedTableName] : [self generateDefaultExportFilename]]; // Only append the extension if necessary if (![[exportFilename pathExtension] length]) { diff --git a/Source/SPExportInterfaceController.h b/Source/SPExportInterfaceController.h new file mode 100644 index 00000000..dfcf8448 --- /dev/null +++ b/Source/SPExportInterfaceController.h @@ -0,0 +1,44 @@ +// +// $Id$ +// +// 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 <http://code.google.com/p/sequel-pro/> + +#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 new file mode 100644 index 00000000..7c2615a4 --- /dev/null +++ b/Source/SPExportInterfaceController.m @@ -0,0 +1,152 @@ +// +// $Id$ +// +// 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 <http://code.google.com/p/sequel-pro/> + +#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/SPExtendedTableInfo.m b/Source/SPExtendedTableInfo.m index 9baaecab..23f3c426 100644 --- a/Source/SPExtendedTableInfo.m +++ b/Source/SPExtendedTableInfo.m @@ -33,7 +33,7 @@ #import "SPAlertSheets.h" #import "SPTableStructure.h" #import "SPServerSupport.h" -#import "SPMySQL.h" +#import <SPMySQL/SPMySQL.h> static NSString *SPUpdateTableTypeCurrentType = @"SPUpdateTableTypeCurrentType"; static NSString *SPUpdateTableTypeNewType = @"SPUpdateTableTypeNewType"; @@ -90,12 +90,18 @@ static NSString *SPUpdateTableTypeNewType = @"SPUpdateTableTypeNewType"; // Check if the user selected the same type if ([currentType isEqualToString:newType]) return; - + + // If the table is empty, perform the change directly + if ([[[tableDataInstance statusValues] objectForKey:@"Rows"] isEqualToString:@"0"]) { + [self _changeCurrentTableTypeFrom:currentType to:newType]; + return; + } + NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Change table type", @"change table type message") defaultButton:NSLocalizedString(@"Change", @"change button") alternateButton:NSLocalizedString(@"Cancel", @"cancel button") otherButton:nil - informativeTextWithFormat:NSLocalizedString(@"Are you sure you want to change this table's type to %@?\n\nPlease be aware that changing a table's type has the potential to cause the loss of some or all of it's data. This action cannot be undone.", @"change table type informative message"), newType]; + informativeTextWithFormat:NSLocalizedString(@"Are you sure you want to change this table's type to %@?\n\nPlease be aware that changing a table's type has the potential to cause the loss of some or all of its data. This action cannot be undone.", @"change table type informative message"), newType]; [alert setAlertStyle:NSCriticalAlertStyle]; @@ -610,16 +616,17 @@ static NSString *SPUpdateTableTypeNewType = @"SPUpdateTableTypeNewType"; if ([connection queryErrored]) { - // Reload the table's data - [tableDocumentInstance loadTable:selectedTable ofType:[tableDocumentInstance tableType]]; - } - else { [tableTypePopUpButton selectItemWithTitle:currentType]; SPBeginAlertSheet(NSLocalizedString(@"Error changing table type", @"error changing table type message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [NSApp mainWindow], self, nil, nil, [NSString stringWithFormat:NSLocalizedString(@"An error occurred when trying to change the table type to '%@'.\n\nMySQL said: %@", @"error changing table type informative message"), newType, [connection lastErrorMessage]]); + + return; } + + // Reload the table's data + [tableDocumentInstance loadTable:selectedTable ofType:[tableDocumentInstance tableType]]; } /** diff --git a/Source/SPFieldEditorController.m b/Source/SPFieldEditorController.m index 549c994e..3a4ee14c 100644 --- a/Source/SPFieldEditorController.m +++ b/Source/SPFieldEditorController.m @@ -35,7 +35,7 @@ #include <objc/objc-runtime.h> #import "SPCustomQuery.h" #import "SPTableContent.h" -#import "SPMySQLGeometryData.h" +#import <SPMySQL/SPMySQL.h> @interface SPFieldEditorController (SPFieldEditorControllerDelegate) @@ -43,6 +43,10 @@ @end +#ifdef SP_REFACTOR +/* Suppress deprecation warning for beginSheetForDirectory: until Sequel Pro team can migrate */ +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif @implementation SPFieldEditorController @@ -1135,7 +1139,7 @@ NSUInteger bitValue = 0x1; for(i=0; i<maxBit; i++) { - if([[self valueForKeyPath:[NSString stringWithFormat:@"bitSheetBitButton%ld", i]] state] == NSOnState) { + if([(NSButton*)[self valueForKeyPath:[NSString stringWithFormat:@"bitSheetBitButton%ld", i]] state] == NSOnState) { intValue += bitValue; [bitString replaceCharactersInRange:NSMakeRange((NSUInteger)maxTextLength-i-1, 1) withString:@"1"]; } @@ -1175,31 +1179,31 @@ break; case 2: // negate for(i=0; i<maxBit; i++) - [[self valueForKeyPath:[NSString stringWithFormat:@"bitSheetBitButton%ld", i]] setState:![[self valueForKeyPath:[NSString stringWithFormat:@"bitSheetBitButton%ld", i]] state]]; + [(NSButton*)[self valueForKeyPath:[NSString stringWithFormat:@"bitSheetBitButton%ld", i]] setState:![(NSButton*)[self valueForKeyPath:[NSString stringWithFormat:@"bitSheetBitButton%ld", i]] state]]; break; case 3: // shift left for(i=maxBit-1; i>0; i--) { - [[self valueForKeyPath:[NSString stringWithFormat:@"bitSheetBitButton%ld", i]] setState:[[self valueForKeyPath:[NSString stringWithFormat:@"bitSheetBitButton%ld", i-1]] state]]; + [(NSButton*)[self valueForKeyPath:[NSString stringWithFormat:@"bitSheetBitButton%ld", i]] setState:[(NSButton*)[self valueForKeyPath:[NSString stringWithFormat:@"bitSheetBitButton%ld", i-1]] state]]; } [[self valueForKeyPath:@"bitSheetBitButton0"] setState:NSOffState]; break; case 4: // shift right for(i=0; i<maxBit-1; i++) { - [[self valueForKeyPath:[NSString stringWithFormat:@"bitSheetBitButton%ld", i]] setState:[[self valueForKeyPath:[NSString stringWithFormat:@"bitSheetBitButton%ld", i+1]] state]]; + [(NSButton*)[self valueForKeyPath:[NSString stringWithFormat:@"bitSheetBitButton%ld", i]] setState:[(NSButton*)[self valueForKeyPath:[NSString stringWithFormat:@"bitSheetBitButton%ld", i+1]] state]]; } [[self valueForKeyPath:[NSString stringWithFormat:@"bitSheetBitButton%ld", maxBit-1]] setState:NSOffState]; break; case 5: // rotate left - aBit = [[self valueForKeyPath:[NSString stringWithFormat:@"bitSheetBitButton%ld", maxBit-1]] state]; + aBit = [(NSButton*)[self valueForKeyPath:[NSString stringWithFormat:@"bitSheetBitButton%ld", maxBit-1]] state]; for(i=maxBit-1; i>0; i--) { - [[self valueForKeyPath:[NSString stringWithFormat:@"bitSheetBitButton%ld", i]] setState:[[self valueForKeyPath:[NSString stringWithFormat:@"bitSheetBitButton%ld", i-1]] state]]; + [(NSButton*)[self valueForKeyPath:[NSString stringWithFormat:@"bitSheetBitButton%ld", i]] setState:[(NSButton*)[self valueForKeyPath:[NSString stringWithFormat:@"bitSheetBitButton%ld", i-1]] state]]; } [[self valueForKeyPath:@"bitSheetBitButton0"] setState:aBit]; break; case 6: // rotate right - aBit = [[self valueForKeyPath:@"bitSheetBitButton0"] state]; + aBit = [(NSButton*)[self valueForKeyPath:@"bitSheetBitButton0"] state]; for(i=0; i<maxBit-1; i++) { - [[self valueForKeyPath:[NSString stringWithFormat:@"bitSheetBitButton%ld", i]] setState:[[self valueForKeyPath:[NSString stringWithFormat:@"bitSheetBitButton%ld", i+1]] state]]; + [(NSButton*)[self valueForKeyPath:[NSString stringWithFormat:@"bitSheetBitButton%ld", i]] setState:[(NSButton*)[self valueForKeyPath:[NSString stringWithFormat:@"bitSheetBitButton%ld", i+1]] state]]; } [[self valueForKeyPath:[NSString stringWithFormat:@"bitSheetBitButton%ld", maxBit-1]] setState:aBit]; break; @@ -1225,7 +1229,7 @@ NSUInteger i; NSUInteger maxBit = (NSUInteger)((maxTextLength > 64) ? 64 : maxTextLength); - if([sender state] == NSOnState) { + if([(NSButton*)sender state] == NSOnState) { for(i=0; i<maxBit; i++) [[self valueForKeyPath:[NSString stringWithFormat:@"bitSheetBitButton%ld", i]] setEnabled:NO]; [bitSheetHexTextField setEnabled:NO]; diff --git a/Source/SPFieldMapperController.h b/Source/SPFieldMapperController.h index 6fb31b8e..bf88890c 100644 --- a/Source/SPFieldMapperController.h +++ b/Source/SPFieldMapperController.h @@ -125,7 +125,7 @@ BOOL newTableMode; BOOL addGlobalSheetIsOpen; - NSString *primaryKeyField; + NSArray *primaryKeyFields; NSNumber *lastDisabledCSVFieldcolumn; SPMySQLConnection *mySQLConnection; diff --git a/Source/SPFieldMapperController.m b/Source/SPFieldMapperController.m index 5e97903b..aabdc171 100644 --- a/Source/SPFieldMapperController.m +++ b/Source/SPFieldMapperController.m @@ -31,7 +31,7 @@ #import "SPCategoryAdditions.h" #import "RegexKitLite.h" #import "SPDatabaseData.h" -#import "SPMySQL.h" +#import <SPMySQL/SPMySQL.h> #define SP_NUMBER_OF_RECORDS_STRING NSLocalizedString(@"%ld of %@%lu records", @"Label showing the index of the selected CSV row") @@ -192,7 +192,7 @@ static NSString *SPTableViewSqlColumnID = @"sql"; showAdvancedView = NO; targetTableHasPrimaryKey = NO; - primaryKeyField = nil; + primaryKeyFields = nil; heightOffset = 0; [advancedReplaceView setHidden:YES]; [advancedUpdateView setHidden:YES]; @@ -225,7 +225,7 @@ static NSString *SPTableViewSqlColumnID = @"sql"; if (fieldMappingGlobalValues) [fieldMappingGlobalValues release]; if (fieldMappingGlobalValuesSQLMarked) [fieldMappingGlobalValuesSQLMarked release]; if (fieldMappingTableDefaultValues) [fieldMappingTableDefaultValues release]; - if (primaryKeyField) [primaryKeyField release]; + if (primaryKeyFields) [primaryKeyFields release]; if (toBeEditedRowIndexes) [toBeEditedRowIndexes release]; [super dealloc]; } @@ -293,7 +293,7 @@ static NSString *SPTableViewSqlColumnID = @"sql"; NSMutableArray *globals = [NSMutableArray array]; for(NSUInteger i=0; i < [fieldMappingGlobalValues count]; i++) { id glob = NSArrayObjectAtIndex(fieldMappingGlobalValues, i); - if([NSArrayObjectAtIndex(fieldMappingGlobalValuesSQLMarked, i) boolValue] || glob == [NSNull null]) + if([NSArrayObjectAtIndex(fieldMappingGlobalValuesSQLMarked, i) boolValue] || [glob isNSNull]) [globals addObject:glob]; else [globals addObject:[NSString stringWithFormat:@"'%@'", [(NSString*)glob stringByReplacingOccurrencesOfString:@"'" withString:@"\\'"]]]; @@ -567,8 +567,8 @@ static NSString *SPTableViewSqlColumnID = @"sql"; [fieldMappingTableDefaultValues addObject:@"0"]; } targetTableHasPrimaryKey = YES; - if (primaryKeyField) [primaryKeyField release]; - primaryKeyField = [[tableDetails objectForKey:@"primarykeyfield"] retain]; + if (primaryKeyFields) [primaryKeyFields release]; + primaryKeyFields = [[tableDetails objectForKey:@"primarykeyfield"] retain]; } else { if([column objectForKey:@"unique"]) { [type appendFormat:@",%@",@"UNIQUE"]; @@ -841,7 +841,7 @@ static NSString *SPTableViewSqlColumnID = @"sql"; } columnCounter = 0; for(id col in row) { - if(col && col != [NSNull null]) { + if(col && ![col isNSNull]) { if([col isKindOfClass:[NSString class]] && maxLengthOfSourceColumns[columnCounter] < (NSInteger)[(NSString*)col length]) { maxLengthOfSourceColumns[columnCounter] = [(NSString*)col length]; } @@ -1248,7 +1248,11 @@ static NSString *SPTableViewSqlColumnID = @"sql"; [onupdateCheckBox setEnabled:NO]; [onupdateTextView setEditable:YES]; [onupdateTextView setSelectedRange:NSMakeRange(0,[[onupdateTextView string] length])]; - [onupdateTextView insertText:[NSString stringWithFormat:@"%@ = %@", [primaryKeyField backtickQuotedString], [primaryKeyField backtickQuotedString]]]; + NSMutableArray *queryParts = [NSMutableArray arrayWithCapacity:[primaryKeyFields count]]; + for (NSString *eachFieldName in primaryKeyFields) { + [queryParts addObject:[NSString stringWithFormat:@"%@ = %@", [eachFieldName backtickQuotedString], [eachFieldName backtickQuotedString]]]; + } + [onupdateTextView insertText:[queryParts componentsJoinedByString:@" AND "]]; [onupdateTextView setBackgroundColor:[NSColor lightGrayColor]]; [onupdateTextView setEditable:NO]; } else { @@ -1487,7 +1491,7 @@ static NSString *SPTableViewSqlColumnID = @"sql"; fieldMappingArray = [[NSMutableArray alloc] init]; for (i = 0; i < [fieldMappingTableColumnNames count]; i++) { if (i < [NSArrayObjectAtIndex(fieldMappingImportArray, fieldMappingCurrentRow) count] - && NSArrayObjectAtIndex(NSArrayObjectAtIndex(fieldMappingImportArray, fieldMappingCurrentRow), i) != [NSNull null]) { + && ![NSArrayObjectAtIndex(NSArrayObjectAtIndex(fieldMappingImportArray, fieldMappingCurrentRow), i) isNSNull]) { value = i; } else { value = 0; diff --git a/Source/SPFileHandle.m b/Source/SPFileHandle.m index 3701fc8c..94c26211 100644 --- a/Source/SPFileHandle.m +++ b/Source/SPFileHandle.m @@ -414,7 +414,7 @@ } // Copy the data into a local buffer - NSData *dataToBeWritten = [NSData dataWithData:buffer]; + NSData *dataToBeWritten = [buffer copy]; [buffer setLength:0]; bufferDataLength = 0; pthread_mutex_unlock(&bufferLock); @@ -453,6 +453,8 @@ allDataWritten = YES; } pthread_mutex_unlock(&bufferLock); + + [dataToBeWritten release]; } [writePool drain]; diff --git a/Source/SPFontPreviewTextField.h b/Source/SPFontPreviewTextField.h index f6383f77..edadbab0 100644 --- a/Source/SPFontPreviewTextField.h +++ b/Source/SPFontPreviewTextField.h @@ -4,9 +4,6 @@ // SPFontPreviewTextField.h // sequel-pro // -// This is a heavily modified version of JVFontPreviewField from -// the Colloquy Project <http://colloquy.info/> -// // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or @@ -24,9 +21,6 @@ // More info at <http://code.google.com/p/sequel-pro/> @interface SPFontPreviewTextField : NSTextField -{ - NSFont *_actualFont; -} - (void)setFont:(NSFont *)font; diff --git a/Source/SPFontPreviewTextField.m b/Source/SPFontPreviewTextField.m index 90e9f93f..60243233 100644 --- a/Source/SPFontPreviewTextField.m +++ b/Source/SPFontPreviewTextField.m @@ -4,9 +4,6 @@ // SPFontPreviewTextField.m // sequel-pro // -// This is a heavily modified version of JVFontPreviewField from -// the Colloquy Project <http://colloquy.info/> -// // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or @@ -27,37 +24,39 @@ @implementation SPFontPreviewTextField -- (void)setFont:(NSFont *)font +/** + * Add a method to set the font to use for the preview. The font metrics + * are applied to the textField, and the font name is displayed in the textField + * for an easy preview. + */ +- (void)setFont:(NSFont *)theFont { - if (!font) return; - - if (_actualFont) [_actualFont release]; - - _actualFont = [font retain]; - [super setFont:[[NSFontManager sharedFontManager] convertFont:font toSize:11.0f]]; + // If no font was supplied, clear the preview + if (!theFont) { + [self setObjectValue:@""]; + return; + } - NSMutableAttributedString *text = [[NSMutableAttributedString alloc] initWithString:[_actualFont displayName]]; - NSMutableParagraphStyle *paraStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; + // Take the supplied font and apply all its traits except for a standardised + // font size to the text field + NSFont *displayFont = [[NSFontManager sharedFontManager] convertFont:theFont toSize:11.0f]; + [super setFont:displayFont]; - [paraStyle setMinimumLineHeight:NSHeight([self bounds])]; - [paraStyle setMaximumLineHeight:NSHeight([self bounds])]; - - [text addAttribute:NSParagraphStyleAttributeName value:paraStyle range:NSMakeRange(0, [text length])]; + // Set up a paragraph style for display, setting bounds and display settings + NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle new] autorelease]; + [paragraphStyle setAlignment:NSNaturalTextAlignment]; + [paragraphStyle setLineBreakMode:NSLineBreakByTruncatingMiddle]; + [paragraphStyle setMaximumLineHeight:NSHeight([self bounds]) + [displayFont descender]]; - [self setObjectValue:text]; - - [text release]; - [paraStyle release]; -} + // Set up the text to display - the font display name and the point size. + NSMutableAttributedString *displayString = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@, %.1f pt", [theFont displayName], [theFont pointSize]]]; -#pragma mark - + // Apply the paragraph style + [displayString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0, [displayString length])]; -- (void)dealloc -{ - if (_actualFont) [_actualFont release], _actualFont = nil; - - [super dealloc]; + // Update the display + [self setObjectValue:displayString]; } @end diff --git a/Source/SPHistoryController.m b/Source/SPHistoryController.m index 3bd3b0b3..2729397c 100644 --- a/Source/SPHistoryController.m +++ b/Source/SPHistoryController.m @@ -267,7 +267,7 @@ NSString *contentSortCol = [tableContentInstance sortColumnName]; BOOL contentSortColIsAsc = [tableContentInstance sortColumnIsAscending]; NSUInteger contentPageNumber = [tableContentInstance pageNumber]; - NSIndexSet *contentSelectedIndexSet = [tableContentInstance selectedRowIndexes]; + NSDictionary *contentSelectedRows = [tableContentInstance selectionDetailsAllowingIndexSelection:YES]; NSRect contentViewport = [tableContentInstance viewport]; NSDictionary *contentFilter = [tableContentInstance filterSettings]; NSData *filterTableData = [tableContentInstance filterTableData]; @@ -283,7 +283,7 @@ [NSNumber numberWithBool:contentSortColIsAsc], @"sortIsAsc", nil]; if (contentSortCol) [contentState setObject:contentSortCol forKey:@"sortCol"]; - if (contentSelectedIndexSet) [contentState setObject:contentSelectedIndexSet forKey:@"selection"]; + if (contentSelectedRows) [contentState setObject:contentSelectedRows forKey:@"selection"]; if (contentFilter) [contentState setObject:contentFilter forKey:@"filter"]; if (filterTableData) [contentState setObject:filterTableData forKey:@"filterTable"]; @@ -322,7 +322,7 @@ || ![[currentHistoryEntry objectForKey:@"contentFilter"] isEqualToDictionary:contentFilter]))) { [currentHistoryEntry setObject:[NSValue valueWithRect:contentViewport] forKey:@"contentViewport"]; - if (contentSelectedIndexSet) [currentHistoryEntry setObject:contentSelectedIndexSet forKey:@"contentSelectedIndexSet"]; + if (contentSelectedRows) [currentHistoryEntry setObject:contentSelectedRows forKey:@"contentSelection"]; // Special case: if the last history item is currently active, and has no table, // but the new selection does - delete the last entry, in order to replace it. @@ -344,7 +344,7 @@ [NSValue valueWithRect:contentViewport], @"contentViewport", nil]; if (contentSortCol) [newEntry setObject:contentSortCol forKey:@"contentSortCol"]; - if (contentSelectedIndexSet) [newEntry setObject:contentSelectedIndexSet forKey:@"contentSelectedIndexSet"]; + if (contentSelectedRows) [newEntry setObject:contentSelectedRows forKey:@"contentSelection"]; if (contentFilter) [newEntry setObject:contentFilter forKey:@"contentFilter"]; [history addObject:newEntry]; @@ -397,7 +397,7 @@ // Set table content details for restore [tableContentInstance setSortColumnNameToRestore:[historyEntry objectForKey:@"contentSortCol"] isAscending:[[historyEntry objectForKey:@"contentSortColIsAsc"] boolValue]]; [tableContentInstance setPageToRestore:[[historyEntry objectForKey:@"contentPageNumber"] integerValue]]; - [tableContentInstance setSelectedRowIndexesToRestore:[historyEntry objectForKey:@"contentSelectedIndexSet"]]; + [tableContentInstance setSelectionToRestore:[historyEntry objectForKey:@"contentSelection"]]; [tableContentInstance setViewportToRestore:[[historyEntry objectForKey:@"contentViewport"] rectValue]]; [tableContentInstance setFiltersToRestore:[historyEntry objectForKey:@"contentFilter"]]; @@ -514,7 +514,7 @@ // Restore the content view state [tableContentInstance setSortColumnNameToRestore:[contentState objectForKey:@"sortCol"] isAscending:[[contentState objectForKey:@"sortIsAsc"] boolValue]]; [tableContentInstance setPageToRestore:[[contentState objectForKey:@"page"] unsignedIntegerValue]]; - [tableContentInstance setSelectedRowIndexesToRestore:[contentState objectForKey:@"selection"]]; + [tableContentInstance setSelectionToRestore:[contentState objectForKey:@"selection"]]; [tableContentInstance setViewportToRestore:[[contentState objectForKey:@"viewport"] rectValue]]; [tableContentInstance setFiltersToRestore:[contentState objectForKey:@"filter"]]; } diff --git a/Source/SPIndexesController.m b/Source/SPIndexesController.m index def31ea2..44de06c8 100644 --- a/Source/SPIndexesController.m +++ b/Source/SPIndexesController.m @@ -28,7 +28,7 @@ #import "SPServerSupport.h" #import "SPTableContent.h" #import "SPTableData.h" -#import "SPMySQL.h" +#import <SPMySQL/SPMySQL.h> #import "SPDatabaseDocument.h" #import "SPTablesList.h" #import "SPTableView.h" @@ -176,22 +176,7 @@ static const NSString *SPNewIndexKeyBlockSize = @"IndexKeyBlockSize"; // Check to see whether a primary key already exists for the table, and if so select INDEX instead for (NSDictionary *field in fields) { - BOOL hasCompositePrimaryKey = NO; - BOOL isPrimaryKey = [[field objectForKey:@"isprimarykey"] boolValue]; - - // The 'isprimarykey' key of a field is only present for single column primary keys, not composite keys, - // so we need to check the indexes manually. - if (!isPrimaryKey) { - for (NSDictionary *index in indexes) - { - if ([[index objectForKey:@"Key_name"] isEqualToString:@"PRIMARY"]) { - hasCompositePrimaryKey = YES; - break; - } - } - } - - if (isPrimaryKey || hasCompositePrimaryKey) { + if ([[field objectForKey:@"isprimarykey"] boolValue]) { // Hide primary key option [[[indexTypePopUpButton menu] itemWithTag:SPPrimaryKeyMenuTag] setHidden:YES]; diff --git a/Source/SPLogger.m b/Source/SPLogger.m index 40bcaa69..8b808d39 100644 --- a/Source/SPLogger.m +++ b/Source/SPLogger.m @@ -33,10 +33,11 @@ static SPLogger *logger = nil; -@interface SPLogger (PrivateAPI) +@interface SPLogger () - (void)_initLogFile; - (void)_outputTimeString; + int _isSPLeaksLog(struct direct *entry); @end diff --git a/Source/SPNarrowDownCompletion.m b/Source/SPNarrowDownCompletion.m index 4dc2711d..db3f5e3a 100644 --- a/Source/SPNarrowDownCompletion.m +++ b/Source/SPNarrowDownCompletion.m @@ -153,11 +153,11 @@ - (void)dealloc { [NSObject cancelPreviousPerformRequestsWithTarget:self]; - if(stateTimer != nil) { + if (stateTimer != nil) { [stateTimer invalidate]; [stateTimer release]; + stateTimer = nil; } - stateTimer = nil; if (staticPrefix) [staticPrefix release]; [mutablePrefix release]; [textualInputCharacters release]; @@ -178,6 +178,7 @@ if (stateTimer != nil) { [stateTimer invalidate]; [stateTimer release]; + stateTimer = nil; } closeMe = YES; diff --git a/Source/SPNavigatorController.m b/Source/SPNavigatorController.m index 2b27a598..54f817cc 100644 --- a/Source/SPNavigatorController.m +++ b/Source/SPNavigatorController.m @@ -33,7 +33,7 @@ #import "SPTooltip.h" #import "SPAppController.h" #import "SPDatabaseViewController.h" -#import "SPMySQL.h" +#import <SPMySQL/SPMySQL.h> #import "SPDatabaseStructure.h" #import <objc/message.h> @@ -581,10 +581,10 @@ static NSComparisonResult compareStrings(NSString *s1, NSString *s2, void* conte { // Reset everything for current active doc connection - id doc = [[NSApp delegate] frontDocument]; - if(!doc) return; + SPDatabaseDocument *doc = [[NSApp delegate] frontDocument]; + if (!doc) return; NSString *connectionID = [doc connectionID]; - if(!connectionID || [connectionID length] < 2) return; + if (!connectionID || [connectionID length] < 2) return; [searchField setStringValue:@""]; [schemaDataFiltered removeAllObjects]; @@ -600,8 +600,8 @@ static NSComparisonResult compareStrings(NSString *s1, NSString *s2, void* conte [syncButton setState:NSOffState]; isFiltered = NO; - if(![[doc valueForKeyPath:@"mySQLConnection"] isConnected]) return; - [NSThread detachNewThreadSelector:@selector(queryDbStructureWithUserInfo:) toTarget:[doc valueForKeyPath:@"mySQLConnection"] withObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], @"forceUpdate", nil]]; + if (![[doc getConnection] isConnected]) return; + [NSThread detachNewThreadSelector:@selector(queryDbStructureWithUserInfo:) toTarget:[doc databaseStructureRetrieval] withObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], @"forceUpdate", nil]]; } diff --git a/Source/SPPrintController.m b/Source/SPPrintController.m index ad297fee..a3a0a343 100644 --- a/Source/SPPrintController.m +++ b/Source/SPPrintController.m @@ -251,7 +251,7 @@ // Table content view else if ([tableTabView indexOfTabViewItem:[tableTabView selectedTabViewItem]] == 1) { - NSArray *data = [tableContentInstance currentDataResultWithNULLs:NO]; + NSArray *data = [tableContentInstance currentDataResultWithNULLs:NO hideBLOBs:YES]; heading = NSLocalizedString(@"Table Content", @"table content print heading"); diff --git a/Source/SPProcessListController.m b/Source/SPProcessListController.m index c8e1f312..5b68586d 100644 --- a/Source/SPProcessListController.m +++ b/Source/SPProcessListController.m @@ -27,7 +27,7 @@ #import "SPDatabaseDocument.h" #import "SPAlertSheets.h" #import "SPAppController.h" -#import "SPMySQL.h" +#import <SPMySQL/SPMySQL.h> // Constants static NSString *SPKillProcessQueryMode = @"SPKillProcessQueryMode"; @@ -799,12 +799,12 @@ static NSString *SPTableViewIDColumnIdentifier = @"Id"; // Perform filtering for (NSDictionary *process in processes) { - if (([[process objectForKey:@"Id"] rangeOfString:filterString options:NSCaseInsensitiveSearch].location != NSNotFound) || + if (([[[process objectForKey:@"Id"] stringValue] rangeOfString:filterString options:NSCaseInsensitiveSearch].location != NSNotFound) || ([[process objectForKey:@"User"] rangeOfString:filterString options:NSCaseInsensitiveSearch].location != NSNotFound) || ([[process objectForKey:@"Host"] rangeOfString:filterString options:NSCaseInsensitiveSearch].location != NSNotFound) || ((![[process objectForKey:@"db"] isNSNull]) && ([[process objectForKey:@"db"] rangeOfString:filterString options:NSCaseInsensitiveSearch].location != NSNotFound)) || ([[process objectForKey:@"Command"] rangeOfString:filterString options:NSCaseInsensitiveSearch].location != NSNotFound) || - ([[process objectForKey:@"Time"] rangeOfString:filterString options:NSCaseInsensitiveSearch].location != NSNotFound) || + ([[[process objectForKey:@"Time"] stringValue] rangeOfString:filterString options:NSCaseInsensitiveSearch].location != NSNotFound) || ((![[process objectForKey:@"State"] isNSNull]) && ([[process objectForKey:@"State"] rangeOfString:filterString options:NSCaseInsensitiveSearch].location != NSNotFound)) || ((![[process objectForKey:@"Info"] isNSNull]) && ([[process objectForKey:@"Info"] rangeOfString:filterString options:NSCaseInsensitiveSearch].location != NSNotFound))) { diff --git a/Source/SPQueryController.m b/Source/SPQueryController.m index 6c425bc7..8a2a5902 100644 --- a/Source/SPQueryController.m +++ b/Source/SPQueryController.m @@ -403,6 +403,7 @@ static SPQueryController *sharedQueryController = nil; #endif } +#ifndef SP_REFACTOR /** * Return the AutoSaveName of the Query Console. */ @@ -410,6 +411,7 @@ static SPQueryController *sharedQueryController = nil; { return SPQueryConsoleWindowAutoSaveName; } +#endif /** * Updates the filtered result set based on any filter string and whether or not diff --git a/Source/SPQueryFavoriteManager.m b/Source/SPQueryFavoriteManager.m index 1b5ce3dc..625f9855 100644 --- a/Source/SPQueryFavoriteManager.m +++ b/Source/SPQueryFavoriteManager.m @@ -38,6 +38,8 @@ #define SP_MULTIPLE_SELECTION_PLACEHOLDER_STRING NSLocalizedString(@"[multiple selection]", @"[multiple selection]") #define SP_NO_SELECTION_PLACEHOLDER_STRING NSLocalizedString(@"[no selection]", @"[no selection]") +#define SP_Int(x) [NSNumber numberWithInteger:x] + @interface SPQueryFavoriteManager (Private) - (void)_initWithNoSelection; @@ -285,6 +287,7 @@ */ - (IBAction)saveFavoriteToFile:(id)sender { +#ifndef SP_REFACTOR NSSavePanel *panel = [NSSavePanel savePanel]; [panel setAllowedFileTypes:[NSArray arrayWithObject:SPFileExtensionSQL]]; @@ -294,17 +297,17 @@ [panel setCanSelectHiddenExtension:YES]; [panel setCanCreateDirectories:YES]; -#ifndef SP_REFACTOR [panel setAccessoryView:[SPEncodingPopupAccessory encodingAccessory:[prefs integerForKey:SPLastSQLFileEncoding] includeDefaultEntry:NO encodingPopUp:&encodingPopUp]]; -#endif [encodingPopUp setEnabled:YES]; [panel beginSheetForDirectory:nil file:[favoriteNameTextField stringValue] modalForWindow:[self window] modalDelegate:self didEndSelector:@selector(savePanelDidEnd:returnCode:contextInfo:) contextInfo:@"saveQuery"]; +#endif } - (IBAction)exportFavorites:(id)sender { +#ifndef SP_REFACTOR NSSavePanel *panel = [NSSavePanel savePanel]; [panel setAllowedFileTypes:[NSArray arrayWithObject:SPFileExtensionDefault]]; @@ -315,10 +318,12 @@ [panel setCanCreateDirectories:YES]; [panel beginSheetForDirectory:nil file:nil modalForWindow:[self window] modalDelegate:self didEndSelector:@selector(savePanelDidEnd:returnCode:contextInfo:) contextInfo:@"exportFavorites"]; +#endif } - (IBAction)importFavoritesByAdding:(id)sender { +#ifndef SP_REFACTOR NSOpenPanel *panel = [NSOpenPanel openPanel]; [panel setCanSelectHiddenExtension:YES]; [panel setDelegate:self]; @@ -333,6 +338,7 @@ modalDelegate:self didEndSelector:@selector(importPanelDidEnd:returnCode:contextInfo:) contextInfo:NULL]; +#endif } - (IBAction)importFavoritesByReplacing:(id)sender @@ -345,7 +351,70 @@ */ - (IBAction)insertPlaceholder:(id)sender { - NSString *placeholder = [[[sender selectedItem] toolTip] substringToIndex:[[[sender selectedItem] toolTip] rangeOfString:@" – "].location]; + + // Look up the sender's tag to determine the placeholder to insert. + // Note that tag values alter behaviour slightly - see below. + NSDictionary *lookupTable = [NSDictionary dictionaryWithObjectsAndKeys: + NSLocalizedString(@"default_value", @"Query snippet default value placeholder"), SP_Int(100), + NSLocalizedString(@"$(shell_command)", @"Query snippet shell command syntax and placeholder"), SP_Int(101), + @"$1", SP_Int(501), + @"¦a¦b¦", SP_Int(102), + @"¦¦a¦b¦¦", SP_Int(103), + @"¦", SP_Int(104), + @"$SP_SELECTED_TABLE", SP_Int(105), + @"$SP_SELECTED_TABLES", SP_Int(106), + @"$SP_SELECTED_DATABASE", SP_Int(107), + @"¦$SP_ASLIST_ALL_FIELDS¦", SP_Int(108), + @"¦¦$SP_ASLIST_ALL_FIELDS¦¦", SP_Int(109), + @"¦$SP_ASLIST_ALL_TABLES¦", SP_Int(110), + @"¦¦$SP_ASLIST_ALL_TABLES¦¦", SP_Int(111), + @"¦$SP_ASLIST_ALL_DATABASES¦", SP_Int(112), + @"¦¦$SP_ASLIST_ALL_DATABASES¦¦", SP_Int(113), + nil]; + NSString *placeholder = [lookupTable objectForKey:SP_Int([[sender selectedItem] tag])]; + if (!placeholder) [NSException raise:NSInternalInconsistencyException format:@"Inserted placeholder (%lld) not found", (long long)[[sender selectedItem] tag]]; + + // Iterate through the current snippets, to get the lowest unused tab counter, and + // to determine whether the current selection is inside a tab snippet or not + NSMutableDictionary *snippetNumbers = [NSMutableDictionary dictionary]; + BOOL selectionInsideSnippet = NO; + NSUInteger rangeStart = 0; + NSString *queryString = [[favoriteQueryTextView textStorage] string]; + NSRange selRange = [favoriteQueryTextView selectedRange]; + NSString *snipRegex = @"(?s)(?<!\\\\)\\$\\{(1?\\d):(.{0}|[^\\{\\}]*?[^\\\\])\\}"; + while (true) { + NSRange matchedRange = [queryString rangeOfRegex:snipRegex inRange:NSMakeRange(rangeStart, [queryString length] - rangeStart)]; + if (matchedRange.location == NSNotFound) break; + + // Check whether the selection range lies within the snippet + if (selRange.location != NSNotFound + && selRange.location > matchedRange.location + 1 + && selRange.location + selRange.length < matchedRange.location + matchedRange.length) + { + selectionInsideSnippet = YES; + } + + // Identify the tab completion index + NSRange snippetNumberRange = [queryString rangeOfRegex:snipRegex options:RKLNoOptions inRange:matchedRange capture:1L error:NULL]; + NSInteger snippetNumber = [[queryString substringWithRange:snippetNumberRange] integerValue]; + [snippetNumbers setObject:[NSNumber numberWithBool:YES] forKey:[NSNumber numberWithInteger:snippetNumber]]; + + rangeStart = matchedRange.location + matchedRange.length; + } + + // If the selection is not inside a snippet, wrap it inside the snippet syntax. + // Never do this for items with a tag above 500: these are not permitted inside a snippet. + if (!selectionInsideSnippet && [[sender selectedItem] tag] < 500) { + + // Work out the lowest unused tab counter to use + NSInteger snippetNumber = 0; + while ([snippetNumbers objectForKey:[NSNumber numberWithInteger:snippetNumber]]) { + snippetNumber++; + } + + placeholder = [NSString stringWithFormat:@"${%lld:%@}", (long long)snippetNumber, placeholder]; + } + [favoriteQueryTextView insertText:placeholder]; } @@ -740,6 +809,7 @@ */ - (void)importPanelDidEnd:(NSOpenPanel *)panel returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo { +#ifndef SP_REFACTOR if (returnCode == NSOKButton) { @@ -798,6 +868,7 @@ } } } +#endif } @@ -806,15 +877,14 @@ */ - (void)savePanelDidEnd:(NSSavePanel *)panel returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo { +#ifndef SP_REFACTOR if([contextInfo isEqualToString:@"saveQuery"]) { if (returnCode == NSOKButton) { NSError *error = nil; -#ifndef SP_REFACTOR [prefs setInteger:[[encodingPopUp selectedItem] tag] forKey:SPLastSQLFileEncoding]; [prefs synchronize]; -#endif [[favoriteQueryTextView string] writeToURL:[panel URL] atomically:YES encoding:[[encodingPopUp selectedItem] tag] error:&error]; @@ -866,6 +936,7 @@ } } +#endif } - (void)_initWithNoSelection diff --git a/Source/SPSQLExporter.m b/Source/SPSQLExporter.m index 607f34d5..dab27872 100644 --- a/Source/SPSQLExporter.m +++ b/Source/SPSQLExporter.m @@ -29,7 +29,7 @@ #import "SPExportUtilities.h" #import "SPExportFile.h" #import "SPTableData.h" -#import "SPMySQL.h" +#import <SPMySQL/SPMySQL.h> @interface SPSQLExporter (PrivateAPI) @@ -106,7 +106,6 @@ NSMutableArray *funcs = [NSMutableArray array]; NSMutableString *metaString = [NSMutableString string]; - NSMutableString *cellValue = [NSMutableString string]; NSMutableString *errors = [[NSMutableString alloc] init]; NSMutableString *sqlString = [[NSMutableString alloc] init]; @@ -280,6 +279,8 @@ { // Check for cancellation flag if ([self isCancelled]) { + [errors release]; + [sqlString release]; [pool release]; return; } @@ -338,50 +339,58 @@ [streamingResult cancelResultLoad]; [streamingResult release]; [sqlExportPool release]; + [errors release]; + [sqlString release]; [pool release]; - + return; } - + j++; k++; - + [sqlString setString:@""]; - - // Update the progress + + // Update the progress NSUInteger progress = (NSUInteger)(j * ([self exportMaxProgress] / rowCount)); - + if (progress > lastProgressValue) { [self setExportProgressValue:progress]; lastProgressValue = progress; - + // Inform the delegate that the export's progress has been updated [delegate performSelectorOnMainThread:@selector(sqlExportProcessProgressUpdated:) withObject:self waitUntilDone:NO]; } - - for (t = 0; t < colCount; t++) + + for (t = 0; t < colCount; t++) { // Check for cancellation flag if ([self isCancelled]) { + [connection cancelCurrentQuery]; + [streamingResult cancelResultLoad]; + [streamingResult release]; [sqlExportPool release]; + [errors release]; + [sqlString release]; [pool release]; - + return; } - + id object = NSArrayObjectAtIndex(row, t); - - // Add NULL values directly to the output row + + // Add NULL values directly to the output row; use a pointer comparison to the singleton + // instance for speed. if (object == [NSNull null]) { [sqlString appendString:@"NULL"]; - } + } // If the field is off type BIT, the values need a binary prefix of b'x'. else if ([[NSArrayObjectAtIndex([tableDetails objectForKey:@"columns"], t) objectForKey:@"type"] isEqualToString:@"BIT"]) { [sqlString appendFormat:@"b'%@'", [object description]]; } // Add data types directly as hex data else if ([object isKindOfClass:[NSData class]]) { - + if ([self sqlOutputEncodeBLOBasHex]) { [sqlString appendString:[connection escapeAndQuoteData:object]]; } @@ -407,19 +416,18 @@ [sqlString appendString:[connection escapeAndQuoteData:[object data]]]; } else { - [cellValue setString:[object description]]; // Add empty strings as a pair of quotes - if ([cellValue length] == 0) { + if ([object length] == 0) { [sqlString appendString:@"''"]; } else { if ([NSArrayObjectAtIndex(tableColumnNumericStatus, t) boolValue]) { - [sqlString appendString:cellValue]; + [sqlString appendString:object]; } // Otherwise add a quoted string with special characters escaped else { - [sqlString appendString:[connection escapeAndQuoteString:cellValue]]; + [sqlString appendString:[connection escapeAndQuoteString:object]]; } } } @@ -486,56 +494,59 @@ } } } + + // Add triggers if the structure export was enabled + if (sqlOutputIncludeStructure) { + queryResult = [connection queryString:[NSString stringWithFormat:@"/*!50003 SHOW TRIGGERS WHERE `Table` = %@ */", [tableName tickQuotedString]]]; - queryResult = [connection queryString:[NSString stringWithFormat:@"/*!50003 SHOW TRIGGERS WHERE `Table` = %@ */;", [tableName tickQuotedString]]]; - - [queryResult setReturnDataAsStrings:YES]; - - if ([queryResult numberOfRows]) { - - [metaString setString:@"\n"]; - [metaString appendString:@"DELIMITER ;;\n"]; + [queryResult setReturnDataAsStrings:YES]; - for (s = 0; s < [queryResult numberOfRows]; s++) - { - // Check for cancellation flag - if ([self isCancelled]) { - [errors release]; - [sqlString release]; - [pool release]; - return; + if ([queryResult numberOfRows]) { + + [metaString setString:@"\n"]; + [metaString appendString:@"DELIMITER ;;\n"]; + + for (s = 0; s < [queryResult numberOfRows]; s++) + { + // Check for cancellation flag + if ([self isCancelled]) { + [errors release]; + [sqlString release]; + [pool release]; + return; + } + + NSDictionary *triggers = [[NSDictionary alloc] initWithDictionary:[queryResult getRowAsDictionary]]; + + // Definer is user@host but we need to escape it to `user`@`host` + NSArray *triggersDefiner = [[triggers objectForKey:@"Definer"] componentsSeparatedByString:@"@"]; + + [metaString appendFormat:@"/*!50003 SET SESSION SQL_MODE=\"%@\" */;;\n/*!50003 CREATE */ ", [triggers objectForKey:@"sql_mode"]]; + [metaString appendFormat:@"/*!50017 DEFINER=%@@%@ */ /*!50003 TRIGGER %@ %@ %@ ON %@ FOR EACH ROW %@ */;;\n", + [NSArrayObjectAtIndex(triggersDefiner, 0) backtickQuotedString], + [NSArrayObjectAtIndex(triggersDefiner, 1) backtickQuotedString], + [[triggers objectForKey:@"Trigger"] backtickQuotedString], + [triggers objectForKey:@"Timing"], + [triggers objectForKey:@"Event"], + [[triggers objectForKey:@"Table"] backtickQuotedString], + [triggers objectForKey:@"Statement"] + ]; + + [triggers release]; } - NSDictionary *triggers = [[NSDictionary alloc] initWithDictionary:[queryResult getRowAsDictionary]]; + [metaString appendString:@"DELIMITER ;\n/*!50003 SET SESSION SQL_MODE=@OLD_SQL_MODE */;\n"]; - // Definer is user@host but we need to escape it to `user`@`host` - NSArray *triggersDefiner = [[triggers objectForKey:@"Definer"] componentsSeparatedByString:@"@"]; - - [metaString appendFormat:@"/*!50003 SET SESSION SQL_MODE=\"%@\" */;;\n/*!50003 CREATE */ ", [triggers objectForKey:@"sql_mode"]]; - [metaString appendFormat:@"/*!50017 DEFINER=%@@%@ */ /*!50003 TRIGGER %@ %@ %@ ON %@ FOR EACH ROW %@ */;;\n", - [NSArrayObjectAtIndex(triggersDefiner, 0) backtickQuotedString], - [NSArrayObjectAtIndex(triggersDefiner, 1) backtickQuotedString], - [[triggers objectForKey:@"Trigger"] backtickQuotedString], - [triggers objectForKey:@"Timing"], - [triggers objectForKey:@"Event"], - [[triggers objectForKey:@"Table"] backtickQuotedString], - [triggers objectForKey:@"Statement"] - ]; - - [triggers release]; + [[self exportOutputFile] writeData:[metaString dataUsingEncoding:NSUTF8StringEncoding]]; } - [metaString appendString:@"DELIMITER ;\n/*!50003 SET SESSION SQL_MODE=@OLD_SQL_MODE */;\n"]; - - [[self exportOutputFile] writeData:[metaString dataUsingEncoding:NSUTF8StringEncoding]]; - } - - if ([connection queryErrored]) { - [errors appendFormat:@"%@\n", [connection lastErrorMessage]]; - - if ([self sqlOutputIncludeErrors]) { - [[self exportOutputFile] writeData:[[NSString stringWithFormat:@"# Error: %@\n", [connection lastErrorMessage]] - dataUsingEncoding:NSUTF8StringEncoding]]; + if ([connection queryErrored]) { + [errors appendFormat:@"%@\n", [connection lastErrorMessage]]; + + if ([self sqlOutputIncludeErrors]) { + [[self exportOutputFile] writeData:[[NSString stringWithFormat:@"# Error: %@\n", [connection lastErrorMessage]] + dataUsingEncoding:NSUTF8StringEncoding]]; + } } } @@ -583,7 +594,7 @@ if ([items count] == 0) continue; // Retrieve the definitions - queryResult = [connection queryString:[NSString stringWithFormat:@"/*!50003 SHOW %@ STATUS WHERE `Db` = %@ */;", procedureType, + queryResult = [connection queryString:[NSString stringWithFormat:@"/*!50003 SHOW %@ STATUS WHERE `Db` = %@ */", procedureType, [[self sqlDatabaseName] tickQuotedString]]]; [queryResult setReturnDataAsStrings:YES]; @@ -658,7 +669,7 @@ [NSArrayObjectAtIndex(procedureDefiner, 1) backtickQuotedString] ]; - SPMySQLResult *createProcedureResult = [connection queryString:[NSString stringWithFormat:@"/*!50003 SHOW CREATE %@ %@ */;;", procedureType, + SPMySQLResult *createProcedureResult = [connection queryString:[NSString stringWithFormat:@"/*!50003 SHOW CREATE %@ %@ */", procedureType, [procedureName backtickQuotedString]]]; [createProcedureResult setReturnDataAsStrings:YES]; if ([connection queryErrored]) { @@ -824,7 +835,8 @@ // Provide the field default if appropriate if ([column objectForKey:@"default"]) { - // Some MySQL server versions show a default of NULL for NOT NULL columns - don't export those + // Some MySQL server versions show a default of NULL for NOT NULL columns - don't export those. + // Check against the NSNull singleton instance for speed. if ([column objectForKey:@"default"] == [NSNull null]) { if ([[column objectForKey:@"null"] integerValue]) { [fieldString appendString:@" DEFAULT NULL"]; @@ -862,6 +874,7 @@ [sqlDatabaseName release], sqlDatabaseName = nil; [sqlExportCurrentTable release], sqlExportCurrentTable = nil; [sqlDatabaseVersion release], sqlDatabaseVersion = nil; + [sqlExportErrors release], sqlExportErrors = nil; [super dealloc]; } diff --git a/Source/SPSSHTunnel.h b/Source/SPSSHTunnel.h index 231a41a6..0984cd43 100644 --- a/Source/SPSSHTunnel.h +++ b/Source/SPSSHTunnel.h @@ -23,8 +23,8 @@ // // More info at <http://code.google.com/p/sequel-pro/> -#import "SPMySQLConnectionProxy.h" -#import "SPMySQLConstants.h" +#import <SPMySQL/SPMySQLConnectionProxy.h> +#import <SPMySQL/SPMySQLConstants.h> @interface SPSSHTunnel : NSObject <SPMySQLConnectionProxy> { diff --git a/Source/SPSSHTunnel.m b/Source/SPSSHTunnel.m index 1d4870b2..aae71891 100644 --- a/Source/SPSSHTunnel.m +++ b/Source/SPSSHTunnel.m @@ -27,7 +27,7 @@ #import "RegexKitLite.h" #import "SPKeychain.h" #import "SPAlertSheets.h" -#import "SPMySQL.h" +#import <SPMySQL/SPMySQL.h> #import <netinet/in.h> diff --git a/Source/SPServerSupport.h b/Source/SPServerSupport.h index fd4535a2..ee75af31 100644 --- a/Source/SPServerSupport.h +++ b/Source/SPServerSupport.h @@ -59,6 +59,7 @@ // User account related BOOL supportsCreateUser; + BOOL supportsRenameUser; BOOL supportsDropUser; BOOL supportsFullDropUser; BOOL supportsUserMaxVars; @@ -153,6 +154,11 @@ @property (readonly) BOOL supportsCreateUser; /** + * @property supportsRenameUser Indicates if the server supports the RENAME USER statement + */ +@property (readonly) BOOL supportsRenameUser; + +/** * @property supportsDropUser Indicates if the server supports the DROP USER statement */ @property (readonly) BOOL supportsDropUser; diff --git a/Source/SPServerSupport.m b/Source/SPServerSupport.m index 3d38ff79..5ddfbaf6 100644 --- a/Source/SPServerSupport.m +++ b/Source/SPServerSupport.m @@ -50,6 +50,7 @@ @synthesize supportsCharacterSetDatabaseVar; @synthesize supportsPost41CharacterSetHandling; @synthesize supportsCreateUser; +@synthesize supportsRenameUser; @synthesize supportsDropUser; @synthesize supportsFullDropUser; @synthesize supportsUserMaxVars; @@ -141,6 +142,9 @@ // The CREATE USER statement wasn't added until MySQL 5.0.2 supportsCreateUser = [self isEqualToOrGreaterThanMajorVersion:5 minor:0 release:2]; + // The RENAME USER statement wasn't added until MySQL 5.0.2 + supportsRenameUser = [self isEqualToOrGreaterThanMajorVersion:5 minor:0 release:2]; + // The DROP USER statement wasn't added until MySQL 4.1.1 supportsDropUser = [self isEqualToOrGreaterThanMajorVersion:4 minor:1 release:1]; @@ -249,6 +253,7 @@ supportsCharacterSetDatabaseVar = NO; supportsPost41CharacterSetHandling = NO; supportsCreateUser = NO; + supportsRenameUser = NO; supportsDropUser = NO; supportsFullDropUser = NO; supportsUserMaxVars = NO; diff --git a/Source/SPServerVariablesController.m b/Source/SPServerVariablesController.m index 50f03e31..4cb85e5c 100644 --- a/Source/SPServerVariablesController.m +++ b/Source/SPServerVariablesController.m @@ -26,7 +26,7 @@ #import "SPServerVariablesController.h" #import "SPDatabaseDocument.h" #import "SPAppController.h" -#import "SPMySQL.h" +#import <SPMySQL/SPMySQL.h> @interface SPServerVariablesController (PrivateAPI) diff --git a/Source/SPTableContent.h b/Source/SPTableContent.h index a95dbae3..7e77c749 100644 --- a/Source/SPTableContent.h +++ b/Source/SPTableContent.h @@ -25,8 +25,22 @@ // // More info at <http://code.google.com/p/sequel-pro/> -@class SPDatabaseDocument, SPCopyTable, SPTextAndLinkCell, SPHistoryController, SPTableInfo, SPDataStorage, SPTextView, SPFieldEditorController, SPMySQLConnection, SPMySQLFastStreamingResult; -@class SPTableData, SPDatabaseDocument, SPTablesList, SPTableStructure, SPTableList, SPContentFilterManager; +@class SPDatabaseDocument, + SPCopyTable, + SPTextAndLinkCell, + SPHistoryController, + SPTableInfo, + SPDataStorage, + SPTextView, + SPFieldEditorController, + SPMySQLConnection, + SPMySQLFastStreamingResult, + SPTableData, + SPDatabaseDocument, + SPTablesList, + SPTableStructure, + SPTableList, + SPContentFilterManager; @interface SPTableContent : NSObject #ifdef SP_REFACTOR @@ -129,7 +143,7 @@ BOOL tableRowsSelectable; NSString *sortColumnToRestore; NSUInteger pageToRestore; - NSIndexSet *selectionIndexToRestore; + NSDictionary *selectionToRestore; NSRect selectionViewportToRestore; NSString *filterFieldToRestore, *filterComparisonToRestore, *filterValueToRestore, *firstBetweenValueToRestore, *secondBetweenValueToRestore; @@ -155,35 +169,57 @@ NSRange fieldEditorSelectedRange; } +#ifdef SP_REFACTOR /* glue */ +@property (assign) id filterButton; +@property (assign) id fieldField; +@property (assign) id compareField; +@property (assign) id betweenTextField; +@property (assign) id firstBetweenField; +@property (assign) id secondBetweenField; +@property (assign) id argumentField; +@property (assign) NSButton* addButton; +@property (assign) NSButton* duplicateButton; +@property (assign) NSButton* removeButton; +@property (assign) NSButton* reloadButton; +@property (assign) NSButton* paginationNextButton; +@property (assign) NSButton* paginationPreviousButton; +@property (assign) NSTextField* paginationPageField; +@property (assign) SPDatabaseDocument* tableDocumentInstance; +@property (assign) SPTablesList* tablesListInstance; +@property (assign) SPCopyTable* tableContentView; +@property (assign) SPTableData* tableDataInstance; +@property (assign) SPTableStructure* tableSourceInstance; +#endif + - (void)setFieldEditorSelectedRange:(NSRange)aRange; - (NSRange)fieldEditorSelectedRange; // Table loading methods and information -- (void) loadTable:(NSString *)aTable; -- (void) clearTableValues; -- (void) loadTableValues; -- (NSString *) tableFilterString; -- (void) updateCountText; -- (void) initTableLoadTimer; -- (void) clearTableLoadTimer; -- (void) tableLoadUpdate:(NSTimer *)theTimer; +- (void)loadTable:(NSString *)aTable; +- (void)clearTableValues; +- (void)loadTableValues; +- (NSString *)tableFilterString; +- (void)updateCountText; +- (void)initTableLoadTimer; +- (void)clearTableLoadTimer; +- (void)tableLoadUpdate:(NSTimer *)theTimer; // Table interface actions -- (IBAction) reloadTable:(id)sender; -- (void) reloadTableTask; -- (IBAction) filterTable:(id)sender; +- (IBAction)reloadTable:(id)sender; +- (void)reloadTableTask; +- (IBAction)filterTable:(id)sender; - (void)filterTableTask; -- (IBAction) toggleFilterField:(id)sender; -- (NSString *) usedQuery; -- (void) setUsedQuery:(NSString *)query; +- (IBAction)toggleFilterField:(id)sender; +- (NSString *)usedQuery; +- (void)setUsedQuery:(NSString *)query; // Pagination -- (IBAction) navigatePaginationFromButton:(id)sender; +- (IBAction)navigatePaginationFromButton:(id)sender; #ifndef SP_REFACTOR -- (IBAction) togglePagination:(NSButton *)sender; +- (IBAction)togglePagination:(NSButton *)sender; #endif -- (void) setPaginationViewVisibility:(BOOL)makeVisible; -- (void) updatePaginationState; +- (void)setPaginationViewVisibility:(BOOL)makeVisible; +- (void)updatePaginationState; // Edit methods - (IBAction)addRow:(id)sender; @@ -204,11 +240,11 @@ // Data accessors - (NSArray *)currentResult; -- (NSArray *)currentDataResultWithNULLs:(BOOL)includeNULLs; +- (NSArray *)currentDataResultWithNULLs:(BOOL)includeNULLs hideBLOBs:(BOOL)hide; // Task interaction -- (void) startDocumentTaskForTab:(NSNotification *)aNotification; -- (void) endDocumentTaskForTab:(NSNotification *)aNotification; +- (void)startDocumentTaskForTab:(NSNotification *)aNotification; +- (void)endDocumentTaskForTab:(NSNotification *)aNotification; // Additional methods - (void)setConnection:(SPMySQLConnection *)theConnection; @@ -233,23 +269,23 @@ - (void)saveViewCellValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSUInteger)rowIndex; // Retrieving and setting table state -- (NSString *) sortColumnName; -- (BOOL) sortColumnIsAscending; -- (NSUInteger) pageNumber; -- (NSIndexSet *) selectedRowIndexes; -- (NSRect) viewport; -- (CGFloat) tablesListWidth; -- (NSDictionary *) filterSettings; +- (NSString *)sortColumnName; +- (BOOL)sortColumnIsAscending; +- (NSUInteger)pageNumber; +- (NSDictionary *)selectionDetailsAllowingIndexSelection:(BOOL)allowIndexFallback; +- (NSRect)viewport; +- (CGFloat)tablesListWidth; +- (NSDictionary *)filterSettings; - (NSArray *)dataColumnDefinitions; -- (void) setSortColumnNameToRestore:(NSString *)theSortColumnName isAscending:(BOOL)isAscending; -- (void) setPageToRestore:(NSUInteger)thePage; -- (void) setSelectedRowIndexesToRestore:(NSIndexSet *)theIndexSet; -- (void) setViewportToRestore:(NSRect)theViewport; -- (void) setFiltersToRestore:(NSDictionary *)filterSettings; -- (void) storeCurrentDetailsForRestoration; -- (void) clearDetailsToRestore; -- (void) setFilterTableData:(NSData*)arcData; -- (NSData*) filterTableData; +- (void)setSortColumnNameToRestore:(NSString *)theSortColumnName isAscending:(BOOL)isAscending; +- (void)setPageToRestore:(NSUInteger)thePage; +- (void)setSelectionToRestore:(NSDictionary *)theSelection; +- (void)setViewportToRestore:(NSRect)theViewport; +- (void)setFiltersToRestore:(NSDictionary *)filterSettings; +- (void)storeCurrentDetailsForRestoration; +- (void)clearDetailsToRestore; +- (void)setFilterTableData:(NSData*)arcData; +- (NSData*)filterTableData; - (NSString *)escapeFilterArgument:(NSString *)argument againstClause:(NSString *)clause; - (void)openContentFilterManager; @@ -260,26 +296,4 @@ - (void)updateFilterTableClause:(id)currentValue; - (NSString*)escapeFilterTableDefaultOperator:(NSString*)anOperator; -#ifdef SP_REFACTOR /* glue */ -@property (assign) id filterButton; -@property (assign) id fieldField; -@property (assign) id compareField; -@property (assign) id betweenTextField; -@property (assign) id firstBetweenField; -@property (assign) id secondBetweenField; -@property (assign) id argumentField; -@property (assign) NSButton* addButton; -@property (assign) NSButton* duplicateButton; -@property (assign) NSButton* removeButton; -@property (assign) NSButton* reloadButton; -@property (assign) NSButton* paginationNextButton; -@property (assign) NSButton* paginationPreviousButton; -@property (assign) NSTextField* paginationPageField; -@property (assign) SPDatabaseDocument* tableDocumentInstance; -@property (assign) SPTablesList* tablesListInstance; -@property (assign) SPCopyTable* tableContentView; -@property (assign) SPTableData* tableDataInstance; -@property (assign) SPTableStructure* tableSourceInstance; -#endif - @end diff --git a/Source/SPTableContent.m b/Source/SPTableContent.m index 7afbfe84..29064a5e 100644 --- a/Source/SPTableContent.m +++ b/Source/SPTableContent.m @@ -37,10 +37,10 @@ #import "SPQueryController.h" #import "SPQueryDocumentsController.h" #import "SPTextAndLinkCell.h" -#import "SPMySQL.h" #ifndef SP_REFACTOR #import "QLPreviewPanel.h" #endif +#import <SPMySQL/SPMySQL.h> #import "SPFieldEditorController.h" #import "SPTooltip.h" #import "RegexKitLite.h" @@ -51,8 +51,10 @@ #import "SPGeometryDataView.h" #import "SPTextView.h" #import "SPDatabaseViewController.h" +#ifndef SP_REFACTOR /* headers */ #import "SPAppController.h" #import "SPBundleHTMLOutputController.h" +#endif #import "SPCustomQuery.h" #import <pthread.h> @@ -129,7 +131,7 @@ sortColumnToRestore = nil; sortColumnToRestoreIsAsc = YES; pageToRestore = 1; - selectionIndexToRestore = nil; + selectionToRestore = nil; selectionViewportToRestore = NSZeroRect; filterFieldToRestore = nil; filterComparisonToRestore = nil; @@ -303,14 +305,6 @@ [(SPCopyTable*)[tableContentView onMainThread] scrollRectToVisible:selectionViewportToRestore]; } - // Restore selection indexes if appropriate - if (selectionIndexToRestore) { - BOOL previousTableRowsSelectable = tableRowsSelectable; - tableRowsSelectable = YES; - [[tableContentView onMainThread] selectRowIndexes:selectionIndexToRestore byExtendingSelection:NO]; - tableRowsSelectable = previousTableRowsSelectable; - } - // Update display if necessary if (!NSEqualRects(selectionViewportToRestore, NSZeroRect)) [[tableContentView onMainThread] setNeedsDisplayInRect:selectionViewportToRestore]; @@ -338,6 +332,7 @@ #endif NSArray *columnNames; NSDictionary *columnDefinition; + NSMutableDictionary *preservedColumnWidths = nil; NSTableColumn *theCol; #ifndef SP_REFACTOR NSTableColumn *filterCol; @@ -373,6 +368,12 @@ // reload the data in-place to maintain table state if possible. if ([selectedTable isEqualToString:newTableName]) { previousTableRowsCount = tableRowsCount; + + // Store the column widths for later restoration + preservedColumnWidths = [NSMutableDictionary dictionaryWithCapacity:[[tableContentView tableColumns] count]]; + for (NSTableColumn *eachColumn in [tableContentView tableColumns]) { + [preservedColumnWidths setObject:[NSNumber numberWithFloat:[eachColumn width]] forKey:[[eachColumn headerCell] stringValue]]; + } // Otherwise store the newly selected table name and reset the data } else { @@ -531,6 +532,12 @@ ([columnDefinition objectForKey:@"values"]) ? [NSString stringWithFormat:@"(\n- %@\n)", [[columnDefinition objectForKey:@"values"] componentsJoinedByString:@"\n- "]] : @"", ([columnDefinition objectForKey:@"comment"] && [(NSString *)[columnDefinition objectForKey:@"comment"] length]) ? [NSString stringWithFormat:@"\n%@", [[columnDefinition objectForKey:@"comment"] stringByReplacingOccurrencesOfString:@"\\n" withString:@"\n"]] : @"" ]]; + + // Copy in the width if present in a reloaded table + if ([preservedColumnWidths objectForKey:[columnDefinition objectForKey:@"name"]]) { + [theCol setWidth:[[preservedColumnWidths objectForKey:[columnDefinition objectForKey:@"name"]] floatValue]]; + } + [theCol setEditable:YES]; #ifndef SP_REFACTOR @@ -841,6 +848,69 @@ // End cancellation ability [tableDocumentInstance disableTaskCancellation]; + // Restore selection indexes if appropriate + if (selectionToRestore) { + BOOL previousTableRowsSelectable = tableRowsSelectable; + tableRowsSelectable = YES; + NSMutableIndexSet *selectionSet = [NSMutableIndexSet indexSet]; + + // Currently two types of stored selection are supported: primary keys and direct index sets. + if ([[selectionToRestore objectForKey:@"type"] isEqualToString:SPSelectionDetailTypePrimaryKeyed]) { + + // Check whether the keys are still present and get their positions + BOOL columnsFound = YES; + NSArray *primaryKeyFieldNames = [selectionToRestore objectForKey:@"keys"]; + NSUInteger primaryKeyFieldCount = [primaryKeyFieldNames count]; + NSUInteger primaryKeyFieldIndexes[primaryKeyFieldCount]; + for (NSUInteger i = 0; i < primaryKeyFieldCount; i++) { + primaryKeyFieldIndexes[i] = [[tableDataInstance columnNames] indexOfObject:[primaryKeyFieldNames objectAtIndex:i]]; + if (primaryKeyFieldIndexes[i] == NSNotFound) { + columnsFound = NO; + } + } + + // Only proceed with reselection if all columns were found + if (columnsFound) { + NSDictionary *selectionKeysToRestore = [selectionToRestore objectForKey:@"rows"]; + NSUInteger rowsToSelect = [selectionKeysToRestore count]; + BOOL rowMatches = NO; + + for (NSUInteger i = 0; i < tableRowsCount; i++) { + + // For single-column primary keys look up the cell value in the dictionary for a match + if (primaryKeyFieldCount == 1) { + if ([selectionKeysToRestore objectForKey:SPDataStorageObjectAtRowAndColumn(tableValues, i, primaryKeyFieldIndexes[0])]) { + rowMatches = YES; + } + + // For multi-column primary keys, convert all the cells to a string for lookup. + } else { + NSMutableString *lookupString = [[NSMutableString alloc] initWithString:[SPDataStorageObjectAtRowAndColumn(tableValues, i, primaryKeyFieldIndexes[0]) description]]; + for (NSUInteger j = 1; j < primaryKeyFieldCount; j++) { + [lookupString appendString:SPUniqueSchemaDelimiter]; + [lookupString appendString:[SPDataStorageObjectAtRowAndColumn(tableValues, i, primaryKeyFieldIndexes[j]) description]]; + } + if ([selectionKeysToRestore objectForKey:lookupString]) rowMatches = YES; + [lookupString release]; + } + + if (rowMatches) { + [selectionSet addIndex:i]; + rowsToSelect--; + if (rowsToSelect <= 0) break; + rowMatches = NO; + } + } + } + + } else if ([[selectionToRestore objectForKey:@"type"] isEqualToString:SPSelectionDetailTypeIndexed]) { + selectionSet = [selectionToRestore objectForKey:@"rows"]; + } + + [[tableContentView onMainThread] selectRowIndexes:selectionSet byExtendingSelection:NO]; + tableRowsSelectable = previousTableRowsSelectable; + } + if ([prefs boolForKey:SPLimitResults] && (contentPage > 1 || (NSInteger)tableRowsCount == [prefs integerForKey:SPLimitResultsValue])) { isLimited = YES; @@ -1452,6 +1522,7 @@ #endif // Reset and reload data using the new filter settings + [self setSelectionToRestore:[self selectionDetailsAllowingIndexSelection:NO]]; previousTableRowsCount = 0; [self clearTableValues]; [self loadTableValues]; @@ -1529,6 +1600,78 @@ usedQuery = [[NSString alloc] initWithString:query]; } +- (void)sortTableTaskWithColumn:(NSTableColumn *)tableColumn +{ + NSAutoreleasePool *sortPool = [[NSAutoreleasePool alloc] init]; + + // Check whether a save of the current row is required. + if (![[self onMainThread] saveRowOnDeselect]) { + [sortPool drain]; + return; + } + + // Sets column order as tri-state descending, ascending, no sort, descending, ascending etc. order if the same + // header is clicked several times + if (sortCol && [[tableColumn identifier] integerValue] == [sortCol integerValue]) { + if (isDesc) { + [sortCol release]; + sortCol = nil; + } + else { + if (sortCol) [sortCol release]; + + sortCol = [[NSNumber alloc] initWithInteger:[[tableColumn identifier] integerValue]]; + isDesc = !isDesc; + } + } + else { + isDesc = NO; + + [[tableContentView onMainThread] setIndicatorImage:nil inTableColumn:[tableContentView tableColumnWithIdentifier:[NSString stringWithFormat:@"%lld", (long long)[sortCol integerValue]]]]; + + if (sortCol) [sortCol release]; + + sortCol = [[NSNumber alloc] initWithInteger:[[tableColumn identifier] integerValue]]; + } + + if (sortCol) { + // Set the highlight and indicatorImage + [[tableContentView onMainThread] setHighlightedTableColumn:tableColumn]; + + if (isDesc) { + [[tableContentView onMainThread] setIndicatorImage:[NSImage imageNamed:@"NSDescendingSortIndicator"] inTableColumn:tableColumn]; + } + else { + [[tableContentView onMainThread] setIndicatorImage:[NSImage imageNamed:@"NSAscendingSortIndicator"] inTableColumn:tableColumn]; + } + } + else { + // If no sort order deselect column header and + // remove indicator image + [[tableContentView onMainThread] setHighlightedTableColumn:nil]; + [[tableContentView onMainThread] setIndicatorImage:nil inTableColumn:tableColumn]; + } + + // Update data using the new sort order + previousTableRowsCount = tableRowsCount; + [self setSelectionToRestore:[self selectionDetailsAllowingIndexSelection:NO]]; + [[tableContentView onMainThread] selectRowIndexes:[NSIndexSet indexSet] byExtendingSelection:NO]; + [self loadTableValues]; + + if ([mySQLConnection queryErrored] && ![mySQLConnection lastQueryWasCancelled]) { + SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, + [NSString stringWithFormat:NSLocalizedString(@"Couldn't sort table. MySQL said: %@", @"message of panel when sorting of table failed"), [mySQLConnection lastErrorMessage]]); + + [tableDocumentInstance endTask]; + [sortPool drain]; + + return; + } + + [tableDocumentInstance endTask]; + [sortPool drain]; +} + #pragma mark - #pragma mark Pagination @@ -1734,7 +1877,7 @@ for ( i = 0 ; i < [dataColumns count] ; i++ ) { column = NSArrayObjectAtIndex(dataColumns, i); - if ([column objectForKey:@"default"] == nil || [column objectForKey:@"default"] == [NSNull null]) { + if ([column objectForKey:@"default"] == nil || [[column objectForKey:@"default"] isNSNull]) { [newRow addObject:[NSNull null]]; } else if ([[column objectForKey:@"default"] isEqualToString:@""] && ![[column objectForKey:@"null"] boolValue] @@ -1868,10 +2011,15 @@ NSArray *buttons = [alert buttons]; +#ifndef SP_REFACTOR // Change the alert's cancel button to have the key equivalent of return [[buttons objectAtIndex:0] setKeyEquivalent:@"d"]; [[buttons objectAtIndex:0] setKeyEquivalentModifierMask:NSCommandKeyMask]; [[buttons objectAtIndex:1] setKeyEquivalent:@"\r"]; +#else + [[buttons objectAtIndex:0] setKeyEquivalent:@"\r"]; + [[buttons objectAtIndex:1] setKeyEquivalent:@"\e"]; +#endif [alert setShowsSuppressionButton:NO]; [[alert suppressionButton] setState:NSOffState]; @@ -2212,7 +2360,49 @@ * Returns the current result (as shown in table content view) as array, the first object containing the field * names as array, the following objects containing the rows as array. */ -- (NSArray *)currentDataResultWithNULLs:(BOOL)includeNULLs +- (NSArray *)currentResult +{ + NSInteger i; + NSArray *tableColumns; + NSMutableArray *currentResult = [NSMutableArray array]; + NSMutableArray *tempRow = [NSMutableArray array]; + + // Load the table if not already loaded + if (![tableDocumentInstance contentLoaded]) { + [self loadTable:[tableDocumentInstance table]]; + } + + tableColumns = [tableContentView tableColumns]; + + // Add the field names as the first line + for (NSTableColumn *tableColumn in tableColumns) + { + [tempRow addObject:[[tableColumn headerCell] stringValue]]; + } + + [currentResult addObject:[NSArray arrayWithArray:tempRow]]; + + // Add the rows + for (i = 0 ; i < [self numberOfRowsInTableView:tableContentView]; i++) + { + [tempRow removeAllObjects]; + + for (NSTableColumn *tableColumn in tableColumns) + { + [tempRow addObject:[self tableView:tableContentView objectValueForTableColumn:tableColumn row:i]]; + } + + [currentResult addObject:[NSArray arrayWithArray:tempRow]]; + } + + return currentResult; +} + +/** + * Returns the current result (as shown in table content view) as array, the first object containing the field + * names as array, the following objects containing the rows as array. + */ +- (NSArray *)currentDataResultWithNULLs:(BOOL)includeNULLs hideBLOBs:(BOOL)hide { NSInteger i; NSArray *tableColumns; @@ -2244,7 +2434,7 @@ id o = SPDataStorageObjectAtRowAndColumn(tableValues, i, [[aTableColumn identifier] integerValue]); if ([o isNSNull]) { - [tempRow addObject:(includeNULLs) ? [NSNull null] : [prefs objectForKey:SPNullValue]]; + [tempRow addObject:includeNULLs ? [NSNull null] : [prefs objectForKey:SPNullValue]]; } else if ([o isSPNotLoaded]) { [tempRow addObject:NSLocalizedString(@"(not loaded)", @"value shown for hidden blob and text fields")]; @@ -2257,7 +2447,7 @@ NSImage *image = [v thumbnailImage]; NSString *imageStr = @""; - if(image) { + if (image) { NSString *maxSizeValue = @"WIDTH"; NSInteger imageWidth = [image size].width; NSInteger imageHeight = [image size].height; @@ -2292,10 +2482,10 @@ [[image TIFFRepresentationUsingCompression:NSTIFFCompressionJPEG factor:0.01f] base64Encoding]]]; } else { - [tempRow addObject:@"<BLOB>"]; + [tempRow addObject:hide ? @"<BLOB>" : [o stringRepresentationUsingEncoding:[mySQLConnection stringEncoding]]]; } - if(image) [image release]; + if (image) [image release]; } } @@ -2305,42 +2495,6 @@ return currentResult; } -/** - * Returns the current result (as shown in table content view) as array, the first object containing the field - * names as array, the following objects containing the rows as array. - */ -- (NSArray *)currentResult -{ - NSArray *tableColumns; - NSMutableArray *currentResult = [NSMutableArray array]; - NSMutableArray *tempRow = [NSMutableArray array]; - NSInteger i; - - // Load the table if not already loaded - if ( ![tableDocumentInstance contentLoaded] ) { - [self loadTable:[tableDocumentInstance table]]; - } - - tableColumns = [tableContentView tableColumns]; - - // Add the field names as the first line - for (NSTableColumn *aTableColumn in tableColumns) { - [tempRow addObject:[[aTableColumn headerCell] stringValue]]; - } - [currentResult addObject:[NSArray arrayWithArray:tempRow]]; - - // Add the rows - for ( i = 0 ; i < [self numberOfRowsInTableView:tableContentView] ; i++) { - [tempRow removeAllObjects]; - for (NSTableColumn *aTableColumn in tableColumns) { - [tempRow addObject:[self tableView:tableContentView objectValueForTableColumn:aTableColumn row:i]]; - } - [currentResult addObject:[NSArray arrayWithArray:tempRow]]; - } - - return currentResult; -} - #pragma mark - // Additional methods @@ -2537,7 +2691,6 @@ { [[contentFilters objectForKey:compareType] addObjectsFromArray:[[prefs objectForKey:SPContentFilters] objectForKey:compareType]]; } -#endif // Load doc-based user-defined content filters if([[SPQueryController sharedQueryController] contentFilterForFileURL:[tableDocumentInstance fileURL]]) { @@ -2545,6 +2698,7 @@ if([filters objectForKey:compareType]) [[contentFilters objectForKey:compareType] addObjectsFromArray:[filters objectForKey:compareType]]; } +#endif // Rebuild operator popup menu NSUInteger i = 0; @@ -2807,7 +2961,7 @@ // Report errors which have occurred } else { - SPBeginAlertSheet(NSLocalizedString(@"Couldn't write row", @"Couldn't write row error"), NSLocalizedString(@"Edit row", @"Edit row button"), NSLocalizedString(@"Discard changes", @"discard changes button"), nil, [tableDocumentInstance parentWindow], self, @selector(addRowErrorSheetDidEnd:returnCode:contextInfo:), nil, + SPBeginAlertSheet(NSLocalizedString(@"Unable to write row", @"Unable to write row error"), NSLocalizedString(@"Edit row", @"Edit row button"), NSLocalizedString(@"Discard changes", @"discard changes button"), nil, [tableDocumentInstance parentWindow], self, @selector(addRowErrorSheetDidEnd:returnCode:contextInfo:), nil, [NSString stringWithFormat:NSLocalizedString(@"MySQL said:\n\n%@", @"message of panel when error while adding row to db"), [mySQLConnection lastErrorMessage]]); return NO; } @@ -3536,11 +3690,85 @@ } /** - * Provide a getter for the table's selected rows index set + * Provide a getter for the table's selected rows. If a primary key is available, + * the returned dictionary will contain details of the primary key used, and an + * identifier for each selected row. If no primary key is available, the returned + * dictionary will contain details and a list of the selected row *indexes* if the + * supplied argument is set to true, which may not always be appropriate. */ -- (NSIndexSet *) selectedRowIndexes +- (NSDictionary *)selectionDetailsAllowingIndexSelection:(BOOL)allowIndexFallback { - return [tableContentView selectedRowIndexes]; + + // If a primary key is available, store the selection details for rows using the primary key. + NSArray *primaryKeyFieldNames = [tableDataInstance primaryKeyColumnNames]; + if (primaryKeyFieldNames) { + + // Set up an array of the column indexes to store + NSUInteger primaryKeyFieldCount = [primaryKeyFieldNames count]; + NSUInteger primaryKeyFieldIndexes[primaryKeyFieldCount]; + BOOL problemColumns = NO; + for (NSUInteger i = 0; i < primaryKeyFieldCount; i++) { + primaryKeyFieldIndexes[i] = [[tableDataInstance columnNames] indexOfObject:[primaryKeyFieldNames objectAtIndex:i]]; + if (primaryKeyFieldIndexes[i] == NSNotFound) { + problemColumns = YES; +#ifndef SP_REFACTOR + } else { + if ([prefs boolForKey:SPLoadBlobsAsNeeded]) { + if ([tableDataInstance columnIsBlobOrText:[primaryKeyFieldNames objectAtIndex:i]]) { + problemColumns = YES; + } + } +#endif + } + } + + // Only proceed with key-based selection if there were no problem columns + if (!problemColumns) { + NSIndexSet *selectedRowIndexes = [tableContentView selectedRowIndexes]; + NSUInteger *indexBuffer = malloc(sizeof(NSUInteger) * [selectedRowIndexes count]); + NSUInteger indexCount = [selectedRowIndexes getIndexes:indexBuffer maxCount:[selectedRowIndexes count] inIndexRange:NULL]; + + NSMutableDictionary *selectedRowLookupTable = [NSMutableDictionary dictionaryWithCapacity:indexCount]; + NSNumber *trueNumber = [NSNumber numberWithBool:YES]; + for (NSUInteger i = 0; i < indexCount; i++) { + + // For single-column primary keys, use the cell value as a dictionary key for fast lookups + if (primaryKeyFieldCount == 1) { + [selectedRowLookupTable setObject:trueNumber forKey:SPDataStorageObjectAtRowAndColumn(tableValues, indexBuffer[i], primaryKeyFieldIndexes[0])]; + + // For multi-column primary keys, convert all the cell values to a string and use that as the key. + } else { + NSMutableString *lookupString = [NSMutableString stringWithString:[SPDataStorageObjectAtRowAndColumn(tableValues, indexBuffer[i], primaryKeyFieldIndexes[0]) description]]; + for (NSUInteger j = 1; j < primaryKeyFieldCount; j++) { + [lookupString appendString:SPUniqueSchemaDelimiter]; + [lookupString appendString:[SPDataStorageObjectAtRowAndColumn(tableValues, indexBuffer[i], primaryKeyFieldIndexes[j]) description]]; + } + [selectedRowLookupTable setObject:trueNumber forKey:lookupString]; + } + } + free(indexBuffer); + + return [NSDictionary dictionaryWithObjectsAndKeys: + SPSelectionDetailTypePrimaryKeyed, @"type", + selectedRowLookupTable, @"rows", + primaryKeyFieldNames, @"keys", + nil]; + } + } + + // If no primary key was available, fall back to using just the selected row indexes if permitted + if (allowIndexFallback) { + return [NSDictionary dictionaryWithObjectsAndKeys: + SPSelectionDetailTypeIndexed, @"type", + [tableContentView selectedRowIndexes], @"rows", + nil]; + } + + // Otherwise return a blank selection + return [NSDictionary dictionaryWithObjectsAndKeys: + SPSelectionDetailTypeIndexed, @"type", + [NSIndexSet indexSet], @"rows", + nil]; } /** @@ -3621,11 +3849,11 @@ /** * Set the selected row indexes to restore on next table load */ -- (void) setSelectedRowIndexesToRestore:(NSIndexSet *)theIndexSet +- (void) setSelectionToRestore:(NSDictionary *)theSelection { - if (selectionIndexToRestore) [selectionIndexToRestore release], selectionIndexToRestore = nil; + if (selectionToRestore) [selectionToRestore release], selectionToRestore = nil; - if (theIndexSet) selectionIndexToRestore = [[NSIndexSet alloc] initWithIndexSet:theIndexSet]; + if (theSelection) selectionToRestore = [theSelection copy]; } /** @@ -3687,7 +3915,7 @@ { [self setSortColumnNameToRestore:[self sortColumnName] isAscending:[self sortColumnIsAscending]]; [self setPageToRestore:[self pageNumber]]; - [self setSelectedRowIndexesToRestore:[self selectedRowIndexes]]; + [self setSelectionToRestore:[self selectionDetailsAllowingIndexSelection:YES]]; [self setViewportToRestore:[self viewport]]; [self setFiltersToRestore:[self filterSettings]]; } @@ -3699,7 +3927,7 @@ { [self setSortColumnNameToRestore:nil isAscending:YES]; [self setPageToRestore:1]; - [self setSelectedRowIndexesToRestore:nil]; + [self setSelectionToRestore:nil]; [self setViewportToRestore:NSZeroRect]; [self setFiltersToRestore:nil]; } @@ -3859,741 +4087,6 @@ } [tableContentView setDelegate:self]; } -#ifndef SP_REFACTOR - -#pragma mark - -#pragma mark TableView delegate methods - -/** - * 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 *)aTableView toolTipForCell:(id)aCell rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)row mouseLocation:(NSPoint)mouseLocation -{ - if (aTableView == filterTableView) { - return nil; - } - else if (aTableView == 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 && [[aTableColumn identifier] integerValue] < (NSInteger)[tableValues columnCount]) { - theValue = [[SPDataStorageObjectAtRowAndColumn(tableValues, row, [[aTableColumn identifier] integerValue]) copy] autorelease]; - } - pthread_mutex_unlock(&tableValuesLock); - - if (!theValue) theValue = @"..."; - } else { - theValue = SPDataStorageObjectAtRowAndColumn(tableValues, row, [[aTableColumn 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 - -- (NSInteger)numberOfRowsInTableView:(SPCopyTable *)aTableView -{ -#ifndef SP_REFACTOR - if (aTableView == filterTableView) { - if (filterTableIsSwapped) - return [filterTableData count]; - else - return [[[filterTableData objectForKey:[NSNumber numberWithInteger:0]] objectForKey:@"filter"] count]; - } - else -#endif - if (aTableView == tableContentView) { - return tableRowsCount; - } - - return 0; -} - -- (id)tableView:(SPCopyTable *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex -{ -#ifndef SP_REFACTOR - if (aTableView == filterTableView) { - if (filterTableIsSwapped) - // First column shows the field names - if([[aTableColumn identifier] integerValue] == 0) { - NSTableHeaderCell *c = [[[NSTableHeaderCell alloc] initTextCell:[[filterTableData objectForKey:[NSNumber numberWithInteger:rowIndex]] objectForKey:@"name"]] autorelease]; - return c; - } else - return NSArrayObjectAtIndex([[filterTableData objectForKey:[NSNumber numberWithInteger:rowIndex]] objectForKey:@"filter"], [[aTableColumn identifier] integerValue]-1); - else { - return NSArrayObjectAtIndex([[filterTableData objectForKey:[aTableColumn identifier]] objectForKey:@"filter"], rowIndex); - } - } - else -#endif - if (aTableView == tableContentView) { - - NSUInteger columnIndex = [[aTableColumn identifier] integerValue]; - id theValue = nil; - - // While the table is being loaded, additional validation is required - data - // locks must be used to avoid crashes, and indexes higher than the available - // rows or columns may be requested. Return "..." to indicate loading in these - // cases. - if (isWorking) { - pthread_mutex_lock(&tableValuesLock); - if (rowIndex < (NSInteger)tableRowsCount && columnIndex < [tableValues columnCount]) { - theValue = [[SPDataStorageObjectAtRowAndColumn(tableValues, rowIndex, columnIndex) copy] autorelease]; - } - pthread_mutex_unlock(&tableValuesLock); - - if (!theValue) return @"..."; - } else { - theValue = SPDataStorageObjectAtRowAndColumn(tableValues, rowIndex, columnIndex); - } - - if([theValue isKindOfClass:[SPMySQLGeometryData class]]) - return [theValue wktString]; - - if ([theValue isNSNull]) - return [prefs objectForKey:SPNullValue]; - - if ([theValue isKindOfClass:[NSData class]]) - return [theValue shortStringRepresentationUsingEncoding:[mySQLConnection stringEncoding]]; - - if ([theValue isSPNotLoaded]) - return NSLocalizedString(@"(not loaded)", @"value shown for hidden blob and text fields"); - - return theValue; - } - - return nil; -} - -/** - * This function changes the text color of text/blob fields which are null or not yet loaded to gray - */ -- (void)tableView:(SPCopyTable *)aTableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn*)aTableColumn row:(NSInteger)rowIndex -{ -#ifndef SP_REFACTOR - if(aTableView == filterTableView) { - if(filterTableIsSwapped && [[aTableColumn identifier] integerValue] == 0) { - [cell setDrawsBackground:YES]; - [cell setBackgroundColor:lightGrayColor]; - } else { - [cell setDrawsBackground:NO]; - } - return; - } - else -#endif - if(aTableView == tableContentView) { - - if (![cell respondsToSelector:@selector(setTextColor:)]) return; - - NSUInteger columnIndex = [[aTableColumn identifier] integerValue]; - id theValue = nil; - - // While the table is being loaded, additional validation is required - data - // locks must be used to avoid crashes, and indexes higher than the available - // rows or columns may be requested. Use gray to indicate loading in these cases. - if (isWorking) { - pthread_mutex_lock(&tableValuesLock); - if (rowIndex < (NSInteger)tableRowsCount && columnIndex < [tableValues columnCount]) { - theValue = SPDataStorageObjectAtRowAndColumn(tableValues, rowIndex, columnIndex); - } - pthread_mutex_unlock(&tableValuesLock); - - if (!theValue) { - [cell setTextColor:[NSColor lightGrayColor]]; - return; - } - } else { - theValue = SPDataStorageObjectAtRowAndColumn(tableValues, rowIndex, columnIndex); - } - - // If user wants to edit 'cell' set text color to black and return to avoid - // writing in gray if value was NULL - if ([aTableView editedColumn] != -1 - && [aTableView editedRow] == rowIndex - && (NSUInteger)[[NSArrayObjectAtIndex([aTableView tableColumns], [aTableView editedColumn]) identifier] integerValue] == columnIndex) { - [cell setTextColor:blackColor]; - return; - } - - // For null cells and not loaded cells, display the contents in gray. - if ([theValue isNSNull] || [theValue isSPNotLoaded]) { - [cell setTextColor:lightGrayColor]; - - // Otherwise, set the color to black - required as NSTableView reuses NSCells. - } else { - [cell setTextColor:blackColor]; - } - } -} - -- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex -{ -#ifndef SP_REFACTOR - if(aTableView == filterTableView) { - if(filterTableIsSwapped) - [[[filterTableData objectForKey:[NSNumber numberWithInteger:rowIndex]] objectForKey:@"filter"] replaceObjectAtIndex:([[aTableColumn identifier] integerValue]-1) withObject:(NSString*)anObject]; - else - [[[filterTableData objectForKey:[aTableColumn identifier]] objectForKey:@"filter"] replaceObjectAtIndex:rowIndex withObject:(NSString*)anObject]; - [self updateFilterTableClause:nil]; - return; - } - else -#endif - if(aTableView == tableContentView) { - - // If the current cell should have been edited in a sheet, do nothing - field closing will have already - // updated the field. - if ([tableContentView shouldUseFieldEditorForRow:rowIndex column:[[aTableColumn identifier] integerValue]]) { - return; - } - - // If table data comes from a view, save back to the view - if([tablesListInstance tableType] == SPTableTypeView) { - [self saveViewCellValue:anObject forTableColumn:aTableColumn row:rowIndex]; - return; - } - - // Catch editing events in the row and if the row isn't currently being edited, - // 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, [[aTableColumn identifier] integerValue]); - - if (anObject) { - - // Restore NULLs if necessary - if ([anObject isEqualToString:[prefs objectForKey:SPNullValue]] && [[column objectForKey:@"null"] boolValue]) - anObject = [NSNull null]; - - [tableValues replaceObjectInRow:rowIndex column:[[aTableColumn identifier] integerValue] withObject:anObject]; - } else { - [tableValues replaceObjectInRow:rowIndex column:[[aTableColumn identifier] integerValue] withObject:@""]; - } - } -} - -#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 detachNewThreadSelector:@selector(sortTableTaskWithColumn:) toTarget:self withObject:tableColumn]; - } else { - [self sortTableTaskWithColumn:tableColumn]; - } -} - -- (void)sortTableTaskWithColumn:(NSTableColumn *)tableColumn -{ - NSAutoreleasePool *sortPool = [[NSAutoreleasePool alloc] init]; - - // Check whether a save of the current row is required. - if (![[self onMainThread] saveRowOnDeselect]) { - [sortPool drain]; - return; - } - - // Sets column order as tri-state descending, ascending, no sort, descending, ascending etc. order if the same - // header is clicked several times - if (sortCol && [[tableColumn identifier] integerValue] == [sortCol integerValue]) { - if(isDesc) { - [sortCol release]; - sortCol = nil; - } else { - if (sortCol) [sortCol release]; - sortCol = [[NSNumber alloc] initWithInteger:[[tableColumn identifier] integerValue]]; - isDesc = !isDesc; - } - } else { - isDesc = NO; - [[tableContentView onMainThread] setIndicatorImage:nil inTableColumn:[tableContentView tableColumnWithIdentifier:[NSString stringWithFormat:@"%lld", (long long)[sortCol integerValue]]]]; - if (sortCol) [sortCol release]; - sortCol = [[NSNumber alloc] initWithInteger:[[tableColumn identifier] integerValue]]; - } - - if (sortCol) { - // Set the highlight and indicatorImage - [[tableContentView onMainThread] setHighlightedTableColumn:tableColumn]; - if (isDesc) { - [[tableContentView onMainThread] setIndicatorImage:[NSImage imageNamed:@"NSDescendingSortIndicator"] inTableColumn:tableColumn]; - } else { - [[tableContentView onMainThread] setIndicatorImage:[NSImage imageNamed:@"NSAscendingSortIndicator"] inTableColumn:tableColumn]; - } - } else { - // If no sort order deselect column header and - // remove indicator image - [[tableContentView onMainThread] setHighlightedTableColumn:nil]; - [[tableContentView onMainThread] setIndicatorImage:nil inTableColumn:tableColumn]; - } - - // Update data using the new sort order - previousTableRowsCount = tableRowsCount; - [self loadTableValues]; - - if ([mySQLConnection queryErrored]) { - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"Couldn't sort table. MySQL said: %@", @"message of panel when sorting of table failed"), [mySQLConnection lastErrorMessage]]); - [tableDocumentInstance endTask]; - [sortPool drain]; - return; - } - - [tableDocumentInstance endTask]; - [sortPool drain]; -} - -- (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_REFACTOR /* triggered commands */ - NSArray *triggeredCommands = [[NSApp delegate] 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) { - if([[data objectAtIndex:1] isEqualToString:SPBundleScopeGeneral]) { - [[[NSApp delegate] onMainThread] executeBundleItemForApp:aMenuItem]; - } - else if([[data objectAtIndex:1] isEqualToString:SPBundleScopeDataTable]) { - if([[[[[NSApp mainWindow] firstResponder] class] description] isEqualToString:@"SPCopyTable"]) - [[[[NSApp mainWindow] firstResponder] onMainThread] executeBundleItemForDataTable:aMenuItem]; - } - else if([[data objectAtIndex:1] isEqualToString:SPBundleScopeInputField]) { - if([[[NSApp mainWindow] firstResponder] isKindOfClass:[NSTextView class]]) - [[[[NSApp mainWindow] firstResponder] onMainThread] executeBundleItemForInputField:aMenuItem]; - } - } - } -#endif -} - -/** - saves the new column size in the preferences - */ -- (void)tableViewColumnDidResize:(NSNotification *)aNotification -{ - - // Check our notification object is our table content view - if ([aNotification 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 (![[[aNotification userInfo] objectForKey:@"NSTableColumn"] identifier]) - return; - - NSMutableDictionary *tableColumnWidths; - NSString *database = [NSString stringWithFormat:@"%@@%@", [tableDocumentInstance database], [tableDocumentInstance host]]; - NSString *table = [tablesListInstance tableName]; - - // get tableColumnWidths object -#ifndef SP_REFACTOR - if ( [prefs objectForKey:SPTableColumnWidths] != nil ) { - tableColumnWidths = [NSMutableDictionary dictionaryWithDictionary:[prefs objectForKey:SPTableColumnWidths]]; - } else { -#endif - tableColumnWidths = [NSMutableDictionary dictionary]; -#ifndef SP_REFACTOR - } -#endif - // get database object - if ( [tableColumnWidths objectForKey:database] == nil ) { - [tableColumnWidths setObject:[NSMutableDictionary dictionary] forKey:database]; - } else { - [tableColumnWidths setObject:[NSMutableDictionary dictionaryWithDictionary:[tableColumnWidths objectForKey:database]] forKey:database]; - - } - // get 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 *)[[aNotification userInfo] objectForKey:@"NSTableColumn"] width]] forKey:[[[[aNotification userInfo] objectForKey:@"NSTableColumn"] headerCell] stringValue]]; -#ifndef SP_REFACTOR - [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 *)aTableView shouldEditTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex -{ - if ([tableDocumentInstance isWorking]) return NO; - -#ifndef SP_REFACTOR - if(aTableView == filterTableView) { - if(filterTableIsSwapped && [[aTableColumn identifier] integerValue] == 0) - return NO; - else - return YES; - } - else -#endif - if ( aTableView == tableContentView ) { - - // Ensure that row is editable since it could contain "(not loaded)" columns together with - // issue that the table has no primary key - NSString *wherePart = [NSString stringWithString:[self argumentForRow:[tableContentView selectedRow]]]; - if ([wherePart length] == 0) return NO; - - // If the selected cell hasn't been loaded, load it. - if ([[tableValues cellDataAtRow:rowIndex column:[[aTableColumn identifier] integerValue]] isSPNotLoaded]) { - - // Only get the data for the selected column, not all of them - NSString *query = [NSString stringWithFormat:@"SELECT %@ FROM %@ WHERE %@", [[[aTableColumn headerCell] stringValue] backtickQuotedString], [selectedTable backtickQuotedString], wherePart]; - - SPMySQLResult *tempResult = [mySQLConnection queryString:query]; - if (![tempResult numberOfRows]) { - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - 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:aTableColumn] withObject:[tempRow objectAtIndex:0]]; - [tableContentView reloadData]; - } - - // Open the editing sheet if required - if ([tableContentView shouldUseFieldEditorForRow:rowIndex column:[[aTableColumn identifier] integerValue]]) - { - - // Retrieve the column definition - NSDictionary *columnDefinition = [cqColumnDefinition objectAtIndex:[[aTableColumn identifier] integerValue]]; - BOOL isBlob = [tableDataInstance columnIsBlobOrText:[[aTableColumn 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:[[aTableColumn identifier] integerValue]]; - isFieldEditable = ([[editStatus objectAtIndex:0] integerValue] == 1) ? YES : NO; - } - - NSString *fieldType = nil; - NSUInteger fieldLength = 0; - NSString *fieldEncoding = nil; - BOOL allowNULL = YES; - - 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"]; - - if(fieldEditor) [fieldEditor release], fieldEditor = nil; - fieldEditor = [[SPFieldEditorController alloc] init]; - [fieldEditor setEditedFieldInfo:[NSDictionary dictionaryWithObjectsAndKeys: - [[aTableColumn 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:[[aTableColumn identifier] integerValue]]; - if ([cellValue isNSNull]) - cellValue = [NSString stringWithString:[prefs objectForKey:SPNullValue]]; - - NSInteger editedColumn = 0; - for(NSTableColumn* col in [tableContentView tableColumns]) { - if([[col identifier] isEqualToString:[aTableColumn identifier]]) break; - editedColumn++; - } - - [fieldEditor editWithObject:cellValue - fieldName:[[aTableColumn 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 *)aTableView writeRowsWithIndexes:(NSIndexSet *)rows toPasteboard:(NSPasteboard*)pboard -{ - if (aTableView == 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 ( nil != tmp && [tmp length] ) - { - [pboard declareTypes:[NSArray arrayWithObjects: NSTabularTextPboardType, - NSStringPboardType, nil] - 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 *)aTableView shouldSelectRow:(NSInteger)rowIndex -{ -#ifndef SP_REFACTOR - - if(aTableView == filterTableView) - return YES; - else -#endif - if(aTableView == tableContentView) - return tableRowsSelectable; - else - return YES; - -} - -/** - * Resize a column when it's double-clicked. (10.6+) - */ -- (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_REFACTOR - // 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; -} - -#ifndef SP_REFACTOR /* 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 + 200); -} - -// 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 Task interaction @@ -4657,124 +4150,6 @@ #pragma mark - #pragma mark Other methods -- (void)controlTextDidChange:(NSNotification *)notification -{ -#ifndef SP_REFACTOR - if ([notification object] == filterTableView) { - - NSString *str = [[[[notification userInfo] objectForKey:@"NSFieldEditor"] textStorage] string]; - if(str && [str length]) { - if(lastEditedFilterTableValue) [lastEditedFilterTableValue release]; - lastEditedFilterTableValue = [[NSString stringWithString:str] retain]; - } - [self updateFilterTableClause:str]; - - } -#endif -} -/** - * If user selected a table cell which is a blob field and tried to edit it - * cancel the fieldEditor, display the field editor sheet instead for editing - * and re-enable the fieldEditor after editing. - */ -- (BOOL)control:(NSControl *)control textShouldBeginEditing:(NSText *)aFieldEditor -{ - - if(control != 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, (numberOfPossibleUpdateRows>1)?NSLocalizedString(@"es", @"Plural suffix for row count, eg 4 match*es*"):@""] - atLocation:pos - ofType:@"text"]; - shouldBeginEditing = NO; - } - - } - - // Open the field editor sheet if required - if ([tableContentView shouldUseFieldEditorForRow:row column:column]) - { - [tableContentView setFieldEditorSelectedRange:[aFieldEditor selectedRange]]; - - // Cancel editing - [control abortEditing]; - - // 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 *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)command -{ - - // Check firstly if SPCopyTable can handle command -#ifndef SP_REFACTOR - if([control control:control textView:textView doCommandBySelector:(SEL)command]) -#else - if([(id<NSControlTextEditingDelegate>)control control:control textView:textView doCommandBySelector:(SEL)command]) -#endif - return YES; - - // Trap the escape key - if ( [[control window] methodForSelector:command] == [[control window] methodForSelector:@selector(cancelOperation:)] ) - { - // Abort editing - [control abortEditing]; - if(control == tableContentView) - [self cancelRowEditing]; - return TRUE; - } - - return FALSE; - -} - /** * This method is called as part of Key Value Observing which is used to watch for prefernce changes which effect the interface. */ @@ -5043,6 +4418,9 @@ [dataColumns release]; [oldRow release]; #ifndef SP_REFACTOR + for (id retainedObject in nibObjectsToRelease) [retainedObject release]; + [nibObjectsToRelease release]; + [filterTableData release]; if (lastEditedFilterTableValue) [lastEditedFilterTableValue release]; if (filterTableDefaultOperator) [filterTableDefaultOperator release]; @@ -5054,7 +4432,7 @@ if (sortCol) [sortCol release]; [usedQuery release]; if (sortColumnToRestore) [sortColumnToRestore release]; - if (selectionIndexToRestore) [selectionIndexToRestore release]; + if (selectionToRestore) [selectionToRestore release]; if (filterFieldToRestore) filterFieldToRestore = nil; if (filterComparisonToRestore) filterComparisonToRestore = nil; if (filterValueToRestore) filterValueToRestore = nil; diff --git a/Source/SPTableContentDataSource.h b/Source/SPTableContentDataSource.h new file mode 100644 index 00000000..7276f736 --- /dev/null +++ b/Source/SPTableContentDataSource.h @@ -0,0 +1,37 @@ +// +// $Id$ +// +// 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 <http://code.google.com/p/sequel-pro/> + +#import "SPTableContent.h" + +@interface SPTableContent (SPTableContentDataSource) + +@end diff --git a/Source/SPTableContentDataSource.m b/Source/SPTableContentDataSource.m new file mode 100644 index 00000000..a833add7 --- /dev/null +++ b/Source/SPTableContentDataSource.m @@ -0,0 +1,182 @@ +// +// $Id$ +// +// 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 <http://code.google.com/p/sequel-pro/> + +#import "SPTableContentDataSource.h" +#import "SPDataStorage.h" +#import "SPCopyTable.h" +#import "SPTablesList.h" + +#import <SPMySQL/SPMySQL.h> +#import <pthread.h> + +@implementation SPTableContent (SPTableContentDataSource) + +#pragma mark - +#pragma mark TableView datasource methods + +- (NSInteger)numberOfRowsInTableView:(SPCopyTable *)tableView +{ +#ifndef SP_REFACTOR + if (tableView == filterTableView) { + return filterTableIsSwapped ? [filterTableData count] : [[[filterTableData objectForKey:[NSNumber numberWithInteger:0]] objectForKey:@"filter"] count]; + } + else +#endif + if (tableView == tableContentView) { + return tableRowsCount; + } + + return 0; +} + +- (id)tableView:(SPCopyTable *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex +{ +#ifndef SP_REFACTOR + if (tableView == filterTableView) { + if (filterTableIsSwapped) + + // First column shows the field names + if ([[tableColumn identifier] integerValue] == 0) { + return [[[NSTableHeaderCell alloc] initTextCell:[[filterTableData objectForKey:[NSNumber numberWithInteger:rowIndex]] objectForKey:@"name"]] autorelease]; + } + else { + return NSArrayObjectAtIndex([[filterTableData objectForKey:[NSNumber numberWithInteger:rowIndex]] objectForKey:@"filter"], [[tableColumn identifier] integerValue] - 1); + } + else { + return NSArrayObjectAtIndex([[filterTableData objectForKey:[tableColumn identifier]] objectForKey:@"filter"], rowIndex); + } + } + else +#endif + if (tableView == tableContentView) { + + id value = nil; + NSUInteger columnIndex = [[tableColumn identifier] integerValue]; + + // 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 = [[SPDataStorageObjectAtRowAndColumn(tableValues, rowIndex, columnIndex) copy] autorelease]; + } + + pthread_mutex_unlock(&tableValuesLock); + + if (!value) return @"..."; + } + else { + value = SPDataStorageObjectAtRowAndColumn(tableValues, rowIndex, columnIndex); + } + + if ([value isKindOfClass:[SPMySQLGeometryData class]]) + return [value wktString]; + + if ([value isNSNull]) + return [prefs objectForKey:SPNullValue]; + + if ([value isKindOfClass:[NSData class]]) + 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_REFACTOR + if(tableView == filterTableView) { + if (filterTableIsSwapped) { + [[[filterTableData objectForKey:[NSNumber numberWithInteger:rowIndex]] objectForKey:@"filter"] replaceObjectAtIndex:([[tableColumn identifier] integerValue] - 1) withObject:(NSString *)object]; + } + else { + [[[filterTableData objectForKey:[tableColumn identifier]] objectForKey:@"filter"] replaceObjectAtIndex:rowIndex withObject:(NSString *)object]; + } + + [self updateFilterTableClause:nil]; + + return; + } + else +#endif + if (tableView == tableContentView) { + + // If the current cell should have been edited in a sheet, do nothing - field closing will have already + // updated the field. + if ([tableContentView shouldUseFieldEditorForRow:rowIndex column:[[tableColumn identifier] integerValue]]) { + 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, [[tableColumn identifier] integerValue]); + + if (object) { + // Restore NULLs if necessary + if ([object isEqualToString:[prefs objectForKey:SPNullValue]] && [[column objectForKey:@"null"] boolValue]) { + object = [NSNull null]; + } + + [tableValues replaceObjectInRow:rowIndex column:[[tableColumn identifier] integerValue] withObject:object]; + } + else { + [tableValues replaceObjectInRow:rowIndex column:[[tableColumn identifier] integerValue] withObject:@""]; + } + } +} + +@end diff --git a/Source/SPTableContentDelegate.h b/Source/SPTableContentDelegate.h new file mode 100644 index 00000000..25a19a50 --- /dev/null +++ b/Source/SPTableContentDelegate.h @@ -0,0 +1,37 @@ +// +// $Id$ +// +// 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 <http://code.google.com/p/sequel-pro/> + +#import "SPTableContent.h" + +@interface SPTableContent (SPTableContentDelegate) + +@end diff --git a/Source/SPTableContentDelegate.m b/Source/SPTableContentDelegate.m new file mode 100644 index 00000000..3fdf1232 --- /dev/null +++ b/Source/SPTableContentDelegate.m @@ -0,0 +1,782 @@ +// +// $Id$ +// +// 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 <http://code.google.com/p/sequel-pro/> + +#import "SPTableContentDelegate.h" +#ifndef SP_REFACTOR /* headers */ +#import "SPAppController.h" +#endif +#import "SPDatabaseDocument.h" +#import "SPDataStorage.h" +#import "SPGeometryDataView.h" +#import "SPTooltip.h" +#import "SPTablesList.h" +#import <SPMySQL/SPMySQL.h> +#ifndef SP_REFACTOR /* headers */ +#import "SPBundleHTMLOutputController.h" +#endif +#import "SPCopyTable.h" +#import "SPAlertSheets.h" +#import "SPTableData.h" +#import "SPFieldEditorController.h" + +#import <pthread.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 detachNewThreadSelector:@selector(sortTableTaskWithColumn:) toTarget:self withObject: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_REFACTOR /* triggered commands */ + NSArray *triggeredCommands = [[NSApp delegate] 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) { + + if ([[data objectAtIndex:1] isEqualToString:SPBundleScopeGeneral]) { + [[[NSApp delegate] onMainThread] executeBundleItemForApp:aMenuItem]; + } + else if ([[data objectAtIndex:1] isEqualToString:SPBundleScopeDataTable]) { + if ([[[[[NSApp mainWindow] firstResponder] class] description] isEqualToString:@"SPCopyTable"]) { + [[[[NSApp mainWindow] firstResponder] onMainThread] executeBundleItemForDataTable:aMenuItem]; + } + } + else if ([[data objectAtIndex:1] isEqualToString:SPBundleScopeInputField]) { + if ([[[NSApp mainWindow] firstResponder] isKindOfClass:[NSTextView class]]) { + [[[[NSApp mainWindow] 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_REFACTOR + if ([prefs objectForKey:SPTableColumnWidths] != nil ) { + tableColumnWidths = [NSMutableDictionary dictionaryWithDictionary:[prefs objectForKey:SPTableColumnWidths]]; + } + else { +#endif + tableColumnWidths = [NSMutableDictionary dictionary]; +#ifndef SP_REFACTOR + } +#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_REFACTOR + [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_REFACTOR + if (tableView == filterTableView) { + return (filterTableIsSwapped && [[tableColumn identifier] integerValue] == 0) ? NO : YES; + } + else +#endif + if (tableView == tableContentView) { + + // 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] == 0) 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]) { + SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, + 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]; + } + + // Open the editing sheet if required + if ([tableContentView shouldUseFieldEditorForRow:rowIndex column:[[tableColumn identifier] integerValue]]) { + + // Retrieve the column definition + NSDictionary *columnDefinition = [cqColumnDefinition objectAtIndex:[[tableColumn identifier] integerValue]]; + 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; + } + + NSString *fieldType = nil; + NSUInteger fieldLength = 0; + NSString *fieldEncoding = nil; + BOOL allowNULL = YES; + + 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"]; + } + + if(fieldEditor) [fieldEditor release], fieldEditor = nil; + + 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]]; + } + + 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:[NSArray arrayWithObjects: NSTabularTextPboardType, NSStringPboardType, nil] 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_REFACTOR + 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_REFACTOR + // 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_REFACTOR + 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; + + id theValue = nil; + NSUInteger columnIndex = [[tableColumn identifier] integerValue]; + + // 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]) { + theValue = SPDataStorageObjectAtRowAndColumn(tableValues, rowIndex, columnIndex); + } + + pthread_mutex_unlock(&tableValuesLock); + + if (!theValue) { + [cell setTextColor:[NSColor lightGrayColor]]; + return; + } + } + else { + theValue = SPDataStorageObjectAtRowAndColumn(tableValues, rowIndex, columnIndex); + } + + // 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]; + return; + } + + // For null cells and not loaded cells, display the contents in gray. + if ([theValue isNSNull] || [theValue isSPNotLoaded]) { + [cell setTextColor:lightGrayColor]; + + // Otherwise, set the color to black - required as NSTableView reuses NSCells. + } + else { + [cell setTextColor:blackColor]; + } + } +} + +#ifndef SP_REFACTOR +/** + * 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_REFACTOR /* 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 + 200; +} + +/** + * 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 + +- (void)controlTextDidChange:(NSNotification *)notification +{ +#ifndef SP_REFACTOR + 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 fieldEditor, display the field editor sheet instead for editing + * and re-enable the fieldEditor after editing. + */ +- (BOOL)control:(NSControl *)control textShouldBeginEditing:(NSText *)aFieldEditor +{ + if (control != 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, (numberOfPossibleUpdateRows>1)?NSLocalizedString(@"es", @"Plural suffix for row count, eg 4 match*es*"):@""] + atLocation:pos + ofType:@"text"]; + shouldBeginEditing = NO; + } + + } + + // Open the field editor sheet if required + if ([tableContentView shouldUseFieldEditorForRow:row column:column]) + { + [tableContentView setFieldEditorSelectedRange:[aFieldEditor selectedRange]]; + + // Cancel editing + [control abortEditing]; + + // 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 *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)command +{ +#ifndef SP_REFACTOR + // Check firstly if SPCopyTable can handle command + if ([control control:control textView:textView doCommandBySelector:(SEL)command]) +#else + if ([(id<NSControlTextEditingDelegate>)control control:control textView:textView doCommandBySelector:(SEL)command]) +#endif + return YES; + + // Trap the escape key + if ([[control window] methodForSelector:command] == [[control window] methodForSelector:@selector(cancelOperation:)]) { + // Abort editing + [control abortEditing]; + + if (control == tableContentView) { + [self cancelRowEditing]; + } + + return YES; + } + + return NO; +} + +@end diff --git a/Source/SPTableCopy.m b/Source/SPTableCopy.m index 1059e032..137e18a6 100644 --- a/Source/SPTableCopy.m +++ b/Source/SPTableCopy.m @@ -24,7 +24,7 @@ #import "SPDBActionCommons.h" #import "SPTableCopy.h" -#import "SPMySQL.h" +#import <SPMySQL/SPMySQL.h> @implementation SPTableCopy diff --git a/Source/SPTableData.h b/Source/SPTableData.h index 94c95da4..e292fe7a 100644 --- a/Source/SPTableData.h +++ b/Source/SPTableData.h @@ -35,6 +35,7 @@ NSMutableArray *constraints; NSArray *triggers; NSMutableDictionary *status; + NSMutableArray *primaryKeyColumns; NSString *tableEncoding; NSString *tableCreateSyntax; diff --git a/Source/SPTableData.m b/Source/SPTableData.m index 0c0814d6..f7bbeec2 100644 --- a/Source/SPTableData.m +++ b/Source/SPTableData.m @@ -30,7 +30,7 @@ #import "SPAlertSheets.h" #import "RegexKitLite.h" #import "SPServerSupport.h" -#import "SPMySQL.h" +#import <SPMySQL/SPMySQL.h> #include <pthread.h> @interface SPTableData (PrivateAPI) @@ -53,6 +53,7 @@ columnNames = [[NSMutableArray alloc] init]; constraints = [[NSMutableArray alloc] init]; status = [[NSMutableDictionary alloc] init]; + primaryKeyColumns = [[NSMutableArray alloc] init]; triggers = nil; tableEncoding = nil; @@ -375,6 +376,7 @@ [columnNames removeAllObjects]; [constraints removeAllObjects]; tableHasAutoIncrementField = NO; + [primaryKeyColumns removeAllObjects]; if( [tableListInstance tableType] == SPTableTypeTable || [tableListInstance tableType] == SPTableTypeView ) { tableData = [self informationForTable:[tableListInstance tableName]]; @@ -400,6 +402,7 @@ [tableEncoding release]; } tableEncoding = [[NSString alloc] initWithString:[tableData objectForKey:@"encoding"]]; + [primaryKeyColumns addObjectsFromArray:[tableData objectForKey:@"primarykeyfield"]]; pthread_mutex_unlock(&dataProcessingLock); @@ -419,6 +422,7 @@ NSEnumerator *enumerator; tableHasAutoIncrementField = NO; + [primaryKeyColumns removeAllObjects]; if (viewData == nil) { [columns removeAllObjects]; @@ -707,33 +711,38 @@ // add "isprimarykey" to the corresponding tableColumn // add dict root "primarykeyfield" = <field> for faster accessing else if( [NSArrayObjectAtIndex(parts, 0) hasPrefix:@"PRIMARY"] && [parts count] == 3) { - NSString *parsedString = [(NSString*)NSArrayObjectAtIndex(parts, 2) stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; - if([parsedString length]>4) { - NSString *priFieldName = [[parsedString substringWithRange:NSMakeRange(2,[parsedString length]-4)] stringByReplacingOccurrencesOfString:@"``" withString:@"`"]; - [tableData setObject:priFieldName forKey:@"primarykeyfield"]; - for(id theTableColumn in tableColumns) - if([[theTableColumn objectForKey:@"name"] isEqualToString:priFieldName]) { - [theTableColumn setObject:[NSNumber numberWithInteger:1] forKey:@"isprimarykey"]; - break; + SPSQLParser *keyParser = [SPSQLParser stringWithString:NSArrayObjectAtIndex(parts, 2)]; + keyParser = [SPSQLParser stringWithString:[keyParser stringFromCharacter:'(' toCharacter:')' inclusively:NO]]; + NSArray *primaryKeyQuotedNames = [keyParser splitStringByCharacter:',']; + if ([keyParser length]) { + NSMutableArray *primaryKeyFields = [NSMutableArray array]; + for (NSString *quotedKeyName in primaryKeyQuotedNames) { + NSString *primaryFieldName = [[SPSQLParser stringWithString:quotedKeyName] unquotedString]; + [primaryKeyFields addObject:primaryFieldName]; + for (NSMutableDictionary *theTableColumn in tableColumns) { + if ([[theTableColumn objectForKey:@"name"] isEqualToString:primaryFieldName]) { + [theTableColumn setObject:[NSNumber numberWithInteger:1] forKey:@"isprimarykey"]; + break; + } } + } + [tableData setObject:primaryKeyFields forKey:@"primarykeyfield"]; } } // unique keys // add to each corresponding tableColumn the tag "unique" if given else if( [NSArrayObjectAtIndex(parts, 0) hasPrefix:@"UNIQUE"] && [parts count] == 4) { - NSString *parsedString = [(NSString*)NSArrayObjectAtIndex(parts, 3) stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; - if([parsedString length]>4) { - NSArray *uniqueFieldNames = [parsedString componentsSeparatedByString:@"`,`"]; - for(NSString* uniq in uniqueFieldNames) { - NSString *uniqField = [[uniq stringByReplacingOccurrencesOfRegex:@"^\\(`|`\\)" withString:@""] stringByReplacingOccurrencesOfString:@"``" withString:@"`"]; - for(id theTableColumn in tableColumns) - if([[theTableColumn objectForKey:@"name"] isEqualToString:uniqField]) { - [theTableColumn setObject:[NSNumber numberWithInteger:1] forKey:@"unique"]; - break; - } + SPSQLParser *keyParser = [SPSQLParser stringWithString:NSArrayObjectAtIndex(parts, 3)]; + keyParser = [SPSQLParser stringWithString:[keyParser stringFromCharacter:'(' toCharacter:')' inclusively:NO]]; + for (NSString *quotedUniqueKey in [keyParser splitStringByCharacter:',']) { + NSString *uniqueFieldName = [[SPSQLParser stringWithString:quotedUniqueKey] unquotedString]; + for (NSMutableDictionary *theTableColumn in tableColumns) { + if ([[theTableColumn objectForKey:@"name"] isEqualToString:uniqueFieldName]) { + [theTableColumn setObject:[NSNumber numberWithInteger:1] forKey:@"unique"]; + break; + } } - } } // who knows @@ -764,7 +773,10 @@ encodingString = [[NSString alloc] initWithString:[createTableParser substringWithRange:NSMakeRange(stringStart, i-stringStart)]]; } - // If no DEFAULT CHARSET is present, it's likely MySQL < 4; fall back to latin1. + // If no DEFAULT CHARSET is present, fall back to either the database encoding (works back to MySQL 3), + // or if no document is available to supply the database encoding, Latin1. + } else if ([tableDocumentInstance databaseEncoding]) { + encodingString = [[NSString alloc] initWithString:[tableDocumentInstance databaseEncoding]]; } else { encodingString = [[NSString alloc] initWithString:@"latin1"]; } @@ -1284,52 +1296,19 @@ - (NSArray *)primaryKeyColumnNames { - // Ensure that identifier queries occur over UTF8 - BOOL changeEncoding = ![[mySQLConnection encoding] isEqualToString:@"utf8"]; - if (changeEncoding) { - [mySQLConnection storeEncodingForRestoration]; - [mySQLConnection setEncoding:@"utf8"]; - } - - NSString *selectedTable = [tableListInstance tableName]; - if(![selectedTable length]) return nil; - - SPMySQLResult *r; - NSMutableArray *keyColumns = [NSMutableArray array]; - - // select all columns that are primary keys - // MySQL before 5.0.3 does not support the WHERE syntax - r = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW COLUMNS FROM %@ /*!50003 WHERE `key` = 'PRI'*/", [selectedTable backtickQuotedString]]]; - [r setReturnDataAsStrings:YES]; - [r setDefaultRowReturnType:SPMySQLResultRowAsArray]; - - if ([r numberOfRows] < 1) { - if (changeEncoding && [mySQLConnection isConnected]) [mySQLConnection restoreStoredEncoding]; - return nil; - } - - if ([mySQLConnection queryErrored]) { - if ([mySQLConnection isConnected]) { - NSRunAlertPanel(@"Error", [NSString stringWithFormat:NSLocalizedString(@"An error occured while retrieving the PRIMARY KEY data:\n\n%@",@"message when the query that fetches the primary keys fails"), [mySQLConnection lastErrorMessage]], @"OK", nil, nil); - if (changeEncoding) [mySQLConnection restoreStoredEncoding]; - } - return nil; - } - - - for (NSArray *resultRow in r) { + // If processing is already in action, wait for it to complete + [self _loopWhileWorking]; - // check if the row is indeed a key (for MySQL servers before 5.0.3) - if ([[NSArrayObjectAtIndex(resultRow ,3) description] isEqualToString:@"PRI"]) { - [keyColumns addObject:[NSArrayObjectAtIndex(resultRow ,0) description]]; + if ([columns count] == 0) { + if ([tableListInstance tableType] == SPTableTypeView) { + [self updateInformationForCurrentView]; + } else { + [self updateInformationForCurrentTable]; } } - if (changeEncoding) [mySQLConnection restoreStoredEncoding]; - - if([keyColumns count]) return keyColumns; - - return nil; + if (![primaryKeyColumns count]) return nil; + return primaryKeyColumns; } #pragma mark - @@ -1343,6 +1322,7 @@ [columnNames release]; [constraints release]; [status release]; + [primaryKeyColumns release]; if (triggers) [triggers release]; if (tableEncoding) [tableEncoding release]; diff --git a/Source/SPTableRelations.m b/Source/SPTableRelations.m index 44d05ee7..2cc3c9d2 100644 --- a/Source/SPTableRelations.m +++ b/Source/SPTableRelations.m @@ -30,7 +30,7 @@ #import "SPTableView.h" #import "SPAlertSheets.h" #import "RegexKitLite.h" -#import "SPMySQL.h" +#import <SPMySQL/SPMySQL.h> static NSString *SPRemoveRelation = @"SPRemoveRelation"; diff --git a/Source/SPTableStructure.m b/Source/SPTableStructure.m index 2fbd6b5b..e443710c 100644 --- a/Source/SPTableStructure.m +++ b/Source/SPTableStructure.m @@ -36,7 +36,7 @@ #import "SPIndexesController.h" #import "RegexKitLite.h" #import "SPTableFieldValidation.h" -#import "SPMySQL.h" +#import <SPMySQL/SPMySQL.h> @interface SPTableStructure (PrivateAPI) @@ -292,15 +292,19 @@ // Set up the encoding PopUpButtonCell NSArray *encodings = [databaseDataInstance getDatabaseCharacterSetEncodings]; if ([encodings count]) { - [[encodingPopupCell onMainThread] removeAllItems]; - [[encodingPopupCell onMainThread] addItemWithTitle:@""]; // Populate encoding popup button + NSMutableArray *encodingTitles = [[NSMutableArray alloc] initWithCapacity:[encodings count]+1]; + [encodingTitles addObject:@""]; for (NSDictionary *encoding in encodings) - [[encodingPopupCell onMainThread] addItemWithTitle:(![encoding objectForKey:@"DESCRIPTION"]) ? [encoding objectForKey:@"CHARACTER_SET_NAME"] : [NSString stringWithFormat:@"%@ (%@)", [encoding objectForKey:@"DESCRIPTION"], [encoding objectForKey:@"CHARACTER_SET_NAME"]]]; + [encodingTitles addObject:(![encoding objectForKey:@"DESCRIPTION"]) ? [encoding objectForKey:@"CHARACTER_SET_NAME"] : [NSString stringWithFormat:@"%@ (%@)", [encoding objectForKey:@"DESCRIPTION"], [encoding objectForKey:@"CHARACTER_SET_NAME"]]]; + [[encodingPopupCell onMainThread] removeAllItems]; + [[encodingPopupCell onMainThread] addItemsWithTitles:encodingTitles]; + [encodingTitles release]; } else { + [[encodingPopupCell onMainThread] removeAllItems]; [[encodingPopupCell onMainThread] addItemWithTitle:NSLocalizedString(@"Not available", @"not available label")]; } @@ -547,6 +551,7 @@ return; } + [theResult setReturnDataAsStrings:YES]; NSDictionary *analysisResult = [theResult getRowAsDictionary]; NSString *type = [analysisResult objectForKey:@"Optimal_fieldtype"]; @@ -694,10 +699,15 @@ NSArray *buttons = [alert buttons]; +#ifndef SP_REFACTOR // Change the alert's cancel button to have the key equivalent of return [[buttons objectAtIndex:0] setKeyEquivalent:@"d"]; [[buttons objectAtIndex:0] setKeyEquivalentModifierMask:NSCommandKeyMask]; [[buttons objectAtIndex:1] setKeyEquivalent:@"\r"]; +#else + [[buttons objectAtIndex:0] setKeyEquivalent:@"\r"]; + [[buttons objectAtIndex:1] setKeyEquivalent:@"\e"]; +#endif [alert beginSheetModalForWindow:[tableDocumentInstance parentWindow] modalDelegate:self didEndSelector:@selector(removeFieldSheetDidEnd:returnCode:contextInfo:) contextInfo:(hasForeignKey) ? @"removeFieldAndForeignKey" : @"removeField"]; } diff --git a/Source/SPTableStructureDelegate.m b/Source/SPTableStructureDelegate.m index fdcf6b3b..6fc5e7b6 100644 --- a/Source/SPTableStructureDelegate.m +++ b/Source/SPTableStructureDelegate.m @@ -30,7 +30,7 @@ #import "SPTableData.h" #import "SPTableView.h" #import "SPTableFieldValidation.h" -#import "SPMySQL.h" +#import <SPMySQL/SPMySQL.h> @implementation SPTableStructure (SPTableStructureDelegate) @@ -49,7 +49,7 @@ if([[tableColumn identifier] isEqualToString:@"collation"]) { NSInteger idx = 0; - if((idx = [[NSArrayObjectAtIndex(tableFields,rowIndex) objectForKey:@"encoding"] integerValue]) > 0) { + if((idx = [[NSArrayObjectAtIndex(tableFields,rowIndex) objectForKey:@"encoding"] integerValue]) > 0 && idx < [encodingPopupCell numberOfItems]) { NSString *enc = [[encodingPopupCell itemAtIndex:idx] title]; NSInteger start = [enc rangeOfString:@"("].location+1; NSInteger end = [enc length] - start - 1; diff --git a/Source/SPTableTriggers.m b/Source/SPTableTriggers.m index ee5a6d5a..d798a76b 100644 --- a/Source/SPTableTriggers.m +++ b/Source/SPTableTriggers.m @@ -30,7 +30,7 @@ #import "SPTableView.h" #import "SPAlertSheets.h" #import "SPServerSupport.h" -#import "SPMySQL.h" +#import <SPMySQL/SPMySQL.h> // Constants static const NSString *SPTriggerName = @"TriggerName"; diff --git a/Source/SPTableView.m b/Source/SPTableView.m index e135e16b..3fced944 100644 --- a/Source/SPTableView.m +++ b/Source/SPTableView.m @@ -241,6 +241,34 @@ emptyDoubleClickAction = aSelector; } +#ifdef SP_REFACTOR + +- (void)delete:(id)sender +{ + if ( [[self delegate] respondsToSelector:@selector(removeField:)] ) + { + [[self delegate] performSelector:@selector(removeField:) withObject:self]; + } + else if ( [[self delegate] respondsToSelector:@selector(removeIndex:)] ) + { + [[self delegate] performSelector:@selector(removeIndex:) withObject:self]; + } +} + + +- (BOOL)validateMenuItem:(NSMenuItem *)menuItem +{ + if ( [menuItem action] == @selector(delete:) ) + { + if ( [self numberOfSelectedRows] == 0 ) + return NO; + } + + return YES; +} + +#endif + @end diff --git a/Source/SPTablesList.h b/Source/SPTablesList.h index 6da4f6cc..897fb6b3 100644 --- a/Source/SPTablesList.h +++ b/Source/SPTablesList.h @@ -78,8 +78,8 @@ #endif #ifndef SP_REFACTOR IBOutlet id toolbarActionsButton; - IBOutlet id toolbarReloadButton; #endif + IBOutlet id toolbarReloadButton; IBOutlet id addTableButton; #ifndef SP_REFACTOR IBOutlet id truncateTableButton; @@ -139,10 +139,10 @@ // IBAction methods - (IBAction)updateTables:(id)sender; - - (IBAction)addTable:(id)sender; - (IBAction)closeSheet:(id)sender; - (IBAction)removeTable:(id)sender; + #ifndef SP_REFACTOR /* method decls */ - (IBAction)copyTable:(id)sender; - (IBAction)renameTable:(id)sender; @@ -150,15 +150,19 @@ - (IBAction)openTableInNewTab:(id)sender; - (IBAction)togglePaneCollapse:(id)sender; #endif + // Additional methods - (void)setConnection:(SPMySQLConnection *)theConnection; - (void)setSelectionState:(NSDictionary *)selectionDetails; + #ifndef SP_REFACTOR /* method decls */ - (void)selectTableAtIndex:(NSNumber *)row; - (void)makeTableListFilterHaveFocus; +#endif // Getters - (NSArray *)selectedTableNames; +#ifndef SP_REFACTOR /* method decls */ - (NSArray *)selectedTableItems; - (NSArray *)selectedTableTypes; #endif @@ -181,9 +185,9 @@ - (BOOL)selectItemsWithNames:(NSArray *)theNames; // Table list filter interaction -- (void) showFilter; -- (void) hideFilter; -- (void) clearFilter; +- (void)showFilter; +- (void)hideFilter; +- (void)clearFilter; #endif - (IBAction) updateFilter:(id)sender; @@ -199,16 +203,19 @@ @property (assign) SPTableContent* tableContentInstance; @property (assign) id toolbarAddButton; @property (assign) id toolbarDeleteButton; +@property (assign) id toolbarReloadButton; @property (assign) id tableSheet; @property (assign) id tableNameField; @property (assign) id tableEncodingButton; @property (assign) id tableTypeButton; @property (assign) id databaseDataInstance; @property (assign) id addTableButton; -@property (assign) NSTableView* tablesListView; +@property (assign) SPTableView* tablesListView; @property (assign) SQLSidebarViewController* sidebarViewController; - (BOOL)selectionShouldChangeInTableView:(NSTableView *)aTableView; - (void)setDatabaseDocument:(SPDatabaseDocument*)val; +- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex; + #endif @end diff --git a/Source/SPTablesList.m b/Source/SPTablesList.m index 0283eb98..690b30d9 100644 --- a/Source/SPTablesList.m +++ b/Source/SPTablesList.m @@ -27,7 +27,7 @@ #import "SPDatabaseDocument.h" #import "SPTableStructure.h" #import "SPDatabaseViewController.h" -#import "SPMySQL.h" +#import <SPMySQL/SPMySQL.h> #ifndef SP_REFACTOR /* headers */ #import "SPTableContent.h" @@ -74,8 +74,8 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; - (void)_addTable; #ifndef SP_REFACTOR - (void)_copyTable; -- (void)_renameTableOfType:(SPTableType)tableType from:(NSString *)oldTableName to:(NSString *)newTableName; #endif +- (void)_renameTableOfType:(SPTableType)tableType from:(NSString *)oldTableName to:(NSString *)newTableName; @end @@ -86,6 +86,7 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; @synthesize databaseDataInstance; @synthesize toolbarAddButton; @synthesize toolbarDeleteButton; +@synthesize toolbarReloadButton; @synthesize tableSourceInstance; @synthesize tableContentInstance; @synthesize tableSheet; @@ -97,6 +98,83 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; #endif #pragma mark - +#pragma mark Initialisation + +/** + * Standard init method. Performs various ivar initialisations. + */ +- (id)init +{ + if ((self = [super init])) { + tables = [[NSMutableArray alloc] init]; + filteredTables = tables; + tableTypes = [[NSMutableArray alloc] init]; + filteredTableTypes = tableTypes; + isTableListFiltered = NO; + tableListIsSelectable = YES; + tableListContainsViews = NO; + selectedTableType = SPTableTypeNone; + selectedTableName = nil; +#ifndef SP_REFACTOR + [tables addObject:NSLocalizedString(@"TABLES", @"header for table list")]; + + smallSystemFont = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]; +#endif + } + + return self; +} + +/** + * Standard awakeFromNib method for interface loading. + */ +- (void)awakeFromNib +{ +#ifndef SP_REFACTOR + // Collapse the table information pane if preference to do so is set + if ([[[NSUserDefaults standardUserDefaults] objectForKey:SPTableInformationPanelCollapsed] boolValue] + && [tableListSplitView collapsibleSubview]) { + [tableInfoCollapseButton setNextState]; + [tableInfoCollapseButton setToolTip:NSLocalizedString(@"Show Table Information",@"Show Table Information")]; + [tableListSplitView setValue:[NSNumber numberWithFloat:[tableListSplitView collapsibleSubview].frame.size.height] forKey:@"uncollapsedSize"]; + [[tableListSplitView collapsibleSubview] setAutoresizesSubviews:NO]; + [[tableListSplitView collapsibleSubview] setFrameSize:NSMakeSize([tableListSplitView collapsibleSubview].frame.size.width, 0)]; + [tableListSplitView setCollapsibleSubviewCollapsed:YES]; + [[tableListSplitView collapsibleSubview] setAutoresizesSubviews:YES]; + } + else { + [tableInfoCollapseButton setToolTip:NSLocalizedString(@"Hide Table Information",@"Hide Table Information")]; + } + + // Start the table filter list collapsed + if ([tableListFilterSplitView collapsibleSubview]) { + [tableListFilterSplitView setValue:[NSNumber numberWithFloat:[tableListFilterSplitView collapsibleSubview].frame.size.height] forKey:@"uncollapsedSize"]; + // Set search bar view to the height of 1 instead of 0 to ensure that the view will be visible + // after opening a next connection window which has more than 20 tables + [[tableListFilterSplitView collapsibleSubview] setFrameSize:NSMakeSize([tableListFilterSplitView collapsibleSubview].frame.size.width, 1)]; + [tableListFilterSplitView setCollapsibleSubviewCollapsed:YES]; + } + + // Disable tab edit behaviour in the tables list + [tablesListView setTabEditingDisabled:YES]; +#endif + + // Add observers for document task activity + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(startDocumentTaskForTab:) + name:SPDocumentTaskStartNotification + object:tableDocumentInstance]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(endDocumentTaskForTab:) + name:SPDocumentTaskEndNotification + object:tableDocumentInstance]; + +#ifndef SP_REFACTOR + [tablesListView registerForDraggedTypes:[NSArray arrayWithObjects:SPNavigatorTableDataPasteboardDragType, nil]]; +#endif +} + +#pragma mark - #pragma mark IBAction methods /** @@ -214,58 +292,8 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; } } } -#endif /* - BOOL addedPFHeader = FALSE; - NSString *pQuery = [NSString stringWithFormat:@"SHOW PROCEDURE STATUS WHERE db = '%@'",[tableDocumentInstance database]]; - theResult = [mySQLConnection queryString:pQuery]; - - if( [theResult numOfRows] ) { - // add the header row - [tables addObject:NSLocalizedString(@"PROCS & FUNCS",@"header for procs & funcs list")]; - [tableTypes addObject:[NSNumber numberWithInt:SPTableTypeNone]]; - addedPFHeader = TRUE; - [theResult dataSeek:0]; - - if( [theResult numOfFields] == 1 ) { - for( i = 0; i < [theResult numOfRows]; i++ ) { - [tables addObject:[[theResult fetchRowAsArray] objectAtIndex:1]]; - [tableTypes addObject:[NSNumber numberWithInt:SPTableTypeProc]]; - } - } else { - for( i = 0; i < [theResult numOfRows]; i++ ) { - resultRow = [theResult fetchRowAsArray]; - [tables addObject:[resultRow objectAtIndex:1]]; - [tableTypes addObject:[NSNumber numberWithInt:SPTableTypeProc]]; - } - } - } - - pQuery = [NSString stringWithFormat:@"SHOW FUNCTION STATUS WHERE db = '%@'",[tableDocumentInstance database]]; - theResult = [mySQLConnection queryString:pQuery]; - - if( [theResult numOfRows] ) { - if( !addedPFHeader ) { - // add the header row - [tables addObject:NSLocalizedString(@"PROCS & FUNCS",@"header for procs & funcs list")]; - [tableTypes addObject:[NSNumber numberWithInt:SPTableTypeNone]]; - } - [theResult dataSeek:0]; - - if( [theResult numOfFields] == 1 ) { - for( i = 0; i < [theResult numOfRows]; i++ ) { - [tables addObject:[[theResult fetchRowAsArray] objectAtIndex:1]]; - [tableTypes addObject:[NSNumber numberWithInt:SPTableTypeFunc]]; - } - } else { - for( i = 0; i < [theResult numOfRows]; i++ ) { - resultRow = [theResult fetchRowAsArray]; - [tables addObject:[resultRow objectAtIndex:1]]; - [tableTypes addObject:[NSNumber numberWithInt:SPTableTypeFunc]]; - } - } - } - */ - +#endif + // Restore encoding if appropriate if (changeEncoding) [mySQLConnection restoreStoredEncoding]; @@ -273,6 +301,7 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; } +#ifndef SP_REFACTOR // Add the table headers even if no tables were found if (tableListContainsViews) { [tables insertObject:NSLocalizedString(@"TABLES & VIEWS",@"header for table & views list") atIndex:0]; @@ -282,9 +311,14 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; } [tableTypes insertObject:[NSNumber numberWithInteger:SPTableTypeNone] atIndex:0]; +#endif #ifndef SP_REFACTOR /* ui manipulation */ [[tablesListView onMainThread] reloadData]; +#else + [sidebarViewController setTableNames:[self allTableNames] selectedTableName:selectedTableName]; + [sidebarViewController tableViewSelectionDidChange:nil]; + #endif // if the previous selected table still exists, select it @@ -432,10 +466,15 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; NSArray *buttons = [alert buttons]; +#ifndef SP_REFACTOR // Change the alert's cancel button to have the key equivalent of return [[buttons objectAtIndex:0] setKeyEquivalent:@"d"]; [[buttons objectAtIndex:0] setKeyEquivalentModifierMask:NSCommandKeyMask]; [[buttons objectAtIndex:1] setKeyEquivalent:@"\r"]; +#else + [[buttons objectAtIndex:0] setKeyEquivalent:@"\r"]; // Return = OK + [[buttons objectAtIndex:1] setKeyEquivalent:@"\e"]; // Esc = Cancel +#endif NSIndexSet *indexes = [tablesListView selectedRowIndexes]; @@ -558,38 +597,6 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; if (![[self tableName] length]) return; [tablesListView editColumn:0 row:[tablesListView selectedRow] withEvent:nil select:YES]; - - /* - - [tableRenameField setStringValue:[self tableName]]; - [renameTableButton setEnabled:NO]; - - NSString *tableType; - - switch([self tableType]){ - case SPTableTypeTable: - tableType = NSLocalizedString(@"table",@"table"); - break; - case SPTableTypeView: - tableType = NSLocalizedString(@"view",@"view"); - break; - case SPTableTypeProc: - tableType = NSLocalizedString(@"procedure",@"procedure"); - break; - case SPTableTypeFunc: - tableType = NSLocalizedString(@"function",@"function"); - break; - } - - [tableRenameText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Rename %@ '%@' to:",@"rename item name to:"), tableType, [self tableName]]]; - - - [NSApp beginSheet:tableRenameSheet - modalForWindow:[tableDocumentInstance parentWindow] - modalDelegate:self - didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) - contextInfo:@"renameTable"]; - */ } /** @@ -677,10 +684,12 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; - (void)sheetDidEnd:(id)sheet returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo { // Order out current sheet to suppress overlapping of sheets - if ([sheet respondsToSelector:@selector(orderOut:)]) + if ([sheet respondsToSelector:@selector(orderOut:)]) { [sheet orderOut:nil]; - else if ([sheet respondsToSelector:@selector(window)]) + } + else if ([sheet respondsToSelector:@selector(window)]) { [[sheet window] orderOut:nil]; + } if ([contextInfo isEqualToString:SPAddRow]) { alertSheetOpened = NO; @@ -721,6 +730,7 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; - (void)setConnection:(SPMySQLConnection *)theConnection { mySQLConnection = theConnection; + [self updateTables:self]; } @@ -777,7 +787,9 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; { // First handle empty or multiple selections if (!selectionDetails || ![selectionDetails objectForKey:@"name"]) { +#ifndef SP_REFACTOR NSIndexSet *indexes = [tablesListView selectedRowIndexes]; +#endif // Update the selected table name and type if (selectedTableName) [selectedTableName release]; selectedTableName = nil; @@ -1099,12 +1111,9 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; #endif } -#ifndef SP_REFACTOR /* getters */ - #pragma mark - #pragma mark Getter methods - - (NSArray *)selectedTableNames { NSIndexSet *indexes = [tablesListView selectedRowIndexes]; @@ -1120,6 +1129,7 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; return selTables; } +#ifndef SP_REFACTOR /* getters */ - (NSArray *)selectedTableItems { NSIndexSet *indexes = [tablesListView selectedRowIndexes]; @@ -1187,6 +1197,7 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; } return returnArray; } + - (NSArray *)allTableNames { NSMutableArray *returnArray = [NSMutableArray array]; @@ -1198,6 +1209,7 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; } return returnArray; } + - (NSArray *)allViewNames { NSMutableArray *returnArray = [NSMutableArray array]; @@ -1210,6 +1222,7 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; [returnArray sortUsingSelector:@selector(compare:)]; return returnArray; } + - (NSArray *)allProcedureNames { NSMutableArray *returnArray = [NSMutableArray array]; @@ -1262,11 +1275,9 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; return tableTypes; } - #pragma mark - #pragma mark Setter methods - /** * Select an item using the provided name; returns YES if the * supplied name could be selected, or NO if not. @@ -1445,7 +1456,6 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; */ - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex { - // During imports the table view sometimes appears to request items beyond the end of the array. // Using a hinted noteNumberOfRowsChanged after dropping tables fixes this but then seems to stick // even after override, so check here for the time being and display empty rows during import. @@ -1461,7 +1471,7 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; { return ![tableDocumentInstance isWorking]; } - +#endif /** * Renames a table (in tables-array and mysql-db). @@ -1516,13 +1526,16 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; SPBeginAlertSheet( NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, [myException reason]); } +#ifndef SP_REFACTOR // Set window title to reflect the new table name [tableDocumentInstance updateWindowTitle:self]; +#endif // Query the structure of all databases in the background (mainly for completion) [NSThread detachNewThreadSelector:@selector(queryDbStructureWithUserInfo:) toTarget:[tableDocumentInstance databaseStructureRetrieval] withObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], @"forceUpdate", nil]]; } +#ifndef SP_REFACTOR #pragma mark - #pragma mark TableView delegate methods @@ -1704,7 +1717,8 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; [aCell setFont:smallSystemFont]; } - } else { + } + else { [aCell setImage:nil]; [aCell setIndentationLevel:0]; } @@ -1832,7 +1846,6 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; */ - (IBAction) updateFilter:(id)sender { - // Don't try and maintain selections of multiple rows through filtering if ([tablesListView numberOfSelectedRows] > 1) { [tablesListView deselectAll:self]; @@ -1862,6 +1875,7 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; if (substringRange.location == NSNotFound) continue; } +#ifndef SP_REFACTOR // Add a title if necessary if ((tableType == SPTableTypeTable || tableType == SPTableTypeView) && lastTableType == NSNotFound) { @@ -1877,6 +1891,7 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; [filteredTables addObject:NSLocalizedString(@"PROCS & FUNCS",@"header for procs & funcs list")]; [filteredTableTypes addObject:[NSNumber numberWithInteger:SPTableTypeNone]]; } +#endif lastTableType = tableType; // Add the item @@ -1899,7 +1914,8 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; } isTableListFiltered = YES; - } else if (isTableListFiltered) { + } + else if (isTableListFiltered) { isTableListFiltered = NO; [filteredTables release]; #endif @@ -1910,18 +1926,12 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; } #endif -#ifdef SP_REFACTOR - [sidebarViewController setTableNames:[self allTableNames]]; -#endif - +#ifndef SP_REFACTOR // Reselect correct row and reload the table view display if ([tablesListView numberOfRows] < (NSInteger)[filteredTables count]) [tablesListView noteNumberOfRowsChanged]; - if (selectedTableName) [tablesListView selectRowIndexes:[NSIndexSet indexSetWithIndex:[filteredTables indexOfObject:selectedTableName] -#ifdef SP_REFACTOR - - 1 -#endif - ] byExtendingSelection:NO]; + if (selectedTableName) [tablesListView selectRowIndexes:[NSIndexSet indexSetWithIndex:[filteredTables indexOfObject:selectedTableName]] byExtendingSelection:NO]; [tablesListView reloadData]; +#endif } /** @@ -1948,33 +1958,33 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; /** * Disable all table list interactive elements during an ongoing task. */ -- (void) startDocumentTaskForTab:(NSNotification *)aNotification +- (void)startDocumentTaskForTab:(NSNotification *)aNotification { tableListIsSelectable = NO; [toolbarAddButton setEnabled:NO]; #ifndef SP_REFACTOR [toolbarActionsButton setEnabled:NO]; - [toolbarReloadButton setEnabled:NO]; #endif + [toolbarReloadButton setEnabled:NO]; } /** * Enable all table list interactive elements after an ongoing task. */ -- (void) endDocumentTaskForTab:(NSNotification *)aNotification +- (void)endDocumentTaskForTab:(NSNotification *)aNotification { tableListIsSelectable = YES; [toolbarAddButton setEnabled:YES]; #ifndef SP_REFACTOR [toolbarActionsButton setEnabled:YES]; - [toolbarReloadButton setEnabled:YES]; #endif + [toolbarReloadButton setEnabled:YES]; } /** * Set the table list to selectable or not during the task process. */ -- (void) setTableListSelectability:(BOOL)isSelectable +- (void)setTableListSelectability:(BOOL)isSelectable { tableListIsSelectable = isSelectable; } @@ -1993,77 +2003,14 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; #pragma mark - #pragma mark Other -/** - * Standard init method. Performs various ivar initialisations. - */ -- (id)init +#ifdef SP_REFACTOR /* glue */ +- (void)setDatabaseDocument:(SPDatabaseDocument*)val { - if ((self = [super init])) { - tables = [[NSMutableArray alloc] init]; - filteredTables = tables; - tableTypes = [[NSMutableArray alloc] init]; - filteredTableTypes = tableTypes; - isTableListFiltered = NO; - tableListIsSelectable = YES; - tableListContainsViews = NO; - selectedTableType = SPTableTypeNone; - selectedTableName = nil; - [tables addObject:NSLocalizedString(@"TABLES",@"header for table list")]; -#ifndef SP_REFACTOR /* font */ - smallSystemFont = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]; -#endif - } - - return self; + tableDocumentInstance = val; } - -/** - * Standard awakeFromNib method for interface loading. - */ -- (void)awakeFromNib -{ -#ifndef SP_REFACTOR - // Collapse the table information pane if preference to do so is set - if ([[[NSUserDefaults standardUserDefaults] objectForKey:SPTableInformationPanelCollapsed] boolValue] - && [tableListSplitView collapsibleSubview]) { - [tableInfoCollapseButton setNextState]; - [tableInfoCollapseButton setToolTip:NSLocalizedString(@"Show Table Information",@"Show Table Information")]; - [tableListSplitView setValue:[NSNumber numberWithFloat:[tableListSplitView collapsibleSubview].frame.size.height] forKey:@"uncollapsedSize"]; - [[tableListSplitView collapsibleSubview] setAutoresizesSubviews:NO]; - [[tableListSplitView collapsibleSubview] setFrameSize:NSMakeSize([tableListSplitView collapsibleSubview].frame.size.width, 0)]; - [tableListSplitView setCollapsibleSubviewCollapsed:YES]; - [[tableListSplitView collapsibleSubview] setAutoresizesSubviews:YES]; - } else { - [tableInfoCollapseButton setToolTip:NSLocalizedString(@"Hide Table Information",@"Hide Table Information")]; - } - - // Start the table filter list collapsed - if ([tableListFilterSplitView collapsibleSubview]) { - [tableListFilterSplitView setValue:[NSNumber numberWithFloat:[tableListFilterSplitView collapsibleSubview].frame.size.height] forKey:@"uncollapsedSize"]; - // Set search bar view to the height of 1 instead of 0 to ensure that the view will be visible - // after opening a next connection window which has more than 20 tables - [[tableListFilterSplitView collapsibleSubview] setFrameSize:NSMakeSize([tableListFilterSplitView collapsibleSubview].frame.size.width, 1)]; - [tableListFilterSplitView setCollapsibleSubviewCollapsed:YES]; - } - - // Disable tab edit behaviour in the tables list - [tablesListView setTabEditingDisabled:YES]; #endif - // Add observers for document task activity - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(startDocumentTaskForTab:) - name:SPDocumentTaskStartNotification - object:tableDocumentInstance]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(endDocumentTaskForTab:) - name:SPDocumentTaskEndNotification - object:tableDocumentInstance]; - -#ifndef SP_REFACTOR - [tablesListView registerForDraggedTypes:[NSArray arrayWithObjects:SPNavigatorTableDataPasteboardDragType, nil]]; -#endif -} +#pragma mark - /** * Standard dealloc method. @@ -2083,15 +2030,8 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; [super dealloc]; } - -#ifdef SP_REFACTOR /* glue */ -- (void)setDatabaseDocument:(SPDatabaseDocument*)val -{ - tableDocumentInstance = val; -} -#endif - -#ifndef SP_REFACTOR /* operations performed on whole tables */ +#pragma mark - +#pragma mark Private API /** * Removes the selected object (table, view, procedure, function, etc.) from the database and tableView. @@ -2178,15 +2118,21 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; [tablesListView deselectAll:self]; +#ifndef SP_REFACTOR // set window title [tableDocumentInstance updateWindowTitle:self]; - +#endif +#ifdef SP_REFACTOR + [sidebarViewController setTableNames:filteredTables selectedTableName:nil]; +#endif // Query the structure of all databases in the background (mainly for completion) [NSThread detachNewThreadSelector:@selector(queryDbStructureWithUserInfo:) toTarget:[tableDocumentInstance databaseStructureRetrieval] withObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], @"forceUpdate", nil]]; } +#ifndef SP_REFACTOR /* operations performed on whole tables */ + /** * Trucates the selected table(s). */ @@ -2211,12 +2157,11 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; [filteredTables objectAtIndex:currentIndex], [mySQLConnection lastErrorMessage]]]; [alert setAlertStyle:NSCriticalAlertStyle]; - // NSArray *buttons = [alert buttons]; - // // Change the alert's cancel button to have the key equivalent of return - // [[buttons objectAtIndex:0] setKeyEquivalent:@"t"]; - // [[buttons objectAtIndex:0] setKeyEquivalentModifierMask:NSCommandKeyMask]; - // [[buttons objectAtIndex:1] setKeyEquivalent:@"\r"]; - [alert beginSheetModalForWindow:[tableDocumentInstance parentWindow] modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:@"truncateTableError"]; + + [alert beginSheetModalForWindow:[tableDocumentInstance parentWindow] + modalDelegate:self + didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) + contextInfo:@"truncateTableError"]; } // Get next index (beginning from the end) @@ -2240,7 +2185,9 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; [NSThread detachNewThreadSelector:@selector(_addTable) toTarget:self withObject:nil]; return; } + NSAutoreleasePool *tableAdditionPool = [[NSAutoreleasePool alloc] init]; + [tableDocumentInstance startTaskWithDescription:[NSString stringWithFormat:NSLocalizedString(@"Creating %@...", @"Creating table task string"), [tableNameField stringValue]]]; NSString *charSetStatement = @""; @@ -2251,6 +2198,7 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; // Ensure the use of UTF8 when creating new tables BOOL changeEncoding = ![[mySQLConnection encoding] isEqualToString:@"utf8"]; + if (changeEncoding) { [mySQLConnection storeEncodingForRestoration]; [mySQLConnection setEncoding:@"utf8"]; @@ -2259,7 +2207,9 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; // If there is an encoding selected other than the default we must specify it in CREATE TABLE statement if ([tableEncodingButton indexOfSelectedItem] > 0) { NSString *encodingName = [[tableEncodingButton title] stringByMatching:@"\\((.*)\\)" capture:1L]; + if (!encodingName) encodingName = @"utf8"; + charSetStatement = [NSString stringWithFormat:@"DEFAULT CHARACTER SET %@", [encodingName backtickQuotedString]]; } @@ -2318,9 +2268,14 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; [tableDocumentInstance viewStructure:self]; #endif - // Query the structure of all databases in the background (mainly for completion) - [NSThread detachNewThreadSelector:@selector(queryDbStructureWithUserInfo:) toTarget:[tableDocumentInstance databaseStructureRetrieval] withObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], @"forceUpdate", nil]]; +#ifdef SP_REFACTOR + [sidebarViewController setTableNames:[self allTableNames] selectedTableName:selectedTableName]; +#endif + // Query the structure of all databases in the background (mainly for completion) + [NSThread detachNewThreadSelector:@selector(queryDbStructureWithUserInfo:) + toTarget:[tableDocumentInstance databaseStructureRetrieval] + withObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], @"forceUpdate", nil]]; } else { // Error while creating new table @@ -2330,14 +2285,15 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, @selector(sheetDidEnd:returnCode:contextInfo:), SPAddRow, [NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to add the new table '%@'.\n\nMySQL said: %@", @"error adding new table informative message"), tableName, [mySQLConnection lastErrorMessage]]); - + if (changeEncoding) [mySQLConnection restoreStoredEncoding]; + [[tablesListView onMainThread] reloadData]; } // Clear table name [[tableNameField onMainThread] setStringValue:@""]; - + [tableDocumentInstance endTask]; [tableAdditionPool release]; } @@ -2527,6 +2483,7 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; } } } +#endif /** * Renames a table, view, procedure or function. Also handles only changes in case! @@ -2616,6 +2573,5 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; [NSException raise:@"Object of unknown type" format:NSLocalizedString(@"An error occured while renaming. '%@' is of an unknown type.", @"rename error - don't know what type the renamed thing is"), oldTableName]; } -#endif @end diff --git a/Source/SPTablesPreferencePane.m b/Source/SPTablesPreferencePane.m index 90a1893b..6e009cd8 100644 --- a/Source/SPTablesPreferencePane.m +++ b/Source/SPTablesPreferencePane.m @@ -53,7 +53,6 @@ NSFont *font = [NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPGlobalResultTableFont]]; [globalResultTableFontName setFont:font]; - [globalResultTableFontName setStringValue:[NSString stringWithFormat:@"%@, %.1f pt", [font displayName], [font pointSize]]]; } #pragma mark - diff --git a/Source/SPTextView.h b/Source/SPTextView.h index 5c9c4054..3f70edde 100644 --- a/Source/SPTextView.h +++ b/Source/SPTextView.h @@ -110,6 +110,13 @@ @property(assign) BOOL completionIsOpen; @property(assign) BOOL completionWasReinvokedAutomatically; +#ifdef SP_REFACTOR +@property (assign) SPDatabaseDocument *tableDocumentInstance; +@property (assign) SPTablesList *tablesListInstance; +@property (assign) SPCustomQuery *customQueryInstance; +@property (assign) SPMySQLConnection *mySQLConnection; +#endif + #ifndef SP_REFACTOR - (IBAction)showMySQLHelpForCurrentWord:(id)sender; #endif diff --git a/Source/SPTextView.m b/Source/SPTextView.m index 137ff021..a1b80ab4 100644 --- a/Source/SPTextView.m +++ b/Source/SPTextView.m @@ -33,10 +33,14 @@ #import "SPNavigatorController.h" #import "SPAlertSheets.h" #import "RegexKitLite.h" +#ifndef SP_REFACTOR /* headers */ #import "SPBundleHTMLOutputController.h" +#endif #import "SPDatabaseViewController.h" +#ifndef SP_REFACTOR /* headers */ #import "SPAppController.h" -#import "SPMySQL.h" +#endif +#import <SPMySQL/SPMySQL.h> #import "SPDatabaseStructure.h" #pragma mark - @@ -105,6 +109,13 @@ static inline NSPoint SPPointOnLine(NSPoint a, NSPoint b, CGFloat t) { return NS @synthesize completionIsOpen; @synthesize completionWasReinvokedAutomatically; +#ifdef SP_REFACTOR +@synthesize tableDocumentInstance; +@synthesize tablesListInstance; +@synthesize customQueryInstance; +@synthesize mySQLConnection; +#endif + /** * Sort function (mainly used to sort the words in the textView) */ diff --git a/Source/SPTextViewAdditions.m b/Source/SPTextViewAdditions.m index b2a2354b..8c5043cf 100644 --- a/Source/SPTextViewAdditions.m +++ b/Source/SPTextViewAdditions.m @@ -24,9 +24,13 @@ #import "SPAlertSheets.h" #import "SPTooltip.h" +#ifndef SP_REFACTOR /* headers */ #import "SPBundleHTMLOutputController.h" +#endif #import "SPCustomQuery.h" +#ifndef SP_REFACTOR /* headers */ #import "SPAppController.h" +#endif #import "SPFieldEditorController.h" #import "SPTextView.h" #import "SPWindowController.h" @@ -553,7 +557,7 @@ currentLineRange = [[self string] lineRangeForRange:NSMakeRange([self selectedRange].location, 0)]; if(selfIsQueryEditor) { - currentQueryRange = [[self delegate] currentQueryRange]; + currentQueryRange = [(SPCustomQuery*)[self delegate] currentQueryRange]; } else { currentQueryRange = currentLineRange; } @@ -617,8 +621,8 @@ } } - if(selfIsQueryEditor && [[self delegate] currentQueryRange].length) - [env setObject:[[self string] substringWithRange:[[self delegate] currentQueryRange]] forKey:SPBundleShellVariableCurrentQuery]; + if(selfIsQueryEditor && [(SPCustomQuery*)[self delegate] currentQueryRange].length) + [env setObject:[[self string] substringWithRange:[(SPCustomQuery*)[self delegate] currentQueryRange]] forKey:SPBundleShellVariableCurrentQuery]; if(currentSelectionRange.length) [env setObject:[[self string] substringWithRange:currentSelectionRange] forKey:SPBundleShellVariableSelectedText]; diff --git a/Source/SPUserManager.h b/Source/SPUserManager.h index 11bb736e..319d83b5 100644 --- a/Source/SPUserManager.h +++ b/Source/SPUserManager.h @@ -31,9 +31,7 @@ NSManagedObjectContext *managedObjectContext; NSDictionary *privColumnToGrantMap; - BOOL isInitializing; - - SPMySQLConnection *mySqlConnection; + SPMySQLConnection *connection; SPServerSupport *serverSupport; IBOutlet NSOutlineView *outlineView; @@ -70,10 +68,11 @@ NSSortDescriptor *treeSortDescriptor; BOOL isSaving; + BOOL isInitializing; NSMutableString *errorsString; } -@property (nonatomic, retain) SPMySQLConnection *mySqlConnection; +@property (nonatomic, retain) SPMySQLConnection *connection; @property (nonatomic, retain) SPServerSupport *serverSupport; @property (nonatomic, retain) NSPersistentStoreCoordinator *persistentStoreCoordinator; @property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel; diff --git a/Source/SPUserManager.m b/Source/SPUserManager.m index 9fb2cc18..224b36c6 100644 --- a/Source/SPUserManager.m +++ b/Source/SPUserManager.m @@ -29,12 +29,14 @@ #import "SPConnectionController.h" #import "SPServerSupport.h" #import "SPAlertSheets.h" -#import "SPMySQL.h" + +#import <SPMySQL/SPMySQL.h> +#import <QueryKit/QueryKit.h> #import <BWToolkitFramework/BWAnchoredButtonBar.h> static const NSString *SPTableViewNameColumnID = @"NameColumn"; -@interface SPUserManager (PrivateAPI) +@interface SPUserManager () - (void)_initializeTree:(NSArray *)items; - (void)_initializeUsers; @@ -51,12 +53,13 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; - (NSArray *)_fetchPrivsWithUser:(NSString *)username schema:(NSString *)selectedSchema host:(NSString *)host; - (void)_setSchemaPrivValues:(NSArray *)objects enabled:(BOOL)enabled; - (void)_initializeAvailablePrivs; +- (void)_renameUserFrom:(NSString *)originalUser host:(NSString *)originalHost to:(NSString *)newUser host:(NSString *)newHost; @end @implementation SPUserManager -@synthesize mySqlConnection; +@synthesize connection; @synthesize privsSupportedByServer; @synthesize managedObjectContext; @synthesize managedObjectModel; @@ -144,21 +147,21 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; NSMutableArray *usersResultArray = [NSMutableArray array]; // Select users from the mysql.user table - SPMySQLResult *result = [self.mySqlConnection queryString:@"SELECT * FROM mysql.user ORDER BY user"]; + SPMySQLResult *result = [[self connection] queryString:@"SELECT * FROM mysql.user ORDER BY user"]; [result setReturnDataAsStrings:YES]; [usersResultArray addObjectsFromArray:[result getAllRows]]; [self _initializeTree:usersResultArray]; // Set up the array of privs supported by this server. - [self.privsSupportedByServer removeAllObjects]; + [[self privsSupportedByServer] removeAllObjects]; result = nil; // Attempt to obtain user privileges if supported if ([serverSupport supportsShowPrivileges]) { - result = [self.mySqlConnection queryString:@"SHOW PRIVILEGES"]; + result = [[self connection] queryString:@"SHOW PRIVILEGES"]; [result setReturnDataAsStrings:YES]; } @@ -173,12 +176,13 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; [privKey replaceOccurrencesOfString:@" " withString:@"_" options:NSLiteralSearch range:NSMakeRange(0, [privKey length])]; [privKey appendString:@"_priv"]; - [self.privsSupportedByServer setValue:[NSNumber numberWithBool:YES] forKey:privKey]; + [[self privsSupportedByServer] setValue:[NSNumber numberWithBool:YES] forKey:privKey]; } } // If that fails, base privilege support on the mysql.users columns else { - result = [self.mySqlConnection queryString:@"SHOW COLUMNS FROM mysql.user"]; + result = [[self connection] queryString:@"SHOW COLUMNS FROM mysql.user"]; + [result setReturnDataAsStrings:YES]; while ((privRow = [result getRowAsArray])) @@ -189,7 +193,7 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; if ([privColumnToGrantMap objectForKey:privKey]) privKey = [privColumnToGrantMap objectForKey:privKey]; - [self.privsSupportedByServer setValue:[NSNumber numberWithBool:YES] forKey:[privKey lowercaseString]]; + [[self privsSupportedByServer] setValue:[NSNumber numberWithBool:YES] forKey:[privKey lowercaseString]]; } } @@ -270,16 +274,15 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; - (void)_initializeAvailablePrivs { // Initialize available privileges - NSManagedObjectContext *moc = self.managedObjectContext; - NSEntityDescription *privEntityDescription = [NSEntityDescription entityForName:@"Privileges" - inManagedObjectContext:moc]; + NSManagedObjectContext *moc = [self managedObjectContext]; + NSEntityDescription *privEntityDescription = [NSEntityDescription entityForName:@"Privileges" inManagedObjectContext:moc]; NSArray *props = [privEntityDescription attributeKeys]; [availablePrivs removeAllObjects]; for (NSString *prop in props) { - if ([prop hasSuffix:@"_priv"] && [[self.privsSupportedByServer objectForKey:prop] boolValue]) { + if ([prop hasSuffix:@"_priv"] && [[[self privsSupportedByServer] objectForKey:prop] boolValue]) { NSString *displayName = [[prop stringByReplacingOccurrencesOfString:@"_priv" withString:@""] replaceUnderscoreWithSpace]; [availablePrivs addObject:[NSDictionary dictionaryWithObjectsAndKeys:displayName, @"displayName", prop, @"name", nil]]; @@ -296,7 +299,7 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; { // Initialize Databases [schemas removeAllObjects]; - [schemas addObjectsFromArray:[self.mySqlConnection databases]]; + [schemas addObjectsFromArray:[[self connection] databases]]; [schemaController rearrangeObjects]; @@ -354,16 +357,16 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; [child setPrimitiveValue:[child valueForKey:@"host"] forKey:@"originalhost"]; // Select rows from the db table that contains schema privs for each user/host - NSString *queryString = [NSString stringWithFormat:@"SELECT * from mysql.db d WHERE d.user = %@ and d.host = %@", + NSString *queryString = [NSString stringWithFormat:@"SELECT * FROM mysql.db WHERE user = %@ AND host = %@", [[[child parent] valueForKey:@"user"] tickQuotedString], [[child valueForKey:@"host"] tickQuotedString]]; - SPMySQLResult *queryResults = [self.mySqlConnection queryString:queryString]; + SPMySQLResult *queryResults = [[self connection] queryString:queryString]; [queryResults setReturnDataAsStrings:YES]; for (NSDictionary *rowDict in queryResults) { - NSManagedObject *dbPriv = [NSEntityDescription insertNewObjectForEntityForName:@"Privileges" - inManagedObjectContext:[self managedObjectContext]]; + NSManagedObject *dbPriv = [NSEntityDescription insertNewObjectForEntityForName:@"Privileges" inManagedObjectContext:[self managedObjectContext]]; + for (NSString *key in rowDict) { if ([key hasSuffix:@"_priv"]) { @@ -437,7 +440,7 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; if (coordinator != nil) { managedObjectContext = [[NSManagedObjectContext alloc] init]; - [managedObjectContext setPersistentStoreCoordinator: coordinator]; + [managedObjectContext setPersistentStoreCoordinator:coordinator]; } [[NSNotificationCenter defaultCenter] addObserver:self @@ -449,119 +452,6 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; } #pragma mark - -#pragma mark OutlineView 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 - if ([(NSManagedObject *)[item representedObject] parent] != nil) - { - NSImage *image1 = [[NSImage imageNamed:NSImageNameNetwork] retain]; - [image1 setScalesWhenResized:YES]; - [image1 setSize:(NSSize){16,16}]; - [(ImageAndTextCell*)cell setImage:image1]; - [image1 release]; - - } - else { - NSImage *image1 = [[NSImage imageNamed:NSImageNameUser] retain]; - [image1 setScalesWhenResized:YES]; - [image1 setSize:(NSSize){16,16}]; - [(ImageAndTextCell*)cell setImage:image1]; - [image1 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:@"General"]; - } - else { - if ([selectedObject parent] != nil && [[[tabView selectedTabViewItem] identifier] isEqualToString:@"General"]) { - [tabView selectTabViewItemWithIdentifier:@"Global Privileges"]; - } - } - - if ([selectedObject parent] != nil && [selectedObject host] == nil) - { - [selectedObject setValue:@"%" forKey:@"host"]; - [outlineView reloadItem:selectedObject]; - } - - [schemasTableView deselectAll:nil]; - [grantedTableView deselectAll:nil]; - [availableTableView deselectAll:nil]; -} - -- (BOOL)selectionShouldChangeInOutlineView:(NSOutlineView *)outlineView -{ - 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:@"Duplicate User" - defaultButton:NSLocalizedString(@"OK", @"OK button") - alternateButton:nil - otherButton:nil - informativeTextWithFormat:@"A user with that name already exists"]; - [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:@"Duplicate Host" - defaultButton:NSLocalizedString(@"OK", @"OK button") - alternateButton:nil - otherButton:nil - informativeTextWithFormat:@"A user with that host already exists"]; - [alert runModal]; - return NO; - } - } - } - - } - - return YES; -} - -#pragma mark - #pragma mark General IBAction methods /** @@ -569,6 +459,9 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; */ - (IBAction)doCancel:(id)sender { + // Change the first responder to end editing in any field + [[self window] makeFirstResponder:self]; + [[self managedObjectContext] rollback]; // Close sheet @@ -588,19 +481,25 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; [[self window] makeFirstResponder:self]; isSaving = YES; + [[self managedObjectContext] save:&error]; + isSaving = NO; + if (error != nil) [errorsString appendString:[error localizedDescription]]; - [self.mySqlConnection queryString:@"FLUSH PRIVILEGES"]; + [[self connection] queryString:@"FLUSH PRIVILEGES"]; // Display any errors if ([errorsString length]) { [errorsTextView setString:errorsString]; [NSApp beginSheet:errorsSheet modalForWindow:[NSApp keyWindow] modalDelegate:nil didEndSelector:NULL contextInfo:nil]; [errorsString release]; + return; } + + [errorsString release]; // Otherwise, close the sheet [NSApp endSheet:[self window] returnCode:0]; @@ -615,15 +514,15 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; id selectedUser = [[treeController selectedObjects] objectAtIndex:0]; // Iterate through the supported privs, setting the value of each to YES - for (NSString *key in self.privsSupportedByServer) { + for (NSString *key in [self privsSupportedByServer]) + { if (![key hasSuffix:@"_priv"]) continue; // Perform the change in a try/catch check to avoid exceptions for unhandled privs - @try { + NS_DURING [selectedUser setValue:[NSNumber numberWithBool:YES] forKey:key]; - } - @catch (NSException * e) { - } + NS_HANDLER + NS_ENDHANDLER } } @@ -635,15 +534,15 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; id selectedUser = [[treeController selectedObjects] objectAtIndex:0]; // Iterate through the supported privs, setting the value of each to NO - for (NSString *key in self.privsSupportedByServer) { + for (NSString *key in [self privsSupportedByServer]) + { if (![key hasSuffix:@"_priv"]) continue; // Perform the change in a try/catch check to avoid exceptions for unhandled privs - @try { + NS_DURING [selectedUser setValue:[NSNumber numberWithBool:NO] forKey:key]; - } - @catch (NSException * e) { - } + NS_HANDLER + NS_ENDHANDLER } } @@ -674,14 +573,12 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; */ - (IBAction)removeUser:(id)sender { - NSString *username = [[[treeController selectedObjects] objectAtIndex:0] - valueForKey:@"originaluser"]; - NSArray *children = [[[treeController selectedObjects] objectAtIndex:0] - valueForKey:@"children"]; + NSString *username = [[[treeController selectedObjects] objectAtIndex:0] valueForKey:@"originaluser"]; + NSArray *children = [[[treeController selectedObjects] objectAtIndex:0] valueForKey:@"children"]; // On all the children - host entries - set the username to be deleted, // for later query contruction. - for(NSManagedObject *child in children) + for (NSManagedObject *child in children) { [child setPrimitiveValue:username forKey:@"user"]; } @@ -729,6 +626,7 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; // the drop sql command NSManagedObject *child = [[treeController selectedObjects] objectAtIndex:0]; NSManagedObject *parent = [child valueForKey:@"parent"]; + [child setPrimitiveValue:[[child valueForKey:@"parent"] valueForKey:@"user"] forKey:@"user"]; [treeController remove:sender]; @@ -777,13 +675,13 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; */ - (IBAction)doubleClickSchemaPriv:(id)sender { - // Ignore double-clicked header cells if ([sender clickedRow] == -1) return; if (sender == availableTableView) { [self addSchemaPriv:sender]; - } else { + } + else { [self removeSchemaPriv:sender]; } } @@ -793,7 +691,7 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; */ - (IBAction)refresh:(id)sender { - if ([self.managedObjectContext hasChanges]) { + if ([[self managedObjectContext] hasChanges]) { NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Unsaved changes", @"unsaved changes message") defaultButton:NSLocalizedString(@"Continue", @"continue button") @@ -807,36 +705,37 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; if ([alert runModal] == NSAlertAlternateReturn) return; } - [self.managedObjectContext reset]; + [[self managedObjectContext] reset]; + [grantedSchemaPrivs removeAllObjects]; [grantedTableView reloadData]; + [self _initializeAvailablePrivs]; - [outlineView reloadData]; + + [outlineView reloadData]; [treeController rearrangeObjects]; // Get all the stores on the current MOC and remove them. - NSArray *stores = [[self.managedObjectContext persistentStoreCoordinator] persistentStores]; + NSArray *stores = [[[self managedObjectContext] persistentStoreCoordinator] persistentStores]; for (NSPersistentStore* store in stores) { - NSError *error = nil; - [[self.managedObjectContext persistentStoreCoordinator] removePersistentStore:store error:&error]; + [[[self managedObjectContext] persistentStoreCoordinator] removePersistentStore:store error:nil]; } // Add a new store - NSError *error = nil; - [[self.managedObjectContext persistentStoreCoordinator] - addPersistentStoreWithType:NSInMemoryStoreType configuration:nil URL:nil options:nil error:&error]; + [[[self managedObjectContext] persistentStoreCoordinator] addPersistentStoreWithType:NSInMemoryStoreType configuration:nil URL:nil options:nil error:nil]; // Reinitialize the tree with values from the database. [self _initializeUsers]; // After the reset, ensure all original password and user values are up-to-date. - NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"SPUser" - inManagedObjectContext:self.managedObjectContext]; + NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"SPUser" inManagedObjectContext:[self managedObjectContext]]; NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease]; + [request setEntity:entityDescription]; - NSArray *userArray = [self.managedObjectContext executeFetchRequest:request error:nil]; + + NSArray *userArray = [[self managedObjectContext] executeFetchRequest:request error:nil]; for (NSManagedObject *user in userArray) { @@ -853,19 +752,20 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; // of "name". NSManagedObject *selectedHost = [[treeController selectedObjects] objectAtIndex:0]; NSString *selectedDb = [[schemaController selectedObjects] objectAtIndex:0]; + NSArray *selectedPrivs = [self _fetchPrivsWithUser:[selectedHost valueForKeyPath:@"parent.user"] schema:[selectedDb stringByReplacingOccurrencesOfString:@"_" withString:@"\\_"] host:[selectedHost valueForKey:@"host"]]; - NSManagedObject *priv = nil; - BOOL isNew = NO; + BOOL isNew = NO; + NSManagedObject *priv = nil; if ([selectedPrivs count] > 0){ priv = [selectedPrivs objectAtIndex:0]; } else { - priv = [NSEntityDescription insertNewObjectForEntityForName:@"Privileges" - inManagedObjectContext:[self managedObjectContext]]; + priv = [NSEntityDescription insertNewObjectForEntityForName:@"Privileges" inManagedObjectContext:[self managedObjectContext]]; + [priv setValue:selectedDb forKey:@"db"]; isNew = YES; } @@ -886,8 +786,7 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; - (void)_clearData { [managedObjectContext reset]; - [managedObjectContext release]; - managedObjectContext = nil; + [managedObjectContext release], managedObjectContext = nil; } /** @@ -951,10 +850,6 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; { [NSApp endSheet:[sender window] returnCode:[sender tag]]; [[sender window] orderOut:self]; - - // Close the window - [NSApp endSheet:[self window] returnCode:0]; - [[self window] orderOut:self]; } #pragma mark - @@ -971,7 +866,7 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; // If there are multiple user manager windows open, it's possible to get this // notification from foreign windows. Ignore those notifications. - if (notificationContext != self.managedObjectContext) return; + if (notificationContext != [self managedObjectContext]) return; if (!isInitializing) { @@ -998,6 +893,9 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; if (!isInitializing) [outlineView reloadData]; } +/** + * Updates the supplied array of users. + */ - (BOOL)updateUsers:(NSArray *)updatedUsers { for (NSManagedObject *user in updatedUsers) @@ -1009,20 +907,16 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; else if (![user parent]) { NSArray *hosts = [user valueForKey:@"children"]; - // If the user has been changed, update the username on all hosts. Don't check for errors, as some - // hosts may be new. + // If the user has been changed, update the username on all hosts. + // Don't check for errors, as some hosts may be new. if (![[user valueForKey:@"user"] isEqualToString:[user valueForKey:@"originaluser"]]) { for (NSManagedObject *child in hosts) { - NSString *renameUserStatement = [NSString stringWithFormat: - @"RENAME USER %@@%@ TO %@@%@", - [[user valueForKey:@"originaluser"] tickQuotedString], - [([child valueForKey:@"originalhost"]?[child valueForKey:@"originalhost"]:[child host]) tickQuotedString], - [[user valueForKey:@"user"] tickQuotedString], - [[child host] tickQuotedString]]; - - [self.mySqlConnection queryString:renameUserStatement]; + [self _renameUserFrom:[user valueForKey:@"originaluser"] + host:[child valueForKey:@"originalhost"] ? [child valueForKey:@"originalhost"] : [child host] + to:[user valueForKey:@"user"] + host:[child user]]; } } @@ -1037,23 +931,19 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; [[child host] tickQuotedString], ([user valueForKey:@"password"]) ? [[user valueForKey:@"password"] tickQuotedString] : @"''"]; - [self.mySqlConnection queryString:changePasswordStatement]; + [[self connection] queryString:changePasswordStatement]; [self _checkAndDisplayMySqlError]; } } } else { - - // If the hostname has changed, remane the detail before editing details. + // If the hostname has changed, remane the detail before editing details if (![[user valueForKey:@"host"] isEqualToString:[user valueForKey:@"originalhost"]]) { - NSString *renameUserStatement = [NSString stringWithFormat: - @"RENAME USER %@@%@ TO %@@%@", - [[[user parent] valueForKey:@"originaluser"] tickQuotedString], - [[user valueForKey:@"originalhost"] tickQuotedString], - [[[user parent] valueForKey:@"user"] tickQuotedString], - [[user valueForKey:@"host"] tickQuotedString]]; - [self.mySqlConnection queryString:renameUserStatement]; + [self _renameUserFrom:[[user parent] valueForKey:@"originaluser"] + host:[user valueForKey:@"originalhost"] + to:[[user parent] valueForKey:@"user"] + host:[user valueForKey:@"host"]]; } if ([serverSupport supportsUserMaxVars]) [self updateResourcesForUser:user]; @@ -1084,13 +974,13 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; // all their privileges first. Also, REVOKE ALL PRIVILEGES was added in MySQL 4.1.2, so use the // old multiple query approach (damn, I wish there were only one MySQL version!). if (![serverSupport supportsFullDropUser]) { - [mySqlConnection queryString:[NSString stringWithFormat:@"REVOKE ALL PRIVILEGES ON *.* FROM %@", droppedUsers]]; - [mySqlConnection queryString:[NSString stringWithFormat:@"REVOKE GRANT OPTION ON *.* FROM %@", droppedUsers]]; + [connection queryString:[NSString stringWithFormat:@"REVOKE ALL PRIVILEGES ON *.* FROM %@", droppedUsers]]; + [connection queryString:[NSString stringWithFormat:@"REVOKE GRANT OPTION ON *.* FROM %@", droppedUsers]]; } // DROP USER was added in MySQL 4.1.1 if ([serverSupport supportsDropUser]) { - [self.mySqlConnection queryString:[NSString stringWithFormat:@"DROP USER %@", droppedUsers]]; + [[self connection] queryString:[NSString stringWithFormat:@"DROP USER %@", droppedUsers]]; } // Otherwise manually remove the user rows from the mysql.user table else { @@ -1100,7 +990,7 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; { NSArray *userDetails = [user componentsSeparatedByString:@"@"]; - [mySqlConnection queryString:[NSString stringWithFormat:@"DELETE FROM mysql.user WHERE User = %@ and Host = %@", [userDetails objectAtIndex:0], [userDetails objectAtIndex:1]]]; + [connection queryString:[NSString stringWithFormat:@"DELETE FROM mysql.user WHERE User = %@ and Host = %@", [userDetails objectAtIndex:0], [userDetails objectAtIndex:1]]]; } } @@ -1148,7 +1038,7 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; if (createStatement) { // Create user in database - [mySqlConnection queryString:createStatement]; + [connection queryString:createStatement]; if ([self _checkAndDisplayMySqlError]) { if ([serverSupport supportsUserMaxVars]) [self updateResourcesForUser:user]; @@ -1156,7 +1046,7 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; // If we created the user with the GRANT statment (MySQL < 5), then revoke the // privileges we gave the new user. if (![serverSupport supportsUserMaxVars]) { - [mySqlConnection queryString:[NSString stringWithFormat:@"REVOKE SELECT ON mysql.* FROM %@@%@", [[[user parent] valueForKey:@"user"] tickQuotedString], host]]; + [connection queryString:[NSString stringWithFormat:@"REVOKE SELECT ON mysql.* FROM %@@%@", [[[user parent] valueForKey:@"user"] tickQuotedString], host]]; } [self grantPrivilegesToUser:user]; @@ -1183,13 +1073,15 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; [[schemaPriv valueForKeyPath:@"user.host"] tickQuotedString], [dbName tickQuotedString]]; - NSArray *matchingUsers = [self.mySqlConnection getAllRowsFromQuery:statement]; + NSArray *matchingUsers = [[self connection] getAllRowsFromQuery:statement]; - for (NSString *key in self.privsSupportedByServer) + for (NSString *key in [self privsSupportedByServer]) { if (![key hasSuffix:@"_priv"]) continue; + NSString *privilege = [key stringByReplacingOccurrencesOfString:@"_priv" withString:@""]; - @try { + + NS_DURING if ([[schemaPriv valueForKey:key] boolValue] == YES) { [grantPrivileges addObject:[privilege replaceUnderscoreWithSpace]]; } @@ -1198,15 +1090,22 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; [revokePrivileges addObject:[privilege replaceUnderscoreWithSpace]]; } } - } - @catch (NSException * e) { } + NS_HANDLER + NS_ENDHANDLER + } // Grant privileges - [self _grantPrivileges:grantPrivileges onDatabase:dbName forUser:[schemaPriv valueForKeyPath:@"user.parent.user"] host:[schemaPriv valueForKeyPath:@"user.host"]]; + [self _grantPrivileges:grantPrivileges + onDatabase:dbName + forUser:[schemaPriv valueForKeyPath:@"user.parent.user"] + host:[schemaPriv valueForKeyPath:@"user.host"]]; // Revoke privileges - [self _revokePrivileges:revokePrivileges onDatabase:dbName forUser:[schemaPriv valueForKeyPath:@"user.parent.user"] host:[schemaPriv valueForKeyPath:@"user.host"]]; + [self _revokePrivileges:revokePrivileges + onDatabase:dbName + forUser:[schemaPriv valueForKeyPath:@"user.parent.user"] + host:[schemaPriv valueForKeyPath:@"user.host"]]; return YES; } @@ -1225,9 +1124,10 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; [[[user valueForKey:@"parent"] valueForKey:@"user"] tickQuotedString], [[user valueForKey:@"host"] tickQuotedString]]; - [self.mySqlConnection queryString:updateResourcesStatement]; + [[self connection] queryString:updateResourcesStatement]; [self _checkAndDisplayMySqlError]; } + return YES; } @@ -1241,7 +1141,7 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; NSMutableArray *grantPrivileges = [NSMutableArray array]; NSMutableArray *revokePrivileges = [NSMutableArray array]; - for (NSString *key in self.privsSupportedByServer) + for (NSString *key in [self privsSupportedByServer]) { if (![key hasSuffix:@"_priv"]) continue; @@ -1249,26 +1149,32 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; // Check the value of the priv and assign to grant or revoke query as appropriate; do this // in a try/catch check to avoid exceptions for unhandled privs - @try { + NS_DURING if ([[user valueForKey:key] boolValue] == YES) { [grantPrivileges addObject:[privilege replaceUnderscoreWithSpace]]; } else { [revokePrivileges addObject:[privilege replaceUnderscoreWithSpace]]; } - } - @catch (NSException * e) { - } + NS_HANDLER + NS_ENDHANDLER } // Grant privileges - [self _grantPrivileges:grantPrivileges onDatabase:nil forUser:[[user parent] valueForKey:@"user"] host:[user valueForKey:@"host"]]; + [self _grantPrivileges:grantPrivileges + onDatabase:nil + forUser:[[user parent] valueForKey:@"user"] + host:[user valueForKey:@"host"]]; // Revoke privileges - [self _revokePrivileges:revokePrivileges onDatabase:nil forUser:[[user parent] valueForKey:@"user"] host:[user valueForKey:@"host"]]; + [self _revokePrivileges:revokePrivileges + onDatabase:nil + forUser:[[user parent] valueForKey:@"user"] + host:[user valueForKey:@"host"]]; } - for (NSManagedObject *priv in [user valueForKey:@"schema_privileges"]) { + for (NSManagedObject *priv in [user valueForKey:@"schema_privileges"]) + { [self grantDbPrivilegesWithPrivilege:priv]; } @@ -1283,8 +1189,7 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; { NSManagedObjectContext *moc = [self managedObjectContext]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"user == %@ AND parent == nil", username]; - NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"SPUser" - inManagedObjectContext:moc]; + NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"SPUser" inManagedObjectContext:moc]; NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease]; [request setEntity:entityDescription]; @@ -1303,11 +1208,10 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; - (NSArray *)_fetchPrivsWithUser:(NSString *)username schema:(NSString *)selectedSchema host:(NSString *)host { NSManagedObjectContext *moc = [self managedObjectContext]; - NSPredicate *predicate = - [NSPredicate predicateWithFormat:@"(user.parent.user like[cd] %@) AND (user.host like[cd] %@) AND (db like[cd] %@)", username, host, selectedSchema]; - NSEntityDescription *privEntity = [NSEntityDescription entityForName:@"Privileges" - inManagedObjectContext:moc]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(user.parent.user like[cd] %@) AND (user.host like[cd] %@) AND (db like[cd] %@)", username, host, selectedSchema]; + NSEntityDescription *privEntity = [NSEntityDescription entityForName:@"Privileges" inManagedObjectContext:moc]; NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease]; + [request setEntity:privEntity]; [request setPredicate:predicate]; @@ -1339,12 +1243,13 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; NSString *grantStatement; // Special case when all items are checked, to allow GRANT OPTION to work - if ([self.privsSupportedByServer count] == [thePrivileges count]) { + if ([[self privsSupportedByServer] count] == [thePrivileges count]) { grantStatement = [NSString stringWithFormat:@"GRANT ALL ON %@.* TO %@@%@ WITH GRANT OPTION", aDatabase?[aDatabase backtickQuotedString]:@"*", [aUser tickQuotedString], [aHost tickQuotedString]]; - } else { + } + else { grantStatement = [NSString stringWithFormat:@"GRANT %@ ON %@.* TO %@@%@", [[thePrivileges componentsJoinedByCommas] uppercaseString], aDatabase?[aDatabase backtickQuotedString]:@"*", @@ -1352,7 +1257,7 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; [aHost tickQuotedString]]; } - [self.mySqlConnection queryString:grantStatement]; + [[self connection] queryString:grantStatement]; [self _checkAndDisplayMySqlError]; } @@ -1367,20 +1272,21 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; NSString *revokeStatement; // Special case when all items are checked, to allow GRANT OPTION to work - if ([self.privsSupportedByServer count] == [thePrivileges count]) { + if ([[self privsSupportedByServer] count] == [thePrivileges count]) { revokeStatement = [NSString stringWithFormat:@"REVOKE ALL PRIVILEGES ON %@.* FROM %@@%@", aDatabase?[aDatabase backtickQuotedString]:@"*", [aUser tickQuotedString], [aHost tickQuotedString]]; - [self.mySqlConnection queryString:revokeStatement]; + [[self connection] queryString:revokeStatement]; [self _checkAndDisplayMySqlError]; revokeStatement = [NSString stringWithFormat:@"REVOKE GRANT OPTION ON %@.* FROM %@@%@", aDatabase?[aDatabase backtickQuotedString]:@"*", [aUser tickQuotedString], [aHost tickQuotedString]]; - } else { + } + else { revokeStatement = [NSString stringWithFormat:@"REVOKE %@ ON %@.* FROM %@@%@", [[thePrivileges componentsJoinedByCommas] uppercaseString], aDatabase?[aDatabase backtickQuotedString]:@"*", @@ -1388,7 +1294,7 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; [aHost tickQuotedString]]; } - [self.mySqlConnection queryString:revokeStatement]; + [[self connection] queryString:revokeStatement]; [self _checkAndDisplayMySqlError]; } @@ -1397,13 +1303,14 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; */ - (BOOL)_checkAndDisplayMySqlError { - if ([self.mySqlConnection queryErrored]) { + if ([[self connection] queryErrored]) { if (isSaving) { - [errorsString appendFormat:@"%@\n", [self.mySqlConnection lastErrorMessage]]; - } else { + [errorsString appendFormat:@"%@\n", [[self connection] lastErrorMessage]]; + } + else { SPBeginAlertSheet(NSLocalizedString(@"An error occurred", @"mysql error occurred message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self window], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"An error occurred whilst trying to perform the operation.\n\nMySQL said: %@", @"mysql error occurred informative message"), [self.mySqlConnection lastErrorMessage]]); + [NSString stringWithFormat:NSLocalizedString(@"An error occurred whilst trying to perform the operation.\n\nMySQL said: %@", @"mysql error occurred informative message"), [[self connection] lastErrorMessage]]); } return NO; @@ -1413,164 +1320,57 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; } #pragma mark - -#pragma mark Tab View Delegate methods - -- (BOOL)tabView:(NSTabView *)tabView shouldSelectTabViewItem:(NSTabViewItem *)tabViewItem -{ - BOOL retVal = YES; - // If there aren't any selected objects, then can't change tab view item - if ([[treeController selectedObjects] count] == 0) 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:@"Global Privileges"] - || [[tabViewItem identifier] isEqualToString:@"Resources"] - || [[tabViewItem identifier] isEqualToString:@"Schema Privileges"]) { - - id parent = [selectedObject parent]; - - if (parent) { - retVal = ([[parent children] count] > 0); - } - else { - retVal = ([[selectedObject children] count] > 0); - } - - if (retVal == NO) { - NSAlert *alert = [NSAlert alertWithMessageText:@"User doesn't have any hosts." - defaultButton:NSLocalizedString(@"Add Host", @"Add Host") - alternateButton:NSLocalizedString(@"Cancel", @"cancel button") - otherButton:nil - informativeTextWithFormat:@"This user doesn't have any hosts associated with it. User will be deleted unless one is added"]; - - NSInteger ret = [alert runModal]; - - if (ret == 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:@"Resources"]) { - - 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:@"General"]) { - if ([selectedObject parent] != nil) { - [self _selectParentFromSelection]; - } - } - else if ([[tabViewItem identifier] isEqualToString:@"Global Privileges"] - || [[tabViewItem identifier] isEqualToString:@"Resources"] - || [[tabViewItem identifier] isEqualToString:@"Schema Privileges"]) { - // 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]; - } -} - -- (void)tabView:(NSTabView *)usersTabView willSelectTabViewItem:(NSTabViewItem *)tabViewItem -{ - if ([[tabViewItem identifier] isEqualToString:@"Schema Privileges"]) { - [self _initializeSchemaPrivs]; - } -} - -#pragma mark - -#pragma mark SplitView delegate methods +#pragma mark Private API /** - * Return the maximum possible size of the splitview. + * Renames a user account using the supplied parameters. + * + * @param originalUser The user's original user name + * @param originalHost The user's original host + * @param newUser The user's new user name + * @param newHost The user's new host */ -- (CGFloat)splitView:(NSSplitView *)sender constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)offset -{ - return (proposedMax - 620); -} - -/** - * Return the minimum possible size of the splitview. - */ -- (CGFloat)splitView:(NSSplitView *)sender constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)offset -{ - return (proposedMin + 120); -} - -#pragma mark - -#pragma mark TableView Delegate Methods - -- (void)tableViewSelectionDidChange:(NSNotification *)notification +- (void)_renameUserFrom:(NSString *)originalUser host:(NSString *)originalHost to:(NSString *)newUser host:(NSString *)newHost { - if ([notification object] == schemasTableView) { - [grantedSchemaPrivs removeAllObjects]; - [grantedTableView reloadData]; - [self _initializeAvailablePrivs]; + NSString *renameQuery; + + if ([serverSupport supportsRenameUser]) { + renameQuery = [NSString stringWithFormat:@"RENAME USER %@@%@ TO %@@%@", + [originalUser tickQuotedString], + [originalHost tickQuotedString], + [newUser tickQuotedString], + [newHost tickQuotedString]]; + } + else { + // mysql.user is keyed on user and host so there should only ever be one result, + // but double check before we do the update. + QKQuery *query = [QKQuery selectQueryFromTable:@"user"]; - if ([[treeController selectedObjects] count] > 0 && [[schemaController selectedObjects] count] > 0) { - NSManagedObject *user = [[treeController selectedObjects] objectAtIndex:0]; + [query setDatabase:SPMySQLDatabase]; + [query addField:@"COUNT(1)"]; + + [query addParameter:@"User" operator:QKEqualityOperator value:originalUser]; + [query addParameter:@"Host" operator:QKEqualityOperator value:originalHost]; + + SPMySQLResult *result = [connection queryString:[query query]]; + + if ([[[result getRowAsArray] objectAtIndex:0] integerValue] == 1) { + QKQuery *updateQuery = [QKQuery queryTable:@"user"]; - // Check to see if the user host node was selected - if ([user valueForKey:@"host"]) { - NSString *selectedSchema = [[schemaController selectedObjects] objectAtIndex:0]; - 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 ([notification object] == grantedTableView) { - [removeSchemaPrivButton setEnabled:([[grantedController selectedObjects] count] > 0)]; + [updateQuery setQueryType:QKUpdateQuery]; + [updateQuery setDatabase:SPMySQLDatabase]; + + [updateQuery addFieldToUpdate:@"User" toValue:newUser]; + [updateQuery addFieldToUpdate:@"Host" toValue:newHost]; + + [updateQuery addParameter:@"User" operator:QKEqualityOperator value:originalUser]; + [updateQuery addParameter:@"Host" operator:QKEqualityOperator value:originalHost]; + + renameQuery = [updateQuery query]; + } } - else if ([notification object] == availableTableView) { - [addSchemaPrivButton setEnabled:([[availableController selectedObjects] count] > 0)]; - } + + [connection queryString:renameQuery]; } #pragma mark - @@ -1586,7 +1386,7 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; [persistentStoreCoordinator release]; [managedObjectModel release]; [privColumnToGrantMap release]; - [mySqlConnection release]; + [connection release]; [privsSupportedByServer release]; [schemas release]; [availablePrivs release]; diff --git a/Source/SPUserManagerDelegate.h b/Source/SPUserManagerDelegate.h new file mode 100644 index 00000000..0eb6eb8f --- /dev/null +++ b/Source/SPUserManagerDelegate.h @@ -0,0 +1,29 @@ +// +// $Id$ +// +// SPUserManagerDelegate.h +// sequel-pro +// +// Created by Mark Townsend on Jan 01, 2009 +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// More info at <http://code.google.com/p/sequel-pro/> + +#import "SPUserManager.h" + +@interface SPUserManager (SPUserManagerDelegate) + +@end diff --git a/Source/SPUserManagerDelegate.m b/Source/SPUserManagerDelegate.m new file mode 100644 index 00000000..dc409308 --- /dev/null +++ b/Source/SPUserManagerDelegate.m @@ -0,0 +1,316 @@ +// +// $Id$ +// +// SPUserManagerDelegate.m +// sequel-pro +// +// Created by Mark Townsend on Jan 01, 2009 +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// More info at <http://code.google.com/p/sequel-pro/> + +#import "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 (SPDeclaredAPI) + +- (void)_initializeSchemaPrivs; +- (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 SplitView delegate methods + +/** + * Return the maximum possible size of the splitview. + */ +- (CGFloat)splitView:(NSSplitView *)sender constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)offset +{ + return proposedMax - 620; +} + +/** + * Return the minimum possible size of the splitview. + */ +- (CGFloat)splitView:(NSSplitView *)sender constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)offset +{ + return proposedMin + 120; +} + +#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 && [[schemaController selectedObjects] count] > 0) { + NSManagedObject *user = [[treeController selectedObjects] objectAtIndex:0]; + + // Check to see if the user host node was selected + if ([user valueForKey:@"host"]) { + NSString *selectedSchema = [[schemaController selectedObjects] objectAtIndex:0]; + + 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]; + } +} + +#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; + + // 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]; + } +} + +- (void)tabView:(NSTabView *)usersTabView willSelectTabViewItem:(NSTabViewItem *)tabViewItem +{ + if ([[tabViewItem identifier] isEqualToString:SPSchemaPrivilegesTabIdentifier]) { + [self _initializeSchemaPrivs]; + } +} + +#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:[(NSManagedObject *)[item representedObject] parent] ? NSImageNameNetwork : NSImageNameUser] retain]; + + [image setScalesWhenResized:YES]; + [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]; + [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:[NSString stringWithFormat: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:[NSString stringWithFormat: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 ed538312..eeecca68 100644 --- a/Source/SPWindowController.m +++ b/Source/SPWindowController.m @@ -32,9 +32,9 @@ enum { #import "SPWindowController.h" #import "SPDatabaseDocument.h" -#import "PSMTabDragAssistant.h" #import "SPDatabaseViewController.h" #import "SPAppController.h" +#import "PSMTabDragAssistant.h" #import <PSMTabBar/PSMTabBarControl.h> #import <PSMTabBar/PSMTabStyle.h> @@ -101,6 +101,9 @@ enum { { [[NSNotificationCenter defaultCenter] removeObserver:self]; + // Tear down the animations on the tab bar to stop redraws + [tabBar destroyAnimations]; + [managedDatabaseConnections release]; [super dealloc]; @@ -112,7 +115,7 @@ enum { /** * Add a new database connection to the window, in a tab view. */ -- (IBAction) addNewConnection:(id)sender +- (IBAction)addNewConnection:(id)sender { // Create a new database connection view SPDatabaseDocument *newTableDocument = [[SPDatabaseDocument alloc] init]; @@ -356,378 +359,6 @@ enum { } #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; - selectedTableDocument = [tabViewItem identifier]; - [selectedTableDocument didBecomeActiveTabInWindow]; - if ([[self window] isKeyWindow]) [selectedTableDocument tabDidBecomeKey]; - [self updateAllTabTitles:self]; -} - -/** - * Called to determine whether a tab view item can be closed - */ -- (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]; -} - -/** - * 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]; - [viewImage addRepresentation:viewRep]; - - // 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>)[[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 - -#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 -{ - - // Iterate through all tabs if more than one tab is opened only otherwise - // [... parentTabShouldClose] will be called twice [see self closeTab:(id)sender] - if([[tabView tabViewItems] count] > 1) - 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([[NSApp delegate] sessionURL] && [[[NSApp delegate] orderedDatabaseConnectionWindows] count] == 1) { - [[NSApp delegate] setSessionURL:nil]; - [[NSApp delegate] 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]; -} - -/** - * 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 First responder forwarding to active tab /** diff --git a/Source/SPWindowControllerDelegate.h b/Source/SPWindowControllerDelegate.h new file mode 100644 index 00000000..5e940948 --- /dev/null +++ b/Source/SPWindowControllerDelegate.h @@ -0,0 +1,37 @@ +// +// $Id$ +// +// 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 <http://code.google.com/p/sequel-pro/> + +#import "SPWindowController.h" + +@interface SPWindowController (SPWindowControllerDelegate) + +@end diff --git a/Source/SPWindowControllerDelegate.m b/Source/SPWindowControllerDelegate.m new file mode 100644 index 00000000..31876263 --- /dev/null +++ b/Source/SPWindowControllerDelegate.m @@ -0,0 +1,433 @@ +// +// $Id$ +// +// 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 <http://code.google.com/p/sequel-pro/> + +#import "SPWindowControllerDelegate.h" +#import "PSMTabDragAssistant.h" +#import "SPDatabaseDocument.h" +#import "SPDatabaseViewController.h" +#import "SPAppController.h" + +#import <PSMTabBar/PSMTabBarControl.h> +#import <PSMTabBar/PSMTabStyle.h> + +@interface SPWindowController (SPDeclaredAPI) + +- (void)_updateProgressIndicatorForItem:(NSTabViewItem *)theItem; + +@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 +{ + // Iterate through all tabs if more than one tab is opened only otherwise + // [... parentTabShouldClose] will be called twice [see self closeTab:(id)sender] + if ([[tabView tabViewItems] count] > 1) { + 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 ([[NSApp delegate] sessionURL] && [[[NSApp delegate] orderedDatabaseConnectionWindows] count] == 1) { + [[NSApp delegate] setSessionURL:nil]; + [[NSApp delegate] 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]; +} + +/** + * 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; + + selectedTableDocument = [tabViewItem identifier]; + [selectedTableDocument didBecomeActiveTabInWindow]; + + if ([[self window] isKeyWindow]) [selectedTableDocument tabDidBecomeKey]; + + [self updateAllTabTitles:self]; +} + +/** + * Called to determine whether a tab view item can be closed + */ +- (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]; +} + +/** + * 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]; + [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>)[[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/SPXMLExporter.m b/Source/SPXMLExporter.m index 8ff99e0d..1fc29c6f 100644 --- a/Source/SPXMLExporter.m +++ b/Source/SPXMLExporter.m @@ -27,7 +27,7 @@ #import "SPExportFile.h" #import "SPFileHandle.h" #import "SPExportUtilities.h" -#import "SPMySQL.h" +#import <SPMySQL/SPMySQL.h> @implementation SPXMLExporter @@ -258,7 +258,9 @@ [xmlItem setString:[NSString stringWithString:dataConversionString]]; [dataConversionString release]; - } + } + + // Check for null value using a pointer comparison; as [NSNull null] is a singleton this works correctly. else if (data == [NSNull null]) { dataIsNULL = YES; diff --git a/Source/SPXMLExporterDelegate.m b/Source/SPXMLExporterDelegate.m index 013019b2..8b1d9e73 100644 --- a/Source/SPXMLExporterDelegate.m +++ b/Source/SPXMLExporterDelegate.m @@ -27,7 +27,7 @@ #import "SPXMLExporter.h" #import "SPDatabaseDocument.h" #import "SPExportFile.h" -#import "SPMySQL.h" +#import <SPMySQL/SPMySQL.h> @implementation SPExportController (SPXMLExporterDelegate) |