aboutsummaryrefslogtreecommitdiffstats
path: root/Source
diff options
context:
space:
mode:
Diffstat (limited to 'Source')
-rw-r--r--Source/CMTextView.h6
-rw-r--r--Source/CMTextView.m94
-rw-r--r--Source/SPConnectionController.m9
-rw-r--r--Source/SPConnectionDelegate.m24
-rw-r--r--Source/SPConstants.h1
-rw-r--r--Source/SPConstants.m1
-rw-r--r--Source/SPExtendedTableInfo.m6
-rw-r--r--Source/SPFieldEditorController.m1
-rw-r--r--Source/SPGrowlController.m8
-rw-r--r--Source/SPHistoryController.h2
-rw-r--r--Source/SPHistoryController.m69
-rw-r--r--Source/SPNarrowDownCompletion.h2
-rw-r--r--Source/SPNarrowDownCompletion.m192
-rw-r--r--Source/SPPreferenceController.h1
-rw-r--r--Source/SPPreferenceController.m104
-rw-r--r--Source/SPProcessListController.m2
-rw-r--r--Source/SPQueryController.m14
-rw-r--r--Source/SPSSHTunnel.h1
-rw-r--r--Source/SPSSHTunnel.m18
-rw-r--r--Source/SPTableData.m18
-rw-r--r--Source/SPTableRelations.m4
-rw-r--r--Source/SPTableView.m3
-rw-r--r--Source/SPTooltip.m1
-rw-r--r--Source/SPUserManager.xcdatamodel/elementsbin127952 -> 127771 bytes
-rw-r--r--Source/SPUserManager.xcdatamodel/layoutbin9663 -> 9663 bytes
-rw-r--r--Source/TableContent.m81
-rw-r--r--Source/TableDocument.h3
-rw-r--r--Source/TableDocument.m129
-rw-r--r--Source/TableDump.m56
-rw-r--r--Source/TableSource.m65
-rw-r--r--Source/TablesList.h6
-rw-r--r--Source/TablesList.m81
-rw-r--r--Source/main.m2
33 files changed, 690 insertions, 314 deletions
diff --git a/Source/CMTextView.h b/Source/CMTextView.h
index 224349e3..7c09d827 100644
--- a/Source/CMTextView.h
+++ b/Source/CMTextView.h
@@ -31,6 +31,8 @@
#define SP_TEXT_SIZE_TRIGGER_FOR_PARTLY_PARSING 10000
+@class SPNarrowDownCompletion;
+
@interface CMTextView : NSTextView
{
BOOL autoindentEnabled;
@@ -47,6 +49,7 @@
NSString *showMySQLHelpFor;
IBOutlet NSScrollView *scrollView;
+ SPNarrowDownCompletion *completionPopup;
NSUserDefaults *prefs;
@@ -64,6 +67,8 @@
BOOL completionIsOpen;
BOOL completionWasReinvokedAutomatically;
+ BOOL completionWasRefreshed;
+ BOOL completionFuzzyMode;
NSUInteger completionParseRangeLocation;
NSColor *queryHiliteColor;
@@ -120,6 +125,7 @@
- (void) setConnection:(MCPConnection *)theConnection withVersion:(NSInteger)majorVersion;
- (void) doCompletionByUsingSpellChecker:(BOOL)isDictMode fuzzyMode:(BOOL)fuzzySearch autoCompleteMode:(BOOL)autoCompleteMode;
- (void) doAutoCompletion;
+- (void) refreshCompletion;
- (NSArray *)suggestionsForSQLCompletionWith:(NSString *)currentWord dictMode:(BOOL)isDictMode browseMode:(BOOL)dbBrowseMode withTableName:(NSString*)aTableName withDbName:(NSString*)aDbName;
- (void) selectCurrentQuery;
- (void) processMirroredSnippets;
diff --git a/Source/CMTextView.m b/Source/CMTextView.m
index fae2554a..efe18042 100644
--- a/Source/CMTextView.m
+++ b/Source/CMTextView.m
@@ -129,8 +129,10 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
textBufferSizeIncreased = NO;
snippetControlCounter = -1;
mirroredCounter = -1;
+ completionPopup = nil;
completionIsOpen = NO;
isProcessingMirroredSnippets = NO;
+ completionWasRefreshed = NO;
lineNumberView = [[NoodleLineNumberView alloc] initWithScrollView:scrollView];
[scrollView setVerticalRulerView:lineNumberView];
@@ -517,7 +519,6 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
- (void) doAutoCompletion
{
-
if(completionIsOpen || !self || ![self delegate]) return;
// Cancel autocompletion trigger
@@ -532,16 +533,27 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
if(![self delegate] || ![[self delegate] isKindOfClass:[CustomQuery class]] || r.length || snippetControlCounter > -1) return;
if(r.location) {
- if([[[self textStorage] attribute:kQuote atIndex:r.location-1 effectiveRange:nil] isEqualToString:kQuoteValue])
- return;
- if(![[NSCharacterSet whitespaceAndNewlineCharacterSet] characterIsMember:[[self string] characterAtIndex:r.location-1]])
- // Suppress auto-completion if window isn't active anymore
- if([[NSApp keyWindow] firstResponder] == self)
- [self doCompletionByUsingSpellChecker:NO fuzzyMode:NO autoCompleteMode:YES];
+ NSCharacterSet *ignoreCharacterSet = [NSCharacterSet characterSetWithCharactersInString:@"\"'`;,()[]{}=+/<> \t\n\r"];
+
+ // Check the previous character and don't autocomplete if the character is whitespace or certain types of punctuation
+ if ([ignoreCharacterSet characterIsMember:[[self string] characterAtIndex:r.location - 1]]) return;
+
+ // Suppress auto-completion if the window isn't active anymore
+ if ([[NSApp keyWindow] firstResponder] != self) return;
+
+ // Trigger the completion
+ [self doCompletionByUsingSpellChecker:NO fuzzyMode:NO autoCompleteMode:YES];
}
}
+- (void) refreshCompletion
+{
+ if(completionWasRefreshed) return;
+ completionWasRefreshed = YES;
+ [self doCompletionByUsingSpellChecker:NO fuzzyMode:completionFuzzyMode autoCompleteMode:NO];
+}
+
- (void) doCompletionByUsingSpellChecker:(BOOL)isDictMode fuzzyMode:(BOOL)fuzzySearch autoCompleteMode:(BOOL)autoCompleteMode
{
@@ -557,6 +569,9 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
[self breakUndoCoalescing];
+ // Remember state for refreshCompletion
+ completionFuzzyMode = fuzzySearch;
+
NSUInteger caretPos = NSMaxRange([self selectedRange]);
BOOL caretMovedLeft = NO;
@@ -734,15 +749,15 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
filter = [NSString stringWithString:currentWord];
}
- completionIsOpen = YES;
-
// Cancel autocompletion trigger again if user typed something in while parsing
if([prefs boolForKey:SPCustomQueryAutoComplete])
[NSObject cancelPreviousPerformRequestsWithTarget:self
selector:@selector(doAutoCompletion)
object:nil];
- SPNarrowDownCompletion* completionPopUp = [[SPNarrowDownCompletion alloc] initWithItems:[self suggestionsForSQLCompletionWith:currentWord dictMode:isDictMode browseMode:dbBrowseMode withTableName:tableName withDbName:dbName]
+ if (completionIsOpen) [completionPopup close], completionPopup = nil;
+ completionIsOpen = YES;
+ completionPopup = [[SPNarrowDownCompletion alloc] initWithItems:[self suggestionsForSQLCompletionWith:currentWord dictMode:isDictMode browseMode:dbBrowseMode withTableName:tableName withDbName:dbName]
alreadyTyped:filter
staticPrefix:prefix
additionalWordCharacters:allow
@@ -778,10 +793,9 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
// Adjust list location to be under the current word or insertion point
pos.y -= [[self font] pointSize]*1.25;
- [completionPopUp setCaretPos:pos];
- [completionPopUp orderFront:self];
- if(!autoCompleteMode)
- [completionPopUp insertCommonPrefix];
+ [completionPopup setCaretPos:pos];
+ [completionPopup orderFront:self];
+ [completionPopup insertCommonPrefix];
}
@@ -1290,9 +1304,10 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
return;
}
+ if (completionIsOpen) [completionPopup close], completionPopup = nil;
completionIsOpen = YES;
- SPNarrowDownCompletion* completionPopUp = [[SPNarrowDownCompletion alloc] initWithItems:possibleCompletions
+ completionPopup = [[SPNarrowDownCompletion alloc] initWithItems:possibleCompletions
alreadyTyped:@""
staticPrefix:@""
additionalWordCharacters:@"_."
@@ -1320,8 +1335,8 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
NSPoint pos = [[self window] convertBaseToScreen: NSMakePoint(boundingRect.origin.x + boundingRect.size.width,boundingRect.origin.y + boundingRect.size.height)];
// Adjust list location to be under the current word or insertion point
pos.y -= [[self font] pointSize]*1.25;
- [completionPopUp setCaretPos:pos];
- [completionPopUp orderFront:self];
+ [completionPopup setCaretPos:pos];
+ [completionPopup orderFront:self];
}
@@ -1336,15 +1351,6 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
NSInteger i, j, k, deltaLength;
NSRange mirroredRange;
- id aCompletionList;
-
- // Get the completion list pointer if open
- if(completionIsOpen)
- for(id w in [NSApp windows])
- if([w isKindOfClass:[SPNarrowDownCompletion class]]) {
- aCompletionList = w;
- break;
- }
// Go through each defined mirrored snippet and update it
for(i=0; i<=mirroredCounter; i++) {
@@ -1375,7 +1381,7 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
// If a completion list is open adjust the theCharRange and theParseRange if a mirrored snippet
// was updated which is located before the initial position
if(completionIsOpen && snippetMirroredControlArray[i][1] < completionParseRangeLocation)
- [aCompletionList adjustWorkingRangeByDelta:deltaLength];
+ [completionPopup adjustWorkingRangeByDelta:deltaLength];
// Adjust all other snippets accordingly
for(j=0; j<=snippetControlMax; j++) {
@@ -1445,7 +1451,6 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
BOOL fuzzySearchMode = ([snip hasPrefix:@"¦¦"] && [snip hasSuffix:@"¦¦"]) ? YES : NO;
NSInteger offset = (fuzzySearchMode) ? 2 : 1;
NSRange insertRange = NSMakeRange(r2.location,0);
- SPNarrowDownCompletion* completionPopUp;
NSString *newSnip = [snip substringWithRange:NSMakeRange(1*offset,[snip length]-(2*offset))];
if([newSnip hasPrefix:@"$SP_ASLIST_"]) {
[self showCompletionListFor:newSnip atRange:NSMakeRange(r2.location, 0) fuzzySearch:fuzzySearchMode];
@@ -1456,9 +1461,10 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
for(id w in list)
[possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:w, @"display", @"dummy-small", @"image", nil]];
+ if (completionIsOpen) [completionPopup close], completionPopup = nil;
completionIsOpen = YES;
- completionPopUp = [[SPNarrowDownCompletion alloc] initWithItems:possibleCompletions
+ completionPopup = [[SPNarrowDownCompletion alloc] initWithItems:possibleCompletions
alreadyTyped:@""
staticPrefix:@""
additionalWordCharacters:@"_."
@@ -1478,6 +1484,7 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
autoComplete:NO
oneColumn:YES
isQueryingDBStructure:NO];
+
//Get the NSPoint of the first character of the current word
NSRange glyphRange = [[self layoutManager] glyphRangeForCharacterRange:NSMakeRange(r2.location,0) actualCharacterRange:NULL];
NSRect boundingRect = [[self layoutManager] boundingRectForGlyphRange:glyphRange inTextContainer:[self textContainer]];
@@ -1485,8 +1492,8 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
NSPoint pos = [[self window] convertBaseToScreen: NSMakePoint(boundingRect.origin.x + boundingRect.size.width,boundingRect.origin.y + boundingRect.size.height)];
// Adjust list location to be under the current word or insertion point
pos.y -= [[self font] pointSize]*1.25;
- [completionPopUp setCaretPos:pos];
- [completionPopUp orderFront:self];
+ [completionPopup setCaretPos:pos];
+ [completionPopup orderFront:self];
}
}
} else {
@@ -1538,7 +1545,11 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
targetRange = NSIntersectionRange(NSMakeRange(0,[[self string] length]), targetRange);
[snip setString:theSnippet];
- if(snip == nil || ![snip length]) return;
+ if (snip == nil) return;
+ if (![snip length]) {
+ [snip release];
+ return;
+ }
// Replace `${x:…}` by ${x:`…`} for convience
[snip replaceOccurrencesOfRegex:@"`(?s)(?<!\\\\)\\$\\{(1?\\d):(.{0}|.*?[^\\\\])\\}`" withString:@"${$1:`$2`}"];
@@ -1999,7 +2010,7 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
if ([theEvent keyCode] == 53 && [self isEditable]){ // ESC key for internal completion
[self setCompletionWasReinvokedAutomatically:NO];
-
+ completionWasRefreshed = NO;
// Cancel autocompletion trigger
if([prefs boolForKey:SPCustomQueryAutoComplete])
[NSObject cancelPreviousPerformRequestsWithTarget:self
@@ -2930,12 +2941,12 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
// Start autohelp only if the user really changed the text (not e.g. for setting a background color)
if([prefs boolForKey:SPCustomQueryUpdateAutoHelp] && editedMask != 1) {
- [self performSelector:@selector(autoHelp) withObject:nil afterDelay:[[[prefs valueForKey:SPCustomQueryAutoHelpDelay] retain] doubleValue]];
+ [self performSelector:@selector(autoHelp) withObject:nil afterDelay:[[prefs valueForKey:SPCustomQueryAutoHelpDelay] doubleValue]];
}
// Start autocompletion if enabled
if([[NSApp keyWindow] firstResponder] == self && [prefs boolForKey:SPCustomQueryAutoComplete] && !completionIsOpen && editedMask != 1 && [textStore editedRange].length)
- [self performSelector:@selector(doAutoCompletion) withObject:nil afterDelay:[[[prefs valueForKey:SPCustomQueryAutoCompleteDelay] retain] doubleValue]];
+ [self performSelector:@selector(doAutoCompletion) withObject:nil afterDelay:[[prefs valueForKey:SPCustomQueryAutoCompleteDelay] doubleValue]];
// Cancel calling doSyntaxHighlighting for large text
if([[self string] length] > SP_TEXT_SIZE_TRIGGER_FOR_PARTLY_PARSING)
@@ -3137,6 +3148,7 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
}
[self breakUndoCoalescing];
[self insertText:dragString];
+ if (draggedItems) [draggedItems release];
return YES;
}
@@ -3271,15 +3283,8 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
- (void) dealloc
{
- if([prefs boolForKey:SPCustomQueryUpdateAutoHelp])
- [NSObject cancelPreviousPerformRequestsWithTarget:self
- selector:@selector(autoHelp)
- object:nil];
-
- if([prefs boolForKey:SPCustomQueryAutoComplete])
- [NSObject cancelPreviousPerformRequestsWithTarget:self
- selector:@selector(doAutoCompletion)
- object:nil];
+ // Cancel any deferred calls
+ [NSObject cancelPreviousPerformRequestsWithTarget:self];
// Remove observers
[[NSNotificationCenter defaultCenter] removeObserver:self];
@@ -3297,6 +3302,7 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
[prefs removeObserver:self forKeyPath:SPCustomQueryEditorTabStopWidth];
[prefs removeObserver:self forKeyPath:SPCustomQueryAutoUppercaseKeywords];
+ if (completionIsOpen) [completionPopup close], completionIsOpen = NO;
[prefs release];
[lineNumberView release];
if(queryHiliteColor) [queryHiliteColor release];
diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m
index ecfd6bc4..1f19ba1e 100644
--- a/Source/SPConnectionController.m
+++ b/Source/SPConnectionController.m
@@ -436,8 +436,10 @@
[delegate connectionControllerConnectAttemptFailed:self];
}
- // Display the connection error message
- 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);
+ // 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);
+ }
}
/**
@@ -521,7 +523,8 @@
[prefsController showWindow:self];
[prefsController displayFavoritePreferences:self];
- if ([favoritesTable numberOfSelectedRows]) [prefsController selectFavoriteAtIndex:([favoritesTable selectedRow] - 1)];
+
+ if ([favoritesTable numberOfSelectedRows]) [prefsController selectFavorites:[NSArray arrayWithObject:[self valueForKeyPath:@"selectedFavorite"]]];
}
/**
diff --git a/Source/SPConnectionDelegate.m b/Source/SPConnectionDelegate.m
index 56a254fa..c69c3150 100644
--- a/Source/SPConnectionDelegate.m
+++ b/Source/SPConnectionDelegate.m
@@ -110,18 +110,24 @@
*/
- (MCPConnectionCheck)connectionLost:(id)connection
{
+ NSInteger connectionErrorCode = MCPConnectionCheckDisconnect;
- // Display the connection error dialog and wait for the return code
- [NSApp beginSheet:connectionErrorDialog modalForWindow:tableWindow modalDelegate:self didEndSelector:nil contextInfo:nil];
- NSInteger connectionErrorCode = [NSApp runModalForWindow:connectionErrorDialog];
+ // Only display the reconnect dialog if the window is visible
+ if ([tableWindow isVisible]) {
- [NSApp endSheet:connectionErrorDialog];
- [connectionErrorDialog orderOut:nil];
+ // Display the connection error dialog and wait for the return code
+ [NSApp beginSheet:connectionErrorDialog modalForWindow:tableWindow modalDelegate:self didEndSelector:nil contextInfo:nil];
+ connectionErrorCode = [NSApp runModalForWindow:connectionErrorDialog];
+
+ [NSApp endSheet:connectionErrorDialog];
+ [connectionErrorDialog orderOut:nil];
+
+ // If 'disconnect' was selected, trigger a window close.
+ if (connectionErrorCode == MCPConnectionCheckDisconnect) {
+ [self performSelectorOnMainThread:@selector(closeDocumentWindowAndDisconnect) withObject:nil waitUntilDone:YES];
+ }
+ }
- // If 'disconnect' was selected, trigger a window close.
- if (connectionErrorCode == MCPConnectionCheckDisconnect) {
- [self performSelectorOnMainThread:@selector(closeDocumentWindowAndDisconnect) withObject:nil waitUntilDone:YES];
- }
return connectionErrorCode;
}
diff --git a/Source/SPConstants.h b/Source/SPConstants.h
index b7241136..ed25b61e 100644
--- a/Source/SPConstants.h
+++ b/Source/SPConstants.h
@@ -273,6 +273,7 @@ extern NSString *SPMainToolbarTableInfo;
extern NSString *SPMainToolbarTableRelations;
extern NSString *SPMainToolbarTableTriggers;
extern NSString *SPMainToolbarUserManager;
+extern NSString **SPViewModeToMainToolbarMap[];
// Preferences toolbar
extern NSString *SPPreferenceToolbarGeneral;
diff --git a/Source/SPConstants.m b/Source/SPConstants.m
index 8a2729e8..ba4156e2 100644
--- a/Source/SPConstants.m
+++ b/Source/SPConstants.m
@@ -187,6 +187,7 @@ NSString *SPMainToolbarTableInfo = @"SwitchToTableInfoToolbarIte
NSString *SPMainToolbarTableRelations = @"SwitchToTableRelationsToolbarItemIdentifier";
NSString *SPMainToolbarTableTriggers = @"SwitchToTableTriggersToolbarItemIdentifier";
NSString *SPMainToolbarUserManager = @"SwitchToUserManagerToolbarItemIdentifier";
+NSString **SPViewModeToMainToolbarMap[] = {nil, &SPMainToolbarTableStructure, &SPMainToolbarTableContent, &SPMainToolbarCustomQuery, &SPMainToolbarTableInfo, &SPMainToolbarTableRelations, &SPMainToolbarTableTriggers};
// Preferences toolbar
NSString *SPPreferenceToolbarGeneral = @"SPPreferenceToolbarGeneral";
diff --git a/Source/SPExtendedTableInfo.m b/Source/SPExtendedTableInfo.m
index 9636ca4c..fe89d563 100644
--- a/Source/SPExtendedTableInfo.m
+++ b/Source/SPExtendedTableInfo.m
@@ -237,7 +237,8 @@
[tableCreateSyntaxTextView shouldChangeTextInRange:NSMakeRange(0, [[tableCreateSyntaxTextView string] length]) replacementString:@""];
[tableCreateSyntaxTextView setString:@""];
- NSString *createViewSyntax = [[tableDataInstance tableCreateSyntax] createViewSyntaxPrettifier];
+ NSString *createViewSyntax = [[[tableDataInstance tableCreateSyntax] createViewSyntaxPrettifier] stringByAppendingString:@";"];
+
if (createViewSyntax) {
[tableCreateSyntaxTextView shouldChangeTextInRange:NSMakeRange(0, 0) replacementString:createViewSyntax];
[tableCreateSyntaxTextView insertText:createViewSyntax];
@@ -281,6 +282,7 @@
NSArray *collations = [databaseDataInstance getDatabaseCollationsForEncoding:[tableDataInstance tableEncoding]];
if (([engines count] > 0) && ([statusFields objectForKey:@"Engine"])) {
+
// Populate type popup button
for (NSDictionary *engine in engines)
{
@@ -358,7 +360,7 @@
[tableCreateSyntaxTextView setString:@""];
[tableCreateSyntaxTextView didChangeText];
[tableCreateSyntaxTextView shouldChangeTextInRange:NSMakeRange(0, 0) replacementString:[tableDataInstance tableCreateSyntax]];
- [tableCreateSyntaxTextView insertText:[tableDataInstance tableCreateSyntax]];
+ [tableCreateSyntaxTextView insertText:[[tableDataInstance tableCreateSyntax] stringByAppendingString:@";"]];
[tableCreateSyntaxTextView didChangeText];
[tableCreateSyntaxTextView setEditable:NO];
diff --git a/Source/SPFieldEditorController.m b/Source/SPFieldEditorController.m
index 6a481daa..cb4a11f7 100644
--- a/Source/SPFieldEditorController.m
+++ b/Source/SPFieldEditorController.m
@@ -113,6 +113,7 @@
- (void)dealloc
{
+ [NSObject cancelPreviousPerformRequestsWithTarget:self];
// On Mac OSX 10.6 QuickLook runs non-modal thus order out the panel
// if still visible
diff --git a/Source/SPGrowlController.m b/Source/SPGrowlController.m
index 91690546..a92dbdda 100644
--- a/Source/SPGrowlController.m
+++ b/Source/SPGrowlController.m
@@ -25,6 +25,7 @@
#import "SPGrowlController.h"
#import "SPConstants.h"
+#import "SPMainThreadTrampoline.h"
#include <mach/mach_time.h>
@@ -88,6 +89,13 @@ static SPGrowlController *sharedGrowlController = nil;
*/
- (void)notifyWithTitle:(NSString *)title description:(NSString *)description window:(NSWindow *)window notificationName:(NSString *)name
{
+
+ // Ensure that the delayed notification call is made on the main thread
+ if (![NSThread isMainThread]) {
+ [[self onMainThread] notifyWithTitle:title description:description window:window notificationName:name];
+ return;
+ }
+
NSMutableDictionary *notificationDictionary = [NSMutableDictionary dictionary];
[notificationDictionary setObject:title forKey:@"title"];
[notificationDictionary setObject:description forKey:@"description"];
diff --git a/Source/SPHistoryController.h b/Source/SPHistoryController.h
index 1ae1456c..b0f3e082 100644
--- a/Source/SPHistoryController.h
+++ b/Source/SPHistoryController.h
@@ -37,6 +37,7 @@
NSMutableDictionary *tableContentStates;
NSUInteger historyPosition;
BOOL modifyingState;
+ BOOL toolbarItemVisible;
}
@property (readonly) NSUInteger historyPosition;
@@ -49,6 +50,7 @@
- (void)goForwardInHistory;
- (IBAction) historyControlClicked:(NSSegmentedControl *)theControl;
- (NSUInteger) currentlySelectedView;
+- (void) setupInterface;
// Adding or updating history entries
- (void) updateHistoryEntries;
diff --git a/Source/SPHistoryController.m b/Source/SPHistoryController.m
index 2fbf18b5..a1d9de58 100644
--- a/Source/SPHistoryController.m
+++ b/Source/SPHistoryController.m
@@ -56,10 +56,16 @@
{
tableContentInstance = [theDocument valueForKey:@"tableContentInstance"];
tablesListInstance = [theDocument valueForKey:@"tablesListInstance"];
+ toolbarItemVisible = NO;
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(toolbarWillAddItem:) name:NSToolbarWillAddItemNotification object:[theDocument valueForKey:@"mainToolbar"]];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(toolbarDidRemoveItem:) name:NSToolbarDidRemoveItemNotification object:[theDocument valueForKey:@"mainToolbar"]];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(startDocumentTask:) name:SPDocumentTaskStartNotification object:theDocument];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(endDocumentTask:) name:SPDocumentTaskEndNotification object:theDocument];
}
- (void) dealloc
{
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
[tableContentStates release];
[history release];
[super dealloc];
@@ -73,6 +79,11 @@
*/
- (void) updateToolbarItem
{
+
+ // If the toolbar item isn't visible, don't perform any actions - as manipulating
+ // items not on the toolbar can cause crashes.
+ if (!toolbarItemVisible) return;
+
BOOL backEnabled = NO;
BOOL forwardEnabled = NO;
NSInteger i;
@@ -134,6 +145,9 @@
- (IBAction) historyControlClicked:(NSSegmentedControl *)theControl
{
+ // Ensure history navigation is permitted - trigger end editing and any required saves
+ if (![theDocument couldCommitCurrentViewActions]) return;
+
switch ([theControl selectedSegment])
{
// Back button clicked:
@@ -175,6 +189,61 @@
return theView;
}
+/**
+ * Set up the toolbar items as appropriate.
+ * State tracking is necessary as manipulating items not on the toolbar
+ * can cause crashes.
+ */
+- (void) setupInterface
+{
+ NSArray *toolbarItems = [[theDocument valueForKey:@"mainToolbar"] items];
+ for (NSToolbarItem *toolbarItem in toolbarItems) {
+ if ([[toolbarItem itemIdentifier] isEqualToString:SPMainToolbarHistoryNavigation]) {
+ toolbarItemVisible = YES;
+ break;
+ }
+ }
+}
+
+/**
+ * Disable the controls during a task.
+ */
+- (void) startDocumentTask:(NSNotification *)aNotification
+{
+ if (toolbarItemVisible) [historyControl setEnabled:NO];
+}
+
+/**
+ * Enable the controls once a task has completed.
+ */
+- (void) endDocumentTask:(NSNotification *)aNotification
+{
+ if (toolbarItemVisible) [historyControl setEnabled:YES];
+}
+
+/**
+ * Update the state when the item is added from the toolbar.
+ * State tracking is necessary as manipulating items not on the toolbar
+ * can cause crashes.
+ */
+- (void) toolbarWillAddItem:(NSNotification *)aNotification {
+ if ([[[[aNotification userInfo] objectForKey:@"item"] itemIdentifier] isEqualToString:SPMainToolbarHistoryNavigation]) {
+ toolbarItemVisible = YES;
+ [self performSelector:@selector(updateToolbarItem) withObject:nil afterDelay:0.1];
+ }
+}
+
+/**
+ * Update the state when the item is removed from the toolbar
+ * State tracking is necessary as manipulating items not on the toolbar
+ * can cause crashes.
+ */
+- (void) toolbarDidRemoveItem:(NSNotification *)aNotification {
+ if ([[[[aNotification userInfo] objectForKey:@"item"] itemIdentifier] isEqualToString:SPMainToolbarHistoryNavigation]) {
+ toolbarItemVisible = NO;
+ }
+}
+
#pragma mark -
#pragma mark Adding or updating history entries
diff --git a/Source/SPNarrowDownCompletion.h b/Source/SPNarrowDownCompletion.h
index fc8bd942..032aef24 100644
--- a/Source/SPNarrowDownCompletion.h
+++ b/Source/SPNarrowDownCompletion.h
@@ -50,6 +50,8 @@
BOOL autoCompletionMode;
BOOL oneColumnMode;
BOOL isQueryingDatabaseStructure;
+ BOOL commonPrefixWasInsertedByAutoComplete;
+ NSMutableString *originalFilterString;
NSInteger backtickMode;
NSFont *tableFont;
NSRange theCharRange;
diff --git a/Source/SPNarrowDownCompletion.m b/Source/SPNarrowDownCompletion.m
index e635958f..3bef11c8 100644
--- a/Source/SPNarrowDownCompletion.m
+++ b/Source/SPNarrowDownCompletion.m
@@ -61,6 +61,7 @@
- (BOOL)SP_NarrowDownCompletion_canHandleEvent:(NSEvent*)anEvent
{
+
NSInteger visibleRows = (NSInteger)floor(NSHeight([self visibleRect]) / ([self rowHeight]+[self intercellSpacing].height)) - 1;
struct { unichar key; NSInteger rows; } const key_movements[] =
@@ -74,9 +75,8 @@
};
unichar keyCode = 0;
- if([anEvent type] == NSScrollWheel)
- keyCode = [anEvent deltaY] >= 0.0 ? NSUpArrowFunctionKey : NSDownArrowFunctionKey;
- else if([anEvent type] == NSKeyDown && [[anEvent characters] length] == 1)
+
+ if([anEvent type] == NSKeyDown && [[anEvent characters] length] == 1)
keyCode = [[anEvent characters] characterAtIndex:0];
@@ -85,9 +85,14 @@
if(keyCode == key_movements[i].key)
{
NSInteger row = MAX(0, MIN([self selectedRow] + key_movements[i].rows, [self numberOfRows]-1));
- [self selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
- [self scrollRowToVisible:row];
+ if(row == 0 && ![[[self delegate] tableView:self selectionIndexesForProposedSelection:[NSIndexSet indexSetWithIndex:row]] containsIndex:0]) {
+ if(visibleRows > 1)
+ [self selectRowIndexes:[NSIndexSet indexSetWithIndex:row+1] byExtendingSelection:NO];
+ } else {
+ [self selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
+ }
+ [self scrollRowToVisible:row];
return YES;
}
}
@@ -107,7 +112,7 @@
maxWindowWidth = 450;
- if(self = [super initWithContentRect:NSMakeRect(0,0,maxWindowWidth,0) styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO])
+ if(self = [super initWithContentRect:NSMakeRect(0,0,maxWindowWidth,0) styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES])
{
mutablePrefix = [NSMutableString new];
textualInputCharacters = [[NSMutableCharacterSet alphanumericCharacterSet] retain];
@@ -115,18 +120,23 @@
filtered = nil;
spaceCounter = 0;
currentSyncImage = 0;
+ staticPrefix = nil;
+ suggestions = nil;
+ commonPrefixWasInsertedByAutoComplete = NO;
prefs = [NSUserDefaults standardUserDefaults];
+ originalFilterString = [[NSMutableString alloc] init];
+ [originalFilterString setString:@""];
tableFont = [NSUnarchiver unarchiveObjectWithData:[[NSUserDefaults standardUserDefaults] dataForKey:SPCustomQueryEditorFont]];
[self setupInterface];
syncArrowImages = [[NSArray alloc] initWithObjects:
- [[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"sync_arrows_01" ofType:@"tiff"]],
- [[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"sync_arrows_02" ofType:@"tiff"]],
- [[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"sync_arrows_03" ofType:@"tiff"]],
- [[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"sync_arrows_04" ofType:@"tiff"]],
- [[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"sync_arrows_05" ofType:@"tiff"]],
- [[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"sync_arrows_06" ofType:@"tiff"]],
+ [[[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"sync_arrows_01" ofType:@"tiff"]] autorelease],
+ [[[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"sync_arrows_02" ofType:@"tiff"]] autorelease],
+ [[[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"sync_arrows_03" ofType:@"tiff"]] autorelease],
+ [[[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"sync_arrows_04" ofType:@"tiff"]] autorelease],
+ [[[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"sync_arrows_05" ofType:@"tiff"]] autorelease],
+ [[[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"sync_arrows_06" ofType:@"tiff"]] autorelease],
nil];
}
@@ -135,15 +145,17 @@
- (void)dealloc
{
+ [NSObject cancelPreviousPerformRequestsWithTarget:self];
if(stateTimer != nil) {
[stateTimer invalidate];
[stateTimer release];
}
stateTimer = nil;
- [staticPrefix release];
+ if (staticPrefix) [staticPrefix release];
[mutablePrefix release];
[textualInputCharacters release];
-
+ [originalFilterString release];
+ if (syncArrowImages) [syncArrowImages release];
if(suggestions) [suggestions release];
if (filtered) [filtered release];
@@ -151,11 +163,21 @@
[super dealloc];
}
+- (void)close
+{
+ closeMe = YES;
+ [theView setCompletionIsOpen:NO];
+ [super close];
+}
+
- (void)updateSyncArrowStatus
{
+ // update sync arrow image
currentSyncImage++;
- timeCounter++;
if(currentSyncImage >= [syncArrowImages count]) currentSyncImage = 0;
+
+ // check if connection is still querying the db structure
+ timeCounter++;
if(timeCounter > 20) {
timeCounter = 0;
if(![[theView valueForKeyPath:@"mySQLConnection"] isQueryingDatabaseStructure]) {
@@ -165,7 +187,7 @@
[stateTimer release];
stateTimer = nil;
if(syncArrowImages) [syncArrowImages release];
- [self performSelectorOnMainThread:@selector(reInvokeCompletion) withObject:nil waitUntilDone:NO];
+ [self performSelectorOnMainThread:@selector(reInvokeCompletion) withObject:nil waitUntilDone:YES];
closeMe = YES;
return;
}
@@ -178,9 +200,14 @@
- (void)reInvokeCompletion
{
+ if(stateTimer) {
+ [stateTimer invalidate];
+ [stateTimer release];
+ stateTimer = nil;
+ }
[theView setCompletionIsOpen:NO];
- [theView doCompletionByUsingSpellChecker:dictMode fuzzyMode:fuzzyMode autoCompleteMode:NO];
[self close];
+ [theView performSelector:@selector(refreshCompletion) withObject:nil afterDelay:0.0];
}
- (id)initWithItems:(NSArray*)someSuggestions alreadyTyped:(NSString*)aUserString staticPrefix:(NSString*)aStaticPrefix
@@ -199,6 +226,10 @@
[mutablePrefix appendString:aUserString];
autoCompletionMode = autoComplete;
+
+ if(autoCompletionMode)
+ [originalFilterString appendString:aUserString];
+
oneColumnMode = oneColumn;
isQueryingDatabaseStructure = isQueryingDBStructure;
@@ -230,6 +261,8 @@
theView = aView;
dictMode = mode;
+ timeCounter = 0;
+
suggestions = [someSuggestions retain];
if(dictMode || oneColumnMode) {
@@ -306,13 +339,11 @@
theTableView = [[[NSTableView alloc] initWithFrame:NSZeroRect] autorelease];
[theTableView setFocusRingType:NSFocusRingTypeNone];
- [theTableView setAllowsEmptySelection:NO];
+ [theTableView setAllowsEmptySelection:YES];
[theTableView setHeaderView:nil];
- [theTableView setDelegate:self];
NSTableColumn *column0 = [[[NSTableColumn alloc] initWithIdentifier:@"image"] autorelease];
- [column0 setDataCell:[NSImageCell new]];
- // [column0 setEditable:NO];
+ [column0 setDataCell:[[NSImageCell new] autorelease]];
[theTableView addTableColumn:column0];
[column0 setMinWidth:0];
[column0 setWidth:20];
@@ -339,9 +370,11 @@
[column4 setWidth:95];
[theTableView setDataSource:self];
+ [theTableView setDelegate:self];
[scrollView setDocumentView:theTableView];
[self setContentView:scrollView];
+
}
// ========================
@@ -354,10 +387,10 @@
- (NSString *)tableView:(NSTableView *)aTableView toolTipForCell:(id)aCell rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex mouseLocation:(NSPoint)mouseLocation
{
- if(isQueryingDatabaseStructure) rowIndex--;
-
if([[aTableColumn identifier] isEqualToString:@"image"]) {
- if(rowIndex == -1) return NSLocalizedString(@"fetching database structure in progress", @"fetching database structure in progress");
+ if(isQueryingDatabaseStructure && rowIndex == 0)
+ return NSLocalizedString(@"fetching database structure in progress", @"fetching database structure in progress");
+
if(!dictMode) {
NSString *imageName = [[filtered objectAtIndex:rowIndex] objectForKey:@"image"];
if([imageName hasPrefix:@"dummy"])
@@ -377,10 +410,14 @@
}
return @"";
} else if([[aTableColumn identifier] isEqualToString:@"name"]) {
- if(rowIndex == -1) return NSLocalizedString(@"fetching database structure in progress", @"fetching database structure in progress");
+ if(isQueryingDatabaseStructure && rowIndex == 0)
+ return NSLocalizedString(@"fetching database structure in progress", @"fetching database structure in progress");
+
return [[filtered objectAtIndex:rowIndex] objectForKey:@"display"];
} else if ([[aTableColumn identifier] isEqualToString:@"list"] || [[aTableColumn identifier] isEqualToString:@"type"]) {
- if(rowIndex == -1) return NSLocalizedString(@"fetching database structure data in progress", @"fetching database structure data in progress");
+ if(isQueryingDatabaseStructure && rowIndex == 0)
+ return NSLocalizedString(@"fetching database structure data in progress", @"fetching database structure data in progress");
+
if(dictMode) {
return @"";
} else {
@@ -399,7 +436,9 @@
}
} else if ([[aTableColumn identifier] isEqualToString:@"path"]) {
- if(rowIndex == -1) return NSLocalizedString(@"fetching database structure in progress", @"fetching database structure in progress");
+ if(isQueryingDatabaseStructure && rowIndex == 0)
+ return NSLocalizedString(@"fetching database structure in progress", @"fetching database structure in progress");
+
if(dictMode) {
return @"";
} else {
@@ -419,25 +458,22 @@
return @"";
}
-- (BOOL)tableView:(NSTableView *)aTableView shouldSelectRow:(NSInteger)rowIndex
+- (NSIndexSet *)tableView:(NSTableView *)tableView selectionIndexesForProposedSelection:(NSIndexSet *)proposedSelectionIndexes
{
- if(rowIndex == 0 && isQueryingDatabaseStructure)
- return NO;
+ if(isQueryingDatabaseStructure && [proposedSelectionIndexes containsIndex:0])
+ return [tableView selectedRowIndexes];
- return YES;
+ return proposedSelectionIndexes;
}
-
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
NSImage* image = nil;
NSString* imageName = nil;
- if(isQueryingDatabaseStructure) rowIndex--;
-
if([[aTableColumn identifier] isEqualToString:@"image"]) {
if(!dictMode) {
- if(rowIndex == -1) {
+ if(isQueryingDatabaseStructure && rowIndex == 0) {
return [syncArrowImages objectAtIndex:currentSyncImage];
} else {
imageName = [[filtered objectAtIndex:rowIndex] objectForKey:@"image"];
@@ -450,11 +486,14 @@
} else if([[aTableColumn identifier] isEqualToString:@"name"]) {
[[aTableColumn dataCell] setFont:[NSFont systemFontOfSize:12]];
- if(rowIndex == -1) return @"fetching structure…";
+
+ if(isQueryingDatabaseStructure && rowIndex == 0)
+ return @"fetching structure…";
+
return [[filtered objectAtIndex:rowIndex] objectForKey:@"display"];
} else if ([[aTableColumn identifier] isEqualToString:@"list"]) {
- if(rowIndex == -1) {
+ if(isQueryingDatabaseStructure && rowIndex == 0) {
NSPopUpButtonCell *b = [[NSPopUpButtonCell new] autorelease];
[b setPullsDown:NO];
[b setArrowPosition:NSPopUpNoArrow];
@@ -491,7 +530,7 @@
}
} else if([[aTableColumn identifier] isEqualToString:@"type"]) {
- if(rowIndex == -1) {
+ if(isQueryingDatabaseStructure && rowIndex == 0) {
return @"";
}
if(dictMode) {
@@ -506,7 +545,7 @@
}
} else if ([[aTableColumn identifier] isEqualToString:@"path"]) {
- if(rowIndex == -1) {
+ if(isQueryingDatabaseStructure && rowIndex == 0) {
NSPopUpButtonCell *b = [[NSPopUpButtonCell new] autorelease];
[b setPullsDown:NO];
[b setArrowPosition:NSPopUpNoArrow];
@@ -570,6 +609,7 @@
{
NSMutableArray* newFiltered = [[NSMutableArray alloc] initWithCapacity:5];
+
if([mutablePrefix length] > 0)
{
if(dictMode) {
@@ -653,6 +693,11 @@
return;
}
+
+ // if fetching db structure add dummy row for displaying that info on top of the list
+ if(isQueryingDatabaseStructure)
+ [newFiltered insertObject:[NSDictionary dictionaryWithObjectsAndKeys:@"dummy", @"display", @"", @"noCompletion", nil] atIndex:0];
+
NSPoint old = NSMakePoint([self frame].origin.x, [self frame].origin.y + [self frame].size.height);
NSInteger displayedRows = [newFiltered count] < SPNarrowDownCompletionMaxRows ? [newFiltered count] : SPNarrowDownCompletionMaxRows;
@@ -676,6 +721,7 @@
if(!dictMode)
[self checkSpaceForAllowedCharacter];
[theTableView reloadData];
+ [theTableView selectRowIndexes:[NSIndexSet indexSetWithIndex:(isQueryingDatabaseStructure)?1:0] byExtendingSelection:NO];
}
// =========================
@@ -724,6 +770,9 @@
if(!event)
continue;
+ // Exit if closeMe has been set in the meantime
+ if(closeMe) return;
+
NSEventType t = [event type];
if([theTableView SP_NarrowDownCompletion_canHandleEvent:event])
{
@@ -738,6 +787,16 @@
// e.g. for US keyboard "⌥u a" to insert ä
if (([event modifierFlags] & (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask|NSCommandKeyMask)) == NSAlternateKeyMask || [[event characters] length] == 0)
{
+ if(autoCompletionMode) {
+ if(commonPrefixWasInsertedByAutoComplete) {
+ [theView setSelectedRange:theCharRange];
+ [theView insertText:originalFilterString];
+ [theView setCompletionIsOpen:NO];
+ [self close];
+ [NSApp sendEvent:event];
+ break;
+ }
+ }
[NSApp sendEvent: event];
if(commaInsertionMode)
@@ -760,6 +819,15 @@
[theTableView setBackgroundColor:[NSColor colorWithCalibratedRed:0.9f green:0.9f blue:0.9f alpha:1.0f]];
[self filter];
} else {
+ if(autoCompletionMode) {
+ if(commonPrefixWasInsertedByAutoComplete) {
+ [theView setSelectedRange:theCharRange];
+ [theView insertText:originalFilterString];
+ }
+ [theView setCompletionIsOpen:NO];
+ [self close];
+ break;
+ }
if(cursorMovedLeft) [theView performSelector:@selector(moveRight:)];
break;
}
@@ -770,6 +838,14 @@
}
else if(key == NSBackspaceCharacter || key == NSDeleteCharacter)
{
+ if(autoCompletionMode) {
+ if(commonPrefixWasInsertedByAutoComplete) {
+ [theView setSelectedRange:theCharRange];
+ [theView insertText:originalFilterString];
+ }
+ [NSApp sendEvent:event];
+ break;
+ }
[NSApp sendEvent:event];
if([mutablePrefix length] == 0 || commaInsertionMode)
break;
@@ -782,6 +858,18 @@
}
else if([textualInputCharacters characterIsMember:key])
{
+
+ if(autoCompletionMode) {
+ [theView setCompletionIsOpen:NO];
+ [self close];
+ if(commonPrefixWasInsertedByAutoComplete) {
+ [theView setSelectedRange:theCharRange];
+ [theView insertText:originalFilterString];
+ }
+ [NSApp sendEvent:event];
+ return;
+ }
+
[NSApp sendEvent:event];
if(commaInsertionMode)
@@ -808,11 +896,18 @@
if(([event clickCount] == 2)) {
[self completeAndInsertSnippet];
} else {
- [NSApp sendEvent:event];
if(!NSPointInRect([NSEvent mouseLocation], [self frame])) {
+ if(autoCompletionMode) {
+ if(commonPrefixWasInsertedByAutoComplete) {
+ [theView setSelectedRange:theCharRange];
+ [theView insertText:originalFilterString];
+ }
+ }
if(cursorMovedLeft) [theView performSelector:@selector(moveRight:)];
+ [NSApp sendEvent:event];
break;
}
+ [NSApp sendEvent:event];
}
}
else
@@ -860,6 +955,7 @@
theCharRange.length += [toInsert length];
theParseRange.length += [toInsert length];
[theView insertText:[toInsert lowercaseString]];
+ commonPrefixWasInsertedByAutoComplete = YES;
[self checkSpaceForAllowedCharacter];
}
}
@@ -895,17 +991,7 @@
{
if([theTableView selectedRow] == -1) return;
- NSDictionary* selectedItem;
- NSInteger selectedRowNumber = [theTableView selectedRow];
- if(isQueryingDatabaseStructure) {
- if(selectedRowNumber < 1) {
- closeMe = YES;
- return;
- }
- selectedItem = [filtered objectAtIndex:selectedRowNumber-1];
- } else {
- selectedItem = [filtered objectAtIndex:selectedRowNumber];
- }
+ NSDictionary *selectedItem = [filtered objectAtIndex:[theTableView selectedRow]];
if([selectedItem objectForKey:@"noCompletion"]) {
closeMe = YES;
@@ -919,9 +1005,7 @@
if([selectedItem objectForKey:@"isRef"]
&& ([[NSApp currentEvent] modifierFlags] & (NSShiftKeyMask))
&& [[selectedItem objectForKey:@"path"] length]) {
- // NSString *path = [NSString stringWithFormat:@"%@.%@",
- // [[[selectedItem objectForKey:@"path"] componentsSeparatedByString:SPUniqueSchemaDelimiter] componentsJoinedByPeriodAndBacktickQuotedAndIgnoreFirst],
- // [candidateMatch backtickQuotedString]];
+
NSString *path = [[[selectedItem objectForKey:@"path"] componentsSeparatedByString:SPUniqueSchemaDelimiter] componentsJoinedByPeriodAndBacktickQuotedAndIgnoreFirst];
// Check if path's db name is the current selected db name
diff --git a/Source/SPPreferenceController.h b/Source/SPPreferenceController.h
index 9a6b3101..6d72fc1d 100644
--- a/Source/SPPreferenceController.h
+++ b/Source/SPPreferenceController.h
@@ -113,7 +113,6 @@
// Other
- (void)updateDefaultFavoritePopup;
- (void)selectFavorites:(NSArray *)favorite;
-- (void)selectFavoriteAtIndex:(NSUInteger)theIndex;
- (void)changeFont:(id)sender;
- (IBAction)favoriteTypeDidChange:(id)sender;
- (void)updateFavoritePasswordsFromField:(NSControl *)passwordControl;
diff --git a/Source/SPPreferenceController.m b/Source/SPPreferenceController.m
index 20173b32..080a319f 100644
--- a/Source/SPPreferenceController.m
+++ b/Source/SPPreferenceController.m
@@ -346,38 +346,22 @@
- (IBAction)removeFavorite:(id)sender
{
if ([favoritesTableView numberOfSelectedRows] == 1) {
-
- // 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];
- }
+ NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Remove favorite '%@'?", @"remove database message"), [favoritesController valueForKeyPath:@"selection.name"]]
+ defaultButton:NSLocalizedString(@"Remove", @"remove button")
+ alternateButton:NSLocalizedString(@"Cancel", @"cancel button")
+ otherButton:nil
+ informativeTextWithFormat:[NSString stringWithFormat:NSLocalizedString(@"Are you sure you want to remove the favorite '%@'? This operation cannot be undone.", @"remove database informative message"), [favoritesController valueForKeyPath:@"selection.name"]]];
- [favoritesController removeObjectAtArrangedObjectIndex:[favoritesTableView selectedRow]];
-
- [favoritesTableView reloadData];
-
- [self updateDefaultFavoritePopup];
+ NSArray *buttons = [alert buttons];
+
+ // Change the alert's cancel button to have the key equivalent of return
+ [[buttons objectAtIndex:0] setKeyEquivalent:@"d"];
+ [[buttons objectAtIndex:0] setKeyEquivalentModifierMask:NSCommandKeyMask];
+ [[buttons objectAtIndex:1] setKeyEquivalent:@"\r"];
+
+ [alert setAlertStyle:NSCriticalAlertStyle];
+
+ [alert beginSheetModalForWindow:[self window] modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:@"removeFavorite"];
}
}
@@ -1008,6 +992,53 @@
#pragma mark -
#pragma mark Other
+- (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];
+
+ // Remove the current database
+ 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];
+ }
+ }
+}
- (void)setGrowlEnabled:(BOOL)value
{
@@ -1076,17 +1107,6 @@
}
// -------------------------------------------------------------------------------
-// selectFavoriteAtIndex:
-//
-// Selects the favorite at the specified index in the favorites list
-// -------------------------------------------------------------------------------
-- (void)selectFavoriteAtIndex:(NSUInteger)theIndex
-{
- [favoritesController setSelectionIndex:theIndex];
- [favoritesTableView scrollRowToVisible:theIndex];
-}
-
-// -------------------------------------------------------------------------------
// global table font selection
// -------------------------------------------------------------------------------
// show the font panel
diff --git a/Source/SPProcessListController.m b/Source/SPProcessListController.m
index 06cd9881..0d4c653d 100644
--- a/Source/SPProcessListController.m
+++ b/Source/SPProcessListController.m
@@ -407,7 +407,7 @@
*/
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
- id object = [[processesFiltered objectAtIndex:row] valueForKey:[tableColumn identifier]];
+ id object = (row < [processesFiltered count]) ? [[processesFiltered objectAtIndex:row] valueForKey:[tableColumn identifier]] : @"";
return (![object isNSNull]) ? object : [prefs stringForKey:SPNullValue];
}
diff --git a/Source/SPQueryController.m b/Source/SPQueryController.m
index d34655e7..b6a64636 100644
--- a/Source/SPQueryController.m
+++ b/Source/SPQueryController.m
@@ -777,7 +777,8 @@ static SPQueryController *sharedQueryController = nil;
- (void)dealloc
{
messagesVisibleSet = nil;
-
+ [NSObject cancelPreviousPerformRequestsWithTarget:self];
+
[dateFormatter release], dateFormatter = nil;
[messagesFullSet release], messagesFullSet = nil;
@@ -941,8 +942,15 @@ static SPQueryController *sharedQueryController = nil;
*/
- (void)_addMessageToConsole:(NSString *)message connection:(NSString *)connection isError:(BOOL)error
{
- SPConsoleMessage *consoleMessage = [SPConsoleMessage consoleMessageWithMessage:[[[message stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] stringByReplacingOccurrencesOfString:@"\n" withString:@" "] stringByAppendingString:@";"] date:[NSDate date] connection:connection];
-
+ NSString *messageTemp = [[message stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] stringByReplacingOccurrencesOfString:@"\n" withString:@" "];
+
+ // Only append a semi-colon (;) if the supplied message is not an error
+ if (!error) {
+ messageTemp = [messageTemp stringByAppendingString:@";"];
+ }
+
+ SPConsoleMessage *consoleMessage = [SPConsoleMessage consoleMessageWithMessage:messageTemp date:[NSDate date] connection:connection];
+
[consoleMessage setIsError:error];
[messagesFullSet addObject:consoleMessage];
diff --git a/Source/SPSSHTunnel.h b/Source/SPSSHTunnel.h
index 27522d90..9230f7e1 100644
--- a/Source/SPSSHTunnel.h
+++ b/Source/SPSSHTunnel.h
@@ -52,6 +52,7 @@
NSString *keychainAccount;
NSString *requestedPassphrase;
NSMutableArray *debugMessages;
+ NSLock *debugMessagesLock;
BOOL useHostFallback;
BOOL requestedResponse;
BOOL passwordInKeychain;
diff --git a/Source/SPSSHTunnel.m b/Source/SPSSHTunnel.m
index 207dc300..eeef83d2 100644
--- a/Source/SPSSHTunnel.m
+++ b/Source/SPSSHTunnel.m
@@ -56,6 +56,7 @@
stateChangeSelector = nil;
lastError = nil;
debugMessages = [[NSMutableArray alloc] init];
+ debugMessagesLock = [[NSLock alloc] init];
answerAvailableLock = [[NSLock alloc] init];
// Set up a connection for use by the tunnel process
@@ -164,7 +165,10 @@
* by line endings.
*/
- (NSString *) debugMessages {
- return [debugMessages componentsJoinedByString:@"\n"];
+ [debugMessagesLock lock];
+ NSString *debugMessagesString = [debugMessages componentsJoinedByString:@"\n"];
+ [debugMessagesLock unlock];
+ return debugMessagesString;
}
/*
@@ -175,7 +179,9 @@
localPort = 0;
if (connectionState != PROXY_STATE_IDLE) return;
+ [debugMessagesLock lock];
[debugMessages removeAllObjects];
+ [debugMessagesLock unlock];
[NSThread detachNewThreadSelector:@selector(launchTask:) toTarget: self withObject: nil ];
}
@@ -345,7 +351,7 @@
// On tunnel close, clean up
[[NSNotificationCenter defaultCenter] removeObserver:self
name:@"NSFileHandleDataAvailableNotification"
- object:[standardError fileHandleForReading]];
+ object:nil];
[task release], task = nil;
[standardError release], standardError = nil;
[taskEnvironment release], taskEnvironment = nil;
@@ -369,7 +375,8 @@
}
/*
- * Processes messages recieved from the SSH task
+ * Processes messages recieved from the SSH task. These may be received singly
+ * or several stuck together.
*/
- (void)standardErrorHandler:(NSNotification*)aNotification
{
@@ -385,7 +392,9 @@
enumerator = [messages objectEnumerator];
while (message = [[enumerator nextObject] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]) {
if (![message length]) continue;
+ [debugMessagesLock lock];
[debugMessages addObject:[NSString stringWithString:message]];
+ [debugMessagesLock unlock];
if ([message rangeOfString:@"Entering interactive session."].location != NSNotFound) {
connectionState = PROXY_STATE_CONNECTED;
@@ -510,6 +519,7 @@
//show the question window
[NSApp beginSheet:sshQuestionDialog modalForWindow:parentWindow modalDelegate:self didEndSelector:nil contextInfo:nil];
+ [parentWindow makeKeyAndOrderFront:self];
}
/*
* Ends an existing modal session
@@ -575,6 +585,7 @@
windowFrameRect.size.height = ((queryTextSize.height < 40)?40:queryTextSize.height) + 140 + ([sshPasswordDialog isSheet]?0:22);
[sshPasswordDialog setFrame:windowFrameRect display:NO];
[NSApp beginSheet:sshPasswordDialog modalForWindow:parentWindow modalDelegate:self didEndSelector:nil contextInfo:nil];
+ [parentWindow makeKeyAndOrderFront:self];
}
/*
@@ -623,6 +634,7 @@
[tunnelConnection invalidate];
[tunnelConnection release];
[debugMessages release];
+ [debugMessagesLock release];
[answerAvailableLock tryLock];
[answerAvailableLock unlock];
[answerAvailableLock release];
diff --git a/Source/SPTableData.m b/Source/SPTableData.m
index 86cd550e..51d490d9 100644
--- a/Source/SPTableData.m
+++ b/Source/SPTableData.m
@@ -360,7 +360,7 @@
// A NULL value indicates that the user does not have permission to view the syntax
if ([[syntaxResult objectAtIndex:1] isNSNull]) {
[[NSAlert alertWithMessageText:NSLocalizedString(@"Permission Denied", @"Permission Denied")
- defaultButton:NSLocalizedString(@"OK", @"OK")
+ defaultButton:NSLocalizedString(@"OK", @"OK button")
alternateButton:nil otherButton:nil
informativeTextWithFormat:NSLocalizedString(@"The creation syntax could not be retrieved due to a permissions error.\n\nPlease check your user permissions with an administrator.", @"Create syntax permission denied detail")]
beginSheetModalForWindow:[NSApp mainWindow]
@@ -725,7 +725,7 @@
// A NULL value indicates that the user does not have permission to view the syntax
if ([syntaxString isNSNull]) {
[[NSAlert alertWithMessageText:NSLocalizedString(@"Permission Denied", @"Permission Denied")
- defaultButton:NSLocalizedString(@"OK", @"OK")
+ defaultButton:NSLocalizedString(@"OK", @"OK button")
alternateButton:nil otherButton:nil
informativeTextWithFormat:NSLocalizedString(@"The creation syntax could not be retrieved due to a permissions error.\n\nPlease check your user permissions with an administrator.", @"Create syntax permission denied detail")]
beginSheetModalForWindow:[NSApp mainWindow]
@@ -1088,25 +1088,29 @@
NSInteger i;
NSMutableArray *keyColumns = [NSMutableArray array];
- r = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW COLUMNS FROM %@ WHERE `key` = 'PRI'", [selectedTable backtickQuotedString]]];
+ // select all columns that are primary keys
+ // MySQL before 5.0.3 does not support the WHERE syntax
+ r = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW COLUMNS FROM %@ /*!50003 WHERE `key` = 'PRI'*/", [selectedTable backtickQuotedString]]];
[r setReturnDataAsStrings:YES];
if([r numOfRows] < 1) return nil;
if ([mySQLConnection queryErrored]) {
if ([mySQLConnection isConnected])
- NSRunAlertPanel(@"Error", [NSString stringWithFormat:@"An error occured while retrieving the PRIAMRY KEY data:\n\n%@", [mySQLConnection getLastErrorMessage]], @"OK", nil, nil);
+ NSRunAlertPanel(@"Error", [NSString stringWithFormat:NSLocalizedString(@"An error occured while retrieving the PRIMARY KEY data:\n\n%@",@"message when the query that fetches the primary keys fails"), [mySQLConnection getLastErrorMessage]], @"OK", nil, nil);
return nil;
}
for( i = 0; i < [r numOfRows]; i++ ) {
resultRow = [r fetchRowAsArray];
- [keyColumns addObject:[NSArrayObjectAtIndex(resultRow, 0) description]];
+ // check if the row is indeed a key (for MySQL servers before 5.0.3)
+ if ([[[resultRow objectAtIndex:3] description] isEqualToString:@"PRI"]) {
+ [keyColumns addObject:[[resultRow objectAtIndex:0] description]];
+ }
}
- if([keyColumns count])
- return keyColumns;
+ if([keyColumns count]) return keyColumns;
return nil;
}
diff --git a/Source/SPTableRelations.m b/Source/SPTableRelations.m
index 34b18eae..01fec02e 100644
--- a/Source/SPTableRelations.m
+++ b/Source/SPTableRelations.m
@@ -497,8 +497,8 @@
[[constraint objectForKey:@"columns"] objectAtIndex:0], @"columns",
[constraint objectForKey:@"ref_table"], @"fk_table",
[constraint objectForKey:@"ref_columns"], @"fk_columns",
- [constraint objectForKey:@"update"], @"on_update",
- [constraint objectForKey:@"delete"], @"on_delete",
+ ([constraint objectForKey:@"update"] ? [constraint objectForKey:@"update"] : @""), @"on_update",
+ ([constraint objectForKey:@"delete"] ? [constraint objectForKey:@"delete"] : @""), @"on_delete",
nil]];
}
diff --git a/Source/SPTableView.m b/Source/SPTableView.m
index 0a1592ca..e53a38e8 100644
--- a/Source/SPTableView.m
+++ b/Source/SPTableView.m
@@ -38,7 +38,8 @@
{
// If TableDocument is performing a task suppress any context menu
- if([[[self window] delegate] isWorking])
+ if ([[[[[self window] delegate] class] description] isEqualToString:@"TableDocument"]
+ && [[[self window] delegate] isWorking])
return nil;
// If more than one row is selected only returns the default contextual menu
diff --git a/Source/SPTooltip.m b/Source/SPTooltip.m
index 3f8c2529..fc7133e2 100644
--- a/Source/SPTooltip.m
+++ b/Source/SPTooltip.m
@@ -240,6 +240,7 @@ static CGFloat slow_in_out (CGFloat t)
- (void)dealloc
{
+ [NSObject cancelPreviousPerformRequestsWithTarget:self];
[didOpenAtDate release];
[webView release];
[webPreferences release];
diff --git a/Source/SPUserManager.xcdatamodel/elements b/Source/SPUserManager.xcdatamodel/elements
index 06b2000e..33881efc 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 4bc143ed..14b1545d 100644
--- a/Source/SPUserManager.xcdatamodel/layout
+++ b/Source/SPUserManager.xcdatamodel/layout
Binary files differ
diff --git a/Source/TableContent.m b/Source/TableContent.m
index 78e47b9a..4710abef 100644
--- a/Source/TableContent.m
+++ b/Source/TableContent.m
@@ -221,7 +221,7 @@
}
// Update display if necessary
- [tableContentView performSelectorOnMainThread:@selector(displayIfNeeded) withObject:nil waitUntilDone:NO];
+ [[tableContentView onMainThread] setNeedsDisplay:YES];
// Init copyTable with necessary information for copying selected rows as SQL INSERT
[tableContentView setTableInstance:self withTableData:tableValues withColumns:dataColumns withTableName:selectedTable withConnection:mySQLConnection];
@@ -550,6 +550,9 @@
// If no table is selected, return
if (!selectedTable) return;
+ // Wrap the values load in an autorelease pool to ensure full and timely release
+ NSAutoreleasePool *loadPool = [[NSAutoreleasePool alloc] init];
+
NSMutableString *queryString;
NSString *queryStringBeforeLimit = nil;
NSString *filterString;
@@ -670,6 +673,8 @@
// Trigger a full reload if required
if (fullTableReloadRequired) [self reloadTable:self];
+
+ [loadPool drain];
}
/*
@@ -731,16 +736,16 @@
[tableDocumentInstance setTaskPercentage:(rowsProcessed*relativeTargetRowCount)];
} else if (rowsProcessed == targetRowCount) {
[tableDocumentInstance setTaskPercentage:100.0];
- [tableDocumentInstance performSelectorOnMainThread:@selector(setTaskProgressToIndeterminateAfterDelay:) withObject:[NSNumber numberWithBool:YES] waitUntilDone:NO];
+ [[tableDocumentInstance onMainThread] setTaskProgressToIndeterminateAfterDelay:YES];
}
}
// Update the table view with new results every now and then
if (rowsProcessed > nextTableDisplayBoundary) {
if (rowsProcessed > tableRowsCount) tableRowsCount = rowsProcessed;
- [tableContentView performSelectorOnMainThread:@selector(noteNumberOfRowsChanged) withObject:nil waitUntilDone:NO];
+ [[tableContentView onMainThread] noteNumberOfRowsChanged];
if (!tableViewRedrawn) {
- [tableContentView performSelectorOnMainThread:@selector(displayIfNeeded) withObject:nil waitUntilDone:NO];
+ [[tableContentView onMainThread] setNeedsDisplay:YES];
tableViewRedrawn = YES;
}
nextTableDisplayBoundary *= 2;
@@ -1324,8 +1329,6 @@
//copy row
tempRow = [tableValues rowContentsAtIndex:[tableContentView selectedRow]];
- [tableValues insertRowContents:tempRow atIndex:[tableContentView selectedRow]+1];
- tableRowsCount++;
//if we don't show blobs, read data for this duplicate column from db
if ([prefs boolForKey:SPLoadBlobsAsNeeded]) {
@@ -1350,7 +1353,11 @@
[tempRow replaceObjectAtIndex:i withObject:[dbDataRow objectAtIndex:i]];
}
}
-
+
+ //insert the copied row
+ [tableValues insertRowContents:tempRow atIndex:[tableContentView selectedRow]+1];
+ tableRowsCount++;
+
//select row and go in edit mode
[tableContentView reloadData];
[tableContentView selectRowIndexes:[NSIndexSet indexSetWithIndex:[tableContentView selectedRow]+1] byExtendingSelection:NO];
@@ -1367,8 +1374,11 @@
- (IBAction)removeRow:(id)sender
{
// Check whether a save of the current row is required.
- if (![self saveRowOnDeselect])
- return;
+ //if (![self saveRowOnDeselect])
+ // return;
+
+ // cancel editing (maybe this is not the ideal method -- see xcode docs for that method)
+ [tableWindow endEditingFor:nil];
if (![tableContentView numberOfSelectedRows])
return;
@@ -1393,7 +1403,7 @@
NSString *contextInfo = @"removerow";
- if (([tableContentView numberOfSelectedRows] == [tableContentView numberOfRows]) && !isFiltered && !isLimited && !isInterruptedLoad) {
+ if (([tableContentView numberOfSelectedRows] == [tableContentView numberOfRows]) && !isFiltered && !isLimited && !isInterruptedLoad && !isEditingNewRow) {
contextInfo = @"removeallrows";
@@ -2158,6 +2168,14 @@
[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] ) {
@@ -2178,12 +2196,44 @@
}
} else if ( [contextInfo isEqualToString:@"removerow"] ) {
if ( returnCode == NSAlertDefaultReturn ) {
-
- errors = 0;
-
[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];
@@ -2348,6 +2398,7 @@
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"],
@@ -2379,6 +2430,9 @@
[tableContentView reloadData];
}
[tableContentView deselectAll:self];
+ } else {
+ // The user clicked cancel in the "sure you wanna delete" message
+ // restore editing or whatever
}
}
}
@@ -3186,6 +3240,7 @@
isEditingNewRow = NO;
tableRowsCount--;
[tableValues removeRowAtIndex:row];
+ [self updateCountText];
[tableContentView reloadData];
}
currentlyEditingRow = -1;
diff --git a/Source/TableDocument.h b/Source/TableDocument.h
index 5f173f9d..4b151615 100644
--- a/Source/TableDocument.h
+++ b/Source/TableDocument.h
@@ -58,7 +58,7 @@
IBOutlet NSSearchField *listFilterField;
- IBOutlet id tableWindow;
+ IBOutlet NSWindow *tableWindow;
IBOutlet id titleAccessoryView;
IBOutlet id titleImageView;
@@ -168,6 +168,7 @@
}
- (BOOL)isUntitled;
+- (BOOL)couldCommitCurrentViewActions;
- (void)initQueryEditorWithString:(NSString *)query;
- (void)initWithConnectionFile:(NSString *)path;
diff --git a/Source/TableDocument.m b/Source/TableDocument.m
index b8527698..cc1a5bc5 100644
--- a/Source/TableDocument.m
+++ b/Source/TableDocument.m
@@ -239,7 +239,7 @@
[taskProgressWindow setAlphaValue:0.0];
[taskProgressWindow orderFront:self];
[tableWindow addChildWindow:taskProgressWindow ordered:NSWindowAbove];
- [taskProgressWindow release];
+ [taskProgressWindow setReleasedWhenClosed:YES];
[taskProgressWindow setContentView:taskProgressLayer];
[self centerTaskWindow];
}
@@ -273,8 +273,8 @@
[connectionController setSshUser:@""];
[connectionController setSshPort:@""];
[connectionController setDatabase:@""];
- [connectionController setPassword:@""];
- [connectionController setSshPassword:@""];
+ [connectionController setPassword:nil];
+ [connectionController setSshPassword:nil];
// Deselect all favorites
[[connectionController valueForKeyPath:@"favoritesTable"] deselectAll:connectionController];
@@ -615,6 +615,10 @@
*/
- (IBAction)backForwardInHistory:(id)sender
{
+
+ // Ensure history navigation is permitted - trigger end editing and any required saves
+ if (![self couldCommitCurrentViewActions]) return;
+
switch ([sender tag])
{
// Go backward
@@ -1283,7 +1287,8 @@
* Sets the task progress indicator back to indeterminate (also performed
* automatically whenever a new task is started).
* This can optionally be called with afterDelay set, in which case the intederminate
- * switch will be made a fter a short pause to minimise flicker for short actions.
+ * switch will be made after a short pause to minimise flicker for short actions.
+ * Should be called on the main thread.
*/
- (void) setTaskProgressToIndeterminateAfterDelay:(BOOL)afterDelay
{
@@ -1675,7 +1680,7 @@
// A NULL value indicates that the user does not have permission to view the syntax
if ([tableSyntax isNSNull]) {
[[NSAlert alertWithMessageText:NSLocalizedString(@"Permission Denied", @"Permission Denied")
- defaultButton:NSLocalizedString(@"OK", @"OK")
+ defaultButton:NSLocalizedString(@"OK", @"OK button")
alternateButton:nil otherButton:nil
informativeTextWithFormat:NSLocalizedString(@"The creation syntax could not be retrieved due to a permissions error.\n\nPlease check your user permissions with an administrator.", @"Create syntax permission denied detail")]
beginSheetModalForWindow:tableWindow
@@ -1687,7 +1692,7 @@
[createTableSyntaxTextView setEditable:YES];
[createTableSyntaxTextView setString:@""];
- [createTableSyntaxTextView insertText:([tablesListInstance tableType] == SPTableTypeView) ? [tableSyntax createViewSyntaxPrettifier] : tableSyntax];
+ [createTableSyntaxTextView insertText:([tablesListInstance tableType] == SPTableTypeView) ? [[tableSyntax createViewSyntaxPrettifier] stringByAppendingString:@";"] : [tableSyntax stringByAppendingString:@";"]];
[createTableSyntaxTextView setEditable:NO];
[createTableSyntaxWindow makeFirstResponder:createTableSyntaxTextField];
@@ -1744,7 +1749,7 @@
// A NULL value indicates that the user does not have permission to view the syntax
if ([tableSyntax isNSNull]) {
[[NSAlert alertWithMessageText:NSLocalizedString(@"Permission Denied", @"Permission Denied")
- defaultButton:NSLocalizedString(@"OK", @"OK")
+ defaultButton:NSLocalizedString(@"OK", @"OK button")
alternateButton:nil otherButton:nil
informativeTextWithFormat:NSLocalizedString(@"The creation syntax could not be retrieved due to a permissions error.\n\nPlease check your user permissions with an administrator.", @"Create syntax permission denied detail")]
beginSheetModalForWindow:tableWindow
@@ -2397,6 +2402,31 @@
return ([[self fileURL] isFileURL]) ? NO : YES;
}
+/**
+ * Asks any currently editing views to commit their changes;
+ * returns YES if changes were successfully committed, and NO
+ * if an error occurred or user interaction is required.
+ */
+- (BOOL)couldCommitCurrentViewActions
+{
+ [tableWindow endEditingFor:nil];
+ switch ([tableTabView indexOfTabViewItem:[tableTabView selectedTabViewItem]]) {
+
+ // Table structure view
+ case 0:
+ return [tableSourceInstance saveRowOnDeselect];
+
+ // Table content view
+ case 1:
+ return [tableContentInstance saveRowOnDeselect];
+
+ default:
+ break;
+ }
+
+ return YES;
+}
+
#pragma mark -
#pragma mark Accessor methods
@@ -2859,7 +2889,7 @@
break;
case SPSocketConnection:
aString = @"SPSocketConnection";
- [connection setObject:[connectionController socket] forKey:@"socket"];
+ if ([connectionController socket] && [[connectionController socket] length]) [connection setObject:[connectionController socket] forKey:@"socket"];
break;
case SPSSHTunnelConnection:
aString = @"SPSSHTunnelConnection";
@@ -2877,8 +2907,8 @@
if([[spfDocData_temp objectForKey:@"save_password"] boolValue]) {
NSString *pw = [self keychainPasswordForConnection:nil];
if(![pw length]) pw = [connectionController password];
- [connection setObject:pw forKey:@"password"];
- if([connectionController type] == SPSSHTunnelConnection)
+ if (pw) [connection setObject:pw forKey:@"password"];
+ if([connectionController type] == SPSSHTunnelConnection && [connectionController sshPassword])
[connection setObject:[connectionController sshPassword] forKey:@"ssh_password"];
}
@@ -3192,10 +3222,9 @@
- (IBAction)viewStructure:(id)sender
{
- // Cancel the selection if currently editing a content row and unable to save
- if ([tableTabView indexOfTabViewItem:[tableTabView selectedTabViewItem]] == 1
- && ![tableContentInstance saveRowOnDeselect]) {
- [mainToolbar setSelectedItemIdentifier:SPMainToolbarTableContent];
+ // Cancel the selection if currently editing a view and unable to save
+ if (![self couldCommitCurrentViewActions]) {
+ [mainToolbar setSelectedItemIdentifier:*SPViewModeToMainToolbarMap[[prefs integerForKey:SPLastViewMode]]];
return;
}
@@ -3208,10 +3237,10 @@
- (IBAction)viewContent:(id)sender
{
- // Cancel the selection if currently editing structure/a field and unable to save
- if ([tableTabView indexOfTabViewItem:[tableTabView selectedTabViewItem]] == 0
- && ![tableSourceInstance saveRowOnDeselect]) {
- [mainToolbar setSelectedItemIdentifier:SPMainToolbarTableStructure];
+
+ // Cancel the selection if currently editing a view and unable to save
+ if (![self couldCommitCurrentViewActions]) {
+ [mainToolbar setSelectedItemIdentifier:*SPViewModeToMainToolbarMap[[prefs integerForKey:SPLastViewMode]]];
return;
}
@@ -3224,17 +3253,10 @@
- (IBAction)viewQuery:(id)sender
{
- // Cancel the selection if currently editing structure/a field and unable to save
- if ([tableTabView indexOfTabViewItem:[tableTabView selectedTabViewItem]] == 0
- && ![tableSourceInstance saveRowOnDeselect]) {
- [mainToolbar setSelectedItemIdentifier:SPMainToolbarTableStructure];
- return;
- }
- // Cancel the selection if currently editing a content row and unable to save
- if ([tableTabView indexOfTabViewItem:[tableTabView selectedTabViewItem]] == 1
- && ![tableContentInstance saveRowOnDeselect]) {
- [mainToolbar setSelectedItemIdentifier:SPMainToolbarTableContent];
+ // Cancel the selection if currently editing a view and unable to save
+ if (![self couldCommitCurrentViewActions]) {
+ [mainToolbar setSelectedItemIdentifier:*SPViewModeToMainToolbarMap[[prefs integerForKey:SPLastViewMode]]];
return;
}
@@ -3242,25 +3264,18 @@
[mainToolbar setSelectedItemIdentifier:SPMainToolbarCustomQuery];
[spHistoryControllerInstance updateHistoryEntries];
- // Set the focus on the text field if no query has been run
- if (![[customQueryTextView string] length]) [tableWindow makeFirstResponder:customQueryTextView];
+ // Set the focus on the text field
+ [tableWindow makeFirstResponder:customQueryTextView];
[prefs setInteger:SPQueryEditorViewMode forKey:SPLastViewMode];
}
- (IBAction)viewStatus:(id)sender
{
- // Cancel the selection if currently editing structure/a field and unable to save
- if ([tableTabView indexOfTabViewItem:[tableTabView selectedTabViewItem]] == 0
- && ![tableSourceInstance saveRowOnDeselect]) {
- [mainToolbar setSelectedItemIdentifier:SPMainToolbarTableStructure];
- return;
- }
- // Cancel the selection if currently editing a content row and unable to save
- if ([tableTabView indexOfTabViewItem:[tableTabView selectedTabViewItem]] == 1
- && ![tableContentInstance saveRowOnDeselect]) {
- [mainToolbar setSelectedItemIdentifier:SPMainToolbarTableContent];
+ // Cancel the selection if currently editing a view and unable to save
+ if (![self couldCommitCurrentViewActions]) {
+ [mainToolbar setSelectedItemIdentifier:*SPViewModeToMainToolbarMap[[prefs integerForKey:SPLastViewMode]]];
return;
}
@@ -3281,17 +3296,10 @@
- (IBAction)viewRelations:(id)sender
{
- // Cancel the selection if currently editing structure/a field and unable to save
- if ([tableTabView indexOfTabViewItem:[tableTabView selectedTabViewItem]] == 0
- && ![tableSourceInstance saveRowOnDeselect]) {
- [mainToolbar setSelectedItemIdentifier:SPMainToolbarTableStructure];
- return;
- }
- // Cancel the selection if currently editing a content row and unable to save
- if ([tableTabView indexOfTabViewItem:[tableTabView selectedTabViewItem]] == 1
- && ![tableContentInstance saveRowOnDeselect]) {
- [mainToolbar setSelectedItemIdentifier:SPMainToolbarTableContent];
+ // Cancel the selection if currently editing a view and unable to save
+ if (![self couldCommitCurrentViewActions]) {
+ [mainToolbar setSelectedItemIdentifier:*SPViewModeToMainToolbarMap[[prefs integerForKey:SPLastViewMode]]];
return;
}
@@ -3304,21 +3312,13 @@
- (IBAction)viewTriggers:(id)sender
{
- // Cancel the selection if currently editing structure/a field and unable to save
- if ([tableTabView indexOfTabViewItem:[tableTabView selectedTabViewItem]] == 0
- && ![tableSourceInstance saveRowOnDeselect]) {
- [mainToolbar setSelectedItemIdentifier:SPMainToolbarTableStructure];
- return;
- }
-
- // Cancel the selection if currently editing a content row and unable to save
- if ([tableTabView indexOfTabViewItem:[tableTabView selectedTabViewItem]] == 1
- && ![tableContentInstance saveRowOnDeselect]) {
- [mainToolbar setSelectedItemIdentifier:SPMainToolbarTableContent];
+
+ // Cancel the selection if currently editing a view and unable to save
+ if (![self couldCommitCurrentViewActions]) {
+ [mainToolbar setSelectedItemIdentifier:*SPViewModeToMainToolbarMap[[prefs integerForKey:SPLastViewMode]]];
return;
}
-
[tableTabView selectTabViewItemAtIndex:5];
[mainToolbar setSelectedItemIdentifier:SPMainToolbarTableTriggers];
[spHistoryControllerInstance updateHistoryEntries];
@@ -4037,12 +4037,15 @@
[prefs removeObserver:self forKeyPath:SPConsoleEnableLogging];
if (processListController) [prefs removeObserver:processListController forKeyPath:SPDisplayTableViewVerticalGridlines];
if (serverVariablesController) [prefs removeObserver:serverVariablesController forKeyPath:SPDisplayTableViewVerticalGridlines];
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [NSObject cancelPreviousPerformRequestsWithTarget:self];
[_encoding release];
[allDatabases release];
[allSystemDatabases release];
[printWebView release];
-
+ [taskProgressWindow close];
+
if (connectionController) [connectionController release];
if (processListController) [processListController release];
if (serverVariablesController) [serverVariablesController release];
diff --git a/Source/TableDump.m b/Source/TableDump.m
index a905d7be..50d380f0 100644
--- a/Source/TableDump.m
+++ b/Source/TableDump.m
@@ -749,7 +749,7 @@
[tableDocumentInstance setDatabases:self];
// Update current selected database
- [tableDocumentInstance performSelector:@selector(refreshCurrentDatabase) withObject:nil afterDelay:0.1];
+ [[tableDocumentInstance onMainThread] refreshCurrentDatabase];
// Update current database tables
[tablesListInstance updateTables:self];
@@ -867,6 +867,13 @@
[csvDataBuffer release];
[parsedRows release];
[parsePositions release];
+ if(csvImportTailString) [csvImportTailString release], csvImportTailString = nil;
+ if(csvImportHeaderString) [csvImportHeaderString release], csvImportHeaderString = nil;
+ if(fieldMappingArray) [fieldMappingArray release], fieldMappingArray = nil;
+ if(fieldMappingGlobalValueArray) [fieldMappingGlobalValueArray release], fieldMappingGlobalValueArray = nil;
+ if(fieldMappingTableColumnNames) [fieldMappingTableColumnNames release], fieldMappingTableColumnNames = nil;
+ if(fieldMappingTableDefaultValues) [fieldMappingTableDefaultValues release], fieldMappingTableDefaultValues = nil;
+ if(fieldMapperOperator) [fieldMapperOperator release], fieldMapperOperator = nil;
[importPool drain];
[tableDocumentInstance setQueryMode:SPInterfaceQueryMode];
return;
@@ -907,6 +914,13 @@
[csvDataBuffer release];
[parsedRows release];
[parsePositions release];
+ if(csvImportTailString) [csvImportTailString release], csvImportTailString = nil;
+ if(csvImportHeaderString) [csvImportHeaderString release], csvImportHeaderString = nil;
+ if(fieldMappingArray) [fieldMappingArray release], fieldMappingArray = nil;
+ if(fieldMappingGlobalValueArray) [fieldMappingGlobalValueArray release], fieldMappingGlobalValueArray = nil;
+ if(fieldMappingTableColumnNames) [fieldMappingTableColumnNames release], fieldMappingTableColumnNames = nil;
+ if(fieldMappingTableDefaultValues) [fieldMappingTableDefaultValues release], fieldMappingTableDefaultValues = nil;
+ if(fieldMapperOperator) [fieldMapperOperator release], fieldMapperOperator = nil;
[importPool drain];
[tableDocumentInstance setQueryMode:SPInterfaceQueryMode];
return;
@@ -951,6 +965,13 @@
[csvDataBuffer release];
[parsedRows release];
[parsePositions release];
+ if(csvImportTailString) [csvImportTailString release], csvImportTailString = nil;
+ if(csvImportHeaderString) [csvImportHeaderString release], csvImportHeaderString = nil;
+ if(fieldMappingArray) [fieldMappingArray release], fieldMappingArray = nil;
+ if(fieldMappingGlobalValueArray) [fieldMappingGlobalValueArray release], fieldMappingGlobalValueArray = nil;
+ if(fieldMappingTableColumnNames) [fieldMappingTableColumnNames release], fieldMappingTableColumnNames = nil;
+ if(fieldMappingTableDefaultValues) [fieldMappingTableDefaultValues release], fieldMappingTableDefaultValues = nil;
+ if(fieldMapperOperator) [fieldMapperOperator release], fieldMapperOperator = nil;
[importPool drain];
[tableDocumentInstance setQueryMode:SPInterfaceQueryMode];
return;
@@ -988,7 +1009,23 @@
if (!fieldMappingArray) continue;
// Before entering the following loop, check that we actually have a connection. If not, bail.
- if (![mySQLConnection isConnected]) return;
+ if (![mySQLConnection isConnected]) {
+ [self closeAndStopProgressSheet];
+ [csvParser release];
+ [csvDataBuffer release];
+ [parsedRows release];
+ [parsePositions release];
+ if(csvImportTailString) [csvImportTailString release], csvImportTailString = nil;
+ if(csvImportHeaderString) [csvImportHeaderString release], csvImportHeaderString = nil;
+ if(fieldMappingArray) [fieldMappingArray release], fieldMappingArray = nil;
+ if(fieldMappingGlobalValueArray) [fieldMappingGlobalValueArray release], fieldMappingGlobalValueArray = nil;
+ if(fieldMappingTableColumnNames) [fieldMappingTableColumnNames release], fieldMappingTableColumnNames = nil;
+ if(fieldMappingTableDefaultValues) [fieldMappingTableDefaultValues release], fieldMappingTableDefaultValues = nil;
+ if(fieldMapperOperator) [fieldMapperOperator release], fieldMapperOperator = nil;
+ [importPool drain];
+ [tableDocumentInstance setQueryMode:SPInterfaceQueryMode];
+ return;
+ }
// If we have more than the csvRowsPerQuery amount, or if we're at the end of the
// available data, construct and run a query.
@@ -1123,13 +1160,13 @@
[csvDataBuffer release];
[parsedRows release];
[parsePositions release];
- if(csvImportTailString) [csvImportTailString release]; csvImportTailString = nil;
- if(csvImportHeaderString) [csvImportHeaderString release]; csvImportHeaderString = nil;
- if(fieldMappingArray) [fieldMappingArray release]; fieldMappingArray = nil;
- if(fieldMappingGlobalValueArray) [fieldMappingGlobalValueArray release]; fieldMappingGlobalValueArray = nil;
- if(fieldMappingTableColumnNames) [fieldMappingTableColumnNames release]; fieldMappingTableColumnNames = nil;
- if(fieldMappingTableDefaultValues) [fieldMappingTableDefaultValues release]; fieldMappingTableDefaultValues = nil;
- if(fieldMapperOperator) [fieldMapperOperator release]; fieldMapperOperator = nil;
+ if(csvImportTailString) [csvImportTailString release], csvImportTailString = nil;
+ if(csvImportHeaderString) [csvImportHeaderString release], csvImportHeaderString = nil;
+ if(fieldMappingArray) [fieldMappingArray release], fieldMappingArray = nil;
+ if(fieldMappingGlobalValueArray) [fieldMappingGlobalValueArray release], fieldMappingGlobalValueArray = nil;
+ if(fieldMappingTableColumnNames) [fieldMappingTableColumnNames release], fieldMappingTableColumnNames = nil;
+ if(fieldMappingTableDefaultValues) [fieldMappingTableDefaultValues release], fieldMappingTableDefaultValues = nil;
+ if(fieldMapperOperator) [fieldMapperOperator release], fieldMapperOperator = nil;
[importPool drain];
[tableDocumentInstance setQueryMode:SPInterfaceQueryMode];
@@ -1886,6 +1923,7 @@
// Only continue if the "create syntax" is specified in the export dialog
if ([addCreateTableSwitch state] == NSOffState) {
[proceduresList release];
+ [procedureInfo release];
continue;
}
diff --git a/Source/TableSource.m b/Source/TableSource.m
index e80b7c43..6ff0d971 100644
--- a/Source/TableSource.m
+++ b/Source/TableSource.m
@@ -913,16 +913,27 @@ closes the keySheet
}
}
-/*
- * Show Error sheet (can be called from inside of a endSheet selector)
- * via [self performSelector:@selector(showErrorSheetWithTitle:) withObject: afterDelay:]
+/**
+ * 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.
*/
--(void)showErrorSheetWith:(id)error
+-(void)showErrorSheetWith:(NSDictionary *)errorDictionary
{
- // error := first object is the title , second the message, only one button OK
- SPBeginAlertSheet([error objectAtIndex:0], NSLocalizedString(@"OK", @"OK button"),
+
+ // If this method has been called directly, invoke a delay. Invoking the delay
+ // on the main thread ensures the timer fires on the main thread.
+ if (![errorDictionary objectForKey:@"delayed"]) {
+ NSMutableDictionary *delayedErrorDictionary = [NSMutableDictionary dictionaryWithDictionary:errorDictionary];
+ [delayedErrorDictionary setObject:[NSNumber numberWithBool:YES] forKey:@"delayed"];
+ [self performSelector:@selector(showErrorSheetWith:) withObject:delayedErrorDictionary afterDelay:0.3];
+ return;
+ }
+
+ // Display the error sheet
+ SPBeginAlertSheet([errorDictionary objectForKey:@"title"], NSLocalizedString(@"OK", @"OK button"),
nil, nil, tableWindow, self, nil, nil, nil,
- [error objectAtIndex:1]);
+ [errorDictionary objectForKey:@"message"]);
}
/**
@@ -1751,11 +1762,10 @@ 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 remove relation", @"error removing relation message"),
- NSLocalizedString(@"OK", @"OK button"),
- nil, nil, [NSApp mainWindow], nil, nil, nil, nil,
- [NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to remove the relation '%@'.\n\nMySQL said: %@", @"error removing relation informative message"), relationName, [mySQLConnection getLastErrorMessage]]);
+ NSMutableDictionary *errorDictionary = [NSMutableDictionary dictionary];
+ [errorDictionary setObject:NSLocalizedString(@"Unable to remove relation", @"error removing relation message") forKey:@"title"];
+ [errorDictionary setObject:[NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to remove the relation '%@'.\n\nMySQL said: %@", @"error removing relation informative message"), relationName, [mySQLConnection getLastErrorMessage]] forKey:@"message"];
+ [[self onMainThread] showErrorSheetWith:errorDictionary];
}
}
@@ -1766,13 +1776,12 @@ would result in a position change.
// Check for errors, but only if the query wasn't cancelled
if ([mySQLConnection queryErrored] && ![mySQLConnection queryCancelled]) {
- [self performSelector:@selector(showErrorSheetWith:)
- withObject:[NSArray arrayWithObjects:NSLocalizedString(@"Error", @"error"),
- [NSString stringWithFormat:NSLocalizedString(@"Couldn't remove field %@.\nMySQL said: %@", @"message of panel when field cannot be removed"),
- [[tableFields objectAtIndex:[tableSourceView selectedRow]] objectForKey:@"Field"],
- [mySQLConnection getLastErrorMessage]],
- nil]
- afterDelay:0.3];
+ NSMutableDictionary *errorDictionary = [NSMutableDictionary dictionary];
+ [errorDictionary setObject:NSLocalizedString(@"Error", @"error") forKey:@"title"];
+ [errorDictionary setObject:[NSString stringWithFormat:NSLocalizedString(@"Couldn't remove field %@.\nMySQL said: %@", @"message of panel when field cannot be removed"),
+ [[tableFields objectAtIndex:[tableSourceView selectedRow]] objectForKey:@"Field"],
+ [mySQLConnection getLastErrorMessage]] forKey:@"message"];
+ [[self onMainThread] showErrorSheetWith:errorDictionary];
}
else {
[tableDataInstance resetAllData];
@@ -1819,11 +1828,10 @@ 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 remove relation", @"error removing relation message"),
- NSLocalizedString(@"OK", @"OK button"),
- nil, nil, [NSApp mainWindow], nil, nil, nil, nil,
- [NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to remove the relation '%@'.\n\nMySQL said: %@", @"error removing relation informative message"), constraintName, [mySQLConnection getLastErrorMessage]]);
+ NSMutableDictionary *errorDictionary = [NSMutableDictionary dictionary];
+ [errorDictionary setObject:NSLocalizedString(@"Unable to remove relation", @"error removing relation message") forKey:@"title"];
+ [errorDictionary setObject:[NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to remove the relation '%@'.\n\nMySQL said: %@", @"error removing relation informative message"), constraintName, [mySQLConnection getLastErrorMessage]] forKey:@"message"];
+ [[self onMainThread] showErrorSheetWith:errorDictionary];
}
}
@@ -1837,11 +1845,10 @@ would result in a position change.
// Check for errors, but only if the query wasn't cancelled
if ([mySQLConnection queryErrored] && ![mySQLConnection queryCancelled]) {
-
- [self performSelector:@selector(showErrorSheetWith:)
- withObject:[NSArray arrayWithObjects:NSLocalizedString(@"Unable to remove index", @"error removing index message"),
- [NSString stringWithFormat:NSLocalizedString(@"An error occured while trying to remove the index.\n\nMySQL said: %@", @"error removing index informative message"), [mySQLConnection getLastErrorMessage]], nil]
- afterDelay:0.3];
+ NSMutableDictionary *errorDictionary = [NSMutableDictionary dictionary];
+ [errorDictionary setObject:NSLocalizedString(@"Unable to remove index", @"error removing index message") forKey:@"title"];
+ [errorDictionary setObject:[NSString stringWithFormat:NSLocalizedString(@"An error occured while trying to remove the index.\n\nMySQL said: %@", @"error removing index informative message"), [mySQLConnection getLastErrorMessage]] forKey:@"message"];
+ [[self onMainThread] showErrorSheetWith:errorDictionary];
}
else {
[tableDataInstance resetAllData];
diff --git a/Source/TablesList.h b/Source/TablesList.h
index 12ea61e3..f6741369 100644
--- a/Source/TablesList.h
+++ b/Source/TablesList.h
@@ -74,17 +74,23 @@
IBOutlet NSSearchField *listFilterField;
+ // Table list 'gear' menu items
IBOutlet NSMenuItem *removeTableMenuItem;
IBOutlet NSMenuItem *duplicateTableMenuItem;
IBOutlet NSMenuItem *renameTableMenuItem;
IBOutlet NSMenuItem *separatorTableMenuItem;
+ IBOutlet NSMenuItem *showCreateSyntaxMenuItem;
+ IBOutlet NSMenuItem *separatorTableMenuItem2;
MCPConnection *mySQLConnection;
+ // Table list context menu items
IBOutlet NSMenuItem *removeTableContextMenuItem;
IBOutlet NSMenuItem *duplicateTableContextMenuItem;
IBOutlet NSMenuItem *renameTableContextMenuItem;
IBOutlet NSMenuItem *separatorTableContextMenuItem;
+ IBOutlet NSMenuItem *showCreateSyntaxContextMenuItem;
+ IBOutlet NSMenuItem *separatorTableContextMenuItem2;
NSMutableArray *tables;
NSMutableArray *filteredTables;
diff --git a/Source/TablesList.m b/Source/TablesList.m
index 1644c13d..b9e8270a 100644
--- a/Source/TablesList.m
+++ b/Source/TablesList.m
@@ -252,8 +252,12 @@
if (previousSelectedTable) [previousSelectedTable release];
// Query the structure of all databases in the background
- [NSThread detachNewThreadSelector:@selector(queryDbStructureWithUserInfo:) toTarget:mySQLConnection withObject:nil];
-
+ if(sender == self)
+ // Invoked by SP
+ [NSThread detachNewThreadSelector:@selector(queryDbStructureWithUserInfo:) toTarget:mySQLConnection withObject:nil];
+ else
+ // User press refresh button ergo force update
+ [NSThread detachNewThreadSelector:@selector(queryDbStructureWithUserInfo:) toTarget:mySQLConnection withObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], @"forceUpdate", [NSNumber numberWithBool:YES], @"cancelQuerying", nil]];
}
@@ -634,7 +638,7 @@
[spHistoryControllerInstance updateHistoryEntries];
// Notify listeners of the table change now that the state is fully set up
- [[NSNotificationCenter defaultCenter] postNotificationName:SPTableChangedNotification object:tableDocumentInstance];
+ [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:SPTableChangedNotification object:tableDocumentInstance];
return;
}
@@ -751,37 +755,41 @@
// Update the selected table name and type
if (selectedTableName) [selectedTableName release];
+
if ([indexes count]) {
selectedTableName = [[NSString alloc] initWithString:@""];
- } else {
+ }
+ else {
selectedTableName = nil;
}
+
selectedTableType = SPTableTypeNone;
[tableSourceInstance loadTable:nil];
[tableContentInstance loadTable:nil];
[extendedTableInfoInstance loadTable:nil];
+
structureLoaded = NO;
contentLoaded = NO;
statusLoaded = NO;
// Set gear menu items Remove/Duplicate table/view according to the table types
// if at least one item is selected
- if([indexes count]) {
+ if ([indexes count]) {
NSUInteger currentIndex = [indexes lastIndex];
BOOL areTableTypeEqual = YES;
NSInteger lastType = [[filteredTableTypes objectAtIndex:currentIndex] integerValue];
+
while (currentIndex != NSNotFound)
{
- if ([[filteredTableTypes objectAtIndex:currentIndex] integerValue] != lastType)
- {
+ if ([[filteredTableTypes objectAtIndex:currentIndex] integerValue] != lastType) {
areTableTypeEqual = NO;
break;
}
+
currentIndex = [indexes indexLessThanIndex:currentIndex];
}
- if (areTableTypeEqual)
- {
+ if (areTableTypeEqual) {
switch (lastType) {
case SPTableTypeTable:
[removeTableMenuItem setTitle:NSLocalizedString(@"Remove Tables", @"remove tables menu title")];
@@ -818,14 +826,20 @@
[truncateTableContextButton setHidden:YES];
}
}
+
+ // Context menu
[renameTableContextMenuItem setHidden:YES];
[duplicateTableContextMenuItem setHidden:YES];
[separatorTableContextMenuItem setHidden:YES];
+ [separatorTableContextMenuItem2 setHidden:YES];
+ [showCreateSyntaxContextMenuItem setHidden:YES];
+ // 'Gear' menu
[renameTableMenuItem setHidden:YES];
[duplicateTableMenuItem setHidden:YES];
[separatorTableMenuItem setHidden:YES];
- [separatorTableContextMenuItem setHidden:YES];
+ [separatorTableMenuItem2 setHidden:YES];
+ [showCreateSyntaxMenuItem setHidden:YES];
NSMenu *tableSubMenu = [[[NSApp mainMenu] itemWithTitle:@"Table"] submenu];
@@ -875,18 +889,23 @@
// Reset the table information caches
[tableDataInstance resetAllData];
+ // Show menu separatoes
[separatorTableMenuItem setHidden:NO];
[separatorTableContextMenuItem setHidden:NO];
+ [separatorTableMenuItem2 setHidden:NO];
+ [separatorTableContextMenuItem2 setHidden:NO];
// Set gear menu items Remove/Duplicate table/view and mainMenu > Table items
// according to the table types
NSMenu *tableSubMenu = [[[NSApp mainMenu] itemWithTitle:@"Table"] submenu];
- if(selectedTableType == SPTableTypeView)
+ // Enable/disable the various menu items depending on the selected item. Also update their titles.
+ // Note, that this should ideally be moved to menu item validation as opposed to using fixed item positions.
+ if (selectedTableType == SPTableTypeView)
{
// Change mainMenu > Table > ... according to table type
[[tableSubMenu itemAtIndex:3] setTitle:NSLocalizedString(@"Copy Create View Syntax", @"copy create view syntax menu item")];
- [[tableSubMenu itemAtIndex:4] setTitle:NSLocalizedString(@"Show Create View Syntax", @"show create view syntax menu item")];
+ [[tableSubMenu itemAtIndex:4] setTitle:NSLocalizedString(@"Show Create View Syntax...", @"show create view syntax menu item")];
[[tableSubMenu itemAtIndex:5] setHidden:NO]; // Divider
[[tableSubMenu itemAtIndex:6] setHidden:NO];
[[tableSubMenu itemAtIndex:6] setTitle:NSLocalizedString(@"Check View", @"check view menu item")];
@@ -904,6 +923,8 @@
[duplicateTableMenuItem setTitle:NSLocalizedString(@"Duplicate View...", @"duplicate view menu title")];
[truncateTableButton setHidden:YES];
[removeTableMenuItem setTitle:NSLocalizedString(@"Remove View", @"remove view menu title")];
+ [showCreateSyntaxMenuItem setHidden:NO];
+ [showCreateSyntaxMenuItem setTitle:NSLocalizedString(@"Show Create View Syntax...", @"show create view syntax menu item")];
[renameTableContextMenuItem setHidden:NO]; // we don't have to check the mysql version
[renameTableContextMenuItem setTitle:NSLocalizedString(@"Rename View...", @"rename view menu title")];
@@ -911,10 +932,12 @@
[duplicateTableContextMenuItem setTitle:NSLocalizedString(@"Duplicate View...", @"duplicate view menu title")];
[truncateTableContextButton setHidden:YES];
[removeTableContextMenuItem setTitle:NSLocalizedString(@"Remove View", @"remove view menu title")];
+ [showCreateSyntaxContextMenuItem setHidden:NO];
+ [showCreateSyntaxContextMenuItem setTitle:NSLocalizedString(@"Show Create View Syntax...", @"show create view syntax menu item")];
}
- else if(selectedTableType == SPTableTypeTable) {
+ else if (selectedTableType == SPTableTypeTable) {
[[tableSubMenu itemAtIndex:3] setTitle:NSLocalizedString(@"Copy Create Table Syntax", @"copy create table syntax menu item")];
- [[tableSubMenu itemAtIndex:4] setTitle:NSLocalizedString(@"Show Create Table Syntax", @"show create table syntax menu item")];
+ [[tableSubMenu itemAtIndex:4] setTitle:NSLocalizedString(@"Show Create Table Syntax...", @"show create table syntax menu item")];
[[tableSubMenu itemAtIndex:5] setHidden:NO]; // divider
[[tableSubMenu itemAtIndex:6] setHidden:NO];
[[tableSubMenu itemAtIndex:6] setTitle:NSLocalizedString(@"Check Table", @"check table menu item")];
@@ -937,6 +960,8 @@
[truncateTableButton setHidden:NO];
[truncateTableButton setTitle:NSLocalizedString(@"Truncate Table", @"truncate table menu title")];
[removeTableMenuItem setTitle:NSLocalizedString(@"Remove Table", @"remove table menu title")];
+ [showCreateSyntaxMenuItem setHidden:NO];
+ [showCreateSyntaxMenuItem setTitle:NSLocalizedString(@"Show Create Table Syntax...", @"show create table syntax menu item")];
[renameTableContextMenuItem setHidden:NO];
[renameTableContextMenuItem setTitle:NSLocalizedString(@"Rename Table...", @"rename table menu title")];
@@ -945,11 +970,12 @@
[truncateTableContextButton setHidden:NO];
[truncateTableContextButton setTitle:NSLocalizedString(@"Truncate Table", @"truncate table menu title")];
[removeTableContextMenuItem setTitle:NSLocalizedString(@"Remove Table", @"remove table menu title")];
-
+ [showCreateSyntaxContextMenuItem setHidden:NO];
+ [showCreateSyntaxContextMenuItem setTitle:NSLocalizedString(@"Show Create Table Syntax...", @"show create table syntax menu item")];
}
- else if(selectedTableType == SPTableTypeProc) {
+ else if (selectedTableType == SPTableTypeProc) {
[[tableSubMenu itemAtIndex:3] setTitle:NSLocalizedString(@"Copy Create Procedure Syntax", @"copy create proc syntax menu item")];
- [[tableSubMenu itemAtIndex:4] setTitle:NSLocalizedString(@"Show Create Procedure Syntax", @"show create proc syntax menu item")];
+ [[tableSubMenu itemAtIndex:4] setTitle:NSLocalizedString(@"Show Create Procedure Syntax...", @"show create proc syntax menu item")];
[[tableSubMenu itemAtIndex:5] setHidden:YES]; // divider
[[tableSubMenu itemAtIndex:6] setHidden:YES]; // copy columns
[[tableSubMenu itemAtIndex:7] setHidden:YES]; // divider
@@ -965,6 +991,8 @@
[duplicateTableMenuItem setTitle:NSLocalizedString(@"Duplicate Procedure...", @"duplicate proc menu title")];
[truncateTableButton setHidden:YES];
[removeTableMenuItem setTitle:NSLocalizedString(@"Remove Procedure", @"remove proc menu title")];
+ [showCreateSyntaxMenuItem setHidden:NO];
+ [showCreateSyntaxMenuItem setTitle:NSLocalizedString(@"Show Create Procedure Syntax...", @"show create proc syntax menu item")];
[renameTableContextMenuItem setHidden:NO];
[renameTableContextMenuItem setTitle:NSLocalizedString(@"Rename Procedure...", @"rename proc menu title")];
@@ -972,11 +1000,12 @@
[duplicateTableContextMenuItem setTitle:NSLocalizedString(@"Duplicate Procedure...", @"duplicate proc menu title")];
[truncateTableContextButton setHidden:YES];
[removeTableContextMenuItem setTitle:NSLocalizedString(@"Remove Procedure", @"remove proc menu title")];
-
+ [showCreateSyntaxContextMenuItem setHidden:NO];
+ [showCreateSyntaxContextMenuItem setTitle:NSLocalizedString(@"Show Create Procedure Syntax...", @"show create proc syntax menu item")];
}
- else if(selectedTableType == SPTableTypeFunc) {
+ else if (selectedTableType == SPTableTypeFunc) {
[[tableSubMenu itemAtIndex:3] setTitle:NSLocalizedString(@"Copy Create Function Syntax", @"copy create func syntax menu item")];
- [[tableSubMenu itemAtIndex:4] setTitle:NSLocalizedString(@"Show Create Function Syntax", @"show create func syntax menu item")];
+ [[tableSubMenu itemAtIndex:4] setTitle:NSLocalizedString(@"Show Create Function Syntax...", @"show create func syntax menu item")];
[[tableSubMenu itemAtIndex:5] setHidden:YES]; // divider
[[tableSubMenu itemAtIndex:6] setHidden:YES]; // copy columns
[[tableSubMenu itemAtIndex:7] setHidden:YES]; // divider
@@ -992,6 +1021,8 @@
[duplicateTableMenuItem setTitle:NSLocalizedString(@"Duplicate Function...", @"duplicate func menu title")];
[truncateTableButton setHidden:YES];
[removeTableMenuItem setTitle:NSLocalizedString(@"Remove Function", @"remove func menu title")];
+ [showCreateSyntaxMenuItem setHidden:NO];
+ [showCreateSyntaxMenuItem setTitle:NSLocalizedString(@"Show Create Function Syntax...", @"show create func syntax menu item")];
[renameTableContextMenuItem setHidden:NO];
[renameTableContextMenuItem setTitle:NSLocalizedString(@"Rename Function...", @"rename func menu title")];
@@ -999,6 +1030,8 @@
[duplicateTableContextMenuItem setTitle:NSLocalizedString(@"Duplicate Function...", @"duplicate func menu title")];
[truncateTableContextButton setHidden:YES];
[removeTableContextMenuItem setTitle:NSLocalizedString(@"Remove Function", @"remove func menu title")];
+ [showCreateSyntaxContextMenuItem setHidden:NO];
+ [showCreateSyntaxContextMenuItem setTitle:NSLocalizedString(@"Show Create Function Syntax...", @"show create func syntax menu item")];
}
// set window title
@@ -1395,12 +1428,8 @@
return NO;
}
- // We have to be sure that TableSource and TableContent have finished editing
- if ( ![tableSourceInstance saveRowOnDeselect] || ![tableContentInstance saveRowOnDeselect] ) {
- return NO;
- } else {
- return YES;
- }
+ // We have to be sure that document views have finished editing
+ return [tableDocumentInstance couldCommitCurrentViewActions];
}
/**
diff --git a/Source/main.m b/Source/main.m
index abd404da..3ea4e33e 100644
--- a/Source/main.m
+++ b/Source/main.m
@@ -1,7 +1,7 @@
//
// $Id$
//
-// SPExporter.h
+// main.m
// sequel-pro
//
// Copyright (c) 2009 Sequel Pro team. All rights reserved.