aboutsummaryrefslogtreecommitdiffstats
path: root/Source
diff options
context:
space:
mode:
Diffstat (limited to 'Source')
-rw-r--r--Source/CMCopyTable.m4
-rw-r--r--Source/CMTextView.m14
-rw-r--r--Source/CustomQuery.m36
-rw-r--r--Source/SPAlertSheets.h1
-rw-r--r--Source/SPAlertSheets.m50
-rw-r--r--Source/SPAppController.m12
-rw-r--r--Source/SPConnectionController.h23
-rw-r--r--Source/SPConnectionController.m234
-rw-r--r--Source/SPConnectionDelegate.m13
-rw-r--r--Source/SPDataStorage.m14
-rw-r--r--Source/SPExtendedTableInfo.m71
-rw-r--r--Source/SPFieldMapperController.m6
-rw-r--r--Source/SPGrowlController.m5
-rw-r--r--Source/SPHistoryController.m6
-rw-r--r--Source/SPKeychain.m7
-rw-r--r--Source/SPNarrowDownCompletion.m8
-rw-r--r--Source/SPPreferenceController.h2
-rw-r--r--Source/SPPreferenceController.m181
-rw-r--r--Source/SPProcessListController.m4
-rw-r--r--Source/SPSSHTunnel.m1
-rw-r--r--Source/SPTableData.h1
-rw-r--r--Source/SPTableData.m95
-rw-r--r--Source/SPTableInfo.m6
-rw-r--r--Source/SPTableRelations.m4
-rw-r--r--Source/SPTableTriggers.h2
-rw-r--r--Source/SPTableTriggers.m103
-rw-r--r--Source/SPTableView.m5
-rw-r--r--Source/SPUserManager.h5
-rw-r--r--Source/SPUserManager.m178
-rw-r--r--Source/SPUserManager.xcdatamodel/elementsbin131784 -> 132168 bytes
-rw-r--r--Source/SPUserManager.xcdatamodel/layoutbin9663 -> 9663 bytes
-rw-r--r--Source/TableContent.h1
-rw-r--r--Source/TableContent.m672
-rw-r--r--Source/TableDocument.h6
-rw-r--r--Source/TableDocument.m143
-rw-r--r--Source/TableDump.m108
-rw-r--r--Source/TableSource.m298
-rw-r--r--Source/TablesList.h3
-rw-r--r--Source/TablesList.m78
-rw-r--r--Source/YRKSpinningProgressIndicator.h29
-rw-r--r--Source/YRKSpinningProgressIndicator.m136
41 files changed, 1615 insertions, 950 deletions
diff --git a/Source/CMCopyTable.m b/Source/CMCopyTable.m
index 9c12b358..cba561aa 100644
--- a/Source/CMCopyTable.m
+++ b/Source/CMCopyTable.m
@@ -159,6 +159,7 @@ NSInteger MENU_EDIT_COPY_AS_SQL = 2003;
if (displayString) {
[result appendString:displayString];
[displayString release];
+ [result appendString:@"\t"];
}
} else
[result appendString:[NSString stringWithFormat:@"%@\t", [cellData description]]];
@@ -166,7 +167,8 @@ NSInteger MENU_EDIT_COPY_AS_SQL = 2003;
[result appendString:@"\t"];
}
}
-
+
+ // Remove the trailing tab and add the linebreak
if ([result length]){
[result deleteCharactersInRange:NSMakeRange([result length]-1, 1)];
}
diff --git a/Source/CMTextView.m b/Source/CMTextView.m
index efe18042..dcc1190e 100644
--- a/Source/CMTextView.m
+++ b/Source/CMTextView.m
@@ -1538,7 +1538,7 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
NSMutableString *snip = [[NSMutableString alloc] initWithCapacity:[theSnippet length]];
@try{
- NSString *re = @"(?s)(?<!\\\\)\\$\\{(1?\\d):(.{0}|[^{}]*?[^\\\\])\\}";
+ NSString *re = @"(?s)(?<!\\\\)\\$\\{(1?\\d):(.{0}|[^\\{\\}]*?[^\\\\])\\}";
NSString *mirror_re = @"(?<!\\\\)\\$(1?\\d)(?=\\D)";
if(targetRange.length)
@@ -1842,7 +1842,7 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
return [stdout autorelease];
} else {
NSString *error = [[[NSString alloc] initWithData:errdata encoding:NSUTF8StringEncoding] autorelease];
- SPBeginAlertSheet(NSLocalizedString(@"BASH Error", @"bash error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self window], self, nil, nil, nil,
+ SPBeginAlertSheet(NSLocalizedString(@"BASH Error", @"bash error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self window], self, nil, nil,
[NSString stringWithFormat:@"%@ “%@”:\n%@", NSLocalizedString(@"Error for", @"error for message"), command, [error description]]);
[stdout release];
NSBeep();
@@ -2551,21 +2551,26 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
case SPT_SINGLE_QUOTED_TEXT:
case SPT_DOUBLE_QUOTED_TEXT:
tokenColor = quoteColor;
+ allowToCheckForUpperCase = NO;
break;
case SPT_BACKTICK_QUOTED_TEXT:
tokenColor = backtickColor;
+ allowToCheckForUpperCase = NO;
break;
case SPT_RESERVED_WORD:
tokenColor = keywordColor;
break;
case SPT_NUMERIC:
tokenColor = numericColor;
+ allowToCheckForUpperCase = NO;
break;
case SPT_COMMENT:
tokenColor = commentColor;
+ allowToCheckForUpperCase = NO;
break;
case SPT_VARIABLE:
tokenColor = variableColor;
+ allowToCheckForUpperCase = NO;
break;
case SPT_WHITESPACE:
continue;
@@ -2586,7 +2591,10 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
// If the current token is marked as SQL keyword, uppercase it if required.
tokenEnd = tokenRange.location+tokenRange.length-1;
// Check the end of the token
- if (textBufferSizeIncreased && allowToCheckForUpperCase && autouppercaseKeywordsEnabled && !delBackwardsWasPressed
+ if (textBufferSizeIncreased
+ && allowToCheckForUpperCase
+ && autouppercaseKeywordsEnabled
+ && !delBackwardsWasPressed
&& [(NSString*)NSMutableAttributedStringAttributeAtIndex(textStore, kSQLkeyword, tokenEnd, nil) length])
// check if next char is not a kSQLkeyword or current kSQLkeyword is at the end;
// if so then upper case keyword if not already done
diff --git a/Source/CustomQuery.m b/Source/CustomQuery.m
index bc71d6ee..33bb467d 100644
--- a/Source/CustomQuery.m
+++ b/Source/CustomQuery.m
@@ -150,6 +150,10 @@
// and preserve the selection
[textView setSelectedRange:NSMakeRange(selectedRange.location, 0)];
[textView insertText:@""];
+
+ // Inserting empty text may have cancelled a partial accent - range check before
+ // restoring the selection.
+ if (selectedRange.location > [[textView string] length]) selectedRange.location = [[textView string] length];
[textView setSelectedRange:selectedRange];
reloadingExistingResult = NO;
@@ -168,7 +172,7 @@
// This should never evaluate to true as we are now performing menu validation, meaning the 'Save Query to Favorites' menu item will
// only be enabled if the query text view has at least one character present.
if ([[textView string] isEqualToString:@""]) {
- SPBeginAlertSheet(NSLocalizedString(@"Empty query", @"empty query message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil,
+ SPBeginAlertSheet(NSLocalizedString(@"Empty query", @"empty query message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil,
NSLocalizedString(@"Cannot save an empty query.", @"empty query informative message"));
return;
}
@@ -186,7 +190,7 @@
// This should never evaluate to true as we are now performing menu validation, meaning the 'Save Query to Favorites' menu item will
// only be enabled if the query text view has at least one character present.
if ([[textView string] isEqualToString:@""]) {
- SPBeginAlertSheet(NSLocalizedString(@"Empty query", @"empty query message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil,
+ SPBeginAlertSheet(NSLocalizedString(@"Empty query", @"empty query message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil,
NSLocalizedString(@"Cannot save an empty query.", @"empty query informative message"));
return;
}
@@ -499,7 +503,7 @@
[tableDocumentInstance setQueryMode:SPCustomQueryQueryMode];
// Notify listeners that a query has started
- [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:tableDocumentInstance];
+ [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryWillBePerformed" object:tableDocumentInstance];
// Start the notification timer to allow notifications to be shown even if frontmost for long queries
[[SPGrowlController sharedGrowlController] setVisibilityForNotificationName:@"Query Finished"];
@@ -736,7 +740,7 @@
[customQueryView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:YES];
// Notify any listeners that the query has completed
- [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance];
+ [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance];
// Perform the Growl notification for query completion
[[SPGrowlController sharedGrowlController] notifyWithTitle:@"Query Finished"
@@ -790,7 +794,7 @@
[customQueryView setTableInstance:self withTableData:resultData withColumns:cqColumnDefinition withTableName:resultTableName withConnection:mySQLConnection];
//query finished
- [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance];
+ [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance];
// Query finished Growl notification
[[SPGrowlController sharedGrowlController] notifyWithTitle:@"Query Finished"
@@ -1673,7 +1677,7 @@
// Check for errors while UPDATE
if ([mySQLConnection queryErrored]) {
- SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), NSLocalizedString(@"Cancel", @"cancel button"), nil, tableWindow, self, nil, nil, nil,
+ SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), NSLocalizedString(@"Cancel", @"cancel button"), nil, tableWindow, self, nil, nil,
[NSString stringWithFormat:NSLocalizedString(@"Couldn't write field.\nMySQL said: %@", @"message of panel when error while updating field to db"), [mySQLConnection getLastErrorMessage]]);
return;
@@ -1683,22 +1687,26 @@
// This shouldn't happen – for safety reasons
if ( ![mySQLConnection affectedRows] ) {
if ( [prefs boolForKey:SPShowNoAffectedRowsError] ) {
- SPBeginAlertSheet(NSLocalizedString(@"Warning", @"warning"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil,
+ SPBeginAlertSheet(NSLocalizedString(@"Warning", @"warning"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil,
NSLocalizedString(@"The row was not written to the MySQL database. You probably haven't changed anything.\nReload the table to be sure that the row exists and use a primary key for your table.\n(This error can be turned off in the preferences.)", @"message of panel when no rows have been affected after writing to the db"));
} else {
NSBeep();
}
return;
}
-
- // On success reload table data by executing the last query
- reloadingExistingResult = YES;
- [self storeCurrentResultViewForRestoration];
-
- [self performQueries:[NSArray arrayWithObject:lastExecutedQuery] withCallback:NULL];
+ // On success reload table data by executing the last query if reloading is enabled
+ if ([prefs boolForKey:SPReloadAfterEditingRow]) {
+ reloadingExistingResult = YES;
+ [self storeCurrentResultViewForRestoration];
+
+ [self performQueries:[NSArray arrayWithObject:lastExecutedQuery] withCallback:NULL];
+ } else {
+ // otherwise, just update the data in the data storage
+ SPDataStorageReplaceObjectAtRowAndColumn(resultData, rowIndex, [[aTableColumn identifier] intValue], anObject);
+ }
} else {
- SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil,
+ SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil,
[NSString stringWithFormat:NSLocalizedString(@"Updating field content failed. Couldn't identify field origin unambiguously (%ld match%@). It's very likely that while editing this field the table `%@` was changed by an other user.", @"message of panel when error while updating field to db after enabling it"),
(long)numberOfPossibleUpdateRows, (numberOfPossibleUpdateRows>1)?@"es":@"", tableForColumn]);
diff --git a/Source/SPAlertSheets.h b/Source/SPAlertSheets.h
index 2610ee41..1979ef56 100644
--- a/Source/SPAlertSheets.h
+++ b/Source/SPAlertSheets.h
@@ -30,7 +30,6 @@ void SPBeginAlertSheet(
NSWindow *docWindow,
id modalDelegate,
SEL didEndSelector,
- SEL didDismissSelector,
void *contextInfo,
NSString *msg
); \ No newline at end of file
diff --git a/Source/SPAlertSheets.m b/Source/SPAlertSheets.m
index ccc8f337..67fb396e 100644
--- a/Source/SPAlertSheets.m
+++ b/Source/SPAlertSheets.m
@@ -25,10 +25,13 @@
#import "SPMainThreadTrampoline.h"
/**
- * Provide a very simple alias of NSBeginAlertSheet with one difference:
- * printf-type format strings are no longer supported within the "msg"
- * message text argument, preventing access of random stack areas for
- * error text which contains inadvertant printf formatting.
+ * Provide a simple alias of NSBeginAlertSheet, with a few differences:
+ * - printf-type format strings are no longer supported within the "msg"
+ * message text argument, preventing access of random stack areas for
+ * error text which contains inadvertant printf formatting.
+ * - The didDismissSelector is no longer supported
+ * - The sheet no longer needs to be orderOut:ed after use
+ * - The alert is alays shown on the main thread.
*/
void SPBeginAlertSheet(
NSString *title,
@@ -38,22 +41,33 @@ void SPBeginAlertSheet(
NSWindow *docWindow,
id modalDelegate,
SEL didEndSelector,
- SEL didDismissSelector,
void *contextInfo,
NSString *msg
) {
- NSBeginAlertSheet(
- title,
- defaultButton,
- alternateButton,
- otherButton,
- docWindow,
- modalDelegate,
- didEndSelector,
- didDismissSelector,
- contextInfo,
- [msg stringByReplacingOccurrencesOfString:@"%" withString:@"%%"]
- );
-
+ NSButton *aButton;
+
+ // Set up an NSAlert with the supplied details
+ NSAlert *alert = [[[NSAlert alloc] init] autorelease];
+ [alert setMessageText:title];
+ aButton = [alert addButtonWithTitle:defaultButton];
+ [aButton setTag:NSAlertDefaultReturn];
+
+ // Add 'alternate' and 'other' buttons as appropriate
+ if (alternateButton) {
+ aButton = [alert addButtonWithTitle:alternateButton];
+ [aButton setTag:NSAlertAlternateReturn];
+ }
+ if (otherButton) {
+ aButton = [alert addButtonWithTitle:otherButton];
+ [aButton setTag:NSAlertOtherReturn];
+ }
+
+ // Set the informative message if supplied
+ if (msg) [alert setInformativeText:msg];
+
+ // Run the alert on the main thread
+ [[alert onMainThread] beginSheetModalForWindow:docWindow modalDelegate:modalDelegate didEndSelector:didEndSelector contextInfo:contextInfo];
+
+ // Ensure the alerting window is frontmost
[[docWindow onMainThread] makeKeyWindow];
}
diff --git a/Source/SPAppController.m b/Source/SPAppController.m
index f0d4c9bf..69fcca80 100644
--- a/Source/SPAppController.m
+++ b/Source/SPAppController.m
@@ -125,10 +125,8 @@
if ([sender isKindOfClass:[NSOpenPanel class]]) {
if([[[[sender filename] pathExtension] lowercaseString] isEqualToString:@"sql"]) {
[encodingPopUp setEnabled:YES];
- [sender setAllowsMultipleSelection:NO];
} else {
[encodingPopUp setEnabled:NO];
- [sender setAllowsMultipleSelection:YES];
}
}
}
@@ -145,14 +143,11 @@
}
NSOpenPanel *panel = [NSOpenPanel openPanel];
- [panel setCanSelectHiddenExtension:YES];
[panel setDelegate:self];
+ [panel setCanSelectHiddenExtension:YES];
[panel setCanChooseDirectories:NO];
- [panel setAllowsMultipleSelection:YES];
- // TODO: it seems that setting setResolvesAliases to YES causes some
- // problems in dragging files to the panel and changing to directory could crash SP
- // ask Hans for details
- // [panel setResolvesAliases:YES];
+ [panel setAllowsMultipleSelection:NO];
+ [panel setResolvesAliases:YES];
// If no lastSqlFileEncoding in prefs set it to UTF-8
if(![[NSUserDefaults standardUserDefaults] integerForKey:SPLastSQLFileEncoding]) {
@@ -441,6 +436,7 @@
@"queryHistory",
@"tableColumnWidths",
@"savePath",
+ @"NSRecentDocumentRecords",
nil]];
return preferences;
diff --git a/Source/SPConnectionController.h b/Source/SPConnectionController.h
index b1cc541e..58ccc965 100644
--- a/Source/SPConnectionController.h
+++ b/Source/SPConnectionController.h
@@ -29,6 +29,7 @@
#import "TableDocument.h"
#import "SPKeychain.h"
#import "SPSSHTunnel.h"
+#import "SPConstants.h"
@class BWAnchoredButtonBar;
@@ -45,6 +46,12 @@
@end
+@interface SPFlippedView : NSView
+
+- (BOOL)isFlipped;
+
+@end
+
@interface SPConnectionController : NSObject
{
id delegate;
@@ -73,6 +80,7 @@
NSString *sshUser;
NSString *sshPassword;
NSString *sshPort;
+@private NSString *favoritesPBoardType;
NSString *connectionKeychainItemName;
NSString *connectionKeychainItemAccount;
@@ -104,6 +112,11 @@
IBOutlet NSButton *helpButton;
IBOutlet NSProgressIndicator *progressIndicator;
IBOutlet NSTextField *progressIndicatorText;
+ IBOutlet NSMenuItem *favoritesSortByMenuItem;
+
+ BOOL reverseFavoritesSort;
+
+ SPFavoritesSortItem previousSortItem, currentSortItem;
}
@property (readwrite, assign) id delegate;
@@ -124,6 +137,7 @@
@property (readwrite, retain) NSString *connectionKeychainItemAccount;
@property (readwrite, retain) NSString *connectionSSHKeychainItemName;
@property (readwrite, retain) NSString *connectionSSHKeychainItemAccount;
+@property (readonly, assign) NSString *favoritesPBoardType;
- (id)initWithDocument:(TableDocument *)theTableDocument;
@@ -134,13 +148,14 @@
- (void)initiateMySQLConnection;
- (void)cancelConnection;
- (void)failConnectionWithTitle:(NSString *)theTitle errorMessage:(NSString *)theErrorMessage detail:(NSString *)errorDetail;
-- (void)errorSheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo;
- (void)addConnectionToDocument;
// Interface interaction
- (IBAction)editFavorites:(id)sender;
- (IBAction)showHelp:(id)sender;
- (void)resizeTabViewToConnectionType:(NSUInteger)theType animating:(BOOL)animate;
+- (IBAction)sortFavorites:(id)sender;
+- (IBAction)reverseSortFavorites:(id)sender;
// Connection details interaction
- (BOOL)checkHost;
@@ -154,9 +169,3 @@
- (void)splitViewDidResizeSubviews:(NSNotification *)aNotification;
@end
-
-@interface SPFlippedView: NSView
-
-- (BOOL)isFlipped;
-
-@end
diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m
index 1f19ba1e..35434282 100644
--- a/Source/SPConnectionController.m
+++ b/Source/SPConnectionController.m
@@ -31,6 +31,12 @@
#import "SPConstants.h"
#import "SPAlertSheets.h"
+@interface SPConnectionController (PrivateAPI)
+
+- (void)_sortFavorites;
+
+@end
+
@implementation SPConnectionController
@synthesize delegate;
@@ -52,6 +58,8 @@
@synthesize connectionSSHKeychainItemName;
@synthesize connectionSSHKeychainItemAccount;
+#pragma mark -
+
/**
* Initialise the connection controller, linking it to the
* parent document and setting up the parent window.
@@ -69,6 +77,7 @@
mySQLConnection = nil;
sshTunnel = nil;
cancellingConnection = NO;
+ favoritesPBoardType = @"FavoritesPBoardType";
// Load the connection nib
[NSBundle loadNibNamed:@"ConnectionView" owner:self];
@@ -89,22 +98,26 @@
prefs = [[NSUserDefaults standardUserDefaults] retain];
favorites = nil;
[self updateFavorites];
-
+
// Register an observer for changes within the favorites
[prefs addObserver:self forKeyPath:SPFavorites options:NSKeyValueObservingOptionNew context:NULL];
+ // Set sort items
+ currentSortItem = [prefs integerForKey:SPFavoritesSortedBy];
+ reverseFavoritesSort = [prefs boolForKey:SPFavoritesSortedInReverse];
+
// Register double click for the favorites view (double click favorite to connect)
[favoritesTable setTarget:self];
[favoritesTable setDoubleAction:@selector(initiateConnection:)];
+ [favoritesTable registerForDraggedTypes:[NSArray arrayWithObject:favoritesPBoardType]];
+ [favoritesTable setDraggingSourceOperationMask:NSDragOperationMove forLocal:YES];
- // Set the focus to the favorites table and select the appropriate row
+ // Sort the favourites to match prefs, set the focus to the favorites table and select the appropriate row
+ if (currentSortItem > -1) [self _sortFavorites];
[documentWindow setInitialFirstResponder:favoritesTable];
- NSInteger tableRow;
- if ([prefs boolForKey:SPSelectLastFavoriteUsed] == YES) {
- tableRow = [prefs integerForKey:SPLastFavoriteIndex] + 1;
- } else {
- tableRow = [prefs integerForKey:SPDefaultFavorite] + 1;
- }
+
+ NSInteger tableRow = ([prefs integerForKey:[prefs boolForKey:SPSelectLastFavoriteUsed] ? SPLastFavoriteIndex : SPDefaultFavorite] + 1);
+
if (tableRow < [favorites count]) {
previousType = [[[favorites objectAtIndex:tableRow] objectForKey:@"type"] integerValue];
[self resizeTabViewToConnectionType:[[[favorites objectAtIndex:tableRow] objectForKey:@"type"] integerValue] animating:NO];
@@ -120,7 +133,6 @@
[self performSelector:@selector(initiateConnection:) withObject:self afterDelay:0.0];
}
}
-
return self;
}
@@ -153,13 +165,13 @@
{
// Ensure that host is not empty if this is a TCP/IP or SSH connection
if (([self type] == SPTCPIPConnection || [self type] == SPSSHTunnelConnection) && ![[self host] length]) {
- SPBeginAlertSheet(NSLocalizedString(@"Insufficient connection details", @"insufficient details message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, documentWindow, self, nil, nil, nil, NSLocalizedString(@"Insufficient details provided to establish a connection. Please enter at least the hostname.", @"insufficient details informative message"));
+ SPBeginAlertSheet(NSLocalizedString(@"Insufficient connection details", @"insufficient details message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, documentWindow, self, nil, nil, NSLocalizedString(@"Insufficient details provided to establish a connection. Please enter at least the hostname.", @"insufficient details informative message"));
return;
}
// If SSH is enabled, ensure that the SSH host is not nil
if ([self type] == SPSSHTunnelConnection && ![[self sshHost] length]) {
- SPBeginAlertSheet(NSLocalizedString(@"Insufficient connection details", @"insufficient details message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, documentWindow, self, nil, nil, nil, NSLocalizedString(@"Insufficient details provided to establish a connection. Please enter the hostname for the SSH Tunnel, or disable the SSH Tunnel.", @"insufficient SSH tunnel details informative message"));
+ SPBeginAlertSheet(NSLocalizedString(@"Insufficient connection details", @"insufficient details message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, documentWindow, self, nil, nil, NSLocalizedString(@"Insufficient details provided to establish a connection. Please enter the hostname for the SSH Tunnel, or disable the SSH Tunnel.", @"insufficient SSH tunnel details informative message"));
return;
}
@@ -438,16 +450,15 @@
// Only display the connection error message if there is a window visible
if ([documentWindow isVisible]) {
- SPBeginAlertSheet(theTitle, NSLocalizedString(@"OK", @"OK button"), (errorDetail) ? NSLocalizedString(@"Show Detail", @"Show detail button") : nil, (isSSHTunnelBindError) ? NSLocalizedString(@"Use Standard Connection", @"use standard connection button") : nil, documentWindow, self, nil, @selector(errorSheetDidEnd:returnCode:contextInfo:), @"connect", theErrorMessage);
+ SPBeginAlertSheet(theTitle, NSLocalizedString(@"OK", @"OK button"), (errorDetail) ? NSLocalizedString(@"Show Detail", @"Show detail button") : nil, (isSSHTunnelBindError) ? NSLocalizedString(@"Use Standard Connection", @"use standard connection button") : nil, documentWindow, self, @selector(connectionFailureSheetDidEnd:returnCode:contextInfo:), @"connect", theErrorMessage);
}
}
/**
* Alert sheet callback method - invoked when an error sheet is closed.
*/
-- (void)errorSheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo
+- (void)connectionFailureSheetDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo
{
- [sheet orderOut:self];
// Restore the passwords from keychain for editing if appropriate
if (connectionKeychainItemName) {
@@ -632,7 +643,6 @@
documentWindow, // Window to attach to
self, // Modal delegate
@selector(localhostErrorSheetDidEnd:returnCode:contextInfo:), // Did end selector
- nil, // Did dismiss selector
nil, // Contextual info for selectors
NSLocalizedString(@"To MySQL, 'localhost' is a special host and means that a socket connection should be used.\n\nDid you mean to use a socket connection, or to connect to the local machine via a port? If you meant to connect via a port, '127.0.0.1' should be used instead of 'localhost'.", @"message of error when using 'localhost' for a network connection"));
return NO;
@@ -644,9 +654,8 @@
/**
* Alert sheet callback method - invoked when the error sheet is closed.
*/
-- (void)localhostErrorSheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo
+- (void)localhostErrorSheetDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo
{
- [sheet orderOut:self];
if (returnCode == NSAlertAlternateReturn) {
[self setType:SPSocketConnection];
[self setHost:@""];
@@ -658,6 +667,37 @@
#pragma mark -
#pragma mark Favorites interaction
+- (void)sortFavorites:(id)sender
+{
+ previousSortItem = currentSortItem;
+ currentSortItem = [[sender menu] indexOfItem:sender];
+
+ [prefs setInteger:currentSortItem forKey:SPFavoritesSortedBy];
+
+ // Perform sorting
+ [self _sortFavorites];
+
+ if (previousSortItem > -1) [[[sender menu] itemAtIndex:previousSortItem] setState:NSOffState];
+
+ [[[sender menu] itemAtIndex:currentSortItem] setState:NSOnState];
+
+}
+
+/**
+ *
+ */
+- (void)reverseSortFavorites:(id)sender
+{
+ reverseFavoritesSort = (![sender state]);
+
+ [prefs setBool:reverseFavoritesSort forKey:SPFavoritesSortedInReverse];
+
+ // Perform re-sorting
+ [self _sortFavorites];
+
+ [sender setState:reverseFavoritesSort];
+}
+
/**
* Updates the local favorites array from the user defaults
*/
@@ -850,12 +890,93 @@
}
#pragma mark -
-#pragma mark Favorites tableview datasource and delegate methods
+#pragma mark TableView drag & drop delegate methods
+
+- (BOOL)tableView:(NSTableView *)aTableView writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard *)pboard
+{
+ NSData *archivedData = [NSKeyedArchiver archivedDataWithRootObject:rowIndexes];
+ [pboard declareTypes:[NSArray arrayWithObject:favoritesPBoardType] owner:self];
+ [pboard setData:archivedData forType:favoritesPBoardType];
+ return YES;
+}
+
+- (NSDragOperation)tableView:(NSTableView *)aTableView validateDrop:(id <NSDraggingInfo>)info proposedRow:(NSInteger)row proposedDropOperation:(NSTableViewDropOperation)operation
+{
+ if (row == 0) return NSDragOperationNone;
+ if ([info draggingSource] == aTableView)
+ {
+ [aTableView setDropRow:row dropOperation:NSTableViewDropAbove];
+ return NSDragOperationMove;
+ }
+ return NSDragOperationNone;
+}
+
+- (BOOL)tableView:(NSTableView *)aTableView acceptDrop:(id <NSDraggingInfo>)info row:(NSInteger)row dropOperation:(NSTableViewDropOperation)operation
+{
+ BOOL acceptedDrop = NO;
+
+
+ if ((row == 0) || ([info draggingSource] != aTableView)) return acceptedDrop;
+
+ // Disable all automatic sorting
+ currentSortItem = -1;
+ reverseFavoritesSort = NO;
+
+ [prefs setInteger:currentSortItem forKey:SPFavoritesSortedBy];
+ [prefs setBool:NO forKey:SPFavoritesSortedInReverse];
+
+ // Remove sort descriptors
+ [favorites sortUsingDescriptors:[NSArray array]];
+
+ // Uncheck sort by menu items
+ for (NSMenuItem *menuItem in [[favoritesSortByMenuItem submenu] itemArray])
+ {
+ [menuItem setState:NSOffState];
+ }
+
+ NSPasteboard* pboard = [info draggingPasteboard];
+ NSData* rowData = [pboard dataForType:favoritesPBoardType];
+ NSIndexSet* rowIndexes = [NSKeyedUnarchiver unarchiveObjectWithData:rowData];
+ NSInteger dragRow = [rowIndexes firstIndex];
+ NSInteger defaultConnectionRow = [prefs integerForKey:SPLastFavoriteIndex];
+ if (defaultConnectionRow == dragRow)
+ {
+ [prefs setInteger:row forKey:SPLastFavoriteIndex];
+ }
+ NSMutableDictionary *draggedFavorite = [favorites objectAtIndex:dragRow];
+ [favorites removeObjectAtIndex:dragRow];
+ if (row > [favorites count])
+ {
+ row--;
+ }
+ [favorites insertObject:draggedFavorite atIndex:row];
+ [aTableView reloadData];
+ [aTableView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
+
+ // reset the prefs with the new order
+ NSMutableArray *reorderedFavorites = [[NSMutableArray alloc] initWithArray:favorites];
+ [reorderedFavorites removeObjectAtIndex:0];
+ [prefs setObject:reorderedFavorites forKey:SPFavorites];
+
+ [[[NSApp delegate] preferenceController] updateDefaultFavoritePopup];
+
+ [reorderedFavorites release];
+
+ [self updateFavorites];
+
+ acceptedDrop = YES;
+
+ return acceptedDrop;
+
+}
+
+#pragma mark -
+#pragma mark Favorites tableview datasource methods
/**
* Returns the number of favorites to display
*/
-- (NSInteger) numberOfRowsInTableView:(NSTableView *)aTableView
+- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
{
return [favorites count];
}
@@ -863,11 +984,14 @@
/**
* Returns the favorite names to be displayed in the table
*/
-- (id) tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
+- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
return [[favorites objectAtIndex:rowIndex] objectForKey:@"name"];
}
+#pragma mark -
+#pragma mark Favorites tableview delegate methods
+
/**
* Loads a favorite, if any are selected.
*/
@@ -922,8 +1046,9 @@
[(ImageAndTextCell *)aCell setTextColor:[NSColor grayColor]];
}
+
#pragma mark -
-#pragma mark NSSplitView delegate methods
+#pragma mark SplitView delegate methods
/**
* When the split view is resized, trigger a resize in the hidden table
@@ -950,6 +1075,71 @@
return (proposedMin + 80);
}
+#pragma mark -
+#pragma mark Menu Validation
+
+-(BOOL)validateMenuItem:(NSMenuItem *)menuItem
+{
+ SEL action = [menuItem action];
+ if ((action == @selector(sortFavorites:)) || (action == @selector(reverseSortFavorites:))) {
+
+ // Loop all the items in the sort by menu only checking the currently selected one
+ for (NSMenuItem *item in [[menuItem menu] itemArray])
+ {
+ [item setState:([[menuItem menu] indexOfItem:item] == currentSortItem) ? NSOnState : NSOffState];
+ }
+
+ // Check or uncheck the reverse sort item
+ if (action == @selector(reverseSortFavorites:)) {
+ [menuItem setState:reverseFavoritesSort];
+ }
+ }
+ return YES;
+
+}
+
+#pragma mark -
+#pragma mark Private API
+
+- (void)_sortFavorites
+{
+ NSString *sortKey = @"";
+
+ switch (currentSortItem)
+ {
+ case SPFavoritesSortNameItem:
+ sortKey = @"name";
+ break;
+ case SPFavoritesSortHostItem:
+ sortKey = @"host";
+ break;
+ case SPFavoritesSortTypeItem:
+ sortKey = @"type";
+ break;
+ default:
+ sortKey = @"name";
+ break;
+ }
+
+ NSSortDescriptor *sortDescriptor = nil;
+
+ if (currentSortItem == SPFavoritesSortTypeItem) {
+ sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:sortKey ascending:(!reverseFavoritesSort)] autorelease];
+ }
+ else {
+ sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:sortKey ascending:(!reverseFavoritesSort) selector:@selector(caseInsensitiveCompare:)] autorelease];
+ }
+
+ NSDictionary *first = [[favorites objectAtIndex:0] retain];
+
+ [favorites removeObjectAtIndex:0];
+ [favorites sortUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];
+ [favorites insertObject:first atIndex:0];
+ [favoritesTable reloadData];
+
+ [first release];
+}
+
@end
#pragma mark -
@@ -965,4 +1155,6 @@
return YES;
}
+
+
@end
diff --git a/Source/SPConnectionDelegate.m b/Source/SPConnectionDelegate.m
index c69c3150..009abf5d 100644
--- a/Source/SPConnectionDelegate.m
+++ b/Source/SPConnectionDelegate.m
@@ -28,6 +28,7 @@
#import "SPQueryController.h"
#import "SPKeychain.h"
#import "SPConstants.h"
+#import "SPAlertSheets.h"
@implementation TableDocument (SPConnectionDelegate)
@@ -102,7 +103,7 @@
*/
- (void)noConnectionAvailable:(id)connection
{
- NSBeginAlertSheet(NSLocalizedString(@"No connection available", @"no connection available message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil, NSLocalizedString(@"An error has occured and there doesn't seem to be a connection available.", @"no connection available informatie message"));
+ SPBeginAlertSheet(NSLocalizedString(@"No connection available", @"no connection available message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, NSLocalizedString(@"An error has occured and there doesn't seem to be a connection available.", @"no connection available informatie message"));
}
/**
@@ -133,6 +134,16 @@
}
/**
+ * Invoke to display an informative but non-fatal error directly to the user.
+ */
+- (void)showErrorWithTitle:(NSString *)theTitle message:(NSString *)theMessage
+{
+ if ([tableWindow isVisible]) {
+ SPBeginAlertSheet(theTitle, NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, theMessage);
+ }
+}
+
+/**
* Invoked when user dismisses the error sheet displayed as a result of the current connection being lost.
*/
- (IBAction)closeErrorConnectionSheet:(id)sender
diff --git a/Source/SPDataStorage.m b/Source/SPDataStorage.m
index 1b2de47e..200c4873 100644
--- a/Source/SPDataStorage.m
+++ b/Source/SPDataStorage.m
@@ -51,7 +51,7 @@ static inline void SPDataStorageEnsureCapacityForAdditionalRowCount(SPDataStorag
{
// Throw an exception if the index is out of bounds
- if (index >= numRows) [NSException raise:NSRangeException format:@"Requested storage index beyond bounds"];
+ if (index >= numRows) [NSException raise:NSRangeException format:@"Requested storage index (%llu) beyond bounds (%llu)", index, numRows];
// Construct the NSMutableArray
NSMutableArray *rowArray = [NSMutableArray arrayWithCapacity:numColumns];
@@ -71,7 +71,7 @@ static inline void SPDataStorageEnsureCapacityForAdditionalRowCount(SPDataStorag
{
// Throw an exception if the row or column index is out of bounds
- if (rowIndex >= numRows || columnIndex >= numColumns) [NSException raise:NSRangeException format:@"Requested storage index beyond bounds"];
+ if (rowIndex >= numRows || columnIndex >= numColumns) [NSException raise:NSRangeException format:@"Requested storage index (row %llu, col %llu) beyond bounds (%llu, %llu)", rowIndex, columnIndex, numRows, numColumns];
// Return the content
return dataStorage[rowIndex][columnIndex];
@@ -158,7 +158,7 @@ static inline void SPDataStorageEnsureCapacityForAdditionalRowCount(SPDataStorag
{
// Throw an exception if the index is out of bounds
- if (index > numRows) [NSException raise:NSRangeException format:@"Requested storage index beyond bounds"];
+ if (index > numRows) [NSException raise:NSRangeException format:@"Requested storage index (%llu) beyond bounds (%llu)", index, numRows];
// If "inserting" at the end of the array just add a row
if (index == numRows) return SPDataStorageAddRow(self, row);
@@ -198,7 +198,7 @@ static inline void SPDataStorageEnsureCapacityForAdditionalRowCount(SPDataStorag
NSUInteger cellsProcessed = 0;
// Throw an exception if the index is out of bounds
- if (index >= numRows) [NSException raise:NSRangeException format:@"Requested storage index beyond bounds"];
+ if (index >= numRows) [NSException raise:NSRangeException format:@"Requested storage index (%llu) beyond bounds (%llu)", index, numRows];
id *storageRow = dataStorage[index];
@@ -226,7 +226,7 @@ static inline void SPDataStorageEnsureCapacityForAdditionalRowCount(SPDataStorag
{
// Throw an exception of either index is out of bounds
- if (rowIndex >= numRows || columnIndex >= numColumns) [NSException raise:NSRangeException format:@"Requested storage index beyond bounds"];
+ if (rowIndex >= numRows || columnIndex >= numColumns) [NSException raise:NSRangeException format:@"Requested storage index (row %llu, col %llu) beyond bounds (%llu, %llu)", rowIndex, columnIndex, numRows, numColumns];
// Release the old object and retain the new one
if (dataStorage[rowIndex][columnIndex]) CFRelease(dataStorage[rowIndex][columnIndex]);
@@ -241,7 +241,7 @@ static inline void SPDataStorageEnsureCapacityForAdditionalRowCount(SPDataStorag
{
// Throw an exception if the index is out of bounds
- if (index >= numRows) [NSException raise:NSRangeException format:@"Requested storage index beyond bounds"];
+ if (index >= numRows) [NSException raise:NSRangeException format:@"Requested storage index (%llu) beyond bounds (%llu)", index, numRows];
// Free the row
NSUInteger j = numColumns;
@@ -267,7 +267,7 @@ static inline void SPDataStorageEnsureCapacityForAdditionalRowCount(SPDataStorag
{
// Throw an exception if the range is out of bounds
- if (rangeToRemove.location + rangeToRemove.length > numRows) [NSException raise:NSRangeException format:@"Requested storage index beyond bounds"];
+ if (rangeToRemove.location + rangeToRemove.length > numRows) [NSException raise:NSRangeException format:@"Requested storage index (%llu) beyond bounds (%llu)", (rangeToRemove.location + rangeToRemove.length), numRows];
// Free rows in the range
NSUInteger i, j = numColumns;
diff --git a/Source/SPExtendedTableInfo.m b/Source/SPExtendedTableInfo.m
index d58e731e..3199ff29 100644
--- a/Source/SPExtendedTableInfo.m
+++ b/Source/SPExtendedTableInfo.m
@@ -110,7 +110,7 @@
[sender selectItemWithTitle:currentType];
SPBeginAlertSheet(NSLocalizedString(@"Error changing table type", @"error changing table type message"),
- NSLocalizedString(@"OK", @"OK button"), nil, nil, [NSApp mainWindow], self, nil, nil, nil,
+ 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 getLastErrorMessage]]);
}
}
@@ -137,7 +137,7 @@
[sender selectItemWithTitle:currentEncoding];
SPBeginAlertSheet(NSLocalizedString(@"Error changing table encoding", @"error changing table encoding message"),
- NSLocalizedString(@"OK", @"OK button"), nil, nil, [NSApp mainWindow], self, nil, nil, nil,
+ NSLocalizedString(@"OK", @"OK button"), nil, nil, [NSApp mainWindow], self, nil, nil,
[NSString stringWithFormat:NSLocalizedString(@"An error occurred when trying to change the table encoding to '%@'.\n\nMySQL said: %@", @"error changing table encoding informative message"), newEncoding, [connection getLastErrorMessage]]);
}
}
@@ -164,7 +164,7 @@
[sender selectItemWithTitle:currentCollation];
SPBeginAlertSheet(NSLocalizedString(@"Error changing table collation", @"error changing table collation message"),
- NSLocalizedString(@"OK", @"OK button"), nil, nil, [NSApp mainWindow], self, nil, nil, nil,
+ NSLocalizedString(@"OK", @"OK button"), nil, nil, [NSApp mainWindow], self, nil, nil,
[NSString stringWithFormat:NSLocalizedString(@"An error occurred when trying to change the table collation to '%@'.\n\nMySQL said: %@", @"error changing table collation informative message"), newCollation, [connection getLastErrorMessage]]);
}
}
@@ -360,7 +360,9 @@
[tableCreateSyntaxTextView setString:@""];
[tableCreateSyntaxTextView didChangeText];
[tableCreateSyntaxTextView shouldChangeTextInRange:NSMakeRange(0, 0) replacementString:[tableDataInstance tableCreateSyntax]];
- [tableCreateSyntaxTextView insertText:[[tableDataInstance tableCreateSyntax] stringByAppendingString:@";"]];
+ if ([tableDataInstance tableCreateSyntax]) {
+ [tableCreateSyntaxTextView insertText:[[tableDataInstance tableCreateSyntax] stringByAppendingString:@";"]];
+ }
[tableCreateSyntaxTextView didChangeText];
[tableCreateSyntaxTextView setEditable:NO];
@@ -381,28 +383,42 @@
NSMutableDictionary *tableInfo = [NSMutableDictionary dictionary];
NSDictionary *statusFields = [tableDataInstance statusValues];
-
- [tableInfo setObject:[tableTypePopUpButton titleOfSelectedItem] forKey:@"type"];
- [tableInfo setObject:[tableEncodingPopUpButton titleOfSelectedItem] forKey:@"encoding"];
- [tableInfo setObject:[tableCollationPopUpButton titleOfSelectedItem] forKey:@"collation"];
-
- [tableInfo setObject:[self _formatValueWithKey:@"Create_time" inDictionary:statusFields] forKey:@"createdAt"];
- [tableInfo setObject:[self _formatValueWithKey:@"Update_time" inDictionary:statusFields] forKey:@"updatedAt"];
- [tableInfo setObject:[self _formatValueWithKey:@"Rows" inDictionary:statusFields] forKey:@"rowNumber"];
- [tableInfo setObject:[self _formatValueWithKey:@"Row_format" inDictionary:statusFields] forKey:@"rowFormat"];
- [tableInfo setObject:[self _formatValueWithKey:@"Avg_row_length" inDictionary:statusFields] forKey:@"rowAvgLength"];
- [tableInfo setObject:[self _formatValueWithKey:@"Auto_increment" inDictionary:statusFields] forKey:@"rowAutoIncrement"];
- [tableInfo setObject:[self _formatValueWithKey:@"Data_length" inDictionary:statusFields] forKey:@"dataSize"];
- [tableInfo setObject:[self _formatValueWithKey:@"Max_data_length" inDictionary:statusFields] forKey:@"maxDataSize"];
- [tableInfo setObject:[self _formatValueWithKey:@"Index_length" inDictionary:statusFields] forKey:@"indexSize"];
+
+ if([tableTypePopUpButton titleOfSelectedItem])
+ [tableInfo setObject:[tableTypePopUpButton titleOfSelectedItem] forKey:@"type"];
+ if([tableEncodingPopUpButton titleOfSelectedItem])
+ [tableInfo setObject:[tableEncodingPopUpButton titleOfSelectedItem] forKey:@"encoding"];
+ if([tableCollationPopUpButton titleOfSelectedItem])
+ [tableInfo setObject:[tableCollationPopUpButton titleOfSelectedItem] forKey:@"collation"];
+
+ if([self _formatValueWithKey:@"Create_time" inDictionary:statusFields])
+ [tableInfo setObject:[self _formatValueWithKey:@"Create_time" inDictionary:statusFields] forKey:@"createdAt"];
+ if([self _formatValueWithKey:@"Update_time" inDictionary:statusFields])
+ [tableInfo setObject:[self _formatValueWithKey:@"Update_time" inDictionary:statusFields] forKey:@"updatedAt"];
+ if([self _formatValueWithKey:@"Rows" inDictionary:statusFields])
+ [tableInfo setObject:[self _formatValueWithKey:@"Rows" inDictionary:statusFields] forKey:@"rowNumber"];
+ if([self _formatValueWithKey:@"Row_format" inDictionary:statusFields])
+ [tableInfo setObject:[self _formatValueWithKey:@"Row_format" inDictionary:statusFields] forKey:@"rowFormat"];
+ if([self _formatValueWithKey:@"Avg_row_length" inDictionary:statusFields])
+ [tableInfo setObject:[self _formatValueWithKey:@"Avg_row_length" inDictionary:statusFields] forKey:@"rowAvgLength"];
+ if([self _formatValueWithKey:@"Auto_increment" inDictionary:statusFields])
+ [tableInfo setObject:[self _formatValueWithKey:@"Auto_increment" inDictionary:statusFields] forKey:@"rowAutoIncrement"];
+ if([self _formatValueWithKey:@"Data_length" inDictionary:statusFields])
+ [tableInfo setObject:[self _formatValueWithKey:@"Data_length" inDictionary:statusFields] forKey:@"dataSize"];
+ if([self _formatValueWithKey:@"Max_data_length" inDictionary:statusFields])
+ [tableInfo setObject:[self _formatValueWithKey:@"Max_data_length" inDictionary:statusFields] forKey:@"maxDataSize"];
+ if([self _formatValueWithKey:@"Index_length" inDictionary:statusFields])
+ [tableInfo setObject:[self _formatValueWithKey:@"Index_length" inDictionary:statusFields] forKey:@"indexSize"];
[tableInfo setObject:[self _formatValueWithKey:@"Data_free" inDictionary:statusFields] forKey:@"sizeFree"];
-
- [tableInfo setObject:[tableCommentsTextView string] forKey:@"comments"];
-
+
+ if([tableCommentsTextView string])
+ [tableInfo setObject:[tableCommentsTextView string] forKey:@"comments"];
+
NSError *error = nil;
NSArray *HTMLExcludes = [NSArray arrayWithObjects:@"doctype", @"html", @"head", @"body", @"xml", nil];
- NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:NSHTMLTextDocumentType, NSDocumentTypeDocumentAttribute, HTMLExcludes, NSExcludedElementsDocumentAttribute, nil];
+ NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:NSHTMLTextDocumentType,
+ NSDocumentTypeDocumentAttribute, HTMLExcludes, NSExcludedElementsDocumentAttribute, nil];
// Set tableCreateSyntaxTextView's font size temporarily to 10pt for printing
NSFont *oldFont = [tableCreateSyntaxTextView font];
@@ -411,7 +427,8 @@
[tableCreateSyntaxTextView setFont:[NSFont fontWithName:[oldFont fontName] size:10.0]];
// Convert tableCreateSyntaxTextView to HTML
- NSData *HTMLData = [[tableCreateSyntaxTextView textStorage] dataFromRange:NSMakeRange(0, [[tableCreateSyntaxTextView string] length]) documentAttributes:attributes error:&error];
+ NSData *HTMLData = [[tableCreateSyntaxTextView textStorage] dataFromRange:NSMakeRange(0, [[tableCreateSyntaxTextView string] length])
+ documentAttributes:attributes error:&error];
// Restore original font settings
[tableCreateSyntaxTextView setFont:oldFont];
@@ -419,12 +436,12 @@
if (error != nil) {
NSLog(@"Error generating table's create syntax HTML for printing. Excluding from print out. Error was: %@", [error localizedDescription]);
-
+
return tableInfo;
}
-
+
NSString *HTMLString = [[[NSString alloc] initWithData:HTMLData encoding:NSUTF8StringEncoding] autorelease];
-
+
[tableInfo setObject:HTMLString forKey:@"createSyntax"];
return tableInfo;
@@ -454,7 +471,7 @@
}
else {
SPBeginAlertSheet(NSLocalizedString(@"Error changing table comment", @"error changing table comment message"),
- NSLocalizedString(@"OK", @"OK button"), nil, nil, [NSApp mainWindow], self, nil, nil, nil,
+ NSLocalizedString(@"OK", @"OK button"), nil, nil, [NSApp mainWindow], self, nil, nil,
[NSString stringWithFormat:NSLocalizedString(@"An error occurred when trying to change the table's comment to '%@'.\n\nMySQL said: %@", @"error changing table comment informative message"), newComment, [connection getLastErrorMessage]]);
}
}
diff --git a/Source/SPFieldMapperController.m b/Source/SPFieldMapperController.m
index ae935b44..cf997dd0 100644
--- a/Source/SPFieldMapperController.m
+++ b/Source/SPFieldMapperController.m
@@ -155,6 +155,7 @@
if (fieldMappingGlobalValues) [fieldMappingGlobalValues release];
if (fieldMappingGlobalValuesSQLMarked) [fieldMappingGlobalValuesSQLMarked release];
if (fieldMappingTableDefaultValues) [fieldMappingTableDefaultValues release];
+ if (primaryKeyField) [primaryKeyField release];
[super dealloc];
}
@@ -351,7 +352,8 @@
[fieldMappingTableDefaultValues addObject:@"0"];
}
targetTableHasPrimaryKey = YES;
- primaryKeyField = [tableDetails objectForKey:@"primarykeyfield"];
+ if (primaryKeyField) [primaryKeyField release];
+ primaryKeyField = [[tableDetails objectForKey:@"primarykeyfield"] retain];
} else {
if([column objectForKey:@"unique"]) {
[type appendFormat:@",%@",@"UNIQUE"];
@@ -776,7 +778,7 @@
- (void)sheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo
{
- [sheet orderOut:self];
+ if ([sheet respondsToSelector:@selector(orderOut:)]) [sheet orderOut:nil];
if (sheet == globalValuesSheet)
[self updateFieldMappingButtonCell];
}
diff --git a/Source/SPGrowlController.m b/Source/SPGrowlController.m
index a92dbdda..b4f84780 100644
--- a/Source/SPGrowlController.m
+++ b/Source/SPGrowlController.m
@@ -77,10 +77,7 @@ static SPGrowlController *sharedGrowlController = nil;
- (id)autorelease { return self; }
-- (void)release
-{
- if (timingNotificationName) [timingNotificationName release];
-}
+- (void)release { }
/**
* Posts a Growl notification using the supplied details and default values.
diff --git a/Source/SPHistoryController.m b/Source/SPHistoryController.m
index a1d9de58..a855f01a 100644
--- a/Source/SPHistoryController.m
+++ b/Source/SPHistoryController.m
@@ -92,7 +92,9 @@
// Set the active state of the segments if appropriate
if ([history count] && historyPosition > 0) backEnabled = YES;
if ([history count] && historyPosition + 1 < [history count]) forwardEnabled = YES;
-
+
+ if (!historyControl) return;
+
[historyControl setEnabled:backEnabled forSegment:0];
[historyControl setEnabled:forwardEnabled forSegment:1];
@@ -229,7 +231,7 @@
- (void) toolbarWillAddItem:(NSNotification *)aNotification {
if ([[[[aNotification userInfo] objectForKey:@"item"] itemIdentifier] isEqualToString:SPMainToolbarHistoryNavigation]) {
toolbarItemVisible = YES;
- [self performSelector:@selector(updateToolbarItem) withObject:nil afterDelay:0.1];
+ [self performSelectorOnMainThread:@selector(updateToolbarItem) withObject:nil waitUntilDone:YES];
}
}
diff --git a/Source/SPKeychain.m b/Source/SPKeychain.m
index 688091eb..335c0a0b 100644
--- a/Source/SPKeychain.m
+++ b/Source/SPKeychain.m
@@ -24,6 +24,7 @@
// More info at <http://code.google.com/p/sequel-pro/>
#import "SPKeychain.h"
+#import "SPAlertSheets.h"
#import <Security/Security.h>
#import <CoreFoundation/CoreFoundation.h>
@@ -101,10 +102,10 @@
if (status != noErr) {
NSLog(@"Error (%i) while trying to add password for name: %@ account: %@", status, name, account);
- NSBeginAlertSheet(NSLocalizedString(@"Error adding password to Keychain", @"error adding password to keychain message"),
+ SPBeginAlertSheet(NSLocalizedString(@"Error adding password to Keychain", @"error adding password to keychain message"),
NSLocalizedString(@"OK", @"OK button"),
- nil, nil, [NSApp mainWindow], self, nil, nil, nil,
- NSLocalizedString(@"An error occured while trying to add the password to your Keychain. Repairing your Keychain might resolve this, but if it doesn't please report it to the Sequel Pro team, supplying the error code %i.", @"error adding password to keychain informative message"), status);
+ nil, nil, [NSApp mainWindow], self, nil, nil,
+ [NSString stringWithFormat:NSLocalizedString(@"An error occured while trying to add the password to your Keychain. Repairing your Keychain might resolve this, but if it doesn't please report it to the Sequel Pro team, supplying the error code %i.", @"error adding password to keychain informative message"), status]);
}
}
}
diff --git a/Source/SPNarrowDownCompletion.m b/Source/SPNarrowDownCompletion.m
index 1ac2118c..4ea34496 100644
--- a/Source/SPNarrowDownCompletion.m
+++ b/Source/SPNarrowDownCompletion.m
@@ -750,8 +750,12 @@
- (void)orderFront:(id)sender
{
[self filter];
- [super orderFront:sender];
- [self performSelector:@selector(watchUserEvents) withObject:nil afterDelay:0.05];
+ if (!closeMe) {
+ [super orderFront:sender];
+ [self performSelector:@selector(watchUserEvents) withObject:nil afterDelay:0.05];
+ } else {
+ [self close];
+ }
}
- (void)watchUserEvents
diff --git a/Source/SPPreferenceController.h b/Source/SPPreferenceController.h
index 6d72fc1d..cf9d2cc1 100644
--- a/Source/SPPreferenceController.h
+++ b/Source/SPPreferenceController.h
@@ -57,6 +57,7 @@
IBOutlet NSTextField *favoriteUserTextFieldSocket;
IBOutlet NSTextField *favoriteUserTextFieldSSH;
IBOutlet NSTextField *favoriteHostTextFieldSSH;
+ IBOutlet NSMenuItem *favoritesSortByMenuItem;
IBOutlet id tableCell;
@@ -100,6 +101,7 @@
- (IBAction)setDefaultColors:(id)sender;
- (IBAction)sortFavorites:(id)sender;
- (IBAction)reverseFavoritesSortOrder:(id)sender;
+- (IBAction)makeSelectedFavoriteDefault:(id)sender;
// Toolbar item IBAction methods
- (IBAction)displayGeneralPreferences:(id)sender;
diff --git a/Source/SPPreferenceController.m b/Source/SPPreferenceController.m
index 080a319f..fdfac101 100644
--- a/Source/SPPreferenceController.m
+++ b/Source/SPPreferenceController.m
@@ -98,7 +98,9 @@
[prefs synchronize];
- [self _sortFavorites];
+ if (currentSortItem > -1) {
+ [self _sortFavorites];
+ }
}
#pragma mark -
@@ -437,7 +439,8 @@
// Perform sorting
[self _sortFavorites];
- [[[sender menu] itemAtIndex:previousSortItem] setState:NSOffState];
+ if (previousSortItem > -1) [[[sender menu] itemAtIndex:previousSortItem] setState:NSOffState];
+
[[[sender menu] itemAtIndex:currentSortItem] setState:NSOnState];
}
@@ -456,6 +459,19 @@
[sender setState:reverseFavoritesSort];
}
+/**
+ * Makes the selected favorite the default.
+ */
+- (IBAction)makeSelectedFavoriteDefault:(id)sender
+{
+ // Minus 2 from index to account for the "Last Used" and separator items
+ [prefs setInteger:[favoritesTableView selectedRow] forKey:SPDefaultFavorite];
+
+ [favoritesTableView reloadData];
+
+ [self updateDefaultFavoritePopup];
+}
+
#pragma mark -
#pragma mark Toolbar item IBAction methods
@@ -569,13 +585,18 @@
// -------------------------------------------------------------------------------
// tableView:objectValueForTableColumn:row:
// -------------------------------------------------------------------------------
-- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
+- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex
{
- return [[[favoritesController arrangedObjects] objectAtIndex:rowIndex] objectForKey:[aTableColumn identifier]];
+ if ([[tableColumn identifier] isEqualToString:@"default"] && (rowIndex == [prefs integerForKey:SPDefaultFavorite])) {
+ return [NSImage imageNamed:@"blue-tick"];
+ }
+ else {
+ return [[[favoritesController arrangedObjects] objectAtIndex:rowIndex] objectForKey:[tableColumn identifier]];
+ }
}
#pragma mark -
-#pragma mark TableView drag & drop datasource methods
+#pragma mark TableView drag & drop delegate methods
// -------------------------------------------------------------------------------
// tableView:writeRowsWithIndexes:toPasteboard:
@@ -624,6 +645,22 @@
NSInteger lastFavoriteIndexCached;
NSMutableDictionary *draggedRow;
+ // Disable all automatic sorting
+ currentSortItem = -1;
+ reverseFavoritesSort = NO;
+
+ [prefs setInteger:currentSortItem forKey:SPFavoritesSortedBy];
+ [prefs setBool:NO forKey:SPFavoritesSortedInReverse];
+
+ // Remove sort descriptors
+ [favoritesController setSortDescriptors:[NSArray array]];
+
+ // Uncheck sort by menu items
+ for (NSMenuItem *menuItem in [[favoritesSortByMenuItem submenu] itemArray])
+ {
+ [menuItem setState:NSOffState];
+ }
+
originalRow = [[[info draggingPasteboard] stringForType:SPFavoritesPasteboardDragType] integerValue];
destinationRow = row;
@@ -667,9 +704,11 @@
{
if ([cell isKindOfClass:[SPFavoriteTextFieldCell class]]) {
[cell setFavoriteName:[[[favoritesController arrangedObjects] objectAtIndex:index] objectForKey:@"name"]];
+
if ([[[[favoritesController arrangedObjects] objectAtIndex:index] objectForKey:@"type"] integerValue] == SPSocketConnection) {
[cell setFavoriteHost:@"localhost"];
- } else {
+ }
+ else {
[cell setFavoriteHost:[[[favoritesController arrangedObjects] objectAtIndex:index] objectForKey:@"host"]];
}
}
@@ -1005,39 +1044,39 @@
if ([contextInfo isEqualToString:@"removeFavorite"]) {
if (returnCode == NSAlertDefaultReturn) {
- // Get selected favorite's details
- NSString *name = [favoritesController valueForKeyPath:@"selection.name"];
- NSString *user = [favoritesController valueForKeyPath:@"selection.user"];
- NSString *host = [favoritesController valueForKeyPath:@"selection.host"];
- NSString *database = [favoritesController valueForKeyPath:@"selection.database"];
- NSString *sshUser = [favoritesController valueForKeyPath:@"selection.sshUser"];
- NSString *sshHost = [favoritesController valueForKeyPath:@"selection.sshHost"];
- NSString *favoriteid = [favoritesController valueForKeyPath:@"selection.id"];
- NSInteger type = [[favoritesController valueForKeyPath:@"selection.type"] integerValue];
-
- // Remove passwords from the Keychain
- [keychain deletePasswordForName:[keychain nameForFavoriteName:name id:favoriteid]
- account:[keychain accountForUser:user host:((type == SPSocketConnection)?@"localhost":host) database:database]];
- [keychain deletePasswordForName:[keychain nameForSSHForFavoriteName:name id:favoriteid]
- account:[keychain accountForSSHUser:sshUser sshHost:sshHost]];
-
- // Reset last used favorite
- if ([favoritesTableView selectedRow] == [prefs integerForKey:SPLastFavoriteIndex]) {
- [prefs setInteger:0 forKey:SPLastFavoriteIndex];
- }
-
- // Reset default favorite
- if ([favoritesTableView selectedRow] == [prefs integerForKey:SPDefaultFavorite]) {
- [prefs setInteger:[prefs integerForKey:SPLastFavoriteIndex] forKey:SPDefaultFavorite];
- }
-
- [favoritesController removeObjectAtArrangedObjectIndex:[favoritesTableView selectedRow]];
-
- [favoritesTableView reloadData];
-
- [self updateDefaultFavoritePopup];
- }
- }
+ // Get selected favorite's details
+ NSString *name = [favoritesController valueForKeyPath:@"selection.name"];
+ NSString *user = [favoritesController valueForKeyPath:@"selection.user"];
+ NSString *host = [favoritesController valueForKeyPath:@"selection.host"];
+ NSString *database = [favoritesController valueForKeyPath:@"selection.database"];
+ NSString *sshUser = [favoritesController valueForKeyPath:@"selection.sshUser"];
+ NSString *sshHost = [favoritesController valueForKeyPath:@"selection.sshHost"];
+ NSString *favoriteid = [favoritesController valueForKeyPath:@"selection.id"];
+ NSInteger type = [[favoritesController valueForKeyPath:@"selection.type"] integerValue];
+
+ // Remove passwords from the Keychain
+ [keychain deletePasswordForName:[keychain nameForFavoriteName:name id:favoriteid]
+ account:[keychain accountForUser:user host:((type == SPSocketConnection)?@"localhost":host) database:database]];
+ [keychain deletePasswordForName:[keychain nameForSSHForFavoriteName:name id:favoriteid]
+ account:[keychain accountForSSHUser:sshUser sshHost:sshHost]];
+
+ // Reset last used favorite
+ if ([favoritesTableView selectedRow] == [prefs integerForKey:SPLastFavoriteIndex]) {
+ [prefs setInteger:0 forKey:SPLastFavoriteIndex];
+ }
+
+ // Reset default favorite
+ if ([favoritesTableView selectedRow] == [prefs integerForKey:SPDefaultFavorite]) {
+ [prefs setInteger:[prefs integerForKey:SPLastFavoriteIndex] forKey:SPDefaultFavorite];
+ }
+
+ [favoritesController removeObjectAtArrangedObjectIndex:[favoritesTableView selectedRow]];
+
+ [favoritesTableView reloadData];
+
+ [self updateDefaultFavoritePopup];
+ }
+ }
}
- (void)setGrowlEnabled:(BOOL)value
@@ -1064,7 +1103,7 @@
//
// Build the default favorite popup button
// -------------------------------------------------------------------------------
-- (void)updateDefaultFavoritePopup;
+- (void)updateDefaultFavoritePopup
{
[defaultFavoritePopup removeAllItems];
@@ -1072,27 +1111,29 @@
[defaultFavoritePopup addItemWithTitle:@"Last Used"];
[[defaultFavoritePopup menu] addItem:[NSMenuItem separatorItem]];
- NSInteger i;
- for(i=0; i<[[[favoritesController arrangedObjects] valueForKeyPath:@"name"] count]; i++ ){
- NSMenuItem *favoritePrefMenuItem = [[NSMenuItem alloc] initWithTitle:[[[favoritesController arrangedObjects] valueForKeyPath:@"name"] objectAtIndex:i]
- action:NULL
- keyEquivalent:@"" ];
- [[defaultFavoritePopup menu] addItem:favoritePrefMenuItem];
- [favoritePrefMenuItem release];
+ // Add all favorites to the menu
+ for (NSString *favorite in [[favoritesController arrangedObjects] valueForKeyPath:@"name"])
+ {
+ NSMenuItem *favoriteMenuItem = [[NSMenuItem alloc] initWithTitle:favorite action:NULL keyEquivalent:@""];
+
+ [[defaultFavoritePopup menu] addItem:favoriteMenuItem];
+
+ [favoriteMenuItem release];
}
// Add item to switch to edit favorites pane
[[defaultFavoritePopup menu] addItem:[NSMenuItem separatorItem]];
- [defaultFavoritePopup addItemWithTitle:@"Edit Favorites…"];
- [[[defaultFavoritePopup menu] itemWithTitle:@"Edit Favorites…"] setAction:@selector(displayFavoritePreferences:)];
- [[[defaultFavoritePopup menu] itemWithTitle:@"Edit Favorites…"] setTarget:self];
+
+ NSMenuItem *editMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Edit Favorites…", @"edit favorites menu item") action:@selector(displayFavoritePreferences:) keyEquivalent:@""];
+
+ [editMenuItem setTarget:self];
+
+ [[defaultFavoritePopup menu] addItem:editMenuItem];
+
+ [editMenuItem release];
// Select the default favorite from prefs
- if (![prefs boolForKey:SPSelectLastFavoriteUsed]) {
- [defaultFavoritePopup selectItemAtIndex:[prefs integerForKey:SPDefaultFavorite] + 2];
- } else {
- [defaultFavoritePopup selectItemAtIndex:0];
- }
+ [defaultFavoritePopup selectItemAtIndex:(![prefs boolForKey:SPSelectLastFavoriteUsed]) ? ([prefs integerForKey:SPDefaultFavorite] + 2) : 0];
}
// -------------------------------------------------------------------------------
@@ -1180,6 +1221,10 @@
return ([favoritesTableView numberOfSelectedRows] > 0);
}
+ if (action == @selector(makeSelectedFavoriteDefault:)) {
+ return ([favoritesTableView numberOfSelectedRows] == 1);
+ }
+
if ((action == @selector(sortFavorites:)) || (action == @selector(reverseFavoritesSortOrder:))) {
// Loop all the items in the sort by menu only checking the currently selected one
@@ -1213,11 +1258,8 @@
[super dealloc];
}
-@end
-
#pragma mark -
-
-@implementation SPPreferenceController (PrivateAPI)
+#pragma mark Private API
// -------------------------------------------------------------------------------
// _setupToolbar
@@ -1306,7 +1348,7 @@
* Sorts the connection favorites based on the selected criteria.
*/
- (void)_sortFavorites
-{
+{
NSString *sortKey = @"";
switch (currentSortItem)
@@ -1320,9 +1362,6 @@
case SPFavoritesSortTypeItem:
sortKey = @"type";
break;
- default:
- sortKey = @"name";
- break;
}
NSSortDescriptor *sortDescriptor = nil;
@@ -1337,16 +1376,16 @@
[favoritesController setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
[favoritesTableView reloadData];
+
+ [self updateDefaultFavoritePopup];
}
-// -------------------------------------------------------------------------------
-// _resizeWindowForContentView:
-//
-// Resizes the window to the size of the supplied view.
-// -------------------------------------------------------------------------------
+/**
+ * Resizes the window to the size of the supplied view.
+ */
- (void)_resizeWindowForContentView:(NSView *)view
{
- // remove all current views
+ // Remove all current views
NSEnumerator *en = [[[preferencesWindow contentView] subviews] objectEnumerator];
NSView *subview;
@@ -1355,10 +1394,10 @@
[subview removeFromSuperview];
}
- // resize window
+ // Resize window
[preferencesWindow resizeForContentView:view titleBarVisible:YES];
- // add view
+ // Add view
[[preferencesWindow contentView] addSubview:view];
[view setFrameOrigin:NSMakePoint(0, 0)];
}
diff --git a/Source/SPProcessListController.m b/Source/SPProcessListController.m
index 0d4c653d..e5ebcafc 100644
--- a/Source/SPProcessListController.m
+++ b/Source/SPProcessListController.m
@@ -477,7 +477,7 @@
// Check for errors
if ([connection queryErrored]) {
- SPBeginAlertSheet(NSLocalizedString(@"Unable to kill query", @"error killing query message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self window], self, nil, nil, nil,
+ SPBeginAlertSheet(NSLocalizedString(@"Unable to kill query", @"error killing query message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self window], self, nil, nil,
[NSString stringWithFormat:NSLocalizedString(@"An error occured while attempting to kill the query associated with connection %lu.\n\nMySQL said: %@", @"error killing query informative message"), (unsigned long)processId, [connection getLastErrorMessage]]);
}
@@ -495,7 +495,7 @@
// Check for errors
if ([connection queryErrored]) {
- SPBeginAlertSheet(NSLocalizedString(@"Unable to kill connection", @"error killing connection message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self window], self, nil, nil, nil,
+ SPBeginAlertSheet(NSLocalizedString(@"Unable to kill connection", @"error killing connection message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self window], self, nil, nil,
[NSString stringWithFormat:NSLocalizedString(@"An error occured while attempting to kill connection %lu.\n\nMySQL said: %@", @"error killing query informative message"), (unsigned long)processId, [connection getLastErrorMessage]]);
}
diff --git a/Source/SPSSHTunnel.m b/Source/SPSSHTunnel.m
index 2b7c31fb..6d9e64ca 100644
--- a/Source/SPSSHTunnel.m
+++ b/Source/SPSSHTunnel.m
@@ -28,6 +28,7 @@
#import "SPKeychain.h"
#import "SPConstants.h"
#import "SPMainThreadTrampoline.h"
+#import "SPAlertSheets.h"
#import <netinet/in.h>
diff --git a/Source/SPTableData.h b/Source/SPTableData.h
index a63f31dc..88b0a78f 100644
--- a/Source/SPTableData.h
+++ b/Source/SPTableData.h
@@ -64,6 +64,7 @@
- (BOOL) updateInformationForCurrentView;
- (NSDictionary *) informationForView:(NSString *)viewName;
- (BOOL) updateStatusInformationForCurrentTable;
+- (BOOL) updateTriggersForCurrentTable;
- (NSDictionary *) parseFieldDefinitionStringParts:(NSArray *)definitionParts;
- (NSArray *) primaryKeyColumnNames;
diff --git a/Source/SPTableData.m b/Source/SPTableData.m
index 51d490d9..d9332e65 100644
--- a/Source/SPTableData.m
+++ b/Source/SPTableData.m
@@ -42,8 +42,8 @@
columnNames = [[NSMutableArray alloc] init];
constraints = [[NSMutableArray alloc] init];
status = [[NSMutableDictionary alloc] init];
- triggers = [[NSMutableArray alloc] init];
-
+
+ triggers = nil;
tableEncoding = nil;
tableCreateSyntax = nil;
mySQLConnection = nil;
@@ -121,6 +121,19 @@
- (NSArray *) triggers
{
+
+ // If triggers is nil, the triggers need to be loaded - if a table is selected on MySQL >= 5.0.2
+ if (!triggers) {
+ if ([tableListInstance tableType] == SPTableTypeTable
+ && [mySQLConnection serverMajorVersion] >= 5
+ && [mySQLConnection serverMinorVersion] >= 0)
+ {
+ [self updateTriggersForCurrentTable];
+ } else {
+ return [NSArray array];
+ }
+ }
+
return (NSArray *)triggers;
}
@@ -234,6 +247,11 @@
[columnNames removeAllObjects];
[status removeAllObjects];
+ if (triggers != nil) {
+ [triggers release];
+ triggers = nil;
+ }
+
if (tableEncoding != nil) {
[tableEncoding release];
tableEncoding = nil;
@@ -334,7 +352,7 @@
if ([mySQLConnection queryErrored]) {
if ([mySQLConnection isConnected]) {
SPBeginAlertSheet(NSLocalizedString(@"Error retrieving table information", @"error retrieving table information message"), NSLocalizedString(@"OK", @"OK button"),
- nil, nil, [NSApp mainWindow], self, nil, nil, nil,
+ nil, nil, [NSApp mainWindow], self, nil, nil,
[NSString stringWithFormat:NSLocalizedString(@"An error occurred while retrieving the information for table '%@'. Please try again.\n\nMySQL said: %@", @"error retrieving table information informative message"),
tableName, [mySQLConnection getLastErrorMessage]]);
// If the current table doesn't exist anymore reload table list
@@ -608,39 +626,12 @@
[createTableParser release];
[fieldParser release];
- // Triggers
- theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"/*!50003 SHOW TRIGGERS WHERE `Table` = %@ */",
- [tableName tickQuotedString]]];
- [theResult setReturnDataAsStrings:YES];
-
- // Check for any errors, but only display them if a connection still exists
- if ([mySQLConnection queryErrored]) {
- if ([mySQLConnection isConnected]) {
- SPBeginAlertSheet(NSLocalizedString(@"Error retrieving table information", @"error retrieving table information message"), NSLocalizedString(@"OK", @"OK button"),
- nil, nil, [NSApp mainWindow], self, nil, nil, nil,
- [NSString stringWithFormat:NSLocalizedString(@"An error occurred while retrieving the information for table '%@'. Please try again.\n\nMySQL said: %@", @"error retrieving table information informative message"),
- tableName, [mySQLConnection getLastErrorMessage]]);
- }
- [tableColumns release];
- if (encodingString) [encodingString release];
-
- return nil;
- }
-
- [triggers removeAllObjects];
- if( [theResult numOfRows] ) {
- for(i=0; i<[theResult numOfRows]; i++){
- [triggers addObject:[theResult fetchRowAsDictionary]];
- }
- }
-
// this will be 'Table' or 'View'
[tableData setObject:[resultFieldNames objectAtIndex:0] forKey:@"type"];
[tableData setObject:[NSString stringWithString:encodingString] forKey:@"encoding"];
[tableData setObject:[NSArray arrayWithArray:tableColumns] forKey:@"columns"];
[tableData setObject:[NSArray arrayWithArray:constraints] forKey:@"constraints"];
- [tableData setObject:[NSArray arrayWithArray:triggers] forKey:@"triggers"];
[encodingString release];
[tableColumns release];
@@ -711,7 +702,7 @@
if ([mySQLConnection queryErrored]) {
if ([mySQLConnection isConnected]) {
SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"),
- nil, nil, [NSApp mainWindow], self, nil, nil, nil,
+ nil, nil, [NSApp mainWindow], self, nil, nil,
[NSString stringWithFormat:NSLocalizedString(@"An error occurred while retrieving information.\nMySQL said: %@", @"message of panel when retrieving information failed"),
[mySQLConnection getLastErrorMessage]]);
}
@@ -743,7 +734,7 @@
if ([mySQLConnection queryErrored]) {
if ([mySQLConnection isConnected]) {
SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"),
- nil, nil, [NSApp mainWindow], self, nil, nil, nil,
+ nil, nil, [NSApp mainWindow], self, nil, nil,
[NSString stringWithFormat:NSLocalizedString(@"An error occurred while retrieving information.\nMySQL said: %@", @"message of panel when retrieving information failed"),
[mySQLConnection getLastErrorMessage]]);
}
@@ -841,7 +832,7 @@
if ([mySQLConnection queryErrored]) {
if ([mySQLConnection isConnected]) {
SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"),
- nil, nil, [NSApp mainWindow], self, nil, nil, nil,
+ nil, nil, [NSApp mainWindow], self, nil, nil,
[NSString stringWithFormat:NSLocalizedString(@"An error occured while retrieving status data.\nMySQL said: %@", @"message of panel when retrieving view information failed"),
[mySQLConnection getLastErrorMessage]]);
}
@@ -858,6 +849,12 @@
[status setObject:[status objectForKey:@"Type"] forKey:@"Engine"];
}
+ // If the "Engine" key is NULL, a problem occurred when retrieving the table information.
+ if ([[status objectForKey:@"Engine"] isNSNull]) {
+ [status setDictionary:[NSDictionary dictionaryWithObjectsAndKeys:@"Error", @"Engine", [NSString stringWithFormat:@"An error occurred retrieving table information. MySQL said: %@", [status objectForKey:@"Comment"]], @"Comment", [tableListInstance tableName], @"Name", nil]];
+ return FALSE;
+ }
+
// Add a note for whether the row count is accurate or not - only for MyISAM
if ([[status objectForKey:@"Engine"] isEqualToString:@"MyISAM"]) {
[status setObject:@"y" forKey:@"RowsCountAccurate"];
@@ -878,6 +875,36 @@
return TRUE;
}
+/**
+ * Retrieve the triggers for the current table and add to local cache for reuse.
+ */
+- (BOOL) updateTriggersForCurrentTable
+{
+ MCPResult *theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"/*!50003 SHOW TRIGGERS WHERE `Table` = %@ */",
+ [[tableListInstance tableName] tickQuotedString]]];
+ [theResult setReturnDataAsStrings:YES];
+
+ // Check for any errors, but only display them if a connection still exists
+ if ([mySQLConnection queryErrored]) {
+ if ([mySQLConnection isConnected]) {
+ SPBeginAlertSheet(NSLocalizedString(@"Error retrieving trigger information", @"error retrieving trigger information message"), NSLocalizedString(@"OK", @"OK button"),
+ nil, nil, [NSApp mainWindow], self, nil, nil,
+ [NSString stringWithFormat:NSLocalizedString(@"An error occurred while retrieving the trigger information for table '%@'. Please try again.\n\nMySQL said: %@", @"error retrieving table information informative message"),
+ [tableListInstance tableName], [mySQLConnection getLastErrorMessage]]);
+ if (triggers) [triggers release], triggers = nil;
+ }
+
+ return NO;
+ }
+
+ if (triggers) [triggers release];
+ triggers = [[NSMutableArray alloc] init];
+ for (int i=0; i<[theResult numOfRows]; i++) {
+ [triggers addObject:[theResult fetchRowAsDictionary]];
+ }
+
+ return YES;
+}
/*
* Parse an array of field definition parts - not including name but including type and optionally unsigned/zerofill/null
@@ -1122,9 +1149,9 @@
[columns release];
[columnNames release];
[constraints release];
- [triggers release];
[status release];
+ if (triggers) [triggers release];
if (tableEncoding) [tableEncoding release];
if (tableCreateSyntax) [tableCreateSyntax release];
if (mySQLConnection) [mySQLConnection release];
diff --git a/Source/SPTableInfo.m b/Source/SPTableInfo.m
index eb8c0b55..a425df27 100644
--- a/Source/SPTableInfo.m
+++ b/Source/SPTableInfo.m
@@ -67,6 +67,12 @@
[super dealloc];
}
+/**
+ * Notification to indicate the table has changed and that the table info requires
+ * reloading for display. This is called on table changes, and also (with a nil argument)
+ * during certain refresh operations to trigger a data update.
+ * This function is not thread-safe.
+ */
- (void)tableChanged:(NSNotification *)notification
{
NSDictionary *tableStatus;
diff --git a/Source/SPTableRelations.m b/Source/SPTableRelations.m
index 01fec02e..c7853742 100644
--- a/Source/SPTableRelations.m
+++ b/Source/SPTableRelations.m
@@ -136,7 +136,7 @@
if ([connection queryErrored]) {
SPBeginAlertSheet(NSLocalizedString(@"Error creating relation", @"error creating relation message"),
NSLocalizedString(@"OK", @"OK button"),
- nil, nil, [NSApp mainWindow], nil, nil, nil, nil,
+ nil, nil, [NSApp mainWindow], nil, nil, nil,
[NSString stringWithFormat:NSLocalizedString(@"The specified relation was unable to be created.\n\nMySQL said: %@", @"error creating relation informative message"), [connection getLastErrorMessage]]);
}
else {
@@ -405,7 +405,7 @@
SPBeginAlertSheet(NSLocalizedString(@"Unable to remove relation", @"error removing relation message"),
NSLocalizedString(@"OK", @"OK button"),
- nil, nil, [NSApp mainWindow], nil, nil, nil, nil,
+ nil, nil, [NSApp mainWindow], nil, nil, nil,
[NSString stringWithFormat:NSLocalizedString(@"The selected relation couldn't be removed.\n\nMySQL said: %@", @"error removing relation informative message"), [connection getLastErrorMessage]]);
// Abort loop
diff --git a/Source/SPTableTriggers.h b/Source/SPTableTriggers.h
index d492253b..8e97908a 100644
--- a/Source/SPTableTriggers.h
+++ b/Source/SPTableTriggers.h
@@ -57,6 +57,8 @@
@property (readwrite, assign) MCPConnection *connection;
+- (void)loadTriggers;
+
// IB action methods
- (IBAction)addTrigger:(id)sender;
- (IBAction)removeTrigger:(id)sender;
diff --git a/Source/SPTableTriggers.m b/Source/SPTableTriggers.m
index 5c4f13ad..9f3b948c 100644
--- a/Source/SPTableTriggers.m
+++ b/Source/SPTableTriggers.m
@@ -77,12 +77,7 @@
selector:@selector(triggerStatementTextDidChange:)
name:NSTextStorageDidProcessEditingNotification
object:[triggerStatementTextView textStorage]];
-
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(tableSelectionChanged:)
- name:SPTableChangedNotification
- object:tableDocumentInstance];
-
+
// Add observers for document task activity
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(startDocumentTaskForTab:)
@@ -94,6 +89,46 @@
object:tableDocumentInstance];
}
+/**
+ * Called whenever the user selects the triggers tab for the first time,
+ * or switches between tables with the triggers tab active.
+ */
+- (void)loadTriggers
+{
+ BOOL enableInteraction = ((![[tableDocumentInstance selectedToolbarItemIdentifier] isEqualToString:SPMainToolbarTableTriggers]) || (![tableDocumentInstance isWorking]));
+
+ // Disable all interface elements by default
+ [addTriggerButton setEnabled:NO];
+ [refreshTriggersButton setEnabled:NO];
+ [triggersTableView setEnabled:NO];
+ [labelTextField setStringValue:@""];
+
+ // Show a warning if the version of MySQL is too low to support triggers
+ if ([connection serverMajorVersion] < 5
+ || ([connection serverMajorVersion] == 5
+ && [connection serverMinorVersion] == 0
+ && [connection serverReleaseVersion] < 2))
+ {
+ [labelTextField setStringValue:NSLocalizedString(@"This version of MySQL does not support triggers. Support for triggers was added in MySQL 5.0.2", @"triggers not supported label")];
+ return;
+ }
+
+ // If no item is selected, or the item selected is not a table, return.
+ if (![tablesListInstance tableName] || [tablesListInstance tableType] != SPTableTypeTable)
+ return;
+
+ // Update the text label
+ [labelTextField setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Triggers for table: %@", @"triggers for table label"), [tablesListInstance tableName]]];
+
+ // Enable interface elements
+ [addTriggerButton setEnabled:enableInteraction];
+ [refreshTriggersButton setEnabled:enableInteraction];
+ [triggersTableView setEnabled:YES];
+
+ // Ensure trigger data is loaded
+ [self _refreshTriggerDataForcingCacheRefresh:NO];
+}
+
#pragma mark -
#pragma mark IB action methods
@@ -131,7 +166,7 @@
if (([connection queryErrored])) {
SPBeginAlertSheet(NSLocalizedString(@"Error creating trigger", @"error creating trigger message"),
NSLocalizedString(@"OK", @"OK button"),
- nil, nil, [NSApp mainWindow], nil, nil, nil, nil,
+ nil, nil, [NSApp mainWindow], nil, nil, nil,
[NSString stringWithFormat:NSLocalizedString(@"The specified trigger was unable to be created.\n\nMySQL said: %@", @"error creating trigger informative message"), [connection getLastErrorMessage]]);
}
else {
@@ -185,49 +220,6 @@
[self _refreshTriggerDataForcingCacheRefresh:YES];
}
-/**
- * Called whenever the user selects a different table.
- */
-- (void)tableSelectionChanged:(NSNotification *)notification
-{
- BOOL enableInteraction = ((![[tableDocumentInstance selectedToolbarItemIdentifier] isEqualToString:SPMainToolbarTableTriggers]) || (![tableDocumentInstance isWorking]));
-
- // To begin enable all interface elements
- [addTriggerButton setEnabled:enableInteraction];
- [refreshTriggersButton setEnabled:enableInteraction];
- [triggersTableView setEnabled:YES];
-
- if (([connection serverMajorVersion] >= 5) &&
- ([connection serverMinorVersion] >= 0) &&
- ([connection serverReleaseVersion] >= 2)) {
-
- // Update the text label
- [labelTextField setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Triggers for table: %@", @"triggers for table label"), [tablesListInstance tableName]]];
-
- [addTriggerButton setEnabled:enableInteraction];
- [refreshTriggersButton setEnabled:enableInteraction];
- [triggersTableView setEnabled:YES];
-
- [self _refreshTriggerDataForcingCacheRefresh:NO];
- }
- else {
- [addTriggerButton setEnabled:NO];
- [refreshTriggersButton setEnabled:NO];
- [triggersTableView setEnabled:NO];
-
- [labelTextField setStringValue:NSLocalizedString(@"This version of MySQL does not support triggers. Support for triggers was added in MySQL 5.0.2", @"triggers not supported label")];
- }
-
- // If a proc or function is selected disable everything.
- if (([tablesListInstance tableType] == SPTableTypeProc) || ([tablesListInstance tableType] == SPTableTypeFunc)) {
- [addTriggerButton setEnabled:NO];
- [refreshTriggersButton setEnabled:NO];
- [triggersTableView setEnabled:NO];
-
- [labelTextField setStringValue:@""];
- }
-}
-
#pragma mark -
#pragma mark Tableview datasource methods
@@ -330,7 +322,7 @@
SPBeginAlertSheet(NSLocalizedString(@"Unable to remove trigger", @"error removing trigger message"),
NSLocalizedString(@"OK", @"OK button"),
- nil, nil, [NSApp mainWindow], nil, nil, nil, nil,
+ nil, nil, [NSApp mainWindow], nil, nil, nil,
[NSString stringWithFormat:NSLocalizedString(@"The selected trigger couldn't be removed.\n\nMySQL said: %@", @"error removing trigger informative message"), [connection getLastErrorMessage]]);
// Abort loop
@@ -479,9 +471,14 @@
if ([tablesListInstance tableType] == SPTableTypeTable) {
- if (clearAllCaches) [tableDataInstance updateInformationForCurrentTable];
+ if (clearAllCaches) {
+ [tableDataInstance resetAllData];
+ [tableDataInstance updateTriggersForCurrentTable];
+ }
- NSArray *triggers = [tableDataInstance triggers];
+ NSArray *triggers = nil;
+ if ([connection serverMajorVersion] >= 5 && [connection serverMinorVersion] >= 0)
+ triggers = [tableDataInstance triggers];
for (NSDictionary *trigger in triggers)
{
diff --git a/Source/SPTableView.m b/Source/SPTableView.m
index e53a38e8..4acf4879 100644
--- a/Source/SPTableView.m
+++ b/Source/SPTableView.m
@@ -42,6 +42,11 @@
&& [[[self window] delegate] isWorking])
return nil;
+ // Check to see whether any edits-in-progress need to be saved before changing selections
+ if ([[[[[self window] delegate] class] description] isEqualToString:@"TableDocument"]
+ && ![[[self window] delegate] couldCommitCurrentViewActions])
+ return nil;
+
// If more than one row is selected only returns the default contextual menu
if([self numberOfSelectedRows] > 1)
return [self menu];
diff --git a/Source/SPUserManager.h b/Source/SPUserManager.h
index 27590274..66d47e76 100644
--- a/Source/SPUserManager.h
+++ b/Source/SPUserManager.h
@@ -66,9 +66,9 @@
}
@property (nonatomic, retain) MCPConnection *mySqlConnection;
-@property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;
+@property (nonatomic, retain) NSPersistentStoreCoordinator *persistentStoreCoordinator;
@property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
-@property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
+@property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain) NSMutableDictionary *privsSupportedByServer;
@property (nonatomic, retain) NSArray *treeSortDescriptors;
@@ -101,6 +101,7 @@
- (BOOL)insertUsers:(NSArray *)insertedUsers;
- (BOOL)deleteUsers:(NSArray *)deletedUsers;
- (BOOL)updateUsers:(NSArray *)updatedUsers;
+- (BOOL)updateResourcesForUser:(NSManagedObject *)user;
- (BOOL)grantPrivilegesToUser:(NSManagedObject *)user;
- (BOOL)grantDbPrivilegesWithPrivilege:(NSManagedObject *)user;
diff --git a/Source/SPUserManager.m b/Source/SPUserManager.m
index 90cc426d..128e8273 100644
--- a/Source/SPUserManager.m
+++ b/Source/SPUserManager.m
@@ -210,10 +210,10 @@
// We only care about setting the user and password keys on the parent, together with their
// original values for comparison purposes
- [parent setValue:username forKey:@"user"];
- [parent setValue:username forKey:@"originaluser"];
- [parent setValue:[item objectForKey:@"Password"] forKey:@"password"];
- [parent setValue:[item objectForKey:@"Password"] forKey:@"originalpassword"];
+ [parent setPrimitiveValue:username forKey:@"user"];
+ [parent setPrimitiveValue:username forKey:@"originaluser"];
+ [parent setPrimitiveValue:[item objectForKey:@"Password"] forKey:@"password"];
+ [parent setPrimitiveValue:[item objectForKey:@"Password"] forKey:@"originalpassword"];
[self _initializeChild:child withItem:item];
@@ -357,7 +357,10 @@
key = [privColumnToGrantMap objectForKey:key];
}
[dbPriv setValue:[NSNumber numberWithBool:boolValue] forKey:key];
- } else if (![key isEqualToString:@"Host"] && ![key isEqualToString:@"User"]) {
+ } else if ([key isEqualToString:@"Db"]) {
+ [dbPriv setValue:[[rowDict objectForKey:key] stringByReplacingOccurrencesOfString:@"\\_" withString:@"_"]
+ forKey:key];
+ } else if (![key isEqualToString:@"Host"] && ![key isEqualToString:@"User"]) {
[dbPriv setValue:[rowDict objectForKey:key] forKey:key];
}
}
@@ -754,25 +757,41 @@
return;
}
}
-
+
[self.managedObjectContext reset];
- [grantedSchemaPrivs removeAllObjects];
+ [grantedSchemaPrivs removeAllObjects];
[grantedTableView reloadData];
[self _initializeAvailablePrivs];
- [treeController fetch:nil];
+ [outlineView reloadData];
+ [treeController rearrangeObjects];
+
+ // Get all the stores on the current MOC and remove them.
+ NSArray *stores = [[self.managedObjectContext persistentStoreCoordinator] persistentStores];
+ for(NSPersistentStore* store in stores)
+ {
+ NSError *error = nil;
+ [[self.managedObjectContext persistentStoreCoordinator] removePersistentStore:store error:error];
+ }
+ // Add a new store
+ NSError *error = nil;
+ [[self.managedObjectContext persistentStoreCoordinator]
+ addPersistentStoreWithType:NSInMemoryStoreType configuration:nil URL:nil options:nil error:&error];
+
+ // 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];
- NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
- [request setEntity:entityDescription];
- NSArray *userArray = [self.managedObjectContext executeFetchRequest:request error:nil];
- for (NSManagedObject *user in userArray) {
- if (![user parent]) {
- [user setValue:[user valueForKey:@"user"] forKey:@"originaluser"];
- [user setValue:[user valueForKey:@"password"] forKey:@"originalpassword"];
- }
- }
+ 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];
+ for (NSManagedObject *user in userArray) {
+ if (![user parent]) {
+ [user setPrimitiveValue:[user valueForKey:@"user"] forKey:@"originaluser"];
+ [user setPrimitiveValue:[user valueForKey:@"password"] forKey:@"originalpassword"];
+ }
+ }
}
- (void)_setSchemaPrivValues:(NSArray *)objects enabled:(BOOL)enabled
@@ -782,11 +801,12 @@
NSManagedObject *selectedHost = [[treeController selectedObjects] objectAtIndex:0];
NSString *selectedDb = [[[schemaController selectedObjects] objectAtIndex:0] valueForKey:@"Database"];
NSArray *selectedPrivs = [self _fetchPrivsWithUser:[selectedHost valueForKeyPath:@"parent.user"]
- schema:selectedDb
+ schema:[selectedDb stringByReplacingOccurrencesOfString:@"_" withString:@"\\_"]
host:[selectedHost valueForKey:@"host"]];
NSManagedObject *priv = nil;
BOOL isNew = FALSE;
+
if ([selectedPrivs count] > 0)
{
priv = [selectedPrivs objectAtIndex:0];
@@ -919,11 +939,6 @@
if (!isInitializing) [outlineView reloadData];
}
-- (void)userManagerSheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void*)context
-{
- [self refresh:nil];
-}
-
- (BOOL)updateUsers:(NSArray *)updatedUsers
{
for (NSManagedObject *user in updatedUsers)
@@ -965,8 +980,12 @@
[self checkAndDisplayMySqlError];
}
}
+
+
} else {
- [self grantPrivilegesToUser:user];
+ [self updateResourcesForUser:user];
+ [self grantPrivilegesToUser:user];
+
}
}
@@ -988,9 +1007,11 @@
}
- droppedUsers = [[droppedUsers substringToIndex:[droppedUsers length]-2] mutableCopy];
- [self.mySqlConnection queryString:[NSString stringWithFormat:@"DROP USER %@", droppedUsers]];
- [droppedUsers release];
+ if ([droppedUsers length] > 2) {
+ droppedUsers = [[droppedUsers substringToIndex:[droppedUsers length]-2] mutableCopy];
+ [self.mySqlConnection queryString:[NSString stringWithFormat:@"DROP USER %@", droppedUsers]];
+ [droppedUsers release];
+ }
return TRUE;
}
@@ -1028,8 +1049,10 @@
// Create user in database
[self.mySqlConnection queryString:createStatement];
- if ([self checkAndDisplayMySqlError])
- [self grantPrivilegesToUser:user];
+ if ([self checkAndDisplayMySqlError]) {
+ [self updateResourcesForUser:user];
+ [self grantPrivilegesToUser:user];
+ }
}
}
@@ -1046,6 +1069,18 @@
NSString *dbName = [schemaPriv valueForKey:@"db"];
+ NSString *statement = [NSString stringWithFormat:@"SELECT USER,HOST FROM `mysql`.`db` WHERE USER=%@ AND HOST=%@ AND DB=%@",
+ [[schemaPriv valueForKeyPath:@"user.parent.user"] tickQuotedString],
+ [[schemaPriv valueForKeyPath:@"user.host"] tickQuotedString],
+ [dbName tickQuotedString]];
+ MCPResult *result = [self.mySqlConnection queryString:statement];
+ NSUInteger rows = [result numOfRows];
+ BOOL userExists = YES;
+ if (rows == 0)
+ {
+ userExists = NO;
+ }
+
for (NSString *key in self.privsSupportedByServer)
{
if (![key hasSuffix:@"_priv"]) continue;
@@ -1056,7 +1091,10 @@
[grantPrivileges addObject:[privilege replaceUnderscoreWithSpace]];
}
else {
- [revokePrivileges addObject:[privilege replaceUnderscoreWithSpace]];
+ if (userExists || [grantPrivileges count] > 0) {
+ [revokePrivileges addObject:[privilege replaceUnderscoreWithSpace]];
+ }
+
}
}
@catch (NSException * e) {
@@ -1093,6 +1131,24 @@
}
/**
+ * Update resource limites for given user
+ */
+- (BOOL)updateResourcesForUser:(NSManagedObject *)user
+{
+ if ([user valueForKey:@"parent"] != nil) {
+ NSString *updateResourcesStatement = [NSString stringWithFormat:
+ @"UPDATE mysql.user SET max_questions=%@,max_updates=%@,max_connections=%@ WHERE User=%@ AND Host=%@",
+ [user valueForKey:@"max_questions"],
+ [user valueForKey:@"max_updates"],
+ [user valueForKey:@"max_connections"],
+ [[[user valueForKey:@"parent"] valueForKey:@"user"] tickQuotedString],
+ [[user valueForKey:@"host"] tickQuotedString]];
+ [self.mySqlConnection queryString:updateResourcesStatement];
+ [self checkAndDisplayMySqlError];
+
+ }
+}
+/**
* Grant or revoke privileges for the supplied user.
*/
- (BOOL)grantPrivilegesToUser:(NSManagedObject *)user
@@ -1183,7 +1239,8 @@
- (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];
+ 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];
@@ -1233,7 +1290,41 @@
#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:@"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];
+ }
+ }
+ }
+ return retVal;
+}
-(void)tabView:(NSTabView *)tabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem
{
if ([[treeController selectedObjects] count] == 0) return;
@@ -1256,6 +1347,13 @@
}
}
+- (void)tabView:(NSTabView *)usersTabView willSelectTabViewItem:(NSTabViewItem *)tabViewItem
+{
+ if ([[tabViewItem identifier] isEqualToString:@"Schema Privileges"]) {
+ [self _initializeSchemaPrivs];
+ }
+}
+
#pragma mark -
#pragma mark SplitView delegate methods
@@ -1292,7 +1390,9 @@
// Check to see if the user host node was selected
if ([user valueForKey:@"host"]) {
NSString *selectedSchema = [[[schemaController selectedObjects] objectAtIndex:0] valueForKey:@"Database"];
- NSArray *results = [self _fetchPrivsWithUser:[[user parent] valueForKey:@"user"] schema:selectedSchema host:[user valueForKey:@"host"]];
+ 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];
@@ -1348,16 +1448,6 @@
}
#pragma mark -
-#pragma mark Tab view delegate methods
-
-- (void)tabView:(NSTabView *)usersTabView willSelectTabViewItem:(NSTabViewItem *)tabViewItem
-{
- if ([[tabViewItem identifier] isEqualToString:@"Schema Privileges"]) {
- [self _initializeSchemaPrivs];
- }
-}
-
-#pragma mark -
/**
* Dealloc. Get rid of everything.
diff --git a/Source/SPUserManager.xcdatamodel/elements b/Source/SPUserManager.xcdatamodel/elements
index 22c2c418..d9f1e943 100644
--- a/Source/SPUserManager.xcdatamodel/elements
+++ b/Source/SPUserManager.xcdatamodel/elements
Binary files differ
diff --git a/Source/SPUserManager.xcdatamodel/layout b/Source/SPUserManager.xcdatamodel/layout
index 60630b61..7e8fd9cf 100644
--- a/Source/SPUserManager.xcdatamodel/layout
+++ b/Source/SPUserManager.xcdatamodel/layout
Binary files differ
diff --git a/Source/TableContent.h b/Source/TableContent.h
index d6fbf422..9b2e953e 100644
--- a/Source/TableContent.h
+++ b/Source/TableContent.h
@@ -146,7 +146,6 @@
- (NSString *)argumentForRow:(NSInteger)row;
- (BOOL)tableContainsBlobOrTextColumns;
- (NSString *)fieldListForQuery;
-- (void)sheetDidEnd:(id)sheet returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo;
- (void)updateNumberOfRows;
- (NSInteger)fetchNumberOfRows;
- (BOOL)saveRowOnDeselect;
diff --git a/Source/TableContent.m b/Source/TableContent.m
index 90fbe5d1..fcc6653f 100644
--- a/Source/TableContent.m
+++ b/Source/TableContent.m
@@ -190,7 +190,7 @@
}
// Post a notification that a query will be performed
- [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:tableDocumentInstance];
+ [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryWillBePerformed" object:tableDocumentInstance];
// Set up the table details for the new table, and trigger an interface update
NSDictionary *tableDetails = [NSDictionary dictionaryWithObjectsAndKeys:
@@ -226,7 +226,7 @@
// Init copyTable with necessary information for copying selected rows as SQL INSERT
[tableContentView setTableInstance:self withTableData:tableValues withColumns:dataColumns withTableName:selectedTable withConnection:mySQLConnection];
// Post the notification that the query is finished
- [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance];
+ [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance];
// Clear any details to restore now that they have been restored
[self clearDetailsToRestore];
@@ -562,7 +562,7 @@
[countText setStringValue:NSLocalizedString(@"Loading table data...", @"Loading table data string")];
// Notify any listeners that a query has started
- [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:tableDocumentInstance];
+ [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryWillBePerformed" object:tableDocumentInstance];
// Start construction of the query string
queryString = [NSMutableString stringWithFormat:@"SELECT %@ FROM %@", [self fieldListForQuery], [selectedTable backtickQuotedString]];
@@ -669,7 +669,7 @@
[self updatePaginationState];
// Notify listenters that the query has finished
- [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance];
+ [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance];
// Trigger a full reload if required
if (fullTableReloadRequired) [self reloadTable:self];
@@ -1323,7 +1323,7 @@
if ( [tableContentView numberOfSelectedRows] < 1 )
return;
if ( [tableContentView numberOfSelectedRows] > 1 ) {
- SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil, NSLocalizedString(@"You can only copy single rows.", @"message of panel when trying to copy multiple rows"));
+ SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, NSLocalizedString(@"You can only copy single rows.", @"message of panel when trying to copy multiple rows"));
return;
}
@@ -1428,9 +1428,302 @@
[alert setInformativeText:[NSString stringWithFormat:NSLocalizedString(@"Are you sure you want to delete the selected %ld rows from this table? This action cannot be undone.", @"delete rows informative message"), (long)[tableContentView numberOfSelectedRows]]];
}
- [alert beginSheetModalForWindow:tableWindow modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:contextInfo];
+ [alert beginSheetModalForWindow:tableWindow modalDelegate:self didEndSelector:@selector(removeRowSheetDidEnd:returnCode:contextInfo:) contextInfo:contextInfo];
}
+/**
+ * Perform the requested row deletion action.
+ */
+- (void)removeRowSheetDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo
+{
+
+ NSMutableIndexSet *selectedRows = [NSMutableIndexSet indexSet];
+ NSString *wherePart;
+ NSInteger i, errors;
+ BOOL consoleUpdateStatus;
+ BOOL reloadAfterRemovingRow = [prefs boolForKey:SPReloadAfterRemovingRow];
+
+ // Order out current sheet to suppress overlapping of sheets
+ [[alert window] orderOut:nil];
+
+ if ( [contextInfo isEqualToString:@"removeallrows"] ) {
+ if ( returnCode == NSAlertDefaultReturn ) {
+ //check if the user is currently editing a row
+ if (isEditingRow) {
+ //cancel the edit
+ isEditingRow = NO;
+ // in case the delete fails, make sure we at least stay in a somewhat consistent state
+ [tableValues replaceRowAtIndex:currentlyEditingRow withRowContents:oldRow];
+ currentlyEditingRow = -1;
+ }
+
+ [mySQLConnection queryString:[NSString stringWithFormat:@"DELETE FROM %@", [selectedTable backtickQuotedString]]];
+ if ( ![mySQLConnection queryErrored] ) {
+
+ // Reset auto increment if suppression button was ticked
+ if([[alert suppressionButton] state] == NSOnState)
+ [tableSourceInstance setAutoIncrementTo:@"1"];
+
+ [self reloadTable:self];
+
+ } else {
+ [self performSelector:@selector(showErrorSheetWith:)
+ withObject:[NSArray arrayWithObjects:NSLocalizedString(@"Error", @"error"),
+ [NSString stringWithFormat:NSLocalizedString(@"Couldn't delete rows.\n\nMySQL said: %@", @"message when deleteing all rows failed"),
+ [mySQLConnection getLastErrorMessage]],
+ nil]
+ afterDelay:0.3];
+ }
+ }
+ } else if ( [contextInfo isEqualToString:@"removerow"] ) {
+ if ( returnCode == NSAlertDefaultReturn ) {
+ [selectedRows addIndexes:[tableContentView selectedRowIndexes]];
+
+ //check if the user is currently editing a row
+ if (isEditingRow) {
+ //make sure that only one row is selected. This should never happen
+ if ([selectedRows count]!=1) {
+ NSLog(@"Expected only one selected row, but found %d",[selectedRows count]);
+ }
+ // this code is pretty much taken from the escape key handler
+ if ( isEditingNewRow ) {
+ // since the user is currently editing a new row, we don't actually have to delete any rows from the database
+ // we just have to remove the row from the view (and the store)
+ isEditingRow = NO;
+ isEditingNewRow = NO;
+ tableRowsCount--;
+ [tableValues removeRowAtIndex:currentlyEditingRow];
+ currentlyEditingRow = -1;
+ [self updateCountText];
+ [tableContentView reloadData];
+
+ //deselect the row
+ [tableContentView selectRowIndexes:[NSIndexSet indexSet] byExtendingSelection:NO];
+
+ // we also don't have to reload the table, since no query went to the database
+ return;
+ } else {
+ //cancel the edit
+ isEditingRow = NO;
+ // in case the delete fails, make sure we at least stay in a somewhat consistent state
+ [tableValues replaceRowAtIndex:currentlyEditingRow withRowContents:oldRow];
+ currentlyEditingRow = -1;
+ }
+
+ }
+ [tableContentView selectRowIndexes:[NSIndexSet indexSet] byExtendingSelection:NO];
+
+ errors = 0;
+
+ // Disable updating of the Console Log window for large number of queries
+ // to speed the deletion
+ consoleUpdateStatus = [[SPQueryController sharedQueryController] allowConsoleUpdate];
+ if([selectedRows count] > 10)
+ [[SPQueryController sharedQueryController] setAllowConsoleUpdate:NO];
+
+ NSUInteger index = [selectedRows firstIndex];
+
+ NSArray *primaryKeyFieldNames = [tableDataInstance primaryKeyColumnNames];
+
+ // If no PRIMARY KEY is found and numberOfSelectedRows > 3 then
+ // check for uniqueness of rows via combining all column values;
+ // if unique then use the all columns as 'primary keys'
+ if([selectedRows count] >3 && primaryKeyFieldNames == nil) {
+ primaryKeyFieldNames = [tableDataInstance columnNames];
+
+ NSInteger numberOfRows = 0;
+ // Get the number of rows in the table
+ MCPResult *r;
+ r = [mySQLConnection queryString:[NSString stringWithFormat:@"SELECT COUNT(1) FROM %@", [selectedTable backtickQuotedString]]];
+ if (![mySQLConnection queryErrored]) {
+ NSArray *a = [r fetchRowAsArray];
+ if([a count])
+ numberOfRows = [[a objectAtIndex:0] integerValue];
+ }
+ // Check for uniqueness via LIMIT numberOfRows-1,numberOfRows for speed
+ if(numberOfRows > 0) {
+ [mySQLConnection queryString:[NSString stringWithFormat:@"SELECT * FROM %@ GROUP BY %@ LIMIT %ld,%ld", [selectedTable backtickQuotedString], [primaryKeyFieldNames componentsJoinedAndBacktickQuoted], (long)(numberOfRows-1), (long)numberOfRows]];
+ if([mySQLConnection affectedRows] == 0)
+ primaryKeyFieldNames = nil;
+ } else {
+ primaryKeyFieldNames = nil;
+ }
+ }
+
+ if(primaryKeyFieldNames == nil) {
+ // delete row by row
+ while (index != NSNotFound) {
+
+ wherePart = [NSString stringWithString:[self argumentForRow:index]];
+
+ //argumentForRow might return empty query, in which case we shouldn't execute the partial query
+ if([wherePart length]) {
+ [mySQLConnection queryString:[NSString stringWithFormat:@"DELETE FROM %@ WHERE %@", [selectedTable backtickQuotedString], wherePart]];
+
+ // Check for errors
+ if ( ![mySQLConnection affectedRows] || [mySQLConnection queryErrored]) {
+ // If error delete that index from selectedRows for reloading table if
+ // "ReloadAfterRemovingRow" is disbaled
+ if(!reloadAfterRemovingRow)
+ [selectedRows removeIndex:index];
+ errors++;
+ }
+ } else {
+ if(!reloadAfterRemovingRow)
+ [selectedRows removeIndex:index];
+ errors++;
+ }
+ index = [selectedRows indexGreaterThanIndex:index];
+ }
+ } else if ([primaryKeyFieldNames count] == 1) {
+ // if table has only one PRIMARY KEY
+ // delete the fast way by using the PRIMARY KEY in an IN clause
+ NSMutableString *deleteQuery = [NSMutableString string];
+ NSInteger affectedRows = 0;
+
+ [deleteQuery setString:[NSString stringWithFormat:@"DELETE FROM %@ WHERE %@ IN (", [selectedTable backtickQuotedString], [NSArrayObjectAtIndex(primaryKeyFieldNames,0) backtickQuotedString]]];
+
+ while (index != NSNotFound) {
+
+ id keyValue = [tableValues cellDataAtRow:index column:[[[tableDataInstance columnWithName:NSArrayObjectAtIndex(primaryKeyFieldNames,0)] objectForKey:@"datacolumnindex"] integerValue]];
+
+ if([keyValue isKindOfClass:[NSData class]])
+ [deleteQuery appendString:[NSString stringWithFormat:@"X'%@'", [mySQLConnection prepareBinaryData:keyValue]]];
+ else
+ [deleteQuery appendString:[NSString stringWithFormat:@"'%@'", [keyValue description]]];
+
+ // Split deletion query into 256k chunks
+ if([deleteQuery length] > 256000) {
+ [deleteQuery appendString:@")"];
+ [mySQLConnection queryString:deleteQuery];
+ // Remember affected rows for error checking
+ affectedRows += [mySQLConnection affectedRows];
+ // Reinit a new deletion query
+ [deleteQuery setString:[NSString stringWithFormat:@"DELETE FROM %@ WHERE %@ IN (", [selectedTable backtickQuotedString], [NSArrayObjectAtIndex(primaryKeyFieldNames,0) backtickQuotedString]]];
+ } else {
+ [deleteQuery appendString:@","];
+ }
+
+ index = [selectedRows indexGreaterThanIndex:index];
+ }
+
+ // Check if deleteQuery's maximal length was reached for the last index
+ // if yes omit the empty query
+ if(![deleteQuery hasSuffix:@"("]) {
+ // Replace final , by ) and delete the remaining rows
+ [deleteQuery setString:[NSString stringWithFormat:@"%@)", [deleteQuery substringToIndex:([deleteQuery length]-1)]]];
+ [mySQLConnection queryString:deleteQuery];
+ // Remember affected rows for error checking
+ affectedRows += [mySQLConnection affectedRows];
+ }
+
+ errors = (affectedRows > 0) ? [selectedRows count] - affectedRows : [selectedRows count];
+ } else {
+ // if table has more than one PRIMARY KEY
+ // delete the row by using all PRIMARY KEYs in an OR clause
+ NSMutableString *deleteQuery = [NSMutableString string];
+ NSInteger affectedRows = 0;
+
+ [deleteQuery setString:[NSString stringWithFormat:@"DELETE FROM %@ WHERE ", [selectedTable backtickQuotedString]]];
+
+ while (index != NSNotFound) {
+
+ // Build the AND clause of PRIMARY KEYS
+ [deleteQuery appendString:@"("];
+ for(NSString *primaryKeyFieldName in primaryKeyFieldNames) {
+
+ id keyValue = [tableValues cellDataAtRow:index column:[[[tableDataInstance columnWithName:primaryKeyFieldName] objectForKey:@"datacolumnindex"] integerValue]];
+
+ [deleteQuery appendString:[primaryKeyFieldName backtickQuotedString]];
+ if ([keyValue isKindOfClass:[NSData class]]) {
+ [deleteQuery appendString:@"=X'"];
+ [deleteQuery appendString:[mySQLConnection prepareBinaryData:keyValue]];
+ } else {
+ [deleteQuery appendString:@"='"];
+ [deleteQuery appendString:[mySQLConnection prepareString:[keyValue description]]];
+ }
+ [deleteQuery appendString:@"' AND "];
+ }
+
+ // Remove the trailing AND and add the closing bracket
+ [deleteQuery deleteCharactersInRange:NSMakeRange([deleteQuery length]-5, 5)];
+ [deleteQuery appendString:@")"];
+
+ // Split deletion query into 64k chunks
+ if([deleteQuery length] > 64000) {
+ [mySQLConnection queryString:deleteQuery];
+ // Remember affected rows for error checking
+ affectedRows += [mySQLConnection affectedRows];
+ // Reinit a new deletion query
+ [deleteQuery setString:[NSString stringWithFormat:@"DELETE FROM %@ WHERE ", [selectedTable backtickQuotedString]]];
+ } else {
+ [deleteQuery appendString:@" OR "];
+ }
+
+ index = [selectedRows indexGreaterThanIndex:index];
+ }
+
+ // Check if deleteQuery's maximal length was reached for the last index
+ // if yes omit the empty query
+ if(![deleteQuery hasSuffix:@"WHERE "]) {
+ // Remove final ' OR ' and delete the remaining rows
+ [deleteQuery setString:[deleteQuery substringToIndex:([deleteQuery length]-4)]];
+ [mySQLConnection queryString:deleteQuery];
+ // Remember affected rows for error checking
+ affectedRows += [mySQLConnection affectedRows];
+ }
+
+ errors = (affectedRows > 0) ? [selectedRows count] - affectedRows : [selectedRows count];
+ }
+
+ // Restore Console Log window's updating bahaviour
+ [[SPQueryController sharedQueryController] setAllowConsoleUpdate:consoleUpdateStatus];
+
+ if (errors) {
+ NSArray *message;
+ //TODO: The following three messages are NOT localisable!
+ if (errors < 0) {
+ message = [NSArray arrayWithObjects:NSLocalizedString(@"Warning", @"warning"),
+ [NSString stringWithFormat:NSLocalizedString(@"%ld row%@ more %@ deleted! Please check the Console and inform the Sequel Pro team!", @"message of panel when more rows were deleted"), (long)(errors*-1), ((errors*-1)>1)?@"s":@"", (errors>1)?@"were":@"was"],
+ nil];
+ }
+ else {
+ if (primaryKeyFieldNames == nil)
+ message = [NSArray arrayWithObjects:NSLocalizedString(@"Warning", @"warning"),
+ [NSString stringWithFormat:NSLocalizedString(@"%ld row%@ ha%@ not been deleted. Reload the table to be sure that the rows exist and use a primary key for your table.", @"message of panel when not all selected fields have been deleted"), (long)errors, (errors>1)?@"s":@"", (errors>1)?@"ve":@"s"],
+ nil];
+ else
+ message = [NSArray arrayWithObjects:NSLocalizedString(@"Warning", @"warning"),
+ [NSString stringWithFormat:NSLocalizedString(@"%ld row%@ ha%@ not been deleted. Reload the table to be sure that the rows exist and check the Console for possible errors inside the primary key%@ for your table.", @"message of panel when not all selected fields have been deleted by using primary keys"), (long)errors, (errors>1)?@"s":@"", (errors>1)?@"ve":@"s", (errors>1)?@"s":@""],
+ nil];
+ }
+
+ [self performSelector:@selector(showErrorSheetWith:)
+ withObject:message
+ afterDelay:0.3];
+ }
+
+ // Refresh table content
+ if ( errors || reloadAfterRemovingRow ) {
+ previousTableRowsCount = tableRowsCount;
+ [self loadTableValues];
+ } else {
+ for ( i = tableRowsCount - 1 ; i >= 0 ; i-- ) {
+ if ([selectedRows containsIndex:i]) [tableValues removeRowAtIndex:i];
+ }
+ tableRowsCount = [tableValues count];
+ [tableContentView reloadData];
+ }
+ [tableContentView deselectAll:self];
+ } else {
+ // The user clicked cancel in the "sure you wanna delete" message
+ // restore editing or whatever
+ }
+
+ }
+}
+
+
// Accessors
/**
@@ -1798,13 +2091,13 @@
return YES;
}
- [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:tableDocumentInstance];
+ [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryWillBePerformed" object:tableDocumentInstance];
// If editing, compare the new row to the old row and if they are identical finish editing without saving.
if (!isEditingNewRow && [oldRow isEqualToArray:[tableValues rowContentsAtIndex:currentlyEditingRow]]) {
isEditingRow = NO;
currentlyEditingRow = -1;
- [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance];
+ [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance];
return YES;
}
@@ -1904,12 +2197,12 @@
}
[fieldValues release];
- [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance];
+ [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance];
// If no rows have been changed, show error if appropriate.
if ( ![mySQLConnection affectedRows] && ![mySQLConnection queryErrored] ) {
if ( [prefs boolForKey:SPShowNoAffectedRowsError] ) {
- SPBeginAlertSheet(NSLocalizedString(@"Warning", @"warning"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil,
+ SPBeginAlertSheet(NSLocalizedString(@"Warning", @"warning"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil,
NSLocalizedString(@"The row was not written to the MySQL database. You probably haven't changed anything.\nReload the table to be sure that the row exists and use a primary key for your table.\n(This error can be turned off in the preferences.)", @"message of panel when no rows have been affected after writing to the db"));
} else {
NSBeep();
@@ -1958,12 +2251,40 @@
// 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, tableWindow, self, @selector(sheetDidEnd:returnCode:contextInfo:), nil, @"addrow",
+ SPBeginAlertSheet(NSLocalizedString(@"Couldn't write row", @"Couldn't write row error"), NSLocalizedString(@"Edit row", @"Edit row button"), NSLocalizedString(@"Discard changes", @"discard changes button"), nil, tableWindow, self, @selector(addRowErrorSheetDidEnd:returnCode:contextInfo:), nil,
[NSString stringWithFormat:NSLocalizedString(@"MySQL said:\n\n%@", @"message of panel when error while adding row to db"), [mySQLConnection getLastErrorMessage]]);
return NO;
}
}
+/*
+ * Handle the user decision as a result of an addRow error.
+ */
+- (void) addRowErrorSheetDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo
+{
+ // Order out current sheet to suppress overlapping of sheets
+ [[alert window] orderOut:nil];
+
+ // Edit row selected - reselect the row, and start editing.
+ if ( returnCode == NSAlertDefaultReturn ) {
+ [tableContentView selectRowIndexes:[NSIndexSet indexSetWithIndex:currentlyEditingRow] byExtendingSelection:NO];
+ [tableContentView performSelector:@selector(keyDown:) withObject:[NSEvent keyEventWithType:NSKeyDown location:NSMakePoint(0,0) modifierFlags:0 timestamp:0 windowNumber:[tableWindow windowNumber] context:[NSGraphicsContext currentContext] characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:0x24] afterDelay:0.0];
+
+ // Discard changes selected
+ } else {
+ if ( !isEditingNewRow ) {
+ [tableValues replaceRowAtIndex:currentlyEditingRow withRowContents:oldRow];
+ isEditingRow = NO;
+ } else {
+ tableRowsCount--;
+ [tableValues removeRowAtIndex:currentlyEditingRow];
+ isEditingRow = NO;
+ isEditingNewRow = NO;
+ }
+ currentlyEditingRow = -1;
+ }
+ [tableContentView reloadData];
+}
/*
* A method to be called whenever the table selection changes; checks whether the current
@@ -2038,7 +2359,7 @@
// When the option to not show blob or text options is set, we have a problem - we don't have
// the right values to use in the WHERE statement. Throw an error if this is the case.
if ( [prefs boolForKey:SPLoadBlobsAsNeeded] && [self tableContainsBlobOrTextColumns] ) {
- SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil,
+ SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil,
NSLocalizedString(@"You can't hide blob and text fields when working with tables without index.", @"message of panel when trying to edit tables without index and with hidden blob/text fields"));
[keys removeAllObjects];
[tableContentView deselectAll:self];
@@ -2130,315 +2451,12 @@
}
}
-- (void)sheetDidEnd:(id)sheet returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo
/*
- if contextInfo == addrow: remain in edit-mode if user hits OK, otherwise cancel editing
- if contextInfo == removerow: removes row if user hits OK
+ * Close an open sheet.
*/
+- (void)sheetDidEnd:(id)sheet returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo
{
-
- NSMutableIndexSet *selectedRows = [NSMutableIndexSet indexSet];
- NSString *wherePart;
- NSInteger i, errors;
- BOOL consoleUpdateStatus;
- BOOL reloadAfterRemovingRow = [prefs boolForKey:SPReloadAfterRemovingRow];
-
- if([sheet respondsToSelector:@selector(orderOut:)])
- [sheet orderOut:self];
- else if([sheet window] && [[sheet window] respondsToSelector:@selector(orderOut:)])
- [[sheet window] orderOut:self];
-
- if ( [contextInfo isEqualToString:@"addrow"] ) {
-
- if ( returnCode == NSAlertDefaultReturn ) {
- [tableContentView selectRowIndexes:[NSIndexSet indexSetWithIndex:currentlyEditingRow] byExtendingSelection:NO];
- [tableContentView performSelector:@selector(keyDown:) withObject:[NSEvent keyEventWithType:NSKeyDown location:NSMakePoint(0,0) modifierFlags:0 timestamp:0 windowNumber:[tableWindow windowNumber] context:[NSGraphicsContext currentContext] characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:0x24] afterDelay:0.0];
- } else {
- if ( !isEditingNewRow ) {
- [tableValues replaceRowAtIndex:currentlyEditingRow withRowContents:oldRow];
- isEditingRow = NO;
- } else {
- tableRowsCount--;
- [tableValues removeRowAtIndex:currentlyEditingRow];
- isEditingRow = NO;
- isEditingNewRow = NO;
- }
- currentlyEditingRow = -1;
- }
- [tableContentView reloadData];
- } else if ( [contextInfo isEqualToString:@"removeallrows"] ) {
- if ( returnCode == NSAlertDefaultReturn ) {
- //check if the user is currently editing a row
- if (isEditingRow) {
- //cancel the edit
- isEditingRow = NO;
- // in case the delete fails, make sure we at least stay in a somewhat consistent state
- [tableValues replaceRowAtIndex:currentlyEditingRow withRowContents:oldRow];
- currentlyEditingRow = -1;
- }
- [mySQLConnection queryString:[NSString stringWithFormat:@"DELETE FROM %@", [selectedTable backtickQuotedString]]];
- if ( ![mySQLConnection queryErrored] ) {
-
- // Reset auto increment if suppression button was ticked
- if([[sheet suppressionButton] state] == NSOnState)
- [tableSourceInstance setAutoIncrementTo:@"1"];
-
- [self reloadTable:self];
-
- } else {
- [self performSelector:@selector(showErrorSheetWith:)
- withObject:[NSArray arrayWithObjects:NSLocalizedString(@"Error", @"error"),
- [NSString stringWithFormat:NSLocalizedString(@"Couldn't remove rows.\n\nMySQL said: %@", @"message of panel when field cannot be removed"),
- [mySQLConnection getLastErrorMessage]],
- nil]
- afterDelay:0.3];
- }
- }
- } else if ( [contextInfo isEqualToString:@"removerow"] ) {
- if ( returnCode == NSAlertDefaultReturn ) {
- [selectedRows addIndexes:[tableContentView selectedRowIndexes]];
-
- //check if the user is currently editing a row
- if (isEditingRow) {
- //make sure that only one row is selected. This should never happen
- if ([selectedRows count]!=1) {
- NSLog(@"Expected only one selected row, but found %d",[selectedRows count]);
- }
- // this code is pretty much taken from the escape key handler
- if ( isEditingNewRow ) {
- // since the user is currently editing a new row, we don't actually have to delete any rows from the database
- // we just have to remove the row from the view (and the store)
- isEditingRow = NO;
- isEditingNewRow = NO;
- tableRowsCount--;
- [tableValues removeRowAtIndex:currentlyEditingRow];
- currentlyEditingRow = -1;
- [self updateCountText];
- [tableContentView reloadData];
-
- //deselect the row
- [tableContentView selectRowIndexes:[NSIndexSet indexSet] byExtendingSelection:NO];
-
- // we also don't have to reload the table, since no query went to the database
- return;
- } else {
- //cancel the edit
- isEditingRow = NO;
- // in case the delete fails, make sure we at least stay in a somewhat consistent state
- [tableValues replaceRowAtIndex:currentlyEditingRow withRowContents:oldRow];
- currentlyEditingRow = -1;
- }
-
- }
- [tableContentView selectRowIndexes:[NSIndexSet indexSet] byExtendingSelection:NO];
-
- errors = 0;
-
- // Disable updating of the Console Log window for large number of queries
- // to speed the deletion
- consoleUpdateStatus = [[SPQueryController sharedQueryController] allowConsoleUpdate];
- if([selectedRows count] > 10)
- [[SPQueryController sharedQueryController] setAllowConsoleUpdate:NO];
-
- NSUInteger index = [selectedRows firstIndex];
-
- NSArray *primaryKeyFieldNames = [tableDataInstance primaryKeyColumnNames];
-
- // If no PRIMARY KEY is found and numberOfSelectedRows > 3 then
- // check for uniqueness of rows via combining all column values;
- // if unique then use the all columns as 'primary keys'
- if([selectedRows count] >3 && primaryKeyFieldNames == nil) {
- primaryKeyFieldNames = [tableDataInstance columnNames];
-
- NSInteger numberOfRows = 0;
- // Get the number of rows in the table
- MCPResult *r;
- r = [mySQLConnection queryString:[NSString stringWithFormat:@"SELECT COUNT(1) FROM %@", [selectedTable backtickQuotedString]]];
- if (![mySQLConnection queryErrored]) {
- NSArray *a = [r fetchRowAsArray];
- if([a count])
- numberOfRows = [[a objectAtIndex:0] integerValue];
- }
- // Check for uniqueness via LIMIT numberOfRows-1,numberOfRows for speed
- if(numberOfRows > 0) {
- [mySQLConnection queryString:[NSString stringWithFormat:@"SELECT * FROM %@ GROUP BY %@ LIMIT %ld,%ld", [selectedTable backtickQuotedString], [primaryKeyFieldNames componentsJoinedAndBacktickQuoted], (long)(numberOfRows-1), (long)numberOfRows]];
- if([mySQLConnection affectedRows] == 0)
- primaryKeyFieldNames = nil;
- } else {
- primaryKeyFieldNames = nil;
- }
- }
-
- if(primaryKeyFieldNames == nil) {
- // delete row by row
- while (index != NSNotFound) {
-
- wherePart = [NSString stringWithString:[self argumentForRow:index]];
-
- //argumentForRow might return empty query, in which case we shouldn't execute the partial query
- if([wherePart length]) {
- [mySQLConnection queryString:[NSString stringWithFormat:@"DELETE FROM %@ WHERE %@", [selectedTable backtickQuotedString], wherePart]];
-
- // Check for errors
- if ( ![mySQLConnection affectedRows] || [mySQLConnection queryErrored]) {
- // If error delete that index from selectedRows for reloading table if
- // "ReloadAfterRemovingRow" is disbaled
- if(!reloadAfterRemovingRow)
- [selectedRows removeIndex:index];
- errors++;
- }
- } else {
- if(!reloadAfterRemovingRow)
- [selectedRows removeIndex:index];
- errors++;
- }
- index = [selectedRows indexGreaterThanIndex:index];
- }
- } else if ([primaryKeyFieldNames count] == 1) {
- // if table has only one PRIMARY KEY
- // delete the fast way by using the PRIMARY KEY in an IN clause
- NSMutableString *deleteQuery = [NSMutableString string];
- NSInteger affectedRows = 0;
-
- [deleteQuery setString:[NSString stringWithFormat:@"DELETE FROM %@ WHERE %@ IN (", [selectedTable backtickQuotedString], [NSArrayObjectAtIndex(primaryKeyFieldNames,0) backtickQuotedString]]];
-
- while (index != NSNotFound) {
-
- id keyValue = [tableValues cellDataAtRow:index column:[[[tableDataInstance columnWithName:NSArrayObjectAtIndex(primaryKeyFieldNames,0)] objectForKey:@"datacolumnindex"] integerValue]];
-
- if([keyValue isKindOfClass:[NSData class]])
- [deleteQuery appendString:[NSString stringWithFormat:@"X'%@'", [mySQLConnection prepareBinaryData:keyValue]]];
- else
- [deleteQuery appendString:[NSString stringWithFormat:@"'%@'", [keyValue description]]];
-
- // Split deletion query into 256k chunks
- if([deleteQuery length] > 256000) {
- [deleteQuery appendString:@")"];
- [mySQLConnection queryString:deleteQuery];
- // Remember affected rows for error checking
- affectedRows += [mySQLConnection affectedRows];
- // Reinit a new deletion query
- [deleteQuery setString:[NSString stringWithFormat:@"DELETE FROM %@ WHERE %@ IN (", [selectedTable backtickQuotedString], [NSArrayObjectAtIndex(primaryKeyFieldNames,0) backtickQuotedString]]];
- } else {
- [deleteQuery appendString:@","];
- }
-
- index = [selectedRows indexGreaterThanIndex:index];
- }
-
- // Check if deleteQuery's maximal length was reached for the last index
- // if yes omit the empty query
- if(![deleteQuery hasSuffix:@"("]) {
- // Replace final , by ) and delete the remaining rows
- [deleteQuery setString:[NSString stringWithFormat:@"%@)", [deleteQuery substringToIndex:([deleteQuery length]-1)]]];
- [mySQLConnection queryString:deleteQuery];
- // Remember affected rows for error checking
- affectedRows += [mySQLConnection affectedRows];
- }
-
- errors = (affectedRows > 0) ? [selectedRows count] - affectedRows : [selectedRows count];
- } else {
- // if table has more than one PRIMARY KEY
- // delete the row by using all PRIMARY KEYs in an OR clause
- NSMutableString *deleteQuery = [NSMutableString string];
- NSInteger affectedRows = 0;
-
- [deleteQuery setString:[NSString stringWithFormat:@"DELETE FROM %@ WHERE ", [selectedTable backtickQuotedString]]];
-
- while (index != NSNotFound) {
-
- // Build the AND clause of PRIMARY KEYS
- [deleteQuery appendString:@"("];
- for(NSString *primaryKeyFieldName in primaryKeyFieldNames) {
-
- id keyValue = [tableValues cellDataAtRow:index column:[[[tableDataInstance columnWithName:primaryKeyFieldName] objectForKey:@"datacolumnindex"] integerValue]];
-
- [deleteQuery appendString:[primaryKeyFieldName backtickQuotedString]];
- if ([keyValue isKindOfClass:[NSData class]]) {
- [deleteQuery appendString:@"=X'"];
- [deleteQuery appendString:[mySQLConnection prepareBinaryData:keyValue]];
- } else {
- [deleteQuery appendString:@"='"];
- [deleteQuery appendString:[mySQLConnection prepareString:[keyValue description]]];
- }
- [deleteQuery appendString:@"' AND "];
- }
-
- // Remove the trailing AND and add the closing bracket
- [deleteQuery deleteCharactersInRange:NSMakeRange([deleteQuery length]-5, 5)];
- [deleteQuery appendString:@")"];
-
- // Split deletion query into 64k chunks
- if([deleteQuery length] > 64000) {
- [mySQLConnection queryString:deleteQuery];
- // Remember affected rows for error checking
- affectedRows += [mySQLConnection affectedRows];
- // Reinit a new deletion query
- [deleteQuery setString:[NSString stringWithFormat:@"DELETE FROM %@ WHERE ", [selectedTable backtickQuotedString]]];
- } else {
- [deleteQuery appendString:@" OR "];
- }
-
- index = [selectedRows indexGreaterThanIndex:index];
- }
-
- // Check if deleteQuery's maximal length was reached for the last index
- // if yes omit the empty query
- if(![deleteQuery hasSuffix:@"WHERE "]) {
- // Remove final ' OR ' and delete the remaining rows
- [deleteQuery setString:[deleteQuery substringToIndex:([deleteQuery length]-4)]];
- [mySQLConnection queryString:deleteQuery];
- // Remember affected rows for error checking
- affectedRows += [mySQLConnection affectedRows];
- }
-
- errors = (affectedRows > 0) ? [selectedRows count] - affectedRows : [selectedRows count];
- }
-
- // Restore Console Log window's updating bahaviour
- [[SPQueryController sharedQueryController] setAllowConsoleUpdate:consoleUpdateStatus];
-
- if (errors) {
- NSArray *message;
- //TODO: The following three messages are NOT localisable!
- if (errors < 0) {
- message = [NSArray arrayWithObjects:NSLocalizedString(@"Warning", @"warning"),
- [NSString stringWithFormat:NSLocalizedString(@"%ld row%@ more %@ removed! Please check the Console and inform the Sequel Pro team!", @"message of panel when more rows were deleted"), (long)(errors*-1), ((errors*-1)>1)?@"s":@"", (errors>1)?@"were":@"was"],
- nil];
- }
- else {
- if (primaryKeyFieldNames == nil)
- message = [NSArray arrayWithObjects:NSLocalizedString(@"Warning", @"warning"),
- [NSString stringWithFormat:NSLocalizedString(@"%ld row%@ ha%@ not been removed. Reload the table to be sure that the rows exist and use a primary key for your table.", @"message of panel when not all selected fields have been deleted"), (long)errors, (errors>1)?@"s":@"", (errors>1)?@"ve":@"s"],
- nil];
- else
- message = [NSArray arrayWithObjects:NSLocalizedString(@"Warning", @"warning"),
- [NSString stringWithFormat:NSLocalizedString(@"%ld row%@ ha%@ not been removed. Reload the table to be sure that the rows exist and check the Console for possible errors inside the primary key%@ for your table.", @"message of panel when not all selected fields have been deleted by using primary keys"), (long)errors, (errors>1)?@"s":@"", (errors>1)?@"ve":@"s", (errors>1)?@"s":@""],
- nil];
- }
-
- [self performSelector:@selector(showErrorSheetWith:)
- withObject:message
- afterDelay:0.3];
- }
-
- // Refresh table content
- if ( errors || reloadAfterRemovingRow ) {
- previousTableRowsCount = tableRowsCount;
- [self loadTableValues];
- } else {
- for ( i = tableRowsCount - 1 ; i >= 0 ; i-- ) {
- if ([selectedRows containsIndex:i]) [tableValues removeRowAtIndex:i];
- }
- tableRowsCount = [tableValues count];
- [tableContentView reloadData];
- }
- [tableContentView deselectAll:self];
- } else {
- // The user clicked cancel in the "sure you wanna delete" message
- // restore editing or whatever
- }
- }
+ [sheet orderOut:self];
}
/**
@@ -2449,7 +2467,7 @@
{
// error := first object is the title , second the message, only one button OK
SPBeginAlertSheet([error objectAtIndex:0], NSLocalizedString(@"OK", @"OK button"),
- nil, nil, tableWindow, self, nil, nil, nil,
+ nil, nil, tableWindow, self, nil, nil,
[error objectAtIndex:1]);
}
@@ -2643,8 +2661,8 @@
maxNumRowsIsEstimate = NO;
[tableDataInstance setStatusValue:[NSString stringWithFormat:@"%ld", (long)maxNumRows] forKey:@"Rows"];
[tableDataInstance setStatusValue:@"y" forKey:@"RowsCountAccurate"];
- [tableInfoInstance tableChanged:nil];
- [[tableDocumentInstance valueForKey:@"extendedTableInfoInstance"] performSelectorOnMainThread:@selector(loadTable:) withObject:selectedTable waitUntilDone:YES];
+ [[tableInfoInstance onMainThread] tableChanged:nil];
+ [[[tableDocumentInstance valueForKey:@"extendedTableInfoInstance"] onMainThread] loadTable:selectedTable];
// Otherwise, if the table status value is accurate, use it
} else if ([[tableDataInstance statusValueForKey:@"RowsCountAccurate"] boolValue]) {
@@ -2662,8 +2680,8 @@
maxNumRowsIsEstimate = NO;
[tableDataInstance setStatusValue:[NSString stringWithFormat:@"%ld", (long)maxNumRows] forKey:@"Rows"];
[tableDataInstance setStatusValue:@"y" forKey:@"RowsCountAccurate"];
- [tableInfoInstance tableChanged:nil];
- [[tableDocumentInstance valueForKey:@"extendedTableInfoInstance"] performSelectorOnMainThread:@selector(loadTable:) withObject:selectedTable waitUntilDone:YES];
+ [[tableInfoInstance onMainThread] tableChanged:nil];
+ [[[tableDocumentInstance valueForKey:@"extendedTableInfoInstance"] onMainThread] loadTable:selectedTable];
// Use the estimate count
} else {
@@ -2685,7 +2703,7 @@
maxNumRows = foundMaxRows;
maxNumRowsIsEstimate = NO;
}
- } else if (!isInterruptedLoad && tableRowsCount < [prefs integerForKey:SPLimitResultsValue]) {
+ } else if (!isInterruptedLoad && !isFiltered && tableRowsCount < [prefs integerForKey:SPLimitResultsValue]) {
maxNumRows = foundMaxRows;
maxNumRowsIsEstimate = NO;
}
@@ -2695,7 +2713,7 @@
}
[tableDataInstance setStatusValue:[NSString stringWithFormat:@"%ld", (long)maxNumRows] forKey:@"Rows"];
[tableDataInstance setStatusValue:maxNumRowsIsEstimate?@"n":@"y" forKey:@"RowsCountAccurate"];
- [tableInfoInstance tableChanged:nil];
+ [[tableInfoInstance onMainThread] tableChanged:nil];
}
}
@@ -2926,7 +2944,7 @@
[self loadTableValues];
if ([mySQLConnection queryErrored]) {
- SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil,
+ SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil,
[NSString stringWithFormat:NSLocalizedString(@"Couldn't sort table. MySQL said: %@", @"message of panel when sorting of table failed"), [mySQLConnection getLastErrorMessage]]);
[tableDocumentInstance endTask];
[sortPool drain];
@@ -3021,7 +3039,7 @@
MCPResult *tempResult = [mySQLConnection queryString:query];
if (![tempResult numOfRows]) {
- SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil,
+ SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, 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;
}
diff --git a/Source/TableDocument.h b/Source/TableDocument.h
index 4b151615..b9e90256 100644
--- a/Source/TableDocument.h
+++ b/Source/TableDocument.h
@@ -54,7 +54,7 @@
IBOutlet id statusTableView;
IBOutlet id statusTableCopyChecksum;
- IBOutlet SPUserManager *userManagerInstance;
+ SPUserManager *userManagerInstance;
IBOutlet NSSearchField *listFilterField;
@@ -141,7 +141,7 @@
CGFloat taskDisplayLastValue;
CGFloat taskProgressValueDisplayInterval;
NSTimer *taskDrawTimer;
- NSViewAnimation *taskFadeAnimator;
+ NSDate *taskFadeInStartDate;
BOOL taskCanBeCancelled;
id taskCancellationCallbackObject;
SEL taskCancellationCallbackSelector;
@@ -196,7 +196,7 @@
// Task progress and notification methods
- (void)startTaskWithDescription:(NSString *)description;
-- (void)showTaskProgressWindow:(NSTimer *)theTimer;
+- (void)fadeInTaskProgressWindow:(NSTimer *)theTimer;
- (void)setTaskDescription:(NSString *)description;
- (void)setTaskPercentage:(CGFloat)taskPercentage;
- (void)setTaskProgressToIndeterminateAfterDelay:(BOOL)afterDelay;
diff --git a/Source/TableDocument.m b/Source/TableDocument.m
index cc1a5bc5..b12e9af2 100644
--- a/Source/TableDocument.m
+++ b/Source/TableDocument.m
@@ -103,7 +103,7 @@
taskProgressValue = 0;
taskProgressValueDisplayInterval = 1;
taskDrawTimer = nil;
- taskFadeAnimator = nil;
+ taskFadeInStartDate = nil;
taskCanBeCancelled = NO;
taskCancellationCallbackObject = nil;
taskCancellationCallbackSelector = NULL;
@@ -233,6 +233,12 @@
// Set up the progress indicator child window and layer - add to main window, change indicator color and size
[taskProgressIndicator setForeColor:[NSColor whiteColor]];
+ NSShadow *progressIndicatorShadow = [[NSShadow alloc] init];
+ [progressIndicatorShadow setShadowOffset:NSMakeSize(1.0, -1.0)];
+ [progressIndicatorShadow setShadowBlurRadius:1.0];
+ [progressIndicatorShadow setShadowColor:[NSColor colorWithCalibratedWhite:0.0 alpha:0.75]];
+ [taskProgressIndicator setShadow:progressIndicatorShadow];
+ [progressIndicatorShadow release];
taskProgressWindow = [[NSWindow alloc] initWithContentRect:[taskProgressLayer bounds] styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO];
[taskProgressWindow setOpaque:NO];
[taskProgressWindow setBackgroundColor:[NSColor clearColor]];
@@ -679,7 +685,7 @@
[tableDataInstance setConnection:mySQLConnection];
[extendedTableInfoInstance setConnection:mySQLConnection];
[databaseDataInstance setConnection:mySQLConnection];
- userManagerInstance.mySqlConnection = mySQLConnection;
+ //userManagerInstance.mySqlConnection = mySQLConnection;
// Set the cutom query editor's MySQL version
[customQueryInstance setMySQLversion:mySQLVersion];
@@ -1051,7 +1057,7 @@
{
// error := first object is the title , second the message, only one button OK
SPBeginAlertSheet([error objectAtIndex:0], NSLocalizedString(@"OK", @"OK button"),
- nil, nil, tableWindow, self, nil, nil, nil,
+ nil, nil, tableWindow, self, nil, nil,
[error objectAtIndex:1]);
}
@@ -1063,7 +1069,7 @@
NSString *dbName = nil;
// Notify listeners that a query has started
- [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:self];
+ [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryWillBePerformed" object:self];
MCPResult *theResult = [mySQLConnection queryString:@"SELECT DATABASE()"];
if (![mySQLConnection queryErrored]) {
@@ -1088,7 +1094,7 @@
}
//query finished
- [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:self];
+ [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:self];
}
- (BOOL)navigatorSchemaPathExistsForDatabase:(NSString*)dbname
@@ -1197,15 +1203,17 @@
*/
- (void) startTaskWithDescription:(NSString *)description
{
+ // Ensure a call on the main thread
+ if (![NSThread isMainThread]) return [[self onMainThread] startTaskWithDescription:description];
// Set the task text. If a nil string was supplied, a generic query notification is occurring -
// if a task is not already active, use default text.
if (!description) {
- if (!_isWorkingLevel) [taskDescriptionText setStringValue:NSLocalizedString(@"Working...", @"Generic working description")];
+ if (!_isWorkingLevel) [self setTaskDescription:NSLocalizedString(@"Working...", @"Generic working description")];
// Otherwise display the supplied string
} else {
- [taskDescriptionText setStringValue:description];
+ [self setTaskDescription:description];
}
// Increment the task level
@@ -1215,7 +1223,6 @@
if (_isWorkingLevel == 1 || !taskDisplayIsIndeterminate) {
taskDisplayIsIndeterminate = YES;
[taskProgressIndicator setIndeterminate:YES];
- [taskProgressIndicator animate:self];
[taskProgressIndicator startAnimation:self];
taskDisplayLastValue = 0;
}
@@ -1230,55 +1237,86 @@
[[NSNotificationCenter defaultCenter] postNotificationName:SPDocumentTaskStartNotification object:self];
[mainToolbar validateVisibleItems];
- // Schedule appearance of the task window in the near future
- taskDrawTimer = [[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(showTaskProgressWindow:) userInfo:nil repeats:NO] retain];
+ // Schedule appearance of the task window in the near future, using a frame timer.
+ taskFadeInStartDate = [[NSDate alloc] init];
+ taskDrawTimer = [[NSTimer scheduledTimerWithTimeInterval:1.0 / 30.0 target:self selector:@selector(fadeInTaskProgressWindow:) userInfo:nil repeats:YES] retain];
}
}
/**
* Show the task progress window, after a small delay to minimise flicker.
*/
-- (void) showTaskProgressWindow:(NSTimer *)theTimer
+- (void) fadeInTaskProgressWindow:(NSTimer *)theTimer
{
- if (taskDrawTimer) [taskDrawTimer invalidate], [taskDrawTimer release], taskDrawTimer = nil;
+ float timeSinceFadeInStart = [[NSDate date] timeIntervalSinceDate:taskFadeInStartDate];
- // Center the task window and fade it in
- [self centerTaskWindow];
- NSDictionary *animationDetails = [NSDictionary dictionaryWithObjectsAndKeys:
- NSViewAnimationFadeInEffect, NSViewAnimationEffectKey,
- taskProgressWindow, NSViewAnimationTargetKey,
- nil];
- taskFadeAnimator = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:animationDetails]];
- [taskFadeAnimator setDuration:0.6];
- [taskFadeAnimator startAnimation];
-}
+ // Keep the window hidden for the first ~0.5 secs
+ if (timeSinceFadeInStart < 0.5) return;
+
+ CGFloat alphaValue = [taskProgressWindow alphaValue];
+
+ // If the task progress window is still hidden, center it before revealing it
+ if (alphaValue == 0) [self centerTaskWindow];
+
+ // Fade in the task window over 0.6 seconds
+ alphaValue = (timeSinceFadeInStart - 0.5) / 0.6;
+ if (alphaValue > 1.0) alphaValue = 1.0;
+ [taskProgressWindow setAlphaValue:alphaValue];
+ // If the window has been fully faded in, clean up the timer.
+ if (alphaValue == 1.0) {
+ [taskDrawTimer invalidate], [taskDrawTimer release], taskDrawTimer = nil;
+ [taskFadeInStartDate release], taskFadeInStartDate = nil;
+ }
+}
/**
* Updates the task description shown to the user.
*/
- (void) setTaskDescription:(NSString *)description
{
- [taskDescriptionText setStringValue:description];
+ NSShadow *shadow = [[NSShadow alloc] init];
+ [shadow setShadowColor:[NSColor colorWithCalibratedWhite:0.0 alpha:0.75]];
+ [shadow setShadowOffset:NSMakeSize(1.0, -1.0)];
+ [shadow setShadowBlurRadius:3.0];
+
+ NSMutableDictionary *attributes = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
+ [NSFont boldSystemFontOfSize:13.0], NSFontAttributeName,
+ shadow, NSShadowAttributeName,
+ nil];
+ NSAttributedString *string = [[NSAttributedString alloc] initWithString:description attributes:attributes];
+
+ [taskDescriptionText setAttributedStringValue:string];
+
+ [string release];
+ [attributes release];
+ [shadow release];
}
/**
* Sets the task percentage progress - the first call to this automatically
* switches the progress display to determinate.
+ * Can be called from background threads - forwards to main thread as appropriate.
*/
- (void) setTaskPercentage:(CGFloat)taskPercentage
{
+
+ // If the task display is currently indeterminate, set it to determinate on the main thread.
if (taskDisplayIsIndeterminate) {
+ if (![NSThread isMainThread]) return [[self onMainThread] setTaskPercentage:taskPercentage];
+
taskDisplayIsIndeterminate = NO;
[taskProgressIndicator stopAnimation:self];
[taskProgressIndicator setDoubleValue:0.5];
}
+ // Check the supplied progress. Compare it to the display interval - how often
+ // the interface is updated - and update the interface if the value has changed enough.
taskProgressValue = taskPercentage;
if (taskProgressValue > taskDisplayLastValue + taskProgressValueDisplayInterval
|| taskProgressValue < taskDisplayLastValue - taskProgressValueDisplayInterval)
{
- [taskProgressIndicator setDoubleValue:taskProgressValue];
+ [taskProgressIndicator performSelectorOnMainThread:@selector(setNumberValue:) withObject:[NSNumber numberWithDouble:taskProgressValue] waitUntilDone:NO];
taskDisplayLastValue = taskProgressValue;
}
}
@@ -1298,6 +1336,7 @@
}
if (taskDisplayIsIndeterminate) return;
+ [NSObject cancelPreviousPerformRequestsWithTarget:taskProgressIndicator];
taskDisplayIsIndeterminate = YES;
[taskProgressIndicator setIndeterminate:YES];
[taskProgressIndicator startAnimation:self];
@@ -1311,10 +1350,7 @@
{
// Ensure a call on the main thread
- if (![NSThread isMainThread]) {
- [self performSelectorOnMainThread:@selector(endTask) withObject:nil waitUntilDone:YES];
- return;
- }
+ if (![NSThread isMainThread]) return [[self onMainThread] endTask];
// Decrement the working level
_isWorkingLevel--;
@@ -1326,12 +1362,9 @@
if (!_isWorkingLevel) {
// Cancel the draw timer if it exists
- if (taskDrawTimer) [taskDrawTimer invalidate], [taskDrawTimer release], taskDrawTimer = nil;
-
- // Cancel the fade-in animator if it exists
- if (taskFadeAnimator) {
- if ([taskFadeAnimator isAnimating]) [taskFadeAnimator stopAnimation];
- [taskFadeAnimator release], taskFadeAnimator = nil;
+ if (taskDrawTimer) {
+ [taskDrawTimer invalidate], [taskDrawTimer release], taskDrawTimer = nil;
+ [taskFadeInStartDate release], taskFadeInStartDate = nil;
}
// Hide the task interface and reset to indeterminate
@@ -1358,6 +1391,9 @@
// If no task is active, return
if (!_isWorkingLevel) return;
+ // Ensure call on the main thread
+ if (![NSThread isMainThread]) return [[self onMainThread] enableTaskCancellationWithTitle:buttonTitle callbackObject:callbackObject callbackFunction:callbackFunction];
+
if (callbackObject && callbackFunction) {
taskCancellationCallbackObject = callbackObject;
taskCancellationCallbackSelector = callbackFunction;
@@ -1377,7 +1413,10 @@
// If no task is active, return
if (!_isWorkingLevel) return;
-
+
+ // Ensure call on the main thread
+ if (![NSThread isMainThread]) return [[self onMainThread] disableTaskCancellation];
+
taskCanBeCancelled = NO;
taskCancellationCallbackObject = nil;
taskCancellationCallbackSelector = NULL;
@@ -2297,7 +2336,13 @@
* Displays the user account manager.
*/
- (IBAction)showUserManager:(id)sender
-{
+{
+ if (!userManagerInstance)
+ {
+ userManagerInstance = [[SPUserManager alloc] init];
+ userManagerInstance.mySqlConnection = mySQLConnection;
+ }
+
// Before displaying the user manager make sure the current user has access to the mysql.user table.
MCPResult *result = [mySQLConnection queryString:@"SELECT * FROM `mysql`.`user` ORDER BY `user`"];
@@ -2318,11 +2363,16 @@
[NSApp beginSheet:[userManagerInstance window]
modalForWindow:tableWindow
- modalDelegate:userManagerInstance
+ modalDelegate:self
didEndSelector:@selector(userManagerSheetDidEnd:returnCode:contextInfo:)
contextInfo:nil];
}
+- (void)userManagerSheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void*)context
+{
+ [userManagerInstance release], userManagerInstance = nil;
+}
+
/**
* Passes query to tablesListInstance
*/
@@ -2350,10 +2400,10 @@
if (![mySQLConnection queryErrored]) {
//flushed privileges without errors
- SPBeginAlertSheet(NSLocalizedString(@"Flushed Privileges", @"title of panel when successfully flushed privs"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil, NSLocalizedString(@"Successfully flushed privileges.", @"message of panel when successfully flushed privs"));
+ SPBeginAlertSheet(NSLocalizedString(@"Flushed Privileges", @"title of panel when successfully flushed privs"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, NSLocalizedString(@"Successfully flushed privileges.", @"message of panel when successfully flushed privs"));
} else {
//error while flushing privileges
- SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil, [NSString stringWithFormat:NSLocalizedString(@"Couldn't flush privileges.\nMySQL said: %@", @"message of panel when flushing privs failed"),
+ SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, [NSString stringWithFormat:NSLocalizedString(@"Couldn't flush privileges.\nMySQL said: %@", @"message of panel when flushing privs failed"),
[mySQLConnection getLastErrorMessage]]);
}
}
@@ -4052,8 +4102,8 @@
if (mySQLConnection) [mySQLConnection release];
if (selectedDatabase) [selectedDatabase release];
if (mySQLVersion) [mySQLVersion release];
- if (taskDrawTimer) [taskDrawTimer release];
- if (taskFadeAnimator) [taskFadeAnimator release];
+ if (taskDrawTimer) [taskDrawTimer invalidate], [taskDrawTimer release];
+ if (taskFadeInStartDate) [taskFadeInStartDate release];
if (queryEditorInitString) [queryEditorInitString release];
if (spfPreferences) [spfPreferences release];
if (spfSession) [spfSession release];
@@ -4075,7 +4125,7 @@
// This check is not necessary anymore as the add database button is now only enabled if the name field
// has a length greater than zero. We'll leave it in just in case.
if ([[databaseNameField stringValue] isEqualToString:@""]) {
- SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil, NSLocalizedString(@"Database must have a name.", @"message of panel when no db name is given"));
+ SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, NSLocalizedString(@"Database must have a name.", @"message of panel when no db name is given"));
return;
}
@@ -4091,14 +4141,14 @@
if ([mySQLConnection queryErrored]) {
// An error occurred
- SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil, [NSString stringWithFormat:NSLocalizedString(@"Couldn't create database.\nMySQL said: %@", @"message of panel when creation of db failed"), [mySQLConnection getLastErrorMessage]]);
+ SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, [NSString stringWithFormat:NSLocalizedString(@"Couldn't create database.\nMySQL said: %@", @"message of panel when creation of db failed"), [mySQLConnection getLastErrorMessage]]);
return;
}
// Error while selecting the new database (is this even possible?)
if (![mySQLConnection selectDB:[databaseNameField stringValue]] ) {
- SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil, [NSString stringWithFormat:NSLocalizedString(@"Unable to connect to database %@.\nBe sure that you have the necessary privileges.", @"message of panel when connection to db failed after selecting from popupbutton"), [databaseNameField stringValue]]);
+ SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, [NSString stringWithFormat:NSLocalizedString(@"Unable to connect to database %@.\nBe sure that you have the necessary privileges.", @"message of panel when connection to db failed after selecting from popupbutton"), [databaseNameField stringValue]]);
[self setDatabases:self];
@@ -4181,7 +4231,7 @@
|| ![mySQLConnection selectDB:targetDatabaseName])
{
if ( [mySQLConnection isConnected] ) {
- SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil, [NSString stringWithFormat:NSLocalizedString(@"Unable to connect to database %@.\nBe sure that you have the necessary privileges.", @"message of panel when connection to db failed after selecting from popupbutton"), targetDatabaseName]);
+ SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, [NSString stringWithFormat:NSLocalizedString(@"Unable to connect to database %@.\nBe sure that you have the necessary privileges.", @"message of panel when connection to db failed after selecting from popupbutton"), targetDatabaseName]);
// Update the database list
[self setDatabases:self];
@@ -4235,9 +4285,6 @@
}
}
- // Query the structure of all databases in the background (mainly for completion)
- [NSThread detachNewThreadSelector:@selector(queryDbStructureWithUserInfo:) toTarget:mySQLConnection withObject:nil];
-
[self endTask];
[taskPool drain];
}
diff --git a/Source/TableDump.m b/Source/TableDump.m
index 50d380f0..8b6b87bc 100644
--- a/Source/TableDump.m
+++ b/Source/TableDump.m
@@ -158,7 +158,7 @@
NSString *contextInfo;
NSSavePanel *savePanel = [NSSavePanel savePanel];
[savePanel setAllowsOtherFileTypes:YES];
- [savePanel setExtensionHidden:NO];
+ [savePanel setCanSelectHiddenExtension:YES];
NSString *currentDate = [[NSDate date] descriptionWithCalendarFormat:@"%Y-%m-%d" timeZone:nil locale:nil];
switch ( tag ) {
@@ -306,7 +306,7 @@
if ( [[NSFileManager defaultManager] fileExistsAtPath:exportFile] ) {
if ( ![[NSFileManager defaultManager] isWritableFileAtPath:exportFile]
|| !(fileHandle = [NSFileHandle fileHandleForWritingAtPath:exportFile]) ) {
- SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil,
+ SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil,
NSLocalizedString(@"Couldn't replace the file. Be sure that you have the necessary privileges.", @"message of panel when file cannot be replaced"));
[pool release];
return;
@@ -318,7 +318,7 @@
// Otherwise attempt to create a file
} else {
if ( ![[NSFileManager defaultManager] createFileAtPath:exportFile contents:[NSData data] attributes:nil] ) {
- SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil,
+ SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil,
NSLocalizedString(@"Couldn't write to file. Be sure that you have the necessary privileges.", @"message of panel when file cannot be written"));
[pool release];
return;
@@ -328,7 +328,7 @@
fileHandle = [NSFileHandle fileHandleForWritingAtPath:exportFile];
if ( !fileHandle ) {
[[NSFileManager defaultManager] removeFileAtPath:exportFile handler:nil];
- SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil,
+ SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil,
NSLocalizedString(@"Couldn't write to file. Be sure that you have the necessary privileges.", @"message of panel when file cannot be written"));
[pool release];
return;
@@ -458,7 +458,7 @@
// Display error message on problems
if ( !progressCancelled && !success ) {
- SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil,
+ SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil,
NSLocalizedString(@"Couldn't write to file. Be sure that you have the necessary privileges.", @"message of panel when file cannot be written"));
}
@@ -561,7 +561,7 @@
if (!sqlFileHandle) {
SPBeginAlertSheet(NSLocalizedString(@"Import Error title", @"Import Error"),
NSLocalizedString(@"OK button label", @"OK button"),
- nil, nil, tableWindow, self, nil, nil, nil,
+ nil, nil, tableWindow, self, nil, nil,
NSLocalizedString(@"SQL file open error", @"The SQL file you selected could not be found or read."));
return;
}
@@ -602,7 +602,7 @@
[self closeAndStopProgressSheet];
SPBeginAlertSheet(NSLocalizedString(@"SQL read error title", @"File read error"),
NSLocalizedString(@"OK", @"OK button"),
- nil, nil, tableWindow, self, nil, nil, nil,
+ nil, nil, tableWindow, self, nil, nil,
[NSString stringWithFormat:NSLocalizedString(@"SQL read error", @"An error occurred when reading the file.\n\nOnly %ld queries were executed.\n\n(%@)"), (long)queriesPerformed, [exception reason]]);
[sqlParser release];
[sqlDataBuffer release];
@@ -650,7 +650,7 @@
[self closeAndStopProgressSheet];
SPBeginAlertSheet(NSLocalizedString(@"SQL read error title", @"File read error"),
NSLocalizedString(@"OK", @"OK button"),
- nil, nil, tableWindow, self, nil, nil, nil,
+ nil, nil, tableWindow, self, nil, nil,
[NSString stringWithFormat:NSLocalizedString(@"SQL encoding read error", @"An error occurred when reading the file, as it could not be read in either UTF-8 or %@.\n\nOnly %ld queries were executed."), [[tableDocumentInstance connectionEncoding] UTF8String], (long)queriesPerformed]);
[sqlParser release];
[sqlDataBuffer release];
@@ -803,7 +803,7 @@
if (!csvFileHandle) {
SPBeginAlertSheet(NSLocalizedString(@"Import Error title", @"Import Error"),
NSLocalizedString(@"OK button label", @"OK button"),
- nil, nil, tableWindow, self, nil, nil, nil,
+ nil, nil, tableWindow, self, nil, nil,
NSLocalizedString(@"CSV file open error", @"The CSV file you selected could not be found or read."));
return;
}
@@ -861,7 +861,7 @@
[self closeAndStopProgressSheet];
SPBeginAlertSheet(NSLocalizedString(@"CSV read error title", @"File read error"),
NSLocalizedString(@"OK", @"OK button"),
- nil, nil, tableWindow, self, nil, nil, nil,
+ nil, nil, tableWindow, self, nil, nil,
[NSString stringWithFormat:NSLocalizedString(@"CSV read error", @"An error occurred when reading the file.\n\nOnly %ld rows were imported.\n\n(%@)"), (long)rowsImported, [exception reason]]);
[csvParser release];
[csvDataBuffer release];
@@ -908,7 +908,7 @@
[self closeAndStopProgressSheet];
SPBeginAlertSheet(NSLocalizedString(@"CSV read error title", @"File read error"),
NSLocalizedString(@"OK", @"OK button"),
- nil, nil, tableWindow, self, nil, nil, nil,
+ nil, nil, tableWindow, self, nil, nil,
[NSString stringWithFormat:NSLocalizedString(@"CSV encoding read error", @"An error occurred when reading the file, as it could not be read using %@.\n\nOnly %ld rows were imported."), [[tableDocumentInstance connectionEncoding] UTF8String], (long)rowsImported]);
[csvParser release];
[csvDataBuffer release];
@@ -1244,7 +1244,7 @@
NSLocalizedString(@"OK", @"OK button"),
nil, nil,
tableWindow, self,
- nil, nil, nil,
+ nil, nil,
NSLocalizedString(@"Could not parse file as CSV", @"Error when we can't parse/split file as CSV")
);
return FALSE;
@@ -1257,7 +1257,7 @@
NSLocalizedString(@"OK", @"OK button"),
nil, nil,
tableWindow, self,
- nil, nil, nil,
+ nil, nil,
NSLocalizedString(@"The CSV was read as containing more than 512 columns, more than the maximum columns permitted for speed reasons by Sequel Pro.\n\nThis usually happens due to errors reading the CSV; please double-check the CSV to be imported and the line endings and escape characters at the bottom of the CSV selection dialog.", @"Error when CSV appears to have too many columns to import, probably due to line ending mismatch")
);
return FALSE;
@@ -1271,7 +1271,7 @@
NSLocalizedString(@"OK", @"OK button"),
nil, nil,
tableWindow, self,
- nil, nil, nil,
+ nil, nil,
NSLocalizedString(@"Can't import CSV data into a database without any tables!", @"error text when trying to import csv data, but we have no tables in the db")
);
return FALSE;
@@ -1785,52 +1785,52 @@
// Release the result set
[streamingResult release];
+ }
+
+ // Export triggers, if any
+ queryResult = [mySQLConnection queryString:[NSString stringWithFormat:@"/*!50003 SHOW TRIGGERS WHERE `Table` = %@ */;",
+ [tableName tickQuotedString]]];
+ [queryResult setReturnDataAsStrings:YES];
+ if ( [queryResult numOfRows] ) {
+ [metaString setString:@"\n"];
+ [metaString appendString:@"DELIMITER ;;\n"];
- queryResult = [mySQLConnection queryString:[NSString stringWithFormat:@"/*!50003 SHOW TRIGGERS WHERE `Table` = %@ */;",
- [tableName tickQuotedString]]];
- [queryResult setReturnDataAsStrings:YES];
- if ( [queryResult numOfRows] ) {
- [metaString setString:@"\n"];
- [metaString appendString:@"DELIMITER ;;\n"];
+ for (int s=0; s<[queryResult numOfRows]; s++) {
+ NSDictionary *triggers = [[NSDictionary alloc] initWithDictionary:[queryResult fetchRowAsDictionary]];
- for (int s=0; s<[queryResult numOfRows]; s++) {
- NSDictionary *triggers = [[NSDictionary alloc] initWithDictionary:[queryResult fetchRowAsDictionary]];
-
- //Definer is user@host but we need to escape it to `user`@`host`
- NSArray *triggersDefiner = [[triggers objectForKey:@"Definer"] componentsSeparatedByString:@"@"];
- NSString *escapedDefiner = [NSString stringWithFormat:@"%@@%@",
- [[triggersDefiner objectAtIndex:0] backtickQuotedString],
- [[triggersDefiner objectAtIndex:1] backtickQuotedString]
- ];
-
- [metaString appendString:[NSString stringWithFormat:@"/*!50003 SET SESSION SQL_MODE=\"%@\" */;;\n",
- [triggers objectForKey:@"sql_mode"]]];
- [metaString appendString:@"/*!50003 CREATE */ "];
- [metaString appendString:[NSString stringWithFormat:@"/*!50017 DEFINER=%@ */ ",
- escapedDefiner]];
- [metaString appendString:[NSString stringWithFormat:@"/*!50003 TRIGGER %@ %@ %@ ON %@ FOR EACH ROW %@ */;;\n",
- [[triggers objectForKey:@"Trigger"] backtickQuotedString],
- [triggers objectForKey:@"Timing"],
- [triggers objectForKey:@"Event"],
- [[triggers objectForKey:@"Table"] backtickQuotedString],
- [triggers objectForKey:@"Statement"]
- ]];
- [triggers release];
- }
+ //Definer is user@host but we need to escape it to `user`@`host`
+ NSArray *triggersDefiner = [[triggers objectForKey:@"Definer"] componentsSeparatedByString:@"@"];
+ NSString *escapedDefiner = [NSString stringWithFormat:@"%@@%@",
+ [[triggersDefiner objectAtIndex:0] backtickQuotedString],
+ [[triggersDefiner objectAtIndex:1] backtickQuotedString]
+ ];
- [metaString appendString:@"DELIMITER ;\n"];
- [metaString appendString:@"/*!50003 SET SESSION SQL_MODE=@OLD_SQL_MODE */;\n"];
- [fileHandle writeData:[metaString dataUsingEncoding:NSUTF8StringEncoding]];
+ [metaString appendString:[NSString stringWithFormat:@"/*!50003 SET SESSION SQL_MODE=\"%@\" */;;\n",
+ [triggers objectForKey:@"sql_mode"]]];
+ [metaString appendString:@"/*!50003 CREATE */ "];
+ [metaString appendString:[NSString stringWithFormat:@"/*!50017 DEFINER=%@ */ ",
+ escapedDefiner]];
+ [metaString appendString:[NSString stringWithFormat:@"/*!50003 TRIGGER %@ %@ %@ ON %@ FOR EACH ROW %@ */;;\n",
+ [[triggers objectForKey:@"Trigger"] backtickQuotedString],
+ [triggers objectForKey:@"Timing"],
+ [triggers objectForKey:@"Event"],
+ [[triggers objectForKey:@"Table"] backtickQuotedString],
+ [triggers objectForKey:@"Statement"]
+ ]];
+ [triggers release];
}
- if ([mySQLConnection queryErrored]) {
- [errors appendString:[NSString stringWithFormat:@"%@\n", [mySQLConnection getLastErrorMessage]]];
- if ( [addErrorsSwitch state] == NSOnState ) {
- [fileHandle writeData:[[NSString stringWithFormat:@"# Error: %@\n", [mySQLConnection getLastErrorMessage]]
- dataUsingEncoding:NSUTF8StringEncoding]];
- }
+ [metaString appendString:@"DELIMITER ;\n"];
+ [metaString appendString:@"/*!50003 SET SESSION SQL_MODE=@OLD_SQL_MODE */;\n"];
+ [fileHandle writeData:[metaString dataUsingEncoding:NSUTF8StringEncoding]];
+ }
+
+ if ([mySQLConnection queryErrored]) {
+ [errors appendString:[NSString stringWithFormat:@"%@\n", [mySQLConnection getLastErrorMessage]]];
+ if ( [addErrorsSwitch state] == NSOnState ) {
+ [fileHandle writeData:[[NSString stringWithFormat:@"# Error: %@\n", [mySQLConnection getLastErrorMessage]]
+ dataUsingEncoding:NSUTF8StringEncoding]];
}
-
}
// Add an additional separator between tables
diff --git a/Source/TableSource.m b/Source/TableSource.m
index 27314b03..dc3bee22 100644
--- a/Source/TableSource.m
+++ b/Source/TableSource.m
@@ -67,7 +67,7 @@
}
// Send the query started/working notification
- [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:tableDocumentInstance];
+ [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryWillBePerformed" object:tableDocumentInstance];
// Retrieve the column information for this table.
// TODO: update this and indexes to use TableData at some point - tiny bit more parsing required...
@@ -75,12 +75,12 @@
// If an error occurred, reset the interface and abort
if ([mySQLConnection queryErrored]) {
- [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance];
+ [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance];
[[self onMainThread] setTableDetails:nil];
if ([mySQLConnection isConnected]) {
SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"),
- nil, nil, [NSApp mainWindow], self, nil, nil, nil,
+ nil, nil, [NSApp mainWindow], self, nil, nil,
[NSString stringWithFormat:NSLocalizedString(@"An error occurred while retrieving information.\nMySQL said: %@", @"message of panel when retrieving information failed"),
[mySQLConnection getLastErrorMessage]]);
}
@@ -97,12 +97,12 @@
// If an error occurred, reset the interface and abort
if ([mySQLConnection queryErrored]) {
- [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance];
+ [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance];
[[self onMainThread] setTableDetails:nil];
if ([mySQLConnection isConnected]) {
SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"),
- nil, nil, [NSApp mainWindow], self, nil, nil, nil,
+ nil, nil, [NSApp mainWindow], self, nil, nil,
[NSString stringWithFormat:NSLocalizedString(@"An error occurred while retrieving information.\nMySQL said: %@", @"message of panel when retrieving information failed"),
[mySQLConnection getLastErrorMessage]]);
}
@@ -192,7 +192,7 @@
[[self onMainThread] setTableDetails:tableDetails];
// Send the query finished/work complete notification
- [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance];
+ [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance];
}
@@ -400,7 +400,32 @@
[[buttons objectAtIndex:0] setKeyEquivalentModifierMask:NSCommandKeyMask];
[[buttons objectAtIndex:1] setKeyEquivalent:@"\r"];
- [alert beginSheetModalForWindow:tableWindow modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:(hasForeignKey) ? @"removeFieldAndForeignKey" : @"removeField"];
+ [alert beginSheetModalForWindow:tableWindow modalDelegate:self didEndSelector:@selector(removeFieldSheetDidEnd:returnCode:contextInfo:) contextInfo:(hasForeignKey) ? @"removeFieldAndForeignKey" : @"removeField"];
+}
+
+/**
+ * Process the remove field sheet closing, performing the delete if the user
+ * confirmed the action.
+ */
+- (void)removeFieldSheetDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo
+{
+ // Order out current sheet to suppress overlapping of sheets
+ [[alert window] orderOut:nil];
+
+ if (returnCode == NSAlertDefaultReturn) {
+ [tableDocumentInstance startTaskWithDescription:NSLocalizedString(@"Removing field...", @"removing field task status message")];
+
+ NSNumber *removeKey = [NSNumber numberWithBool:[contextInfo hasSuffix:@"AndForeignKey"]];
+
+ if ([NSThread isMainThread]) {
+ [NSThread detachNewThreadSelector:@selector(_removeFieldAndForeignKey:) toTarget:self withObject:removeKey];
+
+ [tableDocumentInstance enableTaskCancellationWithTitle:NSLocalizedString(@"Cancel", @"cancel button") callbackObject:self callbackFunction:NULL];
+ }
+ else {
+ [self _removeFieldAndForeignKey:removeKey];
+ }
+ }
}
/**
@@ -452,7 +477,32 @@
[[buttons objectAtIndex:0] setKeyEquivalentModifierMask:NSCommandKeyMask];
[[buttons objectAtIndex:1] setKeyEquivalent:@"\r"];
- [alert beginSheetModalForWindow:tableWindow modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:(hasForeignKey) ? @"removeIndexAndForeignKey" : @"removeIndex"];
+ [alert beginSheetModalForWindow:tableWindow modalDelegate:self didEndSelector:@selector(removeIndexSheetDidEnd:returnCode:contextInfo:) contextInfo:(hasForeignKey) ? @"removeIndexAndForeignKey" : @"removeIndex"];
+}
+
+/**
+ * Process the remove index sheet closing, performing the delete if the user
+ * confirmed the action.
+ */
+- (void)removeIndexSheetDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo
+{
+ // Order out current sheet to suppress overlapping of sheets
+ [[alert window] orderOut:nil];
+
+ if (returnCode == NSAlertDefaultReturn) {
+ [tableDocumentInstance startTaskWithDescription:NSLocalizedString(@"Removing index...", @"removing index task status message")];
+
+ NSNumber *removeKey = [NSNumber numberWithBool:[contextInfo hasSuffix:@"AndForeignKey"]];
+
+ if ([NSThread isMainThread]) {
+ [NSThread detachNewThreadSelector:@selector(_removeIndexAndForeignKey:) toTarget:self withObject:removeKey];
+
+ [tableDocumentInstance enableTaskCancellationWithTitle:NSLocalizedString(@"Cancel", @"cancel button") callbackObject:self callbackFunction:NULL];
+ }
+ else {
+ [self _removeIndexAndForeignKey:removeKey];
+ }
+ }
}
- (IBAction)resetAutoIncrement:(id)sender
@@ -468,7 +518,7 @@
[NSApp beginSheet:resetAutoIncrementSheet
modalForWindow:tableWindow
modalDelegate:self
- didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
+ didEndSelector:@selector(resetAutoincrementSheetDidEnd:returnCode:contextInfo:)
contextInfo:@"resetAutoIncrement"];
[resetAutoIncrementValue setStringValue:@"1"];
@@ -479,6 +529,20 @@
}
+/**
+ * Process the autoincrement sheet closing, resetting if the user
+ * confirmed the action.
+ */
+- (void)resetAutoincrementSheetDidEnd:(NSWindow *)theSheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo
+{
+ // Order out current sheet to suppress overlapping of sheets
+ [theSheet orderOut:nil];
+
+ if (returnCode == NSAlertDefaultReturn) {
+ [self setAutoIncrementTo:[[resetAutoIncrementValue stringValue] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]];
+ }
+}
+
#pragma mark -
#pragma mark Index sheet methods
@@ -515,11 +579,32 @@
[NSApp beginSheet:indexSheet
modalForWindow:tableWindow
modalDelegate:self
- didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
+ didEndSelector:@selector(addIndexSheetDidEnd:returnCode:contextInfo:)
contextInfo:@"addIndex"];
}
/**
+ * Process the new index sheet closing, adding the index if appropriate
+ */
+- (void)addIndexSheetDidEnd:(NSWindow *)theSheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo
+{
+ [theSheet orderOut:nil];
+
+ if (returnCode == NSOKButton) {
+ [tableDocumentInstance startTaskWithDescription:NSLocalizedString(@"Adding index...", @"adding index task status message")];
+
+ if ([NSThread isMainThread]) {
+ [NSThread detachNewThreadSelector:@selector(_addIndex) toTarget:self withObject:nil];
+
+ [tableDocumentInstance enableTaskCancellationWithTitle:NSLocalizedString(@"Cancel", @"cancel button") callbackObject:self callbackFunction:NULL];
+ }
+ else {
+ [self _addIndex];
+ }
+ }
+}
+
+/**
* Closes the current sheet and stops the modal session
*/
- (IBAction)closeSheet:(id)sender
@@ -606,7 +691,7 @@ closes the keySheet
if ([mySQLConnection queryErrored]) {
SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"),
NSLocalizedString(@"OK", @"OK button"),
- nil, nil, [NSApp mainWindow], nil, nil, nil, nil,
+ nil, nil, [NSApp mainWindow], nil, nil, nil,
[NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to reset AUTO_INCREMENT of table '%@'.\n\nMySQL said: %@", @"error resetting auto_increment informative message"),
selTable, [mySQLConnection getLastErrorMessage]]);
}
@@ -732,7 +817,11 @@ closes the keySheet
([[theRow objectForKey:@"Type"] isEqualToString:@"datetime"]))
{
// If the old row and new row dictionaries are equal then the user didn't actually change anything so don't continue
- if ([oldRow isEqualToDictionary:theRow]) return YES;
+ if ([oldRow isEqualToDictionary:theRow]) {
+ isEditingRow = NO;
+ currentlyEditingRow = -1;
+ return YES;
+ }
queryString = [NSMutableString stringWithFormat:@"ALTER TABLE %@ CHANGE %@ %@ %@",
[selectedTable backtickQuotedString],
@@ -742,7 +831,11 @@ closes the keySheet
}
else {
// If the old row and new row dictionaries are equal then the user didn't actually change anything so don't continue
- if ([oldRow isEqualToDictionary:theRow]) return YES;
+ if ([oldRow isEqualToDictionary:theRow]) {
+ isEditingRow = NO;
+ currentlyEditingRow = -1;
+ return YES;
+ }
queryString = [NSMutableString stringWithFormat:@"ALTER TABLE %@ CHANGE %@ %@ %@(%@)",
[selectedTable backtickQuotedString],
@@ -894,7 +987,7 @@ closes the keySheet
if([mySQLConnection getLastErrorID] == 1146) { // If the current table doesn't exist anymore
SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"),
NSLocalizedString(@"OK", @"OK button"),
- nil, nil, tableWindow, self, @selector(sheetDidEnd:returnCode:contextInfo:), nil, nil,
+ nil, nil, tableWindow, self, nil, nil,
[NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to alter table '%@'.\n\nMySQL said: %@", @"error while trying to alter table message"),
selectedTable, [mySQLConnection getLastErrorMessage]]);
@@ -918,14 +1011,14 @@ closes the keySheet
if (isEditingNewRow) {
SPBeginAlertSheet(NSLocalizedString(@"Error adding field", @"error adding field message"),
NSLocalizedString(@"Edit row", @"Edit row button"),
- NSLocalizedString(@"Discard changes", @"discard changes button"), nil, tableWindow, self, @selector(sheetDidEnd:returnCode:contextInfo:), nil, @"addrow",
+ NSLocalizedString(@"Discard changes", @"discard changes button"), nil, tableWindow, self, @selector(addRowErrorSheetDidEnd:returnCode:contextInfo:), nil,
[NSString stringWithFormat:NSLocalizedString(@"An error occurred when trying to add the field '%@'.\n\nMySQL said: %@", @"error adding field informative message"),
[theRow objectForKey:@"Field"], [mySQLConnection getLastErrorMessage]]);
}
else {
SPBeginAlertSheet(NSLocalizedString(@"Error changing field", @"error changing field message"),
NSLocalizedString(@"Edit row", @"Edit row button"),
- NSLocalizedString(@"Discard changes", @"discard changes button"), nil, tableWindow, self, @selector(sheetDidEnd:returnCode:contextInfo:), nil, @"addrow",
+ NSLocalizedString(@"Discard changes", @"discard changes button"), nil, tableWindow, self, @selector(addRowErrorSheetDidEnd:returnCode:contextInfo:), nil,
[NSString stringWithFormat:NSLocalizedString(@"An error occurred when trying to change the field '%@'.\n\nMySQL said: %@", @"error changing field informative message"),
[theRow objectForKey:@"Field"], [mySQLConnection getLastErrorMessage]]);
}
@@ -935,6 +1028,44 @@ closes the keySheet
}
/**
+ * Perform the action requested in the Add Row error sheet.
+ */
+- (void)addRowErrorSheetDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo
+{
+
+ // Order out current sheet to suppress overlapping of sheets
+ [[alert window] orderOut:nil];
+
+ alertSheetOpened = NO;
+
+ // Remain in edit mode - reselect the row and resume editing
+ if (returnCode == NSAlertDefaultReturn) {
+
+ // Problem: reentering edit mode for first cell doesn't function
+ [tableSourceView selectRowIndexes:[NSIndexSet indexSetWithIndex:currentlyEditingRow] byExtendingSelection:NO];
+ [tableSourceView performSelector:@selector(keyDown:) withObject:[NSEvent keyEventWithType:NSKeyDown location:NSMakePoint(0,0) modifierFlags:0 timestamp:0 windowNumber:[tableWindow windowNumber] context:[NSGraphicsContext currentContext] characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:0x24] afterDelay:0.0];
+ }
+
+ // Discard changes and cancel editing
+ else {
+ if (!isEditingNewRow) {
+ [tableFields replaceObjectAtIndex:currentlyEditingRow
+ withObject:[NSMutableDictionary dictionaryWithDictionary:oldRow]];
+ isEditingRow = NO;
+ }
+ else {
+ [tableFields removeObjectAtIndex:currentlyEditingRow];
+ isEditingRow = NO;
+ isEditingNewRow = NO;
+ }
+
+ currentlyEditingRow = -1;
+ }
+
+ [tableSourceView reloadData];
+}
+
+/**
* A method to show an error sheet after a short delay, so that it can
* be called from within an endSheet selector. This should be called on
* the main thread.
@@ -953,7 +1084,7 @@ closes the keySheet
// Display the error sheet
SPBeginAlertSheet([errorDictionary objectForKey:@"title"], NSLocalizedString(@"OK", @"OK button"),
- nil, nil, tableWindow, self, nil, nil, nil,
+ nil, nil, tableWindow, self, nil, nil,
[errorDictionary objectForKey:@"message"]);
}
@@ -1022,104 +1153,17 @@ closes the keySheet
/**
* Called whenever a sheet is dismissed.
- *
- * if contextInfo == addrow: remain in edit-mode if user hits OK, otherwise cancel editing
- * if contextInfo == removefield: removes row from mysql-db if user hits ok
- * if contextInfo == removeindex: removes index from mysql-db if user hits ok
- * if contextInfo == addIndex: adds and index to the mysql-db if user hits ok
- * if contextInfo == cannotremovefield: do nothing
*/
- (void)sheetDidEnd:(id)sheet returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo
{
+
// Order out current sheet to suppress overlapping of sheets
if ([sheet respondsToSelector:@selector(orderOut:)])
[sheet orderOut:nil];
else if ([sheet respondsToSelector:@selector(window)])
[[sheet window] orderOut:nil];
-
- if ([contextInfo isEqualToString:@"addrow"]) {
-
- alertSheetOpened = NO;
-
- if (returnCode == NSAlertDefaultReturn) {
-
- // Problem: reentering edit mode for first cell doesn't function
- [tableSourceView selectRowIndexes:[NSIndexSet indexSetWithIndex:currentlyEditingRow] byExtendingSelection:NO];
- [tableSourceView performSelector:@selector(keyDown:) withObject:[NSEvent keyEventWithType:NSKeyDown location:NSMakePoint(0,0) modifierFlags:0 timestamp:0 windowNumber:[tableWindow windowNumber] context:[NSGraphicsContext currentContext] characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:0x24] afterDelay:0.0];
- }
- else {
- if (!isEditingNewRow) {
- [tableFields replaceObjectAtIndex:currentlyEditingRow
- withObject:[NSMutableDictionary dictionaryWithDictionary:oldRow]];
- isEditingRow = NO;
- }
- else {
- [tableFields removeObjectAtIndex:currentlyEditingRow];
- isEditingRow = NO;
- isEditingNewRow = NO;
- }
-
- currentlyEditingRow = -1;
- }
-
- [tableSourceView reloadData];
- }
- else if ([contextInfo isEqualToString:@"removeField"] || [contextInfo isEqualToString:@"removeFieldAndForeignKey"]) {
- if (returnCode == NSAlertDefaultReturn) {
- [tableDocumentInstance startTaskWithDescription:NSLocalizedString(@"Removing field...", @"removing field task status message")];
-
- NSNumber *removeKey = [NSNumber numberWithBool:[contextInfo hasSuffix:@"AndForeignKey"]];
-
- if ([NSThread isMainThread]) {
- [NSThread detachNewThreadSelector:@selector(_removeFieldAndForeignKey:) toTarget:self withObject:removeKey];
-
- [tableDocumentInstance enableTaskCancellationWithTitle:NSLocalizedString(@"Cancel", @"cancel button") callbackObject:self callbackFunction:NULL];
- }
- else {
- [self _removeFieldAndForeignKey:removeKey];
- }
- }
- }
- else if ([contextInfo isEqualToString:@"addIndex"]) {
- if (returnCode == NSOKButton) {
- [tableDocumentInstance startTaskWithDescription:NSLocalizedString(@"Adding index...", @"adding index task status message")];
-
- if ([NSThread isMainThread]) {
- [NSThread detachNewThreadSelector:@selector(_addIndex) toTarget:self withObject:nil];
-
- [tableDocumentInstance enableTaskCancellationWithTitle:NSLocalizedString(@"Cancel", @"cancel button") callbackObject:self callbackFunction:NULL];
- }
- else {
- [self _addIndex];
- }
- }
- }
- else if ([contextInfo isEqualToString:@"removeIndex"] || [contextInfo isEqualToString:@"removeIndexAndForeignKey"]) {
- if (returnCode == NSAlertDefaultReturn) {
- [tableDocumentInstance startTaskWithDescription:NSLocalizedString(@"Removing index...", @"removing index task status message")];
-
- NSNumber *removeKey = [NSNumber numberWithBool:[contextInfo hasSuffix:@"AndForeignKey"]];
-
- if ([NSThread isMainThread]) {
- [NSThread detachNewThreadSelector:@selector(_removeIndexAndForeignKey:) toTarget:self withObject:removeKey];
-
- [tableDocumentInstance enableTaskCancellationWithTitle:NSLocalizedString(@"Cancel", @"cancel button") callbackObject:self callbackFunction:NULL];
- }
- else {
- [self _removeIndexAndForeignKey:removeKey];
- }
- }
- }
- else if ([contextInfo isEqualToString:@"cannotremovefield"]) {
- ;
- }
- else if ([contextInfo isEqualToString:@"resetAutoIncrement"]) {
- if (returnCode == NSAlertDefaultReturn) {
- [self setAutoIncrementTo:[[resetAutoIncrementValue stringValue] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]];
- }
- }
- else
- alertSheetOpened = NO;
+
+ alertSheetOpened = NO;
}
#pragma mark -
@@ -1445,14 +1489,18 @@ would result in a position change.
[queryString appendString:[[originalRow objectForKey:@"Extra"] uppercaseString]];
}
+ BOOL isTimestampType = [[originalRow objectForKey:@"Type"] isEqualToString:@"timestamp"];
+
// Add the default value
if ([[originalRow objectForKey:@"Default"] isEqualToString:[prefs objectForKey:SPNullValue]]) {
if ([[originalRow objectForKey:@"Null"] integerValue] == 1) {
- [queryString appendString:@" DEFAULT NULL"];
+ [queryString appendString:(isTimestampType) ? @" NULL DEFAULT NULL" : @" DEFAULT NULL"];
}
- } else if ( [[originalRow objectForKey:@"Type"] isEqualToString:@"timestamp"] && ([[[originalRow objectForKey:@"Default"] uppercaseString] isEqualToString:@"CURRENT_TIMESTAMP"]) ) {
- [queryString appendString:@" DEFAULT CURRENT_TIMESTAMP"];
- } else {
+ }
+ else if (isTimestampType && ([[[originalRow objectForKey:@"Default"] uppercaseString] isEqualToString:@"CURRENT_TIMESTAMP"]) ) {
+ [queryString appendString:@" DEFAULT CURRENT_TIMESTAMP"];
+ }
+ else {
[queryString appendString:[NSString stringWithFormat:@" DEFAULT '%@'", [mySQLConnection prepareString:[originalRow objectForKey:@"Default"]]]];
}
@@ -1480,8 +1528,8 @@ would result in a position change.
// Run the query; report any errors, or reload the table on success
[mySQLConnection queryString:queryString];
if ([mySQLConnection queryErrored]) {
- SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil,
- [NSString stringWithFormat:NSLocalizedString(@"Couldn't move field. MySQL said: %@", @"message of panel when field cannot be added in drag&drop operation"), [mySQLConnection getLastErrorMessage]]);
+ SPBeginAlertSheet(NSLocalizedString(@"Error moving field", @"error moving field message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil,
+ [NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to move the field.\n\nMySQL said: %@", @"error moving field informative message"), [mySQLConnection getLastErrorMessage]]);
} else {
[tableDataInstance resetAllData];
[tablesListInstance setStatusRequiresReload:YES];
@@ -1550,9 +1598,9 @@ would result in a position change.
row = [tableSourceView editedRow];
column = [tableSourceView editedColumn];
- if ( [textView methodForSelector:command] == [textView methodForSelector:@selector(insertNewline:)] ||
- [textView methodForSelector:command] == [textView methodForSelector:@selector(insertTab:)] ) //trap enter and tab
- {
+ // Trap the tab key, selecting the next item in the line
+ if ( [textView methodForSelector:command] == [textView methodForSelector:@selector(insertTab:)] )
+ {
//save current line
[[control window] makeFirstResponder:control];
if ( column == 9 ) {
@@ -1574,9 +1622,19 @@ would result in a position change.
}
return TRUE;
- } else if ( [[control window] methodForSelector:command] == [[control window] methodForSelector:@selector(_cancelKey:)] ||
- [textView methodForSelector:command] == [textView methodForSelector:@selector(complete:)] ) {
- //abort editing
+ // Trap the enter key, triggering a save
+ }
+ else if ( [textView methodForSelector:command] == [textView methodForSelector:@selector(insertNewline:)] )
+ {
+ [[control window] makeFirstResponder:control];
+ [self addRowToDB];
+ return TRUE;
+
+ // Trap escape, aborting the edit and reverting the row
+ }
+ else if ( [[control window] methodForSelector:command] == [[control window] methodForSelector:@selector(_cancelKey:)] ||
+ [textView methodForSelector:command] == [textView methodForSelector:@selector(complete:)] )
+ {
[control abortEditing];
if ( isEditingRow && !isEditingNewRow ) {
isEditingRow = NO;
@@ -1737,7 +1795,7 @@ would result in a position change.
// Check for errors, but only if the query wasn't cancelled
if ([mySQLConnection queryErrored] && ![mySQLConnection queryCancelled]) {
- SPBeginAlertSheet(NSLocalizedString(@"Unable to add index", @"add index error message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil,
+ SPBeginAlertSheet(NSLocalizedString(@"Unable to add index", @"add index error message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil,
[NSString stringWithFormat:NSLocalizedString(@"An error occured while trying to add the index.\n\nMySQL said: %@", @"add index error informative message"), [mySQLConnection getLastErrorMessage]]);
}
else {
diff --git a/Source/TablesList.h b/Source/TablesList.h
index f6741369..71a857bf 100644
--- a/Source/TablesList.h
+++ b/Source/TablesList.h
@@ -48,6 +48,7 @@
IBOutlet id extendedTableInfoInstance;
IBOutlet id databaseDataInstance;
IBOutlet id tableInfoInstance;
+ IBOutlet id tableTriggersInstance;
IBOutlet SPHistoryController *spHistoryControllerInstance;
IBOutlet id tableWindow;
@@ -102,7 +103,7 @@
BOOL tableListIsSelectable;
BOOL tableListContainsViews;
- BOOL structureLoaded, contentLoaded, statusLoaded, alertSheetOpened;
+ BOOL structureLoaded, contentLoaded, statusLoaded, triggersLoaded, alertSheetOpened;
}
// IBAction methods
diff --git a/Source/TablesList.m b/Source/TablesList.m
index b9e8270a..c69a1ffe 100644
--- a/Source/TablesList.m
+++ b/Source/TablesList.m
@@ -90,7 +90,7 @@
if ([tableDocumentInstance database]) {
// Notify listeners that a query has started
- [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:tableDocumentInstance];
+ [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryWillBePerformed" object:tableDocumentInstance];
// Select the table list for the current database. On MySQL versions after 5 this will include
// views; on MySQL versions >= 5.0.02 select the "full" list to also select the table type column.
@@ -208,7 +208,7 @@
}
*/
// Notify listeners that the query has finished
- [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance];
+ [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance];
}
// Add the table headers even if no tables were found
@@ -692,40 +692,35 @@
// Restore view states as appropriate
[spHistoryControllerInstance restoreViewStates];
+ structureLoaded = NO;
+ contentLoaded = NO;
+ statusLoaded = NO;
+ triggersLoaded = NO;
if( selectedTableType == SPTableTypeView || selectedTableType == SPTableTypeTable) {
if ( [tabView indexOfTabViewItem:[tabView selectedTabViewItem]] == 0 ) {
[tableSourceInstance loadTable:selectedTableName];
structureLoaded = YES;
- contentLoaded = NO;
- statusLoaded = NO;
} else if ( [tabView indexOfTabViewItem:[tabView selectedTabViewItem]] == 1 ) {
if(tableEncoding == nil) {
[tableContentInstance loadTable:nil];
} else {
[tableContentInstance loadTable:selectedTableName];
}
- structureLoaded = NO;
contentLoaded = YES;
- statusLoaded = NO;
} else if ( [tabView indexOfTabViewItem:[tabView selectedTabViewItem]] == 3 ) {
- [extendedTableInfoInstance performSelectorOnMainThread:@selector(loadTable:) withObject:selectedTableName waitUntilDone:YES];
- structureLoaded = NO;
- contentLoaded = NO;
+ [[extendedTableInfoInstance onMainThread] loadTable:selectedTableName];
statusLoaded = YES;
- } else {
- structureLoaded = NO;
- contentLoaded = NO;
- statusLoaded = NO;
+ } else if ( [tabView indexOfTabViewItem:[tabView selectedTabViewItem]] == 5 ) {
+ [[tableTriggersInstance onMainThread] loadTriggers];
+ triggersLoaded = YES;
}
} else {
// if we are not looking at a table or view, clear these
[tableSourceInstance loadTable:nil];
[tableContentInstance loadTable:nil];
- [extendedTableInfoInstance performSelectorOnMainThread:@selector(loadTable:) withObject:nil waitUntilDone:YES];
- structureLoaded = NO;
- contentLoaded = NO;
- statusLoaded = NO;
+ [[extendedTableInfoInstance onMainThread] loadTable:nil];
+ [[tableTriggersInstance onMainThread] loadTriggers];
}
// Update the "Show Create Syntax" window if it's already opened
@@ -768,10 +763,12 @@
[tableSourceInstance loadTable:nil];
[tableContentInstance loadTable:nil];
[extendedTableInfoInstance loadTable:nil];
-
+ [tableTriggersInstance loadTriggers];
+
structureLoaded = NO;
contentLoaded = NO;
statusLoaded = NO;
+ triggersLoaded = NO;
// Set gear menu items Remove/Duplicate table/view according to the table types
// if at least one item is selected
@@ -1334,7 +1331,7 @@
// Table has invalid name
// Since we trimmed whitespace and checked for empty string, this means there is already a table with that name
SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self,
- @selector(sheetDidEnd:returnCode:contextInfo:), nil, nil,
+ @selector(sheetDidEnd:returnCode:contextInfo:), nil,
[NSString stringWithFormat: NSLocalizedString(@"The name '%@' is already used.", @"message when trying to rename a table/view/proc/etc to an already used name"), newTableName]);
return;
}
@@ -1355,29 +1352,32 @@
// if the 'table' is a view or a table, reload the currently selected view
if (selectedTableType == SPTableTypeTable || selectedTableType == SPTableTypeView)
{
+ statusLoaded = NO;
+ structureLoaded = NO;
+ contentLoaded = NO;
+ triggersLoaded = NO;
switch ([tabView indexOfTabViewItem:[tabView selectedTabViewItem]]) {
case 0:
[tableSourceInstance loadTable:newTableName];
structureLoaded = YES;
- contentLoaded = statusLoaded = NO;
break;
case 1:
[tableContentInstance loadTable:newTableName];
contentLoaded = YES;
- structureLoaded = statusLoaded = NO;
break;
case 3:
[extendedTableInfoInstance loadTable:newTableName];
statusLoaded = YES;
- structureLoaded = contentLoaded = NO;
break;
- default:
- statusLoaded = structureLoaded = contentLoaded = NO;
+ case 5:
+ [tableTriggersInstance loadTriggers];
+ triggersLoaded = YES;
+ break;
}
}
}
@catch (NSException * myException) {
- SPBeginAlertSheet( NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil, [myException reason]);
+ SPBeginAlertSheet( NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, [myException reason]);
}
// Set window title to reflect the new table name
@@ -1557,8 +1557,9 @@
{
NSAutoreleasePool *tabLoadPool = [[NSAutoreleasePool alloc] init];
- if ( [tablesListView numberOfSelectedRows] == 1 &&
- ([self tableType] == SPTableTypeTable || [self tableType] == SPTableTypeView) ) {
+ if ([tablesListView numberOfSelectedRows] == 1
+ && ([self tableType] == SPTableTypeTable || [self tableType] == SPTableTypeView) )
+ {
if ( ([tabView indexOfTabViewItem:[tabView selectedTabViewItem]] == 0) && !structureLoaded ) {
[tableSourceInstance loadTable:selectedTableName];
@@ -1571,9 +1572,14 @@
}
if ( ([tabView indexOfTabViewItem:[tabView selectedTabViewItem]] == 3) && !statusLoaded ) {
- [extendedTableInfoInstance performSelectorOnMainThread:@selector(loadTable:) withObject:selectedTableName waitUntilDone:YES];
+ [[extendedTableInfoInstance onMainThread] loadTable:selectedTableName];
statusLoaded = YES;
}
+
+ if ( ([tabView indexOfTabViewItem:[tabView selectedTabViewItem]] == 5) && !triggersLoaded ) {
+ [[tableTriggersInstance onMainThread] loadTriggers];
+ triggersLoaded = YES;
+ }
}
else {
[tableSourceInstance loadTable:nil];
@@ -1802,6 +1808,7 @@
structureLoaded = NO;
contentLoaded = NO;
statusLoaded = NO;
+ triggersLoaded = NO;
isTableListFiltered = NO;
tableListIsSelectable = YES;
tableListContainsViews = NO;
@@ -1825,8 +1832,10 @@
[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")];
}
@@ -2084,7 +2093,7 @@
SPBeginAlertSheet(NSLocalizedString(@"Error adding new table", @"error adding new table message"),
NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self,
- @selector(sheetDidEnd:returnCode:contextInfo:), nil, @"addRow",
+ @selector(sheetDidEnd:returnCode:contextInfo:), @"addRow",
[NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to add the new table '%@'.\n\nMySQL said: %@", @"error adding new table informative message"), tableName, [mySQLConnection getLastErrorMessage]]);
[tablesListView reloadData];
@@ -2102,7 +2111,7 @@
NSString *tableType = @"";
if ([[copyTableNameField stringValue] isEqualToString:@""]) {
- SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil, NSLocalizedString(@"Table must have a name.", @"message of panel when no name is given for table"));
+ SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, NSLocalizedString(@"Table must have a name.", @"message of panel when no name is given for table"));
return;
}
@@ -2138,7 +2147,7 @@
if ( ![queryResult numOfRows] ) {
//error while getting table structure
- SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil,
+ SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil,
[NSString stringWithFormat:NSLocalizedString(@"Couldn't get create syntax.\nMySQL said: %@", @"message of panel when table information cannot be retrieved"), [mySQLConnection getLastErrorMessage]]);
} else {
@@ -2184,7 +2193,7 @@
// Check for errors, only displaying if the connection hasn't been terminated
if ([mySQLConnection queryErrored]) {
if ([mySQLConnection isConnected]) {
- SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil,
+ SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil,
[NSString stringWithFormat:NSLocalizedString(@"An error occured while retrieving the create syntax for '%@'.\nMySQL said: %@", @"message of panel when create syntax cannot be retrieved"), selectedTableName, [mySQLConnection getLastErrorMessage]]);
}
return;
@@ -2197,7 +2206,7 @@
[mySQLConnection queryString:[tableSyntax stringByReplacingOccurrencesOfRegex:[NSString stringWithFormat:@"(?<=%@ )(`[^`]+?`)", [tableType uppercaseString]] withString:[[copyTableNameField stringValue] backtickQuotedString]]];
if ([mySQLConnection queryErrored]) {
- SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil,
+ SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil,
[NSString stringWithFormat:NSLocalizedString(@"Couldn't duplicate '%@'.\nMySQL said: %@", @"message of panel when an item cannot be renamed"), [copyTableNameField stringValue], [mySQLConnection getLastErrorMessage]]);
}
@@ -2205,7 +2214,7 @@
if ([mySQLConnection queryErrored]) {
//error while creating new table
- SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil,
+ SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil,
[NSString stringWithFormat:NSLocalizedString(@"Couldn't create '%@'.\nMySQL said: %@", @"message of panel when table cannot be created"), [copyTableNameField stringValue], [mySQLConnection getLastErrorMessage]]);
} else {
@@ -2227,7 +2236,6 @@
self,
nil,
nil,
- nil,
NSLocalizedString(@"There have been errors while copying table content. Please control the new table.", @"message of panel when table content cannot be copied")
);
}
diff --git a/Source/YRKSpinningProgressIndicator.h b/Source/YRKSpinningProgressIndicator.h
index 355ebf2f..e97711b2 100644
--- a/Source/YRKSpinningProgressIndicator.h
+++ b/Source/YRKSpinningProgressIndicator.h
@@ -11,35 +11,48 @@
NSInteger _position;
NSInteger _numFins;
- BOOL _isIndeterminate;
- double _currentValue;
- double _maxValue;
-
BOOL _isAnimating;
+ NSTimer *_animationTimer;
NSThread *_animationThread;
NSColor *_foreColor;
NSColor *_backColor;
BOOL _drawBackground;
+ NSShadow *_shadow;
+
+ NSTimer *_fadeOutAnimationTimer;
+ BOOL _isFadingOut;
+
+ // For determinate mode
+ BOOL _isIndeterminate;
+ double _currentValue;
+ double _maxValue;
+
+ BOOL _usesThreadedAnimation;
}
-- (void)animate:(id)sender;
+
- (void)stopAnimation:(id)sender;
- (void)startAnimation:(id)sender;
+
+// Accessors
+
- (NSColor *)foreColor;
- (void)setForeColor:(NSColor *)value;
-
- (NSColor *)backColor;
- (void)setBackColor:(NSColor *)value;
-
- (BOOL)drawBackground;
- (void)setDrawBackground:(BOOL)value;
- (BOOL)isIndeterminate;
- (void)setIndeterminate:(BOOL)isIndeterminate;
-
- (double)doubleValue;
- (void)setDoubleValue:(double)doubleValue;
+- (void)setNumberValue:(NSNumber *)numberValue;
- (double)maxValue;
- (void)setMaxValue:(double)maxValue;
+
+- (void)setUsesThreadedAnimation:(BOOL)useThreaded;
+- (BOOL)usesThreadedAnimation;
+
@end
diff --git a/Source/YRKSpinningProgressIndicator.m b/Source/YRKSpinningProgressIndicator.m
index b9af4847..b6f3c0ac 100644
--- a/Source/YRKSpinningProgressIndicator.m
+++ b/Source/YRKSpinningProgressIndicator.m
@@ -34,7 +34,10 @@
@interface YRKSpinningProgressIndicator (YRKSpinningProgressIndicatorPrivate)
+- (void)updateFrame:(NSTimer *)timer;
- (void) animateInBackgroundThread;
+- (void)actuallyStartAnimation;
+- (void)actuallyStopAnimation;
@end
@@ -48,12 +51,12 @@
_position = 0;
_numFins = 12;
_isAnimating = NO;
- _animationThread = nil;
- _foreColor = nil;
- _backColor = nil;
+ _isFadingOut = NO;
_isIndeterminate = YES;
_currentValue = 0.0;
_maxValue = 100.0;
+ _usesThreadedAnimation = NO;
+ _shadow = nil;
}
return self;
}
@@ -61,7 +64,9 @@
- (void) dealloc {
if (_foreColor) [_foreColor release];
if (_backColor) [_backColor release];
+ if (_shadow) [_shadow release];
if (_isAnimating) [self stopAnimation:self];
+
[super dealloc];
}
@@ -70,12 +75,11 @@
[super viewDidMoveToWindow];
if ([self window] == nil) {
- // No window? View hierarchy may be going away. Ensure animation is stopped.
- [self stopAnimation:self];
+ // No window? View hierarchy may be going away. Dispose timer to clear circular retain of timer to self to timer.
+ [self actuallyStopAnimation];
}
else if (_isAnimating) {
- [self stopAnimation:self];
- [self startAnimation:self];
+ [self actuallyStartAnimation];
}
}
@@ -92,20 +96,21 @@
else
maxSize = size.width;
+ CGContextRef currentContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
+ [NSGraphicsContext saveGraphicsState];
+
+ if (_shadow) [_shadow set];
+
// fill the background, if set
if(_drawBackground) {
[_backColor set];
[NSBezierPath fillRect:[self bounds]];
}
- CGContextRef currentContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
- [NSGraphicsContext saveGraphicsState];
-
// Move the CTM so 0,0 is at the center of our bounds
CGContextTranslateCTM(currentContext,[self bounds].size.width/2,[self bounds].size.height/2);
if (_isIndeterminate) {
-
// do initial rotation to start place
CGContextRotateCTM(currentContext, 3.14159*2/_numFins * _position);
@@ -157,15 +162,23 @@
# pragma mark -
# pragma mark Subclass
-- (void)animate:(id)sender
+- (void)updateFrame:(NSTimer *)timer;
{
- if(_position > 1) {
+ if(_position > 0) {
_position--;
}
else {
- _position = _numFins;
+ _position = _numFins - 1;
+ }
+
+ if (_usesThreadedAnimation) {
+ // draw now instead of waiting for setNeedsDisplay (that's the whole reason
+ // we're animating from background thread)
+ [self display];
+ }
+ else {
+ [self setNeedsDisplay:YES];
}
- [self display];
}
- (void) animateInBackgroundThread
@@ -177,7 +190,7 @@
NSInteger poolFlushCounter = 0;
do {
- [self animate:nil];
+ [self updateFrame:nil];
usleep(animationDelay);
poolFlushCounter++;
if (poolFlushCounter > 256) {
@@ -194,23 +207,59 @@
{
if (!_isIndeterminate) return;
if (_isAnimating) return;
+
+ [self actuallyStartAnimation];
_isAnimating = YES;
-
- _animationThread = [[NSThread alloc] initWithTarget:self selector:@selector(animateInBackgroundThread) object:nil];
- [_animationThread start];
}
- (void)stopAnimation:(id)sender
{
+ [self actuallyStopAnimation];
_isAnimating = NO;
+}
+
+- (void)actuallyStartAnimation
+{
+ // Just to be safe kill any existing timer.
+ [self actuallyStopAnimation];
+
+ if ([self window]) {
+ // Why animate if not visible? viewDidMoveToWindow will re-call this method when needed.
+ if (_usesThreadedAnimation) {
+ _animationThread = [[NSThread alloc] initWithTarget:self selector:@selector(animateInBackgroundThread) object:nil];
+ [_animationThread start];
+ }
+ else {
+ _animationTimer = [[NSTimer timerWithTimeInterval:(NSTimeInterval)0.05
+ target:self
+ selector:@selector(updateFrame:)
+ userInfo:nil
+ repeats:YES] retain];
+
+ [[NSRunLoop currentRunLoop] addTimer:_animationTimer forMode:NSRunLoopCommonModes];
+ [[NSRunLoop currentRunLoop] addTimer:_animationTimer forMode:NSDefaultRunLoopMode];
+ [[NSRunLoop currentRunLoop] addTimer:_animationTimer forMode:NSEventTrackingRunLoopMode];
+ }
+ }
+}
+
+- (void)actuallyStopAnimation
+{
if (_animationThread) {
+ // we were using threaded animation
[_animationThread cancel];
if (![_animationThread isFinished]) {
[[NSRunLoop currentRunLoop] runMode:NSModalPanelRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.05]];
}
- [_animationThread release], _animationThread = nil;
+ [_animationThread release];
+ _animationThread = nil;
}
-
+ else if (_animationTimer) {
+ // we were using timer-based animation
+ [_animationTimer invalidate];
+ [_animationTimer release];
+ _animationTimer = nil;
+ }
[self setNeedsDisplay:YES];
}
@@ -268,6 +317,20 @@
[self setNeedsDisplay:YES];
}
+- (NSShadow *)shadow
+{
+ return [[_shadow retain] autorelease];
+}
+
+- (void)setShadow:(NSShadow *)value
+{
+ if (_shadow != value) {
+ [_shadow release];
+ _shadow = [value copy];
+ [self setNeedsDisplay:YES];
+ }
+}
+
- (BOOL)isIndeterminate
{
return _isIndeterminate;
@@ -277,7 +340,7 @@
{
_isIndeterminate = isIndeterminate;
if (!_isIndeterminate && _isAnimating) [self stopAnimation:self];
- [self displayIfNeeded];
+ [self setNeedsDisplay:YES];
}
- (double)doubleValue
@@ -287,11 +350,18 @@
- (void)setDoubleValue:(double)doubleValue
{
- if (_isIndeterminate) _isIndeterminate = NO;
+ // Automatically put it into determinate mode if it's not already.
+ if (_isIndeterminate) {
+ [self setIndeterminate:NO];
+ }
_currentValue = doubleValue;
[self setNeedsDisplay:YES];
}
+- (void)setNumberValue:(NSNumber *)numberValue
+{
+ [self setDoubleValue:[numberValue doubleValue]];
+}
- (double)maxValue
{
return _maxValue;
@@ -300,7 +370,25 @@
- (void)setMaxValue:(double)maxValue
{
_maxValue = maxValue;
- [self displayIfNeeded];
+ [self setNeedsDisplay:YES];
+}
+
+- (void)setUsesThreadedAnimation:(BOOL)useThreaded
+{
+ if (_usesThreadedAnimation != useThreaded) {
+ _usesThreadedAnimation = useThreaded;
+
+ if (_isAnimating) {
+ // restart the timer to use the new mode
+ [self stopAnimation:self];
+ [self startAnimation:self];
+ }
+ }
+}
+
+- (BOOL)usesThreadedAnimation
+{
+ return _usesThreadedAnimation;
}
@end