diff options
Diffstat (limited to 'Source')
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 Binary files differindex 06b2000e..33881efc 100644 --- a/Source/SPUserManager.xcdatamodel/elements +++ b/Source/SPUserManager.xcdatamodel/elements diff --git a/Source/SPUserManager.xcdatamodel/layout b/Source/SPUserManager.xcdatamodel/layout Binary files differindex 4bc143ed..14b1545d 100644 --- a/Source/SPUserManager.xcdatamodel/layout +++ b/Source/SPUserManager.xcdatamodel/layout 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. |