aboutsummaryrefslogtreecommitdiffstats
path: root/Source
diff options
context:
space:
mode:
Diffstat (limited to 'Source')
-rw-r--r--Source/DMLocalizedNibBundle.m2
-rw-r--r--Source/DeepMutableCopy.h10
-rw-r--r--Source/MGTemplateEngine.m3
-rw-r--r--Source/SPAppController.h3
-rw-r--r--Source/SPAppController.m15
-rw-r--r--Source/SPBundleHTMLOutputController.m4
-rw-r--r--Source/SPCSVExporter.m2
-rw-r--r--Source/SPCSVParser.m2
-rw-r--r--Source/SPConnectionController.h3
-rw-r--r--Source/SPConnectionController.m4
-rw-r--r--Source/SPConnectionDelegate.h2
-rw-r--r--Source/SPConnectionDelegate.m10
-rw-r--r--Source/SPConnectionHandler.m3
-rw-r--r--Source/SPConstants.h2
-rw-r--r--Source/SPConstants.m2
-rw-r--r--Source/SPContentFilterManager.m8
-rw-r--r--Source/SPCopyTable.m67
-rw-r--r--Source/SPCustomQuery.h2
-rw-r--r--Source/SPCustomQuery.m38
-rw-r--r--Source/SPDataAdditions.h6
-rw-r--r--Source/SPDataAdditions.m40
-rw-r--r--Source/SPDataImport.m14
-rw-r--r--Source/SPDatabaseCopy.m2
-rw-r--r--Source/SPDatabaseData.m14
-rw-r--r--Source/SPDatabaseDocument.h15
-rw-r--r--Source/SPDatabaseDocument.m222
-rw-r--r--Source/SPDatabaseInfo.m2
-rw-r--r--Source/SPDatabaseRename.m2
-rw-r--r--Source/SPDatabaseStructure.h5
-rw-r--r--Source/SPDatabaseStructure.m2
-rw-r--r--Source/SPDatabaseViewController.m4
-rw-r--r--Source/SPEditorPreferencePane.m1
-rw-r--r--Source/SPExportController.h23
-rw-r--r--Source/SPExportController.m130
-rw-r--r--Source/SPExportControllerDelegate.m28
-rw-r--r--Source/SPExportFile.m3
-rw-r--r--Source/SPExportFileUtilities.m2
-rw-r--r--Source/SPExportFilenameUtilities.m89
-rw-r--r--Source/SPExportInitializer.m12
-rw-r--r--Source/SPExportInterfaceController.h44
-rw-r--r--Source/SPExportInterfaceController.m152
-rw-r--r--Source/SPExtendedTableInfo.m21
-rw-r--r--Source/SPFieldEditorController.m24
-rw-r--r--Source/SPFieldMapperController.h2
-rw-r--r--Source/SPFieldMapperController.m22
-rw-r--r--Source/SPFileHandle.m4
-rw-r--r--Source/SPFontPreviewTextField.h6
-rw-r--r--Source/SPFontPreviewTextField.m53
-rw-r--r--Source/SPHistoryController.m12
-rw-r--r--Source/SPIndexesController.m19
-rw-r--r--Source/SPLogger.m3
-rw-r--r--Source/SPNarrowDownCompletion.m5
-rw-r--r--Source/SPNavigatorController.m12
-rw-r--r--Source/SPPrintController.m2
-rw-r--r--Source/SPProcessListController.m6
-rw-r--r--Source/SPQueryController.m2
-rw-r--r--Source/SPQueryFavoriteManager.m81
-rw-r--r--Source/SPSQLExporter.m147
-rw-r--r--Source/SPSSHTunnel.h4
-rw-r--r--Source/SPSSHTunnel.m2
-rw-r--r--Source/SPServerSupport.h6
-rw-r--r--Source/SPServerSupport.m5
-rw-r--r--Source/SPServerVariablesController.m2
-rw-r--r--Source/SPTableContent.h138
-rw-r--r--Source/SPTableContent.m1210
-rw-r--r--Source/SPTableContentDataSource.h37
-rw-r--r--Source/SPTableContentDataSource.m182
-rw-r--r--Source/SPTableContentDelegate.h37
-rw-r--r--Source/SPTableContentDelegate.m782
-rw-r--r--Source/SPTableCopy.m2
-rw-r--r--Source/SPTableData.h1
-rw-r--r--Source/SPTableData.m106
-rw-r--r--Source/SPTableRelations.m2
-rw-r--r--Source/SPTableStructure.m18
-rw-r--r--Source/SPTableStructureDelegate.m4
-rw-r--r--Source/SPTableTriggers.m2
-rw-r--r--Source/SPTableView.m28
-rw-r--r--Source/SPTablesList.h19
-rw-r--r--Source/SPTablesList.m356
-rw-r--r--Source/SPTablesPreferencePane.m1
-rw-r--r--Source/SPTextView.h7
-rw-r--r--Source/SPTextView.m13
-rw-r--r--Source/SPTextViewAdditions.m10
-rw-r--r--Source/SPUserManager.h7
-rw-r--r--Source/SPUserManager.m568
-rw-r--r--Source/SPUserManagerDelegate.h29
-rw-r--r--Source/SPUserManagerDelegate.m316
-rw-r--r--Source/SPWindowController.m379
-rw-r--r--Source/SPWindowControllerDelegate.h37
-rw-r--r--Source/SPWindowControllerDelegate.m433
-rw-r--r--Source/SPXMLExporter.m6
-rw-r--r--Source/SPXMLExporterDelegate.m2
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:@"&lt;BLOB&gt;"];
+ [tempRow addObject:hide ? @"&lt;BLOB&gt;" : [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)