diff options
Diffstat (limited to 'Source')
46 files changed, 1457 insertions, 642 deletions
diff --git a/Source/MGTemplateEngine.m b/Source/MGTemplateEngine.m index 86068b66..89ea3a1d 100644 --- a/Source/MGTemplateEngine.m +++ b/Source/MGTemplateEngine.m @@ -177,10 +177,16 @@ - (void)reportError:(NSString *)errorStr code:(NSInteger)code continuing:(BOOL)continuing { if (delegate) { - NSString *errStr = NSLocalizedString(errorStr, nil); - if (!continuing) { - errStr = [NSString stringWithFormat:@"%@: %@", NSLocalizedString(@"Fatal Error", nil), errStr]; - } + + NSString *errStr; + if(errorStr) + errStr = [NSString stringWithString:errorStr]; + else + errStr = NSLocalizedString(@"MGTemplateEngine Error", @"mgtemplateengine error"); + + if (!continuing) + errStr = [NSString stringWithFormat:@"%@: %@", NSLocalizedString(@"Fatal Error", @"fatal error"), errStr]; + SEL selector = @selector(templateEngine:encounteredError:isContinuing:); if ([(NSObject *)delegate respondsToSelector:selector]) { NSError *error = [NSError errorWithDomain:TEMPLATE_ENGINE_ERROR_DOMAIN diff --git a/Source/NoodleLineNumberView.m b/Source/NoodleLineNumberView.m index 9e0d284a..3df2b423 100644 --- a/Source/NoodleLineNumberView.m +++ b/Source/NoodleLineNumberView.m @@ -191,75 +191,78 @@ NSLayoutManager *layoutManager; NSTextContainer *container; NSRange nullRange; - NSMutableArray *lines; + NSArray *lines; id view; - + view = [self clientView]; visibleRect = [[[self scrollView] contentView] bounds]; - + lines = [self lineIndices]; location += NSMinY(visibleRect); if ([view isKindOfClass:[NSTextView class]]) { + nullRange = NSMakeRange(NSNotFound, 0); layoutManager = [view layoutManager]; container = [view textContainer]; count = [lines count]; - - for (line = 0; line < count; line++) + + // Find the characters that are currently visible + NSRange range = [layoutManager characterRangeForGlyphRange:[layoutManager glyphRangeForBoundingRect:visibleRect inTextContainer:container] actualGlyphRange:NULL]; + + // Fudge the range a tad in case there is an extra new line at end. + // It doesn't show up in the glyphs so would not be accounted for. + range.length++; + + for (line = [self lineNumberForCharacterIndex:range.location inText:@""]; line < count; line++) { + index = [NSArrayObjectAtIndex(lines, line) unsignedIntegerValue]; - + rects = [layoutManager rectArrayForCharacterRange:NSMakeRange(index, 0) withinSelectedCharacterRange:nullRange inTextContainer:container rectCount:&rectCount]; - + for (i = 0; i < rectCount; i++) - { if ((location >= NSMinY(rects[i])) && (location < NSMaxY(rects[i]))) - { return line + 1; - } - } - } + + } } return NSNotFound; } - (void)calculateLines { - id view; - - view = [self clientView]; + id view = [self clientView]; if ([view isKindOfClass:[NSTextView class]]) { - NSUInteger index, numberOfLines, stringLength, lineEnd, contentEnd; + NSUInteger index, stringLength, lineEnd, contentEnd; NSString *text; CGFloat oldThickness, newThickness; text = [view string]; stringLength = [text length]; + // Switch off line numbering if text larger than 6MB // for performance reasons. // TODO improve performance maybe via threading if(stringLength>6000000) return; - if (lineIndices) [lineIndices release]; - lineIndices = [[NSMutableArray alloc] init]; + + if (lineIndices) [lineIndices release], lineIndices = nil; + lineIndices = [[NSMutableArray alloc] initWithCapacity:1]; index = 0; - numberOfLines = 0; do { [lineIndices addObject:[NSNumber numberWithUnsignedInteger:index]]; - index = NSMaxRange([text lineRangeForRange:NSMakeRange(index, 0)]); - numberOfLines++; } while (index < stringLength); @@ -274,7 +277,7 @@ newThickness = [self requiredThickness]; if (fabs(oldThickness - newThickness) > 1) { - NSInvocation *invocation; + NSInvocation *invocation; // Not a good idea to resize the view during calculations (which can happen during // display). Do a delayed perform (using NSInvocation since arg is a float). @@ -381,7 +384,7 @@ CGFloat ypos, yinset; NSDictionary *textAttributes; NSSize stringSize; - NSMutableArray *lines; + NSArray *lines; layoutManager = [view layoutManager]; container = [view textContainer]; @@ -407,7 +410,7 @@ CGFloat boundsRULERMargin2 = NSWidth(bounds) - RULER_MARGIN * 2.0; CGFloat boundsWidthRULER = NSWidth(bounds) - RULER_MARGIN; - CGFloat yinsetMinY = yinset - NSMinY(visibleRect); + CGFloat yinsetMinY = yinset - NSMinY(visibleRect); for (line = [self lineNumberForCharacterIndex:range.location inText:text]; line < count; line++) { @@ -462,11 +465,11 @@ if (line != NSNotFound) { NSUInteger selectionStart, selectionEnd; - NSMutableArray *lines = [self lineIndices]; + NSArray *lines = [self lineIndices]; - selectionStart = [[lines objectAtIndex:(line - 1)] unsignedIntegerValue]; + selectionStart = [NSArrayObjectAtIndex(lines, (line - 1)) unsignedIntegerValue]; if (line < [lines count]) { - selectionEnd = [[lines objectAtIndex:line] unsignedIntegerValue]; + selectionEnd = [NSArrayObjectAtIndex(lines, line) unsignedIntegerValue]; } else { selectionEnd = [[view string] length]; } @@ -489,7 +492,7 @@ if (line != NSNotFound) { NSUInteger selectionStart, selectionEnd; - NSMutableArray *lines = [self lineIndices]; + NSArray *lines = [self lineIndices]; if (line >= dragSelectionStartLine) { startLine = dragSelectionStartLine; endLine = line; @@ -498,9 +501,9 @@ endLine = dragSelectionStartLine; } - selectionStart = [[lines objectAtIndex:(startLine - 1)] unsignedIntegerValue]; + selectionStart = [NSArrayObjectAtIndex(lines, (startLine - 1)) unsignedIntegerValue]; if (endLine < [lines count]) { - selectionEnd = [[lines objectAtIndex:endLine] unsignedIntegerValue]; + selectionEnd = [NSArrayObjectAtIndex(lines, endLine) unsignedIntegerValue]; } else { selectionEnd = [[view string] length]; } diff --git a/Source/SPAppController.m b/Source/SPAppController.m index d89f0cce..ba65df6f 100644 --- a/Source/SPAppController.m +++ b/Source/SPAppController.m @@ -42,6 +42,19 @@ #import <PSMTabBar/PSMTabBarControl.h> #import <Sparkle/Sparkle.h> +#import "SPEditorTokens.h" + +#pragma mark lex init + +/* +* Include all the extern variables and prototypes required for flex (used for syntax highlighting) +*/ +extern NSUInteger yylex(); +extern NSUInteger yyuoffset, yyuleng; +typedef struct yy_buffer_state *YY_BUFFER_STATE; +void yy_switch_to_buffer(YY_BUFFER_STATE); +YY_BUFFER_STATE yy_scan_string (const char *); + @implementation SPAppController @synthesize lastBundleBlobFilesDirectory; @@ -496,7 +509,7 @@ NSFileManager *fm = [NSFileManager defaultManager]; - NSString *bundlePath = [[NSFileManager defaultManager] applicationSupportDirectoryForSubDirectory:SPBundleSupportFolder error:nil]; + NSString *bundlePath = [fm applicationSupportDirectoryForSubDirectory:SPBundleSupportFolder error:nil]; if(!bundlePath) return; @@ -539,6 +552,11 @@ if (cmdData) [cmdData release]; return; } + + // Reload Bundles if Sequel Pro didn't run + if(![installedBundleUUIDs count]) + [self reloadBundles:self]; + if([[installedBundleUUIDs allKeys] containsObject:[cmdData objectForKey:SPBundleFileUUIDKey]]) { NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Installing Bundle", @"Open Files : Bundle : Already-Installed : 'Update Bundle' question dialog title")] defaultButton:NSLocalizedString(@"Update", @"Open Files : Bundle : Already-Installed : Update button") @@ -550,10 +568,11 @@ NSInteger answer = [alert runModal]; if(answer == NSAlertDefaultReturn) { NSError *error = nil; - NSString *moveToTrashCommand = [NSString stringWithFormat:@"osascript -e 'tell application \"Finder\" to move (POSIX file \"%@\") to the trash'", newPath]; + NSString *removePath = [[[installedBundleUUIDs objectForKey:[cmdData objectForKey:SPBundleFileUUIDKey]] objectForKey:@"path"] substringToIndex:([[[installedBundleUUIDs objectForKey:[cmdData objectForKey:SPBundleFileUUIDKey]] objectForKey:@"path"] length]-[SPBundleFileName length]-1)]; + NSString *moveToTrashCommand = [NSString stringWithFormat:@"osascript -e 'tell application \"Finder\" to move (POSIX file \"%@\") to the trash'", removePath]; [moveToTrashCommand runBashCommandWithEnvironment:nil atCurrentDirectoryPath:nil error:&error]; if(error != nil) { - NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while moving “%@” to Trash.", @"Open Files : Bundle : Already-Installed : Delete-Old-Error : Could not delete old bundle before installing new version."), [[installedBundleUUIDs objectForKey:[cmdData objectForKey:SPBundleFileUUIDKey]] objectForKey:@"path"]] + NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while moving “%@” to Trash.", @"Open Files : Bundle : Already-Installed : Delete-Old-Error : Could not delete old bundle before installing new version."), removePath] defaultButton:NSLocalizedString(@"OK", @"Open Files : Bundle : Already-Installed : Delete-Old-Error : OK button") alternateButton:nil otherButton:nil @@ -621,7 +640,12 @@ { NSURL *url = [NSURL URLWithString:[[event paramDescriptorForKeyword:keyDirectObject] stringValue]]; - [self handleEventWithURL:url]; + if(url) + [self handleEventWithURL:url]; + else { + NSBeep(); + NSLog(@"Error in sequelpro URL scheme"); + } } - (void)handleEventWithURL:(NSURL*)url @@ -635,13 +659,14 @@ else parameter = [NSArray array]; + NSFileManager *fm = [NSFileManager defaultManager]; // Handle commands which don't need a connection window if([command isEqualToString:@"chooseItemFromList"]) { NSString *statusFileName = [NSString stringWithFormat:@"%@%@", SPURLSchemeQueryResultStatusPathHeader, (passedProcessID && [passedProcessID length]) ? passedProcessID : @""]; NSString *resultFileName = [NSString stringWithFormat:@"%@%@", SPURLSchemeQueryResultPathHeader, (passedProcessID && [passedProcessID length]) ? passedProcessID : @""]; - [[NSFileManager defaultManager] removeItemAtPath:statusFileName error:nil]; - [[NSFileManager defaultManager] removeItemAtPath:resultFileName error:nil]; + [fm removeItemAtPath:statusFileName error:nil]; + [fm removeItemAtPath:resultFileName error:nil]; NSString *result = @""; NSString *status = @"0"; if([parameter count]) { @@ -659,6 +684,50 @@ return; } + if([command isEqualToString:@"SyntaxHighlighting"]) { + + BOOL isDir; + + NSString *anUUID = (passedProcessID && [passedProcessID length]) ? passedProcessID : @""; + NSString *queryFileName = [NSString stringWithFormat:@"%@%@", SPURLSchemeQueryInputPathHeader, anUUID]; + NSString *resultFileName = [NSString stringWithFormat:@"%@%@", SPURLSchemeQueryResultPathHeader, anUUID]; + NSString *metaFileName = [NSString stringWithFormat:@"%@%@", SPURLSchemeQueryResultMetaPathHeader, anUUID]; + NSString *statusFileName = [NSString stringWithFormat:@"%@%@", SPURLSchemeQueryResultStatusPathHeader, anUUID]; + + NSError *inError = nil; + NSString *query = [NSString stringWithContentsOfFile:queryFileName encoding:NSUTF8StringEncoding error:inError]; + NSString *result = @""; + NSString *status = @"0"; + + if([fm fileExistsAtPath:queryFileName isDirectory:&isDir] && !isDir) { + + if(inError == nil && query && [query length]) { + if([parameter count] > 0) { + if([[parameter lastObject] isEqualToString:@"html"]) + result = [NSString stringWithString:[self doSQLSyntaxHighlightForString:query cssLike:NO]]; + else if([[parameter lastObject] isEqualToString:@"htmlcss"]) + result = [NSString stringWithString:[self doSQLSyntaxHighlightForString:query cssLike:YES]]; + } + } + } + + [fm removeItemAtPath:queryFileName error:nil]; + [fm removeItemAtPath:resultFileName error:nil]; + [fm removeItemAtPath:metaFileName error:nil]; + [fm removeItemAtPath:statusFileName error:nil]; + + if(![result writeToFile:resultFileName atomically:YES encoding:NSUTF8StringEncoding error:nil]) + status = @"1"; + + // write status file as notification that query was finished + BOOL succeed = [status writeToFile:statusFileName atomically:YES encoding:NSUTF8StringEncoding error:nil]; + if(!succeed) { + NSBeep(); + SPBeginAlertSheet(NSLocalizedString(@"BASH Error", @"bash error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self parentWindow], self, nil, nil, + NSLocalizedString(@"Status file for sequelpro url scheme command couldn't be written!", @"status file for sequelpro url scheme command couldn't be written error message")); + } + return; + } NSString *activeProcessID = [[[[self frontDocumentWindow] delegate] selectedTableDocument] processID]; @@ -695,14 +764,33 @@ [cmdDict setObject:parameter forKey:@"parameter"]; [cmdDict setObject:(passedProcessID)?:@"" forKey:@"id"]; [processDocument handleSchemeCommand:cmdDict]; - return; - } - else { + } else { SPBeginAlertSheet(NSLocalizedString(@"sequelpro URL Scheme Error", @"sequelpro url Scheme Error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [NSApp mainWindow], self, nil, nil, [NSString stringWithFormat:@"%@ “%@”:\n%@", NSLocalizedString(@"Error for", @"error for message"), [command description], NSLocalizedString(@"sequelpro URL scheme command not supported.", @"sequelpro URL scheme command not supported.")]); + + // If command failed notify the file handle hand shake mechanism + NSString *out = @"1"; + NSString *anUUID = @""; + if(command && passedProcessID && [passedProcessID length]) + anUUID = passedProcessID; + else + anUUID = command; - return; + [out writeToFile:[NSString stringWithFormat:@"%@%@", SPURLSchemeQueryResultStatusPathHeader, anUUID] + atomically:YES + encoding:NSUTF8StringEncoding + error:nil]; + + out = @"Error"; + [out writeToFile:[NSString stringWithFormat:@"%@%@", SPURLSchemeQueryResultPathHeader, anUUID] + atomically:YES + encoding:NSUTF8StringEncoding + error:nil]; + } + + return; + } if(passedProcessID && [passedProcessID length]) { @@ -723,10 +811,10 @@ usleep(5000); - [[NSFileManager defaultManager] removeItemAtPath:[NSString stringWithFormat:@"%@%@", SPURLSchemeQueryResultStatusPathHeader, passedProcessID] error:nil]; - [[NSFileManager defaultManager] removeItemAtPath:[NSString stringWithFormat:@"%@%@", SPURLSchemeQueryResultPathHeader, passedProcessID] error:nil]; - [[NSFileManager defaultManager] removeItemAtPath:[NSString stringWithFormat:@"%@%@", SPURLSchemeQueryResultMetaPathHeader, passedProcessID] error:nil]; - [[NSFileManager defaultManager] removeItemAtPath:[NSString stringWithFormat:@"%@%@", SPURLSchemeQueryInputPathHeader, passedProcessID] error:nil]; + [fm removeItemAtPath:[NSString stringWithFormat:@"%@%@", SPURLSchemeQueryResultStatusPathHeader, passedProcessID] error:nil]; + [fm removeItemAtPath:[NSString stringWithFormat:@"%@%@", SPURLSchemeQueryResultPathHeader, passedProcessID] error:nil]; + [fm removeItemAtPath:[NSString stringWithFormat:@"%@%@", SPURLSchemeQueryResultMetaPathHeader, passedProcessID] error:nil]; + [fm removeItemAtPath:[NSString stringWithFormat:@"%@%@", SPURLSchemeQueryInputPathHeader, passedProcessID] error:nil]; @@ -745,6 +833,86 @@ } +/** + * Return an HTML formatted string representing the passed SQL string syntax highlighted + */ +- (NSString*)doSQLSyntaxHighlightForString:(NSString*)sqlText cssLike:(BOOL)cssLike +{ + + NSMutableString *sqlHTML = [[[NSMutableString alloc] initWithCapacity:[sqlText length]] autorelease]; + + NSRange textRange = NSMakeRange(0, [sqlText length]); + NSString *tokenColor; + NSString *cssId; + size_t token; + NSRange tokenRange; + + // initialise flex + yyuoffset = 0; yyuleng = 0; + yy_switch_to_buffer(yy_scan_string([sqlText UTF8String])); + BOOL skipFontTag; + + while (token=yylex()){ + skipFontTag = NO; + switch (token) { + case SPT_SINGLE_QUOTED_TEXT: + case SPT_DOUBLE_QUOTED_TEXT: + tokenColor = @"#A7221C"; + cssId = @"sp_sql_quoted"; + break; + case SPT_BACKTICK_QUOTED_TEXT: + tokenColor = @"#001892"; + cssId = @"sp_sql_backtick"; + break; + case SPT_RESERVED_WORD: + tokenColor = @"#0041F6"; + cssId = @"sp_sql_keyword"; + break; + case SPT_NUMERIC: + tokenColor = @"#67350F"; + cssId = @"sp_sql_numeric"; + break; + case SPT_COMMENT: + tokenColor = @"#265C10"; + cssId = @"sp_sql_comment"; + break; + case SPT_VARIABLE: + tokenColor = @"#6C6C6C"; + cssId = @"sp_sql_variable"; + break; + case SPT_WHITESPACE: + skipFontTag = YES; + cssId = @""; + break; + default: + skipFontTag = YES; + cssId = @""; + } + + tokenRange = NSMakeRange(yyuoffset, yyuleng); + + if(skipFontTag) + [sqlHTML appendString:[[sqlText substringWithRange:tokenRange] HTMLEscapeString]]; + else { + if(cssLike) + [sqlHTML appendFormat:@"<span class=\"%@\">%@</span>", cssId, [[sqlText substringWithRange:tokenRange] HTMLEscapeString]]; + else + [sqlHTML appendFormat:@"<font color=%@>%@</font>", tokenColor, [[sqlText substringWithRange:tokenRange] HTMLEscapeString]]; + } + + } + + // Wrap lines, and replace tabs with spaces + [sqlHTML replaceOccurrencesOfString:@"\n" withString:@"<br>" options:NSLiteralSearch range:NSMakeRange(0, [sqlHTML length])]; + [sqlHTML replaceOccurrencesOfString:@"\t" withString:@" " options:NSLiteralSearch range:NSMakeRange(0, [sqlHTML length])]; + + if(sqlHTML) + return sqlHTML; + else + return @""; + +} + - (IBAction)executeBundleItemForApp:(id)sender { @@ -760,6 +928,7 @@ } if(!infoPath) { + NSLog(@"No path to Bundle command passed"); NSBeep(); return; } @@ -939,8 +1108,6 @@ } } - // if(doc && [doc shellVariables]) [env addEntriesFromDictionary:[doc shellVariables]]; - // if(doc) [doc release]; id firstResponder = [[NSApp keyWindow] firstResponder]; if([firstResponder respondsToSelector:@selector(executeBundleItemForInputField:)]) { BOOL selfIsQueryEditor = ([[[firstResponder class] description] isEqualToString:@"SPTextView"]) ; @@ -999,6 +1166,19 @@ { [runningActivitiesArray addObject:commandDict]; [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:SPActivitiesUpdateNotification object:nil]; + + SPDatabaseDocument* frontMostDoc = [self frontDocument]; + if(frontMostDoc) { + if([runningActivitiesArray count] || [[frontMostDoc runningActivities] count]) + [frontMostDoc performSelector:@selector(setActivityPaneHidden:) withObject:[NSNumber numberWithInteger:0] afterDelay:1.0]; + else { + [NSObject cancelPreviousPerformRequestsWithTarget:frontMostDoc + selector:@selector(setActivityPaneHidden:) + object:[NSNumber numberWithInteger:0]]; + [frontMostDoc setActivityPaneHidden:[NSNumber numberWithInteger:1]]; + } + } + } - (void)removeRegisteredActivity:(NSInteger)pid @@ -1009,7 +1189,20 @@ break; } } + [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:SPActivitiesUpdateNotification object:nil]; + + SPDatabaseDocument* frontMostDoc = [self frontDocument]; + if(frontMostDoc) { + if([runningActivitiesArray count] || [[frontMostDoc runningActivities] count]) + [frontMostDoc performSelector:@selector(setActivityPaneHidden:) withObject:[NSNumber numberWithInteger:0] afterDelay:1.0]; + else { + [NSObject cancelPreviousPerformRequestsWithTarget:frontMostDoc + selector:@selector(setActivityPaneHidden:) + object:[NSNumber numberWithInteger:0]]; + [frontMostDoc setActivityPaneHidden:[NSNumber numberWithInteger:1]]; + } + } } - (NSArray*)runningActivities @@ -1324,6 +1517,7 @@ - (IBAction)reloadBundles:(id)sender { + // Force releasing of any HTML output windows for(id c in bundleHTMLOutputController) { if(![[c window] isVisible]) { [c release]; @@ -1346,26 +1540,38 @@ // Clean menu [menu compatibleRemoveAllItems]; + // Set up the bundle search paths + // First process all in Application Support folder installed ones then Default ones + NSFileManager *fm = [NSFileManager defaultManager]; + NSError *appPathError = nil; NSArray *bundlePaths = [NSArray arrayWithObjects: - ([[NSFileManager defaultManager] applicationSupportDirectoryForSubDirectory:SPBundleSupportFolder createIfNotExists:NO error:nil])?:@"", + [fm applicationSupportDirectoryForSubDirectory:SPBundleSupportFolder createIfNotExists:YES error:&appPathError], [NSString stringWithFormat:@"%@/Contents/SharedSupport/Default Bundles", [[NSBundle mainBundle] bundlePath]], nil]; + // If ~/Library/Application Path/Sequel Pro/Bundles couldn't be created bail + if(appPathError != nil) { + NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Bundles Installation Error", @"bundles installation error") + defaultButton:NSLocalizedString(@"OK", @"OK button") + alternateButton:nil + otherButton:nil + informativeTextWithFormat:[NSString stringWithFormat:NSLocalizedString(@"Couldn't create Application Support Bundle folder!\nError: %@", @"Couldn't create Application Support Bundle folder!\nError: %@"), [appPathError localizedDescription]]]; + + [alert runModal]; + return; + } + BOOL processDefaultBundles = NO; - NSFileManager *fm = [NSFileManager defaultManager]; - + NSArray *deletedDefaultBundles; - NSMutableArray *updatedDefaultBundles = [NSMutableArray array]; - if([[NSUserDefaults standardUserDefaults] objectForKey:SPBundleDeletedDefaultBundlesKey]) { + + if([[NSUserDefaults standardUserDefaults] objectForKey:SPBundleDeletedDefaultBundlesKey]) deletedDefaultBundles = [[[NSUserDefaults standardUserDefaults] objectForKey:SPBundleDeletedDefaultBundlesKey] retain]; - } else { + else deletedDefaultBundles = [[NSArray array] retain]; - } - if([[NSUserDefaults standardUserDefaults] objectForKey:SPBundleUpdatedDefaultBundlesKey]) { - [updatedDefaultBundles setArray:[[NSUserDefaults standardUserDefaults] objectForKey:SPBundleUpdatedDefaultBundlesKey]]; - } NSMutableString *infoAboutUpdatedDefaultBundles = [NSMutableString string]; + BOOL doBundleUpdate = ([[NSUserDefaults standardUserDefaults] objectForKey:@"doBundleUpdate"]) ? YES : NO; for(NSString* bundlePath in bundlePaths) { if([bundlePath length]) { @@ -1386,125 +1592,115 @@ NSString *infoPath = [NSString stringWithFormat:@"%@/%@/%@", bundlePath, bundle, SPBundleFileName]; NSData *pData = [NSData dataWithContentsOfFile:infoPath options:NSUncachedRead error:&readError]; - cmdData = [[NSPropertyListSerialization propertyListFromData:pData - mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&convError] retain]; + cmdData = [NSPropertyListSerialization propertyListFromData:pData + mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&convError]; if(!cmdData || readError != nil || [convError length] || !(format == NSPropertyListXMLFormat_v1_0 || format == NSPropertyListBinaryFormat_v1_0)) { - NSLog(@"“%@” file couldn't be read.", infoPath); NSBeep(); + continue; + } - } else { - if((![cmdData objectForKey:SPBundleFileDisabledKey] || ![[cmdData objectForKey:SPBundleFileDisabledKey] intValue]) - && [cmdData objectForKey:SPBundleFileNameKey] - && [[cmdData objectForKey:SPBundleFileNameKey] length] - && [cmdData objectForKey:SPBundleFileScopeKey]) - { - - if([cmdData objectForKey:SPBundleFileUUIDKey] && [[cmdData objectForKey:SPBundleFileUUIDKey] length]) { - - if(processDefaultBundles) { - - // Skip deleted default Bundles - BOOL bundleWasDeleted = NO; - if([deletedDefaultBundles count]) { - for(NSArray* item in deletedDefaultBundles) { - if([[item objectAtIndex:0] isEqualToString:[cmdData objectForKey:SPBundleFileUUIDKey]]) { - bundleWasDeleted = YES; - break; - } + if((![cmdData objectForKey:SPBundleFileDisabledKey] || ![[cmdData objectForKey:SPBundleFileDisabledKey] intValue]) + && [cmdData objectForKey:SPBundleFileNameKey] + && [[cmdData objectForKey:SPBundleFileNameKey] length] + && [cmdData objectForKey:SPBundleFileScopeKey]) + { + + BOOL defaultBundleWasUpdated = NO; + + if([cmdData objectForKey:SPBundleFileUUIDKey] && [[cmdData objectForKey:SPBundleFileUUIDKey] length]) { + + if(processDefaultBundles) { + + // Skip deleted default Bundles + BOOL bundleWasDeleted = NO; + if([deletedDefaultBundles count]) { + for(NSArray* item in deletedDefaultBundles) { + if([[item objectAtIndex:0] isEqualToString:[cmdData objectForKey:SPBundleFileUUIDKey]]) { + bundleWasDeleted = YES; + break; } } - if(bundleWasDeleted) continue; + } + if(bundleWasDeleted) continue; + + // If default Bundle is already installed check for possible update, + // if so duplicate the modified one by appending (user) and updated it + if(doBundleUpdate || [installedBundleUUIDs objectForKey:[cmdData objectForKey:SPBundleFileUUIDKey]] == nil) { - // If default Bundle is already install check for possible update, - // if so duplicate the modified one by appending (user) and updated it if([installedBundleUUIDs objectForKey:[cmdData objectForKey:SPBundleFileUUIDKey]]) { - if([updatedDefaultBundles containsObject:[cmdData objectForKey:SPBundleFileUUIDKey]]) { - - NSString *oldPath = [NSString stringWithFormat:@"%@/%@/%@", [bundlePaths objectAtIndex:0], bundle, SPBundleFileName]; - NSError *readError = nil; - NSString *convError = nil; - NSPropertyListFormat format; - NSDictionary *cmdData = nil; - - NSData *pData = [NSData dataWithContentsOfFile:oldPath options:NSUncachedRead error:&readError]; - cmdData = [[NSPropertyListSerialization propertyListFromData:pData - mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&convError] retain]; - if(!cmdData || readError != nil || [convError length] || !(format == NSPropertyListXMLFormat_v1_0 || format == NSPropertyListBinaryFormat_v1_0)) { - NSLog(@"“%@” file couldn't be read.", oldPath); - NSBeep(); - if (cmdData) [cmdData release]; - continue; - } else { - NSString *oldBundle = [NSString stringWithFormat:@"%@/%@", [bundlePaths objectAtIndex:0], bundle]; - // Check for modifications - if([cmdData objectForKey:SPBundleFileDefaultBundleWasModifiedKey]) { - - // Duplicate Bundle, change the UUID and rename the menu label - NSString *duplicatedBundle = [NSString stringWithFormat:@"%@/%@_%ld.%@", [bundlePaths objectAtIndex:0], [bundle substringToIndex:([bundle length] - [SPUserBundleFileExtension length] - 1)], (NSUInteger)(random() % 35000), SPUserBundleFileExtension]; - if(![[NSFileManager defaultManager] copyItemAtPath:oldBundle toPath:duplicatedBundle error:nil]) { - NSLog(@"Couldn't copy “%@” to update it", bundle); - NSBeep(); - if (cmdData) [cmdData release]; - continue; - } - NSError *readError1 = nil; - NSString *convError1 = nil; - NSMutableDictionary *dupData = [NSMutableDictionary dictionary]; - NSString *duplicatedBundleCommand = [NSString stringWithFormat:@"%@/%@", duplicatedBundle, SPBundleFileName]; - NSData *dData = [NSData dataWithContentsOfFile:duplicatedBundleCommand options:NSUncachedRead error:&readError1]; - [dupData setDictionary:[NSPropertyListSerialization propertyListFromData:dData - mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&convError1]]; - if(!dupData && ![dupData count] || readError1 != nil || [convError1 length] || !(format == NSPropertyListXMLFormat_v1_0 || format == NSPropertyListBinaryFormat_v1_0)) { - NSLog(@"“%@” file couldn't be read.", duplicatedBundleCommand); - NSBeep(); - continue; - } - [dupData setObject:[NSString stringWithNewUUID] forKey:SPBundleFileUUIDKey]; - NSString *orgName = [dupData objectForKey:SPBundleFileNameKey]; - [dupData setObject:[NSString stringWithFormat:@"%@ (user)", orgName] forKey:SPBundleFileNameKey]; - [dupData removeObjectForKey:SPBundleFileIsDefaultBundleKey]; - [dupData writeToFile:duplicatedBundleCommand atomically:YES]; - - NSError *error = nil; - NSString *moveToTrashCommand = [NSString stringWithFormat:@"osascript -e 'tell application \"Finder\" to move (POSIX file \"%@\") to the trash'", oldBundle]; - [moveToTrashCommand runBashCommandWithEnvironment:nil atCurrentDirectoryPath:nil error:&error]; - - if(error != nil) { - NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while moving “%@” to Trash.", @"error while moving “%@” to trash"), [[installedBundleUUIDs objectForKey:[cmdData objectForKey:SPBundleFileUUIDKey]] objectForKey:@"path"]] - defaultButton:NSLocalizedString(@"OK", @"OK button") - alternateButton:nil - otherButton:nil - informativeTextWithFormat:[error localizedDescription]]; - - [alert setAlertStyle:NSCriticalAlertStyle]; - [alert runModal]; - if (cmdData) [cmdData release]; - continue; - } - [infoAboutUpdatedDefaultBundles appendFormat:@"• %@\n", orgName]; - } else { - - // If no modifications are done simply remove the old one - if(![fm removeItemAtPath:oldBundle error:nil]) { - NSLog(@"Couldn't remove “%@” to update it", bundle); - NSBeep(); - if (cmdData) [cmdData release]; - continue; - } + NSString *oldPath = [NSString stringWithFormat:@"%@/%@/%@", [bundlePaths objectAtIndex:0], bundle, SPBundleFileName]; + NSError *readError = nil; + NSString *convError = nil; + NSPropertyListFormat format; + NSDictionary *cmdDataOld = nil; + + NSData *pDataOld = [NSData dataWithContentsOfFile:oldPath options:NSUncachedRead error:&readError]; + cmdDataOld = [NSPropertyListSerialization propertyListFromData:pDataOld + mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&convError]; + if(!cmdDataOld || readError != nil || [convError length] || !(format == NSPropertyListXMLFormat_v1_0 || format == NSPropertyListBinaryFormat_v1_0)) { + NSLog(@"“%@” file couldn't be read.", oldPath); + NSBeep(); + continue; + } else { + NSString *oldBundle = [NSString stringWithFormat:@"%@/%@", [bundlePaths objectAtIndex:0], bundle]; + // Check for modifications + if([cmdDataOld objectForKey:SPBundleFileDefaultBundleWasModifiedKey]) { + + // Duplicate Bundle, change the UUID and rename the menu label + NSString *duplicatedBundle = [NSString stringWithFormat:@"%@/%@_%ld.%@", [bundlePaths objectAtIndex:0], [bundle substringToIndex:([bundle length] - [SPUserBundleFileExtension length] - 1)], (NSUInteger)(random() % 35000), SPUserBundleFileExtension]; + if(![[NSFileManager defaultManager] copyItemAtPath:oldBundle toPath:duplicatedBundle error:nil]) { + NSLog(@"Couldn't copy “%@” to update it", bundle); + NSBeep(); + continue; } + NSError *readError1 = nil; + NSString *convError1 = nil; + NSMutableDictionary *dupData = [NSMutableDictionary dictionary]; + NSString *duplicatedBundleCommand = [NSString stringWithFormat:@"%@/%@", duplicatedBundle, SPBundleFileName]; + NSData *dData = [NSData dataWithContentsOfFile:duplicatedBundleCommand options:NSUncachedRead error:&readError1]; + [dupData setDictionary:[NSPropertyListSerialization propertyListFromData:dData + mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&convError1]]; + if(!dupData && ![dupData count] || readError1 != nil || [convError1 length] || !(format == NSPropertyListXMLFormat_v1_0 || format == NSPropertyListBinaryFormat_v1_0)) { + NSLog(@"“%@” file couldn't be read.", duplicatedBundleCommand); + NSBeep(); + continue; + } + [dupData setObject:[NSString stringWithNewUUID] forKey:SPBundleFileUUIDKey]; + NSString *orgName = [dupData objectForKey:SPBundleFileNameKey]; + [dupData setObject:[NSString stringWithFormat:@"%@ (user)", orgName] forKey:SPBundleFileNameKey]; + [dupData removeObjectForKey:SPBundleFileIsDefaultBundleKey]; + [dupData writeToFile:duplicatedBundleCommand atomically:YES]; + + NSError *error = nil; + NSString *moveToTrashCommand = [NSString stringWithFormat:@"osascript -e 'tell application \"Finder\" to move (POSIX file \"%@\") to the trash'", oldBundle]; + [moveToTrashCommand runBashCommandWithEnvironment:nil atCurrentDirectoryPath:nil error:&error]; + + if(error != nil) { + NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while moving “%@” to Trash.", @"error while moving “%@” to trash"), [[installedBundleUUIDs objectForKey:[cmdDataOld objectForKey:SPBundleFileUUIDKey]] objectForKey:@"path"]] + defaultButton:NSLocalizedString(@"OK", @"OK button") + alternateButton:nil + otherButton:nil + informativeTextWithFormat:[error localizedDescription]]; + + [alert setAlertStyle:NSCriticalAlertStyle]; + [alert runModal]; + continue; + } + [infoAboutUpdatedDefaultBundles appendFormat:@"• %@\n", orgName]; + } else { - // Remove item from update list which will be updated in the Prefs - [updatedDefaultBundles removeObject:[cmdData objectForKey:SPBundleFileUUIDKey]]; + // If no modifications are done simply remove the old one + if(![fm removeItemAtPath:oldBundle error:nil]) { + NSLog(@"Couldn't remove “%@” to update it", bundle); + NSBeep(); + continue; + } } - if (cmdData) [cmdData release]; - - } else { - continue; } } @@ -1523,96 +1719,93 @@ } infoPath = [NSString stringWithString:newInfoPath]; - } - - [installedBundleUUIDs setObject:[NSDictionary dictionaryWithObjectsAndKeys: - [NSString stringWithFormat:@"%@ (%@)", bundle, [cmdData objectForKey:SPBundleFileNameKey]], @"name", - infoPath, @"path", nil] forKey:[cmdData objectForKey:SPBundleFileUUIDKey]]; - - } else { - NSLog(@"No UUID for %@", bundle); - NSBeep(); - continue; - } - - NSArray *scopes = [[cmdData objectForKey:SPBundleFileScopeKey] componentsSeparatedByString:@" "]; - for(NSString *scope in scopes) { + defaultBundleWasUpdated = YES; - if(![bundleUsedScopes containsObject:scope]) { - [bundleUsedScopes addObject:scope]; - [bundleItems setObject:[NSMutableArray array] forKey:scope]; - [bundleCategories setObject:[NSMutableArray array] forKey:scope]; - [bundleKeyEquivalents setObject:[NSMutableDictionary dictionary] forKey:scope]; } - if([cmdData objectForKey:SPBundleFileCategoryKey] && [[cmdData objectForKey:SPBundleFileCategoryKey] length] && ![[bundleCategories objectForKey:scope] containsObject:[cmdData objectForKey:SPBundleFileCategoryKey]]) - [[bundleCategories objectForKey:scope] addObject:[cmdData objectForKey:SPBundleFileCategoryKey]]; - } + if(!defaultBundleWasUpdated) continue; - NSMutableDictionary *aDict = [NSMutableDictionary dictionary]; - [aDict setObject:[cmdData objectForKey:SPBundleFileNameKey] forKey:SPBundleInternLabelKey]; - [aDict setObject:infoPath forKey:SPBundleInternPathToFileKey]; - - // Register trigger - if([cmdData objectForKey:SPBundleFileTriggerKey]) { - if(![bundleTriggers objectForKey:[cmdData objectForKey:SPBundleFileTriggerKey]]) - [bundleTriggers setObject:[NSMutableArray array] forKey:[cmdData objectForKey:SPBundleFileTriggerKey]]; - [[bundleTriggers objectForKey:[cmdData objectForKey:SPBundleFileTriggerKey]] addObject: - [NSString stringWithFormat:@"%@|%@|%@", - infoPath, - [cmdData objectForKey:SPBundleFileScopeKey], - ([[cmdData objectForKey:SPBundleFileOutputActionKey] isEqualToString:SPBundleOutputActionShowAsHTML])?[cmdData objectForKey:SPBundleFileUUIDKey]:@""]]; } - if([cmdData objectForKey:SPBundleFileKeyEquivalentKey] && [[cmdData objectForKey:SPBundleFileKeyEquivalentKey] length]) { - - NSString *theKey = [cmdData objectForKey:SPBundleFileKeyEquivalentKey]; - NSString *theChar = [theKey substringFromIndex:[theKey length]-1]; - NSString *theMods = [theKey substringToIndex:[theKey length]-1]; - NSUInteger mask = 0; - if([theMods rangeOfString:@"^"].length) - mask = mask | NSControlKeyMask; - if([theMods rangeOfString:@"@"].length) - mask = mask | NSCommandKeyMask; - if([theMods rangeOfString:@"~"].length) - mask = mask | NSAlternateKeyMask; - if([theMods rangeOfString:@"$"].length) - mask = mask | NSShiftKeyMask; - for(NSString* scope in scopes) { - if(![[bundleKeyEquivalents objectForKey:scope] objectForKey:[cmdData objectForKey:SPBundleFileKeyEquivalentKey]]) - [[bundleKeyEquivalents objectForKey:scope] setObject:[NSMutableArray array] forKey:[cmdData objectForKey:SPBundleFileKeyEquivalentKey]]; - - [[[bundleKeyEquivalents objectForKey:scope] objectForKey:[cmdData objectForKey:SPBundleFileKeyEquivalentKey]] addObject: - [NSDictionary dictionaryWithObjectsAndKeys: - infoPath, @"path", - [cmdData objectForKey:SPBundleFileNameKey], @"title", - ([cmdData objectForKey:SPBundleFileTooltipKey]) ?: @"", @"tooltip", - nil]]; + [installedBundleUUIDs setObject:[NSDictionary dictionaryWithObjectsAndKeys: + [NSString stringWithFormat:@"%@ (%@)", bundle, [cmdData objectForKey:SPBundleFileNameKey]], @"name", + infoPath, @"path", nil] forKey:[cmdData objectForKey:SPBundleFileUUIDKey]]; - } + } else { + NSLog(@"No UUID for %@", bundle); + NSBeep(); + continue; + } - [aDict setObject:[NSArray arrayWithObjects:theChar, [NSNumber numberWithInteger:mask], nil] forKey:SPBundleInternKeyEquivalentKey]; - } + // Register Bundle + NSString *scope = [cmdData objectForKey:SPBundleFileScopeKey]; - if([cmdData objectForKey:SPBundleFileTooltipKey] && [[cmdData objectForKey:SPBundleFileTooltipKey] length]) - [aDict setObject:[cmdData objectForKey:SPBundleFileTooltipKey] forKey:SPBundleFileTooltipKey]; + // Register scope/category menu structure + if(![bundleUsedScopes containsObject:scope]) { + [bundleUsedScopes addObject:scope]; + [bundleItems setObject:[NSMutableArray array] forKey:scope]; + [bundleCategories setObject:[NSMutableArray array] forKey:scope]; + [bundleKeyEquivalents setObject:[NSMutableDictionary dictionary] forKey:scope]; + } + if([cmdData objectForKey:SPBundleFileCategoryKey] && [[cmdData objectForKey:SPBundleFileCategoryKey] length] && ![[bundleCategories objectForKey:scope] containsObject:[cmdData objectForKey:SPBundleFileCategoryKey]]) + [[bundleCategories objectForKey:scope] addObject:[cmdData objectForKey:SPBundleFileCategoryKey]]; + + NSMutableDictionary *aDict = [NSMutableDictionary dictionary]; + [aDict setObject:[cmdData objectForKey:SPBundleFileNameKey] forKey:SPBundleInternLabelKey]; + [aDict setObject:infoPath forKey:SPBundleInternPathToFileKey]; + + // Register trigger + if([cmdData objectForKey:SPBundleFileTriggerKey]) { + if(![bundleTriggers objectForKey:[cmdData objectForKey:SPBundleFileTriggerKey]]) + [bundleTriggers setObject:[NSMutableArray array] forKey:[cmdData objectForKey:SPBundleFileTriggerKey]]; + [[bundleTriggers objectForKey:[cmdData objectForKey:SPBundleFileTriggerKey]] addObject: + [NSString stringWithFormat:@"%@|%@|%@", + infoPath, + [cmdData objectForKey:SPBundleFileScopeKey], + ([[cmdData objectForKey:SPBundleFileOutputActionKey] isEqualToString:SPBundleOutputActionShowAsHTML])?[cmdData objectForKey:SPBundleFileUUIDKey]:@""]]; + } - if([cmdData objectForKey:SPBundleFileCategoryKey] && [[cmdData objectForKey:SPBundleFileCategoryKey] length]) - [aDict setObject:[cmdData objectForKey:SPBundleFileCategoryKey] forKey:SPBundleFileCategoryKey]; + // Register key equivalent + if([cmdData objectForKey:SPBundleFileKeyEquivalentKey] && [[cmdData objectForKey:SPBundleFileKeyEquivalentKey] length]) { + + NSString *theKey = [cmdData objectForKey:SPBundleFileKeyEquivalentKey]; + NSString *theChar = [theKey substringFromIndex:[theKey length]-1]; + NSString *theMods = [theKey substringToIndex:[theKey length]-1]; + NSUInteger mask = 0; + if([theMods rangeOfString:@"^"].length) mask = mask | NSControlKeyMask; + if([theMods rangeOfString:@"@"].length) mask = mask | NSCommandKeyMask; + if([theMods rangeOfString:@"~"].length) mask = mask | NSAlternateKeyMask; + if([theMods rangeOfString:@"$"].length) mask = mask | NSShiftKeyMask; + + if(![[bundleKeyEquivalents objectForKey:scope] objectForKey:[cmdData objectForKey:SPBundleFileKeyEquivalentKey]]) + [[bundleKeyEquivalents objectForKey:scope] setObject:[NSMutableArray array] forKey:[cmdData objectForKey:SPBundleFileKeyEquivalentKey]]; + + if(!doBundleUpdate || (doBundleUpdate && (![[cmdData objectForKey:SPBundleFileIsDefaultBundleKey] boolValue] || processDefaultBundles))) + [[[bundleKeyEquivalents objectForKey:scope] objectForKey:[cmdData objectForKey:SPBundleFileKeyEquivalentKey]] addObject: + [NSDictionary dictionaryWithObjectsAndKeys: + infoPath, @"path", + [cmdData objectForKey:SPBundleFileNameKey], @"title", + ([cmdData objectForKey:SPBundleFileTooltipKey]) ?: @"", @"tooltip", + nil]]; + + [aDict setObject:[NSArray arrayWithObjects:theChar, [NSNumber numberWithInteger:mask], nil] forKey:SPBundleInternKeyEquivalentKey]; + } - if([cmdData objectForKey:SPBundleFileKeyEquivalentKey] && [[cmdData objectForKey:SPBundleFileKeyEquivalentKey] length]) - [aDict setObject:[cmdData objectForKey:SPBundleFileKeyEquivalentKey] forKey:@"key"]; + if([cmdData objectForKey:SPBundleFileTooltipKey] && [[cmdData objectForKey:SPBundleFileTooltipKey] length]) + [aDict setObject:[cmdData objectForKey:SPBundleFileTooltipKey] forKey:SPBundleFileTooltipKey]; - for(NSString* scope in scopes) - [[bundleItems objectForKey:scope] addObject:aDict]; + if([cmdData objectForKey:SPBundleFileCategoryKey] && [[cmdData objectForKey:SPBundleFileCategoryKey] length]) + [aDict setObject:[cmdData objectForKey:SPBundleFileCategoryKey] forKey:SPBundleFileCategoryKey]; - } + if([cmdData objectForKey:SPBundleFileKeyEquivalentKey] && [[cmdData objectForKey:SPBundleFileKeyEquivalentKey] length]) + [aDict setObject:[cmdData objectForKey:SPBundleFileKeyEquivalentKey] forKey:@"key"]; - if (cmdData) [cmdData release]; + [[bundleItems objectForKey:scope] addObject:aDict]; } } + // Sort items for menus NSSortDescriptor *sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:SPBundleInternLabelKey ascending:YES] autorelease]; for(NSString* scope in [bundleItems allKeys]) { [[bundleItems objectForKey:scope] sortUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]]; @@ -1624,9 +1817,10 @@ } [deletedDefaultBundles release]; - - // Synchronize updated Bundles - [[NSUserDefaults standardUserDefaults] setObject:updatedDefaultBundles forKey:SPBundleUpdatedDefaultBundlesKey]; + if(doBundleUpdate) { + [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"doBundleUpdate"]; + [[NSUserDefaults standardUserDefaults] synchronize]; + } // Inform user about default Bundle updates which were modified by the user and re-run Reload Bundles if([infoAboutUpdatedDefaultBundles length]) { diff --git a/Source/SPBundleCommandTextView.m b/Source/SPBundleCommandTextView.m index 6411ccfd..d9aae0f7 100644 --- a/Source/SPBundleCommandTextView.m +++ b/Source/SPBundleCommandTextView.m @@ -31,8 +31,8 @@ - (void)dealloc { - [prefs removeObserver:self forKeyPath:SPCustomQueryEditorTabStopWidth]; [[NSNotificationCenter defaultCenter] removeObserver:self]; + [prefs removeObserver:self forKeyPath:SPCustomQueryEditorTabStopWidth]; [prefs release]; [lineNumberView release]; } @@ -58,12 +58,13 @@ // Re-define tab stops for a better editing [self setTabStops]; - // add NSViewBoundsDidChangeNotification to scrollView - [[commandScrollView contentView] setPostsBoundsChangedNotifications:YES]; - // disabled to get the current text range in textView safer [[self layoutManager] setBackgroundLayoutEnabled:NO]; + // add NSViewBoundsDidChangeNotification to scrollView + [commandScrollView setPostsBoundsChangedNotifications:YES]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(boundsDidChangeNotification:) name:NSViewBoundsDidChangeNotification object:[commandScrollView contentView]]; + } - (void)drawRect:(NSRect)rect @@ -781,6 +782,15 @@ textWasChanged = YES; } +/** + * Scrollview delegate after the command textView's view port was changed. + * Manily used to render line numbering. + */ +- (void)boundsDidChangeNotification:(NSNotification *)notification +{ + [commandScrollView display]; +} + #pragma mark - // Store the font in the prefs for selected delegates only diff --git a/Source/SPBundleEditorController.h b/Source/SPBundleEditorController.h index ac4c9e6b..1c3c0dab 100644 --- a/Source/SPBundleEditorController.h +++ b/Source/SPBundleEditorController.h @@ -27,7 +27,7 @@ #import "SPBundleCommandTextView.h" #import "SPOutlineView.h" -@class SRRecorderControl; +@class SRRecorderControl, BWSplitView; @interface SPBundleEditorController : NSWindowController { @@ -61,9 +61,9 @@ IBOutlet NSMenuItem *revealInFinderMenuItem; IBOutlet SRRecorderControl *keyEquivalentField; IBOutlet NSButton *disabledCheckbox; - IBOutlet NSView *bundleDataView; IBOutlet NSScrollView *commandScrollView; IBOutlet NSScrollView *descriptionScrollView; + IBOutlet BWSplitView *splitView; IBOutlet id undeleteSheet; IBOutlet NSTableView *undeleteTableView; diff --git a/Source/SPBundleEditorController.m b/Source/SPBundleEditorController.m index de779e67..279002f4 100644 --- a/Source/SPBundleEditorController.m +++ b/Source/SPBundleEditorController.m @@ -38,6 +38,8 @@ #define SP_BUNDLEEDITOR_SCOPE_GENERAL_STRING NSLocalizedString(@"General", @"Bundle Editor : Scope dropdown : 'general' item") #define SP_BUNDLEEDITOR_OUTLINE_BUNDLE_TOOLTIP_STRING NSLocalizedString(@"“%@” Bundle",@"Bundle Editor : Outline View : Bundle item : tooltip") +#define SP_BUNDLEEDITOR_SPLITVIEW_AUTOSAVE_STRING @"SPBundleEditorSplitView" + @interface SPBundleEditorController (PrivateAPI) - (void)_updateBundleDataView; @@ -116,6 +118,15 @@ - (void)awakeFromNib { + // Set up the splitview width manually; autosave appears to save but not restore this value + // here, so restore in code if present. + NSString *splitViewKeyName = [NSString stringWithFormat:@"NSSplitView Subview Frames %@", SP_BUNDLEEDITOR_SPLITVIEW_AUTOSAVE_STRING]; + if ([[NSUserDefaults standardUserDefaults] arrayForKey:splitViewKeyName]) { + NSString *detailString = [[[NSUserDefaults standardUserDefaults] arrayForKey:splitViewKeyName] objectAtIndex:0]; + float dividerPosition = [[[detailString componentsSeparatedByString:@", "] objectAtIndex:2] floatValue]; + [splitView setPosition:dividerPosition ofDividerAtIndex:0]; + } + // Init all needed variables; popup menus (with the chance for localization); and set // defaults @@ -131,6 +142,7 @@ [[commandBundleTree objectForKey:kChildrenKey] addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:[NSMutableArray array], kChildrenKey, SP_BUNDLEEDITOR_SCOPE_INPUTFIELD_STRING, kBundleNameKey, nil]]; [[commandBundleTree objectForKey:kChildrenKey] addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:[NSMutableArray array], kChildrenKey, SP_BUNDLEEDITOR_SCOPE_DATATABLE_STRING, kBundleNameKey, nil]]; [[commandBundleTree objectForKey:kChildrenKey] addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:[NSMutableArray array], kChildrenKey, SP_BUNDLEEDITOR_SCOPE_GENERAL_STRING, kBundleNameKey, nil]]; + [commandBundleTreeController setContent:commandBundleTree]; // Init all needed menus inputGeneralScopePopUpMenu = [[NSMenu alloc] initWithTitle:@""]; @@ -353,6 +365,7 @@ SPBundleShellVariableSelectedTable, SPBundleShellVariableSelectedTables, SPBundleShellVariableSelectedText, + SPBundleShellVariableSelectedTextRange, SPBundleShellVariableUsedQueryForTable, nil ] retain]; @@ -1031,10 +1044,13 @@ if(deletionSuccessfully) { [commandBundleTreeController removeObjectsAtArrangedObjectIndexPaths:selIndexPaths]; [commandBundleTreeController rearrangeObjects]; - } else { - [self reloadBundles:self]; } + [self reloadBundles:self]; + + [commandBundleTreeController setSelectionIndexPath:[[selIndexPaths objectAtIndex:0] indexPathByRemovingLastIndex]]; + [commandsOutlineView expandItem:[self _currentSelectedNode] expandChildren:NO]; + // Set focus to table view to avoid an unstable state [[self window] makeFirstResponder:commandsOutlineView]; @@ -1291,7 +1307,7 @@ [touchedBundleArray addObject:oldBundleName]; [self _updateBundleDataView]; - + [commandTextView setSelectedRange:NSMakeRange(0,0)]; } - (BOOL)outlineView:(NSOutlineView *)outlineView shouldShowOutlineCellForItem:(id)item @@ -1793,7 +1809,6 @@ [removeButton setEnabled:([[commandBundleTreeController selectedObjects] count] == 1 && ![[[commandBundleTreeController selectedObjects] objectAtIndex:0] objectForKey:kChildrenKey])]; [addButton setEnabled:([[commandBundleTreeController selectionIndexPath] length] > 1)]; - [commandBundleTreeController setContent:commandBundleTree]; NSUInteger *selPath[2]; selPath[0] = 0; selPath[1] = 0; diff --git a/Source/SPBundleHTMLOutputController.h b/Source/SPBundleHTMLOutputController.h index ea0b96da..3665fc8e 100644 --- a/Source/SPBundleHTMLOutputController.h +++ b/Source/SPBundleHTMLOutputController.h @@ -33,6 +33,7 @@ NSString *initHTMLSourceString; NSString *windowUUID; NSString *docUUID; + BOOL suppressExceptionAlert; WebPreferences *webPreferences; } @@ -41,6 +42,7 @@ @property(readwrite,retain) NSString *initHTMLSourceString; @property(readwrite,retain) NSString *windowUUID; @property(readwrite,retain) NSString *docUUID; +@property(assign) BOOL suppressExceptionAlert; - (IBAction)printDocument:(id)sender; diff --git a/Source/SPBundleHTMLOutputController.m b/Source/SPBundleHTMLOutputController.m index 7bcff455..3c47964a 100644 --- a/Source/SPBundleHTMLOutputController.m +++ b/Source/SPBundleHTMLOutputController.m @@ -33,6 +33,7 @@ @synthesize initHTMLSourceString; @synthesize windowUUID; @synthesize docUUID; +@synthesize suppressExceptionAlert; /** * Initialisation @@ -50,6 +51,7 @@ [webView setEditable:NO]; [webView setShouldCloseWithWindow:YES]; [webView setShouldUpdateWhileOffscreen:NO]; + suppressExceptionAlert = NO; } @@ -364,8 +366,18 @@ return @"run"; if (aSelector == @selector(getShellEnvironmentForName:)) return @"getShellEnvironmentForName"; + if (aSelector == @selector(insertText:)) + return @"insertText"; + if (aSelector == @selector(setText:)) + return @"setText"; + if (aSelector == @selector(setSelectedTextRange:)) + return @"setSelectedTextRange"; if (aSelector == @selector(makeHTMLOutputWindowKeyWindow)) return @"makeHTMLOutputWindowKeyWindow"; + if (aSelector == @selector(closeHTMLOutputWindow)) + return @"closeHTMLOutputWindow"; + if (aSelector == @selector(suppressExceptionAlert)) + return @"suppressExceptionAlert"; return @""; } @@ -376,9 +388,24 @@ if (selector == @selector(getShellEnvironmentForName:)) { return NO; } + if (selector == @selector(insertText:)) { + return NO; + } + if (selector == @selector(setText:)) { + return NO; + } + if (selector == @selector(setSelectedTextRange:)) { + return NO; + } if (selector == @selector(makeHTMLOutputWindowKeyWindow)) { return NO; } + if (selector == @selector(closeHTMLOutputWindow)) { + return NO; + } + if (selector == @selector(suppressExceptionAlert)) { + return NO; + } return YES; } @@ -389,6 +416,15 @@ if (strcmp(property, "getShellEnvironmentForName") == 0) { return NO; } + if (strcmp(property, "insertText") == 0) { + return NO; + } + if (strcmp(property, "setText") == 0) { + return NO; + } + if (strcmp(property, "setSelectedTextRange") == 0) { + return NO; + } if (strcmp(property, "makeHTMLOutputWindowKeyWindow") == 0) { return NO; } @@ -411,8 +447,14 @@ - (void)webView:(WebView *)webView exceptionWasRaised:(WebScriptCallFrame *)frame sourceId:(NSInteger)sid line:(NSInteger)lineno forWebFrame:(WebFrame *)webFrame { + NSString *mes = [NSString stringWithFormat:@"Exception:\nline = %ld\nfunction = %@\ncaller = %@\nexception = %@", lineno, [frame functionName], [frame caller], [frame userInfo], [frame exception]]; + if([self suppressExceptionAlert]) { + NSLog(@"%@", mes); + return; + } + NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"JavaScript Exception", @"javascript exception") defaultButton:NSLocalizedString(@"OK", @"OK button") alternateButton:nil @@ -441,6 +483,70 @@ } /** + * JavaScript window.system.makeHTMLOutputWindowKeyWindow() function + * to close the HTML window + */ +- (void)closeHTMLOutputWindow +{ + [[self window] close]; +} + +/** + * JavaScript window.system.insertText(text) function + * to insert text into the first responder + */ +- (void)insertText:(NSString*)text +{ + id firstResponder = [[NSApp keyWindow] firstResponder]; + if([firstResponder isKindOfClass:[NSTextView class]]) { + [firstResponder insertText:text]; + return; + } + NSBeep(); +} + +/** + * JavaScript window.system.setText(text) function + * to set the content of the first responder to text + */ +- (void)setText:(NSString*)text +{ + id firstResponder = [[NSApp keyWindow] firstResponder]; + if([firstResponder isKindOfClass:[NSTextView class]]) { + [firstResponder setSelectedRange:NSMakeRange(0, [[firstResponder string] length])]; + [firstResponder insertText:text]; + return; + } + NSBeep(); +} + +/** + * JavaScript window.system.setSelectedRange({location,length}) function + * to set the selection range of the first responder + */ +- (void)setSelectedTextRange:(NSString*)range +{ + id firstResponder = [[NSApp keyWindow] firstResponder]; + if([firstResponder isKindOfClass:[NSTextView class]]) { + NSRange theRange = NSIntersectionRange(NSRangeFromString(range), NSMakeRange(0, [[firstResponder string] length])); + if(theRange.location != NSNotFound) { + [firstResponder setSelectedRange:theRange]; + } + return; + } + NSBeep(); +} + +/** + * JavaScript window.system.suppressExceptionAlert() function + * to suppress an exception alert, instead write the message to NSLog + */ +- (void)suppressExceptionAlert +{ + [self setSuppressExceptionAlert:YES]; +} + +/** * JavaScript window.system.run('a_command'|new Array('a_command', 'uuid')) function * to return the result of the BASH command a_command */ diff --git a/Source/SPConnectionController.h b/Source/SPConnectionController.h index 73a7ef77..590349bc 100644 --- a/Source/SPConnectionController.h +++ b/Source/SPConnectionController.h @@ -83,6 +83,7 @@ NSString *sshKeyLocation; NSString *sshPort; + NSString *connectionKeychainID; NSString *connectionKeychainItemName; NSString *connectionKeychainItemAccount; NSString *connectionSSHKeychainItemName; diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index c12f1ac4..9f542cec 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -285,15 +285,10 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v [favoritesOutlineView setEnabled:NO]; [addToFavoritesButton setHidden:YES]; - [addToFavoritesButton display]; [helpButton setHidden:YES]; - [helpButton display]; [connectButton setEnabled:NO]; - [connectButton display]; [progressIndicator startAnimation:self]; - [progressIndicator display]; [progressIndicatorText setHidden:NO]; - [progressIndicatorText display]; // Start the current tab's progress indicator [dbDocument setIsProcessing:YES]; @@ -424,7 +419,7 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v [self setSslCertificateFileLocation:nil]; return; } - permittedFileTypes = [NSArray arrayWithObjects:@"pem", @"cert", @"", nil]; + permittedFileTypes = [NSArray arrayWithObjects:@"pem", @"cert", @"crt", @"", nil]; [openPanel setAccessoryView:sslCertificateLocationHelp]; // SSL CA certificate file location: @@ -433,7 +428,7 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v [self setSslCACertFileLocation:nil]; return; } - permittedFileTypes = [NSArray arrayWithObjects:@"pem", @"cert", @"", nil]; + permittedFileTypes = [NSArray arrayWithObjects:@"pem", @"cert", @"crt", @"", nil]; [openPanel setAccessoryView:sslCACertLocationHelp]; } diff --git a/Source/SPConnectionHandler.m b/Source/SPConnectionHandler.m index 6d777ff5..adf21ffa 100644 --- a/Source/SPConnectionHandler.m +++ b/Source/SPConnectionHandler.m @@ -330,13 +330,7 @@ certificateAuthorityCertificatePath:[self sslCACertFileLocationEnabled] ? [self for (NSInteger i = 0; i < [toolbarItems count]; i++) [[toolbarItems objectAtIndex:i] setEnabled:YES]; - // Set keychain id for saving SPF files - if ([self valueForKeyPath:@"selectedFavorite.id"]) { - [dbDocument setKeychainID:[[self valueForKeyPath:@"selectedFavorite.id"] stringValue]]; - } - else { - [dbDocument setKeychainID:@""]; - } + if (connectionKeychainID) [dbDocument setKeychainID:connectionKeychainID]; // Pass the connection to the table document, allowing it to set // up the other classes and the rest of the interface. diff --git a/Source/SPConstants.h b/Source/SPConstants.h index c5a932ea..3a15f71c 100644 --- a/Source/SPConstants.h +++ b/Source/SPConstants.h @@ -180,6 +180,16 @@ typedef enum SPEncodingEUCKRKorean = 180 } SPEncodingTypes; +// Table index type menu tags +typedef enum +{ + SPPrimaryKeyMenuTag = 0, + SPIndexMenuTag = 1, + SPUniqueMenuTag = 2, + SPFullTextMenuTag = 3, + SPSpatialMenuTag = 4 +} SPTableIndexTypeTags; + // File compression formats typedef enum { @@ -312,6 +322,8 @@ extern NSString *SPCustomQueryAutoComplete; extern NSString *SPCustomQueryAutoCompleteDelay; extern NSString *SPCustomQueryFunctionCompletionInsertsArguments; extern NSString *SPCustomQueryEditorThemeName; +extern NSString *SPCustomQuerySoftIndent; +extern NSString *SPCustomQuerySoftIndentWidth; // AutoUpdate Prefpane extern NSString *SPLastUsedVersion; @@ -369,7 +381,6 @@ extern NSString *SPLastImportIntoNewTableEncoding; extern NSString *SPLastImportIntoNewTableType; extern NSString *SPGlobalValueHistory; extern NSString *SPBundleDeletedDefaultBundlesKey; -extern NSString *SPBundleUpdatedDefaultBundlesKey; // URLs extern NSString *SPDonationsURL; @@ -510,6 +521,7 @@ extern NSString *SPBundleShellVariableSelectedText; extern NSString *SPBundleShellVariableCurrentWord; extern NSString *SPBundleShellVariableCurrentLine; extern NSString *SPBundleShellVariableSelectedRowIndices; +extern NSString *SPBundleShellVariableSelectedTextRange; extern NSString *SPBundleShellVariableAllDatabases; extern NSString *SPBundleShellVariableSelectedTables; extern NSString *SPBundleShellVariableSelectedDatabase; diff --git a/Source/SPConstants.m b/Source/SPConstants.m index c7ac921d..df1d71dc 100644 --- a/Source/SPConstants.m +++ b/Source/SPConstants.m @@ -123,6 +123,8 @@ NSString *SPCustomQueryAutoComplete = @"CustomQueryAutoComplete"; NSString *SPCustomQueryAutoCompleteDelay = @"CustomQueryAutoCompleteDelay"; NSString *SPCustomQueryFunctionCompletionInsertsArguments = @"CustomQueryFunctionCompletionInsertsArguments"; NSString *SPCustomQueryEditorThemeName = @"CustomQueryEditorThemeName"; +NSString *SPCustomQuerySoftIndent = @"CustomQuerySoftIndent"; +NSString *SPCustomQuerySoftIndentWidth = @"CustomQuerySoftIndentWidth"; // AutoUpdate Prefpane NSString *SPLastUsedVersion = @"LastUsedVersion"; @@ -180,11 +182,10 @@ NSString *SPLastImportIntoNewTableEncoding = @"LastImportIntoNewTableEncod NSString *SPLastImportIntoNewTableType = @"LastImportIntoNewTableType"; NSString *SPGlobalValueHistory = @"GlobalValueHistory"; NSString *SPBundleDeletedDefaultBundlesKey = @"deletedDefaultBundles"; -NSString *SPBundleUpdatedDefaultBundlesKey = @"updatedDefaultBundles"; // URLs NSString *SPDonationsURL = @"http://www.sequelpro.com/donate.html"; -NSString *SPMySQLSearchURL = @"http://search.mysql.com/search?q=%@&site=refman-%@&lr=lang_%@"; +NSString *SPMySQLSearchURL = @"http://search.mysql.com/search/query/search?q=%@&group=refman-%@"; NSString *SPDevURL = @"http://code.google.com/p/sequel-pro/"; // Toolbar constants @@ -325,7 +326,7 @@ NSString *SPBundleShellVariableExitShowAsHTML = @"SP_BUNDLE_EXIT_S NSString *SPBundleShellVariableExitShowAsHTMLTooltip = @"SP_BUNDLE_EXIT_SHOW_AS_HTML_TOOLTIP"; NSString *SPBundleShellVariableExitInsertAsSnippet = @"SP_BUNDLE_EXIT_INSERT_AS_SNIPPET"; NSString *SPBundleShellVariableExitInsertAsText = @"SP_BUNDLE_EXIT_INSERT_AS_TEXT"; -NSString *SPBundleShellVariableExitShowAsTextTooltip = @"SP_BUNDLE_EXIT_SHOW_AS_TEXT_TOOLTIP"; +NSString *SPBundleShellVariableExitShowAsTextTooltip = @"SP_BUNDLE_EXIT_SHOW_AS_TEXT_TOOLTIP"; NSString *SPBundleShellVariableExitNone = @"SP_BUNDLE_EXIT_NONE"; NSString *SPBundleShellVariableExitReplaceContent = @"SP_BUNDLE_EXIT_REPLACE_CONTENT"; NSString *SPBundleShellVariableExitReplaceSelection = @"SP_BUNDLE_EXIT_REPLACE_SELECTION"; @@ -345,6 +346,7 @@ NSString *SPBundleShellVariableSelectedRowIndices = @"SP_SELECTED_ROW_ NSString *SPBundleShellVariableSelectedTable = @"SP_SELECTED_TABLE"; NSString *SPBundleShellVariableSelectedTables = @"SP_SELECTED_TABLES"; NSString *SPBundleShellVariableSelectedText = @"SP_SELECTED_TEXT"; +NSString *SPBundleShellVariableSelectedTextRange = @"SP_SELECTED_TEXT_RANGE"; NSString *SPBundleShellVariableUsedQueryForTable = @"SP_USED_QUERY_FOR_TABLE"; const NSInteger SPBundleRedirectActionNone = 200; diff --git a/Source/SPCopyTable.m b/Source/SPCopyTable.m index 827672b5..3b84d867 100644 --- a/Source/SPCopyTable.m +++ b/Source/SPCopyTable.m @@ -698,6 +698,10 @@ NSInteger kBlobAsImageFile = 4; NSUInteger columnWidth; NSUInteger allColumnWidths = 0; + // Determine the available size + NSScrollView *parentScrollView = [[self superview] superview]; + CGFloat visibleTableWidth = [parentScrollView bounds].size.width - [NSScroller scrollerWidth] - [columnDefinitions count] * 3.5; + for (NSDictionary *columnDefinition in columnDefinitions) { if ([[NSThread currentThread] isCancelled]) return nil; @@ -707,7 +711,7 @@ NSInteger kBlobAsImageFile = 4; } // Compare the column widths to the table width. If wider, narrow down wide columns as necessary - if (allColumnWidths > [self bounds].size.width) { + if (allColumnWidths > visibleTableWidth) { NSUInteger availableWidthToReduce = 0; // Look for columns that are wider than the multi-column max @@ -717,7 +721,7 @@ NSInteger kBlobAsImageFile = 4; } // Determine how much width can be reduced - NSUInteger widthToReduce = allColumnWidths - [self bounds].size.width; + NSUInteger widthToReduce = allColumnWidths - visibleTableWidth; if (availableWidthToReduce < widthToReduce) widthToReduce = availableWidthToReduce; // Proportionally decrease the column sizes @@ -930,6 +934,10 @@ NSInteger kBlobAsImageFile = 4; { NSInteger menuItemTag = [anItem tag]; + if ([anItem action] == @selector(performFindPanelAction:)) { + return (menuItemTag == 1 && [[self delegate] isKindOfClass:[SPTableContent class]]); + } + // Don't validate anything other than the copy commands if (menuItemTag != MENU_EDIT_COPY && menuItemTag != MENU_EDIT_COPY_WITH_COLUMN && menuItemTag != MENU_EDIT_COPY_AS_SQL) { return YES; @@ -1091,6 +1099,13 @@ NSInteger kBlobAsImageFile = 4; [super keyDown:theEvent]; } +- (void)performFindPanelAction:(id)sender +{ + if([sender tag] == 1 && [[self delegate] isKindOfClass:[SPTableContent class]]) { + [[self delegate] showFilterTable:self]; + } +} + #pragma mark - #pragma mark Bundle Command Support @@ -1221,33 +1236,47 @@ NSInteger kBlobAsImageFile = 4; return; } + + // Create an array of table column mappings for fast iteration + NSArray *columns = [self tableColumns]; + NSUInteger numColumns = [columns count]; + NSUInteger *columnMappings = malloc(numColumns * sizeof(NSUInteger)); + NSInteger c; + for ( c = 0; c < numColumns; c++ ) + columnMappings[c] = [[NSArrayObjectAtIndex(columns, c) identifier] unsignedIntValue]; + NSMutableString *tableMetaData = [NSMutableString string]; if([[self delegate] isKindOfClass:[SPCustomQuery class]]) { [env setObject:@"query" forKey:SPBundleShellVariableDataTableSource]; NSArray *defs = [[self delegate] dataColumnDefinitions]; - for(NSDictionary* col in defs) { - [tableMetaData appendFormat:@"%@\t", [col objectForKey:@"type"]]; - [tableMetaData appendFormat:@"%@\t", [col objectForKey:@"typegrouping"]]; - [tableMetaData appendFormat:@"%@\t", ([col objectForKey:@"char_length"]) ? : @""]; - [tableMetaData appendFormat:@"%@\t", [col objectForKey:@"UNSIGNED_FLAG"]]; - [tableMetaData appendFormat:@"%@\t", [col objectForKey:@"AUTO_INCREMENT_FLAG"]]; - [tableMetaData appendFormat:@"%@\t", [col objectForKey:@"PRI_KEY_FLAG"]]; - [tableMetaData appendString:@"\n"]; - } + if(defs && [defs count] == numColumns) + for( c = 0; c < numColumns; c++ ) { + NSDictionary *col = NSArrayObjectAtIndex(defs, columnMappings[c]); + [tableMetaData appendFormat:@"%@\t", [col objectForKey:@"type"]]; + [tableMetaData appendFormat:@"%@\t", [col objectForKey:@"typegrouping"]]; + [tableMetaData appendFormat:@"%@\t", ([col objectForKey:@"char_length"]) ? : @""]; + [tableMetaData appendFormat:@"%@\t", [col objectForKey:@"UNSIGNED_FLAG"]]; + [tableMetaData appendFormat:@"%@\t", [col objectForKey:@"AUTO_INCREMENT_FLAG"]]; + [tableMetaData appendFormat:@"%@\t", [col objectForKey:@"PRI_KEY_FLAG"]]; + [tableMetaData appendString:@"\n"]; + } } else if([[self delegate] isKindOfClass:[SPTableContent class]]) { [env setObject:@"content" forKey:SPBundleShellVariableDataTableSource]; NSArray *defs = [[self delegate] dataColumnDefinitions]; - for(NSDictionary* col in defs) { - [tableMetaData appendFormat:@"%@\t", [col objectForKey:@"type"]]; - [tableMetaData appendFormat:@"%@\t", [col objectForKey:@"typegrouping"]]; - [tableMetaData appendFormat:@"%@\t", ([col objectForKey:@"length"]) ? : @""]; - [tableMetaData appendFormat:@"%@\t", [col objectForKey:@"unsigned"]]; - [tableMetaData appendFormat:@"%@\t", [col objectForKey:@"autoincrement"]]; - [tableMetaData appendFormat:@"%@\t", ([col objectForKey:@"isprimarykey"]) ? : @"0"]; - [tableMetaData appendFormat:@"%@\n", [col objectForKey:@"comment"]]; - } + if(defs && [defs count] == numColumns) + for( c = 0; c < numColumns; c++ ) { + NSDictionary *col = NSArrayObjectAtIndex(defs, columnMappings[c]); + [tableMetaData appendFormat:@"%@\t", [col objectForKey:@"type"]]; + [tableMetaData appendFormat:@"%@\t", [col objectForKey:@"typegrouping"]]; + [tableMetaData appendFormat:@"%@\t", ([col objectForKey:@"length"]) ? : @""]; + [tableMetaData appendFormat:@"%@\t", [col objectForKey:@"unsigned"]]; + [tableMetaData appendFormat:@"%@\t", [col objectForKey:@"autoincrement"]]; + [tableMetaData appendFormat:@"%@\t", ([col objectForKey:@"isprimarykey"]) ? : @"0"]; + [tableMetaData appendFormat:@"%@\n", [col objectForKey:@"comment"]]; + } } + free(columnMappings); inputFileError = nil; [tableMetaData writeToFile:bundleInputTableMetaDataFilePath diff --git a/Source/SPCustomQuery.h b/Source/SPCustomQuery.h index f4c2440d..317db0c9 100644 --- a/Source/SPCustomQuery.h +++ b/Source/SPCustomQuery.h @@ -147,6 +147,7 @@ BOOL queryIsTableSorter; BOOL isDesc; BOOL isFieldEditable; + BOOL textViewWasChanged; NSNumber *sortField; NSIndexSet *selectionIndexToRestore; @@ -170,6 +171,8 @@ NSString *kCellEditorErrorTooManyMatches; } +@property(assign) BOOL textViewWasChanged; + // IBAction methods - (IBAction)runAllQueries:(id)sender; - (void) runAllQueriesCallback; @@ -245,7 +248,7 @@ - (void)commentOutCurrentQueryTakingSelection:(BOOL)takeSelection; - (NSString *)usedQuery; - (NSString *)argumentForRow:(NSUInteger)rowIndex ofTable:(NSString *)tableForColumn andDatabase:(NSString *)database includeBlobs:(BOOL)includeBlobs; -- (NSArray*)fieldEditStatusForRow:(NSInteger)rowIndex andColumn:(NSInteger)columnIndex; +- (NSArray*)fieldEditStatusForRow:(NSInteger)rowIndex andColumn:(NSNumber *)columnIndex; - (NSUInteger)numberOfQueries; - (NSRange)currentQueryRange; - (NSString *)buildHistoryString; diff --git a/Source/SPCustomQuery.m b/Source/SPCustomQuery.m index e8420b7c..b2859010 100644 --- a/Source/SPCustomQuery.m +++ b/Source/SPCustomQuery.m @@ -45,6 +45,8 @@ @implementation SPCustomQuery +@synthesize textViewWasChanged; + #pragma mark IBAction methods /* @@ -940,7 +942,8 @@ // Split the current text into ranges of queries // only if the textView was really changed, otherwise use the cache - if([[textView textStorage] editedMask] != 0) { + if([[textView textStorage] editedMask] != 0 || [self textViewWasChanged]) { + [self setTextViewWasChanged:NO]; customQueryParser = [[SPSQLParser alloc] initWithString:[textView string]]; [customQueryParser setDelimiterSupport:YES]; queries = [[NSArray alloc] initWithArray:[customQueryParser splitStringIntoRangesByCharacter:';']]; @@ -1634,7 +1637,7 @@ * -2 for other errors * and the used WHERE clause to identify */ -- (NSArray*)fieldEditStatusForRow:(NSInteger)rowIndex andColumn:(NSInteger)columnIndex +- (NSArray*)fieldEditStatusForRow:(NSInteger)rowIndex andColumn:(NSNumber*)columnIndex { NSDictionary *columnDefinition = nil; @@ -2580,6 +2583,7 @@ BOOL isLookBehind = YES; NSRange currentSelection = [textView selectedRange]; NSUInteger caretPosition = currentSelection.location; + NSRange qRange = [self queryRangeAtPosition:caretPosition lookBehind:&isLookBehind]; if(qRange.length) @@ -3679,8 +3683,9 @@ column = [customQueryView editedColumn]; // Retrieve the column defintion + NSNumber *colIdentifier = [NSArrayObjectAtIndex([customQueryView tableColumns], column) identifier]; for(id c in cqColumnDefinition) { - if([[c objectForKey:@"datacolumnindex"] isEqualToNumber:[NSNumber numberWithInteger:column]]) { + if([[c objectForKey:@"datacolumnindex"] isEqualToNumber:colIdentifier]) { columnDefinition = [NSDictionary dictionaryWithDictionary:c]; break; } @@ -3688,8 +3693,8 @@ if(!columnDefinition) return NO; - NSArray *editStatus = [self fieldEditStatusForRow:row andColumn:[NSArrayObjectAtIndex([customQueryView tableColumns], column) identifier]]; - NSInteger numberOfPossibleUpdateRows = [[editStatus objectAtIndex:0] integerValue]; + NSArray *editStatus = [self fieldEditStatusForRow:row andColumn:colIdentifier]; + NSInteger numberOfPossibleUpdateRows = [NSArrayObjectAtIndex(editStatus, 0) integerValue]; NSPoint pos = [[tableDocumentInstance parentWindow] convertBaseToScreen:[customQueryView convertPoint:[customQueryView frameOfCellAtColumn:column row:row].origin toView:nil]]; pos.y -= 20; switch(numberOfPossibleUpdateRows) { diff --git a/Source/SPDatabaseDocument.h b/Source/SPDatabaseDocument.h index bcd92d59..3459b9b8 100644 --- a/Source/SPDatabaseDocument.h +++ b/Source/SPDatabaseDocument.h @@ -65,6 +65,9 @@ IBOutlet NSSearchField *listFilterField; + IBOutlet NSScrollView *tableInfoScrollView; + IBOutlet NSScrollView *activitiesScrollView; + IBOutlet NSView *parentView; IBOutlet id titleAccessoryView; @@ -363,6 +366,7 @@ - (void)handleSchemeCommand:(NSDictionary*)commandDict; - (void)registerActivity:(NSDictionary*)commandDict; - (void)removeRegisteredActivity:(NSInteger)pid; +- (void)setActivityPaneHidden:(NSNumber*)hide; - (NSArray*)runningActivities; - (NSDictionary*)shellVariables; diff --git a/Source/SPDatabaseDocument.m b/Source/SPDatabaseDocument.m index 787817bc..e122205e 100644 --- a/Source/SPDatabaseDocument.m +++ b/Source/SPDatabaseDocument.m @@ -56,18 +56,6 @@ #import "SPDatabaseRename.h" #import "SPServerSupport.h" #import "SPTooltip.h" -#import "SPEditorTokens.h" - -#pragma mark lex init - -/* -* Include all the extern variables and prototypes required for flex (used for syntax highlighting) -*/ -extern NSUInteger yylex(); -extern NSUInteger yyuoffset, yyuleng; -typedef struct yy_buffer_state *YY_BUFFER_STATE; -void yy_switch_to_buffer(YY_BUFFER_STATE); -YY_BUFFER_STATE yy_scan_string (const char *); @interface SPDatabaseDocument (PrivateAPI) @@ -2514,8 +2502,10 @@ YY_BUFFER_STATE yy_scan_string (const char *); [saveConnectionEncrypt setState:[[spfDocData objectForKey:@"encrypted"] boolValue]]; if([spfDocData objectForKey:@"include_session"]) [saveConnectionIncludeData setState:[[spfDocData objectForKey:@"include_session"] boolValue]]; - if([spfDocData objectForKey:@"save_editor_content"]) + if([[spfDocData objectForKey:@"save_editor_content"] boolValue]) [saveConnectionIncludeQuery setState:[[spfDocData objectForKey:@"save_editor_content"] boolValue]]; + else + [saveConnectionIncludeQuery setState:NSOnState]; [saveConnectionIncludeQuery setEnabled:([[[[customQueryInstance valueForKeyPath:@"textView"] textStorage] string] length])]; @@ -2568,8 +2558,10 @@ YY_BUFFER_STATE yy_scan_string (const char *); [saveConnectionEncrypt setState:[[spfSessionData objectForKey:@"encrypted"] boolValue]]; if([spfSessionData objectForKey:@"include_session"]) [saveConnectionIncludeData setState:[[spfSessionData objectForKey:@"include_session"] boolValue]]; - if([spfSessionData objectForKey:@"save_editor_content"]) + if([[spfSessionData objectForKey:@"save_editor_content"] boolValue]) [saveConnectionIncludeQuery setState:[[spfSessionData objectForKey:@"save_editor_content"] boolValue]]; + else + [saveConnectionIncludeQuery setState:YES]; // Update accessory button states [self validateSaveConnectionAccessory:nil]; @@ -4558,86 +4550,6 @@ YY_BUFFER_STATE yy_scan_string (const char *); #pragma mark Scheme scripting methods /** - * Return an HTML formatted string representing the passed SQL string syntax highlighted - */ -- (NSString*)doSQLSyntaxHighlightForString:(NSString*)sqlText cssLike:(BOOL)cssLike -{ - - NSMutableString *sqlHTML = [[[NSMutableString alloc] initWithCapacity:[sqlText length]] autorelease]; - - NSRange textRange = NSMakeRange(0, [sqlText length]); - NSString *tokenColor; - NSString *cssId; - size_t token; - NSRange tokenRange; - - // initialise flex - yyuoffset = 0; yyuleng = 0; - yy_switch_to_buffer(yy_scan_string([sqlText UTF8String])); - BOOL skipFontTag; - - while (token=yylex()){ - skipFontTag = NO; - switch (token) { - case SPT_SINGLE_QUOTED_TEXT: - case SPT_DOUBLE_QUOTED_TEXT: - tokenColor = @"#A7221C"; - cssId = @"sp_sql_quoted"; - break; - case SPT_BACKTICK_QUOTED_TEXT: - tokenColor = @"#001892"; - cssId = @"sp_sql_backtick"; - break; - case SPT_RESERVED_WORD: - tokenColor = @"#0041F6"; - cssId = @"sp_sql_keyword"; - break; - case SPT_NUMERIC: - tokenColor = @"#67350F"; - cssId = @"sp_sql_numeric"; - break; - case SPT_COMMENT: - tokenColor = @"#265C10"; - cssId = @"sp_sql_comment"; - break; - case SPT_VARIABLE: - tokenColor = @"#6C6C6C"; - cssId = @"sp_sql_variable"; - break; - case SPT_WHITESPACE: - skipFontTag = YES; - cssId = @""; - break; - default: - skipFontTag = YES; - cssId = @""; - } - - tokenRange = NSMakeRange(yyuoffset, yyuleng); - - if(skipFontTag) - [sqlHTML appendString:[[sqlText substringWithRange:tokenRange] HTMLEscapeString]]; - else { - if(cssLike) - [sqlHTML appendFormat:@"<span class=\"%@\">%@</span>", cssId, [[sqlText substringWithRange:tokenRange] HTMLEscapeString]]; - else - [sqlHTML appendFormat:@"<font color=%@>%@</font>", tokenColor, [[sqlText substringWithRange:tokenRange] HTMLEscapeString]]; - } - - } - - // Wrap lines, and replace tabs with spaces - [sqlHTML replaceOccurrencesOfString:@"\n" withString:@"<br>" options:NSLiteralSearch range:NSMakeRange(0, [sqlHTML length])]; - [sqlHTML replaceOccurrencesOfString:@"\t" withString:@" " options:NSLiteralSearch range:NSMakeRange(0, [sqlHTML length])]; - - if(sqlHTML) - return sqlHTML; - else - return @""; - -} - -/** * Called by handleSchemeCommand: to break a while loop */ - (void)setTimeout @@ -4733,57 +4645,52 @@ YY_BUFFER_STATE yy_scan_string (const char *); return; } - if([command isEqualToString:@"SyntaxHighlighting"]) { - - NSFileManager *fm = [NSFileManager defaultManager]; - BOOL isDir; - - NSString *queryFileName = [NSString stringWithFormat:@"%@%@", SPURLSchemeQueryInputPathHeader, docProcessID]; - NSString *resultFileName = [NSString stringWithFormat:@"%@%@", SPURLSchemeQueryResultPathHeader, docProcessID]; - NSString *metaFileName = [NSString stringWithFormat:@"%@%@", SPURLSchemeQueryResultMetaPathHeader, docProcessID]; - NSString *statusFileName = [NSString stringWithFormat:@"%@%@", SPURLSchemeQueryResultStatusPathHeader, docProcessID]; - - NSError *inError = nil; - NSString *query = [NSString stringWithContentsOfFile:queryFileName encoding:NSUTF8StringEncoding error:inError]; - NSString *result = @""; - NSString *status = @"0"; + // ==== the following commands need an authentication for safety reasons - if([fm fileExistsAtPath:queryFileName isDirectory:&isDir] && !isDir) { + // Authenticate command + if(![docProcessID isEqualToString:[commandDict objectForKey:@"id"]]) { + SPBeginAlertSheet(NSLocalizedString(@"Remote Error", @"remote error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self parentWindow], self, nil, nil, + NSLocalizedString(@"URL scheme command couldn't authenticated", @"URL scheme command couldn't authenticated")); + return; + } - if(inError == nil && query && [query length]) { - if([params count] > 1) { - if([[params lastObject] isEqualToString:@"html"]) - result = [NSString stringWithString:[self doSQLSyntaxHighlightForString:query cssLike:NO]]; - else if([[params lastObject] isEqualToString:@"htmlcss"]) - result = [NSString stringWithString:[self doSQLSyntaxHighlightForString:query cssLike:YES]]; + if([command isEqualToString:@"SetSelectedTextRange"]) { + if([params count] > 1) { + id firstResponder = [parentWindow firstResponder]; + if([firstResponder isKindOfClass:[NSTextView class]]) { + NSRange theRange = NSIntersectionRange(NSRangeFromString([params objectAtIndex:1]), NSMakeRange(0, [[firstResponder string] length])); + if(theRange.location != NSNotFound) { + [firstResponder setSelectedRange:theRange]; } + return; } + NSBeep(); } + return; + } - [fm removeItemAtPath:queryFileName error:nil]; - [fm removeItemAtPath:resultFileName error:nil]; - [fm removeItemAtPath:metaFileName error:nil]; - [fm removeItemAtPath:statusFileName error:nil]; - - if(![result writeToFile:resultFileName atomically:YES encoding:NSUTF8StringEncoding error:nil]) - status = @"1"; - - // write status file as notification that query was finished - BOOL succeed = [status writeToFile:statusFileName atomically:YES encoding:NSUTF8StringEncoding error:nil]; - if(!succeed) { + if([command isEqualToString:@"InsertText"]) { + if([params count] > 1) { + id firstResponder = [parentWindow firstResponder]; + if([firstResponder isKindOfClass:[NSTextView class]]) { + [firstResponder insertText:[params objectAtIndex:1]]; + return; + } NSBeep(); - SPBeginAlertSheet(NSLocalizedString(@"BASH Error", @"bash error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self parentWindow], self, nil, nil, - NSLocalizedString(@"Status file for sequelpro url scheme command couldn't be written!", @"status file for sequelpro url scheme command couldn't be written error message")); } return; } - // ==== the following commands need an authentication for safety reasons - - // Authenticate command - if(![docProcessID isEqualToString:[commandDict objectForKey:@"id"]]) { - SPBeginAlertSheet(NSLocalizedString(@"Remote Error", @"remote error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self parentWindow], self, nil, nil, - NSLocalizedString(@"URL scheme command couldn't authenticated", @"URL scheme command couldn't authenticated")); + if([command isEqualToString:@"SetText"]) { + if([params count] > 1) { + id firstResponder = [parentWindow firstResponder]; + if([firstResponder isKindOfClass:[NSTextView class]]) { + [firstResponder setSelectedRange:NSMakeRange(0, [[firstResponder string] length])]; + [firstResponder insertText:[params objectAtIndex:1]]; + return; + } + NSBeep(); + } return; } @@ -5141,17 +5048,50 @@ YY_BUFFER_STATE yy_scan_string (const char *); { [runningActivitiesArray addObject:commandDict]; [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:SPActivitiesUpdateNotification object:nil]; + + if([runningActivitiesArray count] || [[[NSApp delegate] runningActivities] count]) + [self performSelector:@selector(setActivityPaneHidden:) withObject:[NSNumber numberWithInteger:0] afterDelay:1.0]; + else { + [NSObject cancelPreviousPerformRequestsWithTarget:self + selector:@selector(setActivityPaneHidden:) + object:[NSNumber numberWithInteger:0]]; + [self setActivityPaneHidden:[NSNumber numberWithInteger:1]]; + } + } - (void)removeRegisteredActivity:(NSInteger)pid { + for(id cmd in runningActivitiesArray) { if([[cmd objectForKey:@"pid"] integerValue] == pid) { [runningActivitiesArray removeObject:cmd]; break; } } + + if([runningActivitiesArray count] || [[[NSApp delegate] runningActivities] count]) + [self performSelector:@selector(setActivityPaneHidden:) withObject:[NSNumber numberWithInteger:0] afterDelay:1.0]; + else { + [NSObject cancelPreviousPerformRequestsWithTarget:self + selector:@selector(setActivityPaneHidden:) + object:[NSNumber numberWithInteger:0]]; + [self setActivityPaneHidden:[NSNumber numberWithInteger:1]]; + } + [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:SPActivitiesUpdateNotification object:nil]; + +} + +- (void)setActivityPaneHidden:(NSNumber*)hide +{ + if(![hide integerValue] == 1) { + [tableInfoScrollView setHidden:YES]; + [activitiesScrollView setHidden:NO]; + } else { + [activitiesScrollView setHidden:YES]; + [tableInfoScrollView setHidden:NO]; + } } - (NSArray*)runningActivities diff --git a/Source/SPEditorTokens.l b/Source/SPEditorTokens.l index a5726cce..7b19804a 100644 --- a/Source/SPEditorTokens.l +++ b/Source/SPEditorTokens.l @@ -56,9 +56,9 @@ ops "+"|"-"|"*"|"/" word [a-z_\.0-9À-゚@] variable @{1,2}[a-z_\.0-9À-゚$]+ nonword [^a-z_0-9À-゚#\n\t\r] -keyworda (G(R(OUP{s}BY|ANT(S)?)|E(T_FORMAT|OMETRY(COLLECTION)?)|LOBAL)|B(Y(TE)?|TREE|I(GINT|N(LOG|ARY)|T)|O(TH|OL(EAN)?)|E(GIN|TWEEN|FORE)|LOB|ACKUP{s}TABLE)|H(IGH_PRIORITY|O(STS|UR(_(MI(NUTE|CROSECOND)|SECOND))?)|ELP|A(SH|NDLER|VING))|C(R(OSS|EATE)|H(ECK(SUM)?|A(R(SET|ACTER)?|NGE(D)?|IN))|IPHER|O(M(M(IT(TED)?|ENT)|P(RESSED|LETION|ACT))|N(S(TRAINT|ISTENT)|NECTION|CURRENT|T(RIBUTORS|INUE|AINS)|DITION|VERT)|DE|L(UMN(_FORMAT)?|LATE)|ALESCE{s}PARTITION)|U(R(RENT_(TIME(STAMP)?|DATE|USER)|SOR)|BE)|L(IENT|OSE)|A(S(CADE(D)?|E)|CHE{s}INDEX|LL))|I(GNORE|MPORT{s}TABLESPACE|S(SUER|OLATION)?|N(S(TALL({s}PLUGIN)?|E(RT(_METHOD)?|NSITIVE))|N(O(BASE|DB)|ER)|T(1|2|8|3|O({s}(DUMP|OUT)FILE)?|4|E(RVAL|GER))?|ITIAL_SIZE|OUT|DEX(ES)?|VOKER|FILE)?|TERATE|O_THREAD|DENTIFIED|F)|D(ROP|YNAMIC|I(RECTORY|S(CARD{s}TABLESPACE|TINCT(ROW)?|K|ABLE{s}KEYS)|V)|O(UBLE)?|U(MPFILE|PLICATE|AL)|E(S(C(RIBE)?|_KEY_FILE)|C(IMAL|LARE)?|TERMINISTIC|F(INER|AULT)|L(ETE|AY(_KEY_WRITE|ED))|ALLOCATE)|A(Y(_(MI(NUTE|CROSECOND)|SECOND|HOUR))?|T(E(TIME)?|A(BASE(S)?|FILE)?)))|JOIN|E(RRORS|X(TEN(T_SIZE|DED)|I(STS|T)|P(LAIN|ANSION)|ECUTE)|SCAPE(D{s}BY)?|N(GINE(S)?|CLOSED{s}BY|D(S)?|UM|ABLE{s}KEYS)|VE(RY|NT)|LSE(IF)?|ACH)|K(ILL({s}(CONNECTION|QUERY))?|EY(S|_BLOCK_SIZE)?)|F(R(OM|AC_SECOND)|I(RST|XED|LE)|O(R(CE|EIGN)?|UND)|U(NCTION|LL(TEXT)?)|ETCH|L(OAT(8|4)?|USH)|A(ST|LSE))|A(G(GREGATE|AINST)|S(C(II)?|ENSITIVE)?|N(Y|D|ALYZE)|C(CESSIBLE|TION)|T|DD|UT(HORS|O(_INCREMENT|EXTEND_SIZE))|VG(_ROW_LENGTH)?|FTER|L(GORITHM|TER|L))) -keywordl (R(TREE|IGHT|O(UTINE|W(S|_FORMAT)?|LL(BACK|UP))|E(GEXP|MOVE{s}PARTITIONING|BUILD{s}PARTITION|S(T(RICT|ORE{s}TABLE)|UME|ET)|NAME|COVER|TURN(S)?|ORGANIZE{s}PARTITION|D(O(_BUFFER_SIZE|FILE)|UNDANT)|P(EAT(ABLE)?|L(ICATION|ACE)|AIR)|VOKE|QUIRE|FERENCES|L(OAD|EASE|AY_(THREAD|LOG_(POS|FILE)))|A(D(S|_(ONLY|WRITE))?|L))|LIKE|ANGE)|M(I(GRATE|N(_ROWS|UTE(_(MICROSECOND|SECOND))?)|CROSECOND|DDLEINT)|O(NTH|D(IF(Y|IES)|E)?)|U(TEX|LTI(PO(INT|LYGON)|LINESTRING))|E(RGE|MORY|DIUM(BLOB|TEXT|INT)?)|A(X(_(ROWS|SIZE|CONNECTIONS_PER_HOUR|U(SER_CONNECTIONS|PDATES_PER_HOUR)|QUERIES_PER_HOUR)|VALUE)|STER(_(S(SL(_(C(IPHER|ERT|A(PATH)?)|VERIFY_SERVER_CERT|KEY))?|ERVER_ID)|HOST|CONNECT_RETRY|USER|P(ORT|ASSWORD)|LOG_(POS|FILE)))?|TCH))|N(CHAR|O(NE|_W(RITE_TO_BINLOG|AIT)|T|DEGROUP)?|DB(CLUSTER)?|U(MERIC|LL)|E(XT|W)|VARCHAR|A(ME(S)?|T(IONAL|URAL)))|O(R(DER{s}BY)?|N({s}(DUPLICATE{s}KEY{s}UPDATE)?|E(_SHOT)?|LINE)|UT(ER|FILE)?|P(TI(MIZE|ON(S|ALLY)?)|EN)|FF(SET|LINE)|LD_PASSWORD)|P(R(I(MARY|VILEGES)|OCE(SS|DURE{s}(ANALYSE)?)|E(SERVE|CISION|PARE|V))|HASE|O(INT|LYGON)|URGE|A(R(SER|TI(TION(S|ING)?|AL))|SSWORD|CK_KEYS))|QU(ICK|ERY|ARTER)|L(I(MIT|ST|NE(S(TRING)?|AR)|KE)|O(G(S|FILE({s}GROUP))|NG(BLOB|TEXT)?|C(K(S)?|AL(TIME(STAMP)?)?)|OP|W_PRIORITY|AD{s}(DATA|INDEX{s}INTO{s}CACHE|XML))|E(SS|VEL|FT|A(DING|VE(S)?))|A(ST|NGUAGE))) -keywords (X(OR|509|A)|S(MALLINT|SL|H(OW({s}(E(NGINE(S)?|RRORS)|M(ASTER|UTEX)|BINLOG|GRANTS|INNODB|P(RIVILEGES|ROFILE(S)?|ROCEDURE{s}CODE)|SLAVE{s}(HOSTS|STATUS)|TRIGGERS|VARIABLES|WARNINGS|(FULL{s})?PROCESSLIST|FIELDS|PLUGIN(S)?|STORAGE{s}ENGINES|TABLE{s}TYPES|CO(LUMNS|LLATION)|BINLOG{s}EVENTS))?|UTDOWN|ARE)|NAPSHOT|CHE(MA(S)?|DULE(R)?)|T(R(ING|AIGHT_JOIN)|O(RAGE|P)|A(RT(S|ING{s}BY)?|TUS))|I(GNED|MPLE)|O(ME|NAME|UNDS)|U(B(JECT|PARTITION(S)?)|SPEND|PER)|P(ECIFIC|ATIAL)|E(RIAL(IZABLE)?|SSION|NSITIVE|C(OND(_MICROSECOND)?|URITY)|T({s}(PASSWORD|NAMES|ONE_SHOT))?|PARATOR|LECT)|QL(STATE|_(MAX_JOIN_SIZE|B(IG_(RESULT|SELECTS|TABLES)|UFFER_RESULT)|S(MALL_RESULT|ELECT_LIMIT|LAVE_SKIP_COUNTER|AFE_UPDATES)|NO_CACHE|CA(CHE|LC_FOUND_ROWS)|T(SI_(M(INUTE|ONTH)|SECOND|HOUR|YEAR|DAY|QUARTER|FRAC_SECOND|WEEK)|HREAD)|QUOTE_SHOW_CREATE|WARNINGS|LO(G_(BIN|OFF|UPDATE)|W_PRIORITY_UPDATES)|AUTO_IS_NULL)|EXCEPTION|WARNING)?|LAVE|AVEPOINT)|YEAR(_MONTH)?|T(R(IGGER(S)?|U(NCATE|E)|A(NSACTION|ILING))|H(EN|AN)|YPE|I(ME(STAMP(DIFF|ADD)?)?|NY(BLOB|TEXT|INT))|O|E(RMINATED{s}BY|XT|MP(TABLE|ORARY))|ABLE(S(PACE)?)?)|ZEROFILL|U(S(ING|E(R(_RESOURCES)?|_FRM)?|AGE)|N(SIGNED|COMMITTED|TIL|I(NSTALL({s}PLUGIN)?|CODE|ON|QUE)|D(O(_BUFFER_SIZE|FILE)?|EFINED)|KNOWN|LOCK)|TC_(TIME(STAMP)?|DATE)|P(GRADE|DATE))|V(IEW|A(R(BINARY|YING|CHAR(ACTER)?|IABLES)|LUE(S)?))|W(RITE|H(ILE|E(RE|N))|ITH({s}PARSER)?|ORK|EEK|A(RNINGS|IT))) +keyworda (G(R(OUP{s}BY|ANT(S)?)|E(NERAL|T_FORMAT|OMETRY(COLLECTION)?)|LOBAL)|B(Y(TE)?|TREE|I(GINT|N(LOG|ARY)|T)|O(TH|OL(EAN)?)|E(GIN|TWEEN|FORE)|LOB|ACKUP{s}TABLE)|H(IGH_PRIORITY|O(ST(S)?|UR(_(MI(NUTE|CROSECOND)|SECOND))?)|ELP|A(SH|NDLER|VING))|C(R(OSS|EATE)|H(ECK(SUM)?|A(R(SET|ACTER)?|NGE(D)?|IN))|IPHER|O(M(M(IT(TED)?|ENT)|P(RESSED|LETION|ACT))|N(S(TRAINT(_(SCHEMA|NAME|CATALOG))?|ISTENT)|NECTION|CURRENT|T(RIBUTORS|INUE|AINS)|DITION|VERT)|DE|L(UMN(S|_(NAME|FORMAT))?|LATE)|ALESCE{s}PARTITION)|U(R(RENT_(TIME(STAMP)?|DATE|USER)|SOR(_NAME)?)|BE)|L(IENT|OSE|ASS_ORIGIN)|A(S(CADE(D)?|E)|CHE{s}INDEX|TALOG_NAME|LL))|I(GNORE(_SERVER_IDS)?|MPORT{s}TABLESPACE|S(SUER|OLATION)?|N(S(TALL({s}PLUGIN)?|E(RT(_METHOD)?|NSITIVE))|N(O(BASE|DB)|ER)|T(1|2|8|3|O({s}(DUMP|OUT)FILE)?|4|E(RVAL|GER))?|ITIAL_SIZE|OUT|DEX(ES)?|VOKER|FILE)?|TERATE|O_THREAD|DENTIFIED|F)|D(ROP|YNAMIC|I(RECTORY|S(CARD{s}TABLESPACE|TINCT(ROW)?|K|ABLE{s}KEYS)|V)|O(UBLE)?|U(MPFILE|PLICATE|AL)|E(S(C(RIBE)?|_KEY_FILE)|C(IMAL|LARE)?|TERMINISTIC|F(INER|AULT)|L(ETE|AY(_KEY_WRITE|ED))|ALLOCATE)|A(Y(_(MI(NUTE|CROSECOND)|SECOND|HOUR))?|T(E(TIME)?|A(BASE(S)?|FILE)?)))|JOIN|E(RRORS|X(TEN(T_SIZE|DED)|I(STS|T)|P(LAIN|ANSION)|ECUTE)|SCAPE(D{s}BY)?|N(GINE(S)?|CLOSED{s}BY|D(S)?|UM|ABLE{s}KEYS)|VE(RY|NT)|LSE(IF)?|ACH)|K(ILL({s}(CONNECTION|QUERY))?|EY(S|_BLOCK_SIZE)?)|F(R(OM|AC_SECOND)|I(RST|XED|LE)|O(R(CE|EIGN)?|UND)|U(NCTION|LL(TEXT)?)|ETCH|L(OAT(8|4)?|USH)|A(ST|LSE))|A(G(GREGATE|AINST)|S(C(II)?|ENSITIVE)?|N(Y|D|ALYZE)|C(CESSIBLE|TION)|T|DD|UT(HORS|O(_INCREMENT|EXTEND_SIZE))|VG(_ROW_LENGTH)?|FTER|L(GORITHM|TER|L))) +keywordl (R(TREE|IGHT|O(UTINE|W(S|_FORMAT)?|LL(BACK|UP))|E(GEXP|MOVE{s}PARTITIONING|BUILD{s}PARTITION|S(T(RICT|ORE{s}TABLE)|IGNAL|UME|ET)|NAME|COVER|TURN(S)?|ORGANIZE{s}PARTITION|D(O(_BUFFER_SIZE|FILE)|UNDANT)|P(EAT(ABLE)?|L(ICATION|ACE)|AIR)|VOKE|QUIRE|FERENCES|L(OAD|EASE|AY_(THREAD|LOG_(POS|FILE)))|A(D(S|_(ONLY|WRITE))?|L))|LIKE|ANGE)|M(YSQL_ERRNO|I(GRATE|N(_ROWS|UTE(_(MICROSECOND|SECOND))?)|CROSECOND|DDLEINT)|O(NTH|D(IF(Y|IES)|E)?)|U(TEX|LTI(PO(INT|LYGON)|LINESTRING))|E(RGE|MORY|SSAGE_TEXT|DIUM(BLOB|TEXT|INT)?)|A(X(_(ROWS|SIZE|CONNECTIONS_PER_HOUR|U(SER_CONNECTIONS|PDATES_PER_HOUR)|QUERIES_PER_HOUR)|VALUE)|STER(_(S(SL(_(C(IPHER|ERT|A(PATH)?)|VERIFY_SERVER_CERT|KEY))?|ERVER_ID)|H(OST|EARTBEAT_PERIOD)|CONNECT_RETRY|USER|P(ORT|ASSWORD)|LOG_(POS|FILE)))?|TCH))|N(CHAR|O(NE|_W(RITE_TO_BINLOG|AIT)|T|DEGROUP)?|DB(CLUSTER)?|U(MERIC|LL)|E(XT|W)|VARCHAR|A(ME(S)?|T(IONAL|URAL)))|O(R(DER{s}BY)?|N({s}(DUPLICATE{s}KEY{s}UPDATE)?|E(_SHOT)?|LINE)|UT(ER|FILE)?|P(TI(MIZE|ON(S|ALLY)?)|EN)|FF(SET|LINE)|WNER|LD_PASSWORD)|P(R(I(MARY|VILEGES)|OCE(SS|DURE{s}(ANALYSE)?)|E(SERVE|CISION|PARE|V))|HASE|O(RT|INT|LYGON)|URGE|A(R(SER|TI(TION(S|ING)?|AL))|SSWORD|CK_KEYS))|QU(ICK|ERY|ARTER)|L(I(MIT|ST|NE(S(TRING)?|AR)|KE)|O(G(S|FILE({s}GROUP))|NG(BLOB|TEXT)?|C(K(S)?|AL(TIME(STAMP)?)?)|OP|W_PRIORITY|AD{s}(DATA|INDEX{s}INTO{s}CACHE|XML))|E(SS|VEL|FT|A(DING|VE(S)?))|A(ST|NGUAGE))) +keywords (X(OR|509|A)|S(MALLINT|SL|H(OW({s}(E(NGINE(S)?|RRORS)|M(ASTER|UTEX)|BINLOG|GRANTS|INNODB|P(RIVILEGES|ROFILE(S)?|ROCEDURE{s}CODE)|SLAVE{s}(HOSTS|STATUS)|TRIGGERS|VARIABLES|WARNINGS|(FULL{s})?PROCESSLIST|FIELDS|PLUGIN(S)?|STORAGE{s}ENGINES|TABLE{s}TYPES|CO(LUMNS|LLATION)|BINLOG{s}EVENTS))?|UTDOWN|ARE)|NAPSHOT|CHE(MA(S|_NAME)?|DULE(R)?)|T(R(ING|AIGHT_JOIN)|O(RAGE|P)|A(RT(S|ING{s}BY)?|TUS))|I(GN(ED|AL)|MPLE)|O(ME|NAME|CKET|UNDS)|U(B(CLASS_ORIGIN|JECT|PARTITION(S)?)|SPEND|PER)|P(ECIFIC|ATIAL)|E(R(IAL(IZABLE)?|VER)|SSION|NSITIVE|C(OND(_MICROSECOND)?|URITY)|T({s}(PASSWORD|NAMES|ONE_SHOT))?|PARATOR|LECT)|QL(STATE|_(MAX_JOIN_SIZE|B(IG_(RESULT|SELECTS|TABLES)|UFFER_RESULT)|S(MALL_RESULT|ELECT_LIMIT|LAVE_SKIP_COUNTER|AFE_UPDATES)|NO_CACHE|CA(CHE|LC_FOUND_ROWS)|T(SI_(M(INUTE|ONTH)|SECOND|HOUR|YEAR|DAY|QUARTER|FRAC_SECOND|WEEK)|HREAD)|QUOTE_SHOW_CREATE|WARNINGS|LO(G_(BIN|OFF|UPDATE)|W_PRIORITY_UPDATES)|AUTO_IS_NULL)|EXCEPTION|WARNING)?|L(OW|AVE)|AVEPOINT)|YEAR(_MONTH)?|T(R(IGGER(S)?|U(NCATE|E)|A(NSACTION|ILING))|H(EN|AN)|YPE|I(ME(STAMP(DIFF|ADD)?)?|NY(BLOB|TEXT|INT))|O|E(RMINATED{s}BY|XT|MP(TABLE|ORARY))|ABLE(S(PACE)?|_NAME)?)|ZEROFILL|U(S(ING|E(R(_RESOURCES)?|_FRM)?|AGE)|N(SIGNED|COMMITTED|TIL|I(NSTALL({s}PLUGIN)?|CODE|ON|QUE)|D(O(_BUFFER_SIZE|FILE)?|EFINED)|KNOWN|LOCK)|TC_(TIME(STAMP)?|DATE)|P(GRADE|DATE))|V(IEW|A(R(BINARY|YING|CHAR(ACTER)?|IABLES)|LUE(S)?))|W(R(ITE|APPER)|H(ILE|E(RE|N))|ITH({s}PARSER)?|ORK|EEK|A(RNINGS|IT))) %x comment @@ -87,6 +87,7 @@ keywords (X(OR|509|A)|S(MALLINT|SL|H(OW({s}(E(NGINE(S)?|RRORS)|M(ASTER|UTEX)|BIN */ #[^\n\r]*(\n|\r)? | /* # Comments */ +--[\n\r] { return SPT_COMMENT; } /* -- */ --[ \t][^\n\r]*(\n|\r)? { return SPT_COMMENT; } /* -- Comments */ {variable}/{ops} { BEGIN(varequation); return SPT_VARIABLE; }/* SQL variables before operator*/ @@ -216,6 +217,7 @@ CALL CASCADE CASCADED CASE +CATALOG_NAME CHAIN CHANGE CHANGED @@ -225,6 +227,7 @@ CHARSET CHECK CHECKSUM CIPHER +CLASS_ORIGIN CLIENT CLOSE COALESCE{s}PARTITION @@ -232,6 +235,8 @@ CODE COLLATE COLUMN COLUMN_FORMAT +COLUMN_NAME +COLUMNS COMMENT COMMIT COMMITTED @@ -243,6 +248,9 @@ CONDITION CONNECTION CONSISTENT CONSTRAINT +CONSTRAINT_CATALOG +CONSTRAINT_NAME +CONSTRAINT_SCHEMA CONTAINS CONTINUE CONTRIBUTORS @@ -255,6 +263,7 @@ CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR +CURSOR_NAME DATA DATABASE DATABASES @@ -334,6 +343,7 @@ FROM FULL FULLTEXT FUNCTION +GENERAL GEOMETRY GEOMETRYCOLLECTION GET_FORMAT @@ -346,6 +356,7 @@ HASH HAVING HELP HIGH_PRIORITY +HOST HOSTS HOUR HOUR_MICROSECOND @@ -354,6 +365,7 @@ HOUR_SECOND IDENTIFIED IF IGNORE +IGNORE_SERVER_IDS IMPORT{s}TABLESPACE IN INDEX @@ -417,6 +429,7 @@ LOOP LOW_PRIORITY MASTER MASTER_CONNECT_RETRY +MASTER_HEARTBEAT_PERIOD MASTER_HOST MASTER_LOG_FILE MASTER_LOG_POS @@ -445,6 +458,7 @@ MEDIUMINT MEDIUMTEXT MEMORY MERGE +MESSAGE_TEXT MICROSECOND MIDDLEINT MIGRATE @@ -461,6 +475,7 @@ MULTILINESTRING MULTIPOINT MULTIPOLYGON MUTEX +MYSQL_ERRNO NAME NAMES NATIONAL @@ -489,13 +504,14 @@ ON{s}(DUPLICATE{s}KEY{s}UPDATE)? OPEN OPTIMIZE OPTION -OPTIONS OPTIONALLY +OPTIONS OR ORDER{s}BY OUT OUTER OUTFILE +OWNER PACK_KEYS PARSER PARTIAL @@ -506,6 +522,7 @@ PASSWORD PHASE POINT POLYGON +PORT PRECISION PREPARE PRESERVE @@ -546,6 +563,7 @@ REPLACE REPLICATION REQUIRE RESET +RESIGNAL RESTORE{s}TABLE RESTRICT RESUME @@ -566,6 +584,7 @@ SCHEDULE SCHEDULER SCHEMA SCHEMAS +SCHEMA_NAME SECOND SECOND_MICROSECOND SECURITY @@ -574,16 +593,20 @@ SENSITIVE SEPARATOR SERIAL SERIALIZABLE +SERVER SESSION SET({s}(PASSWORD|NAMES|ONE_SHOT))? SHARE SHOW({s}(E(NGINE(S)?|RRORS)|M(ASTER|UTEX)|BINLOG|GRANTS|INNODB|P(RIVILEGES|ROFILE(S)?|ROCEDURE{s}CODE)|SLAVE{s}(HOSTS|STATUS)|TRIGGERS|VARIABLES|WARNINGS|(FULL{s})?PROCESSLIST|FIELDS|PLUGIN(S)?|STORAGE{s}ENGINES|TABLE{s}TYPES|CO(LUMNS|LLATION)|BINLOG{s}EVENTS))? SHUTDOWN +SIGNAL SIGNED SIMPLE SLAVE +SLOW SMALLINT SNAPSHOT +SOCKET SOME SONAME SOUNDS @@ -631,6 +654,7 @@ STOP STORAGE STRAIGHT_JOIN STRING +SUBCLASS_ORIGIN SUBJECT SUBPARTITION SUBPARTITIONS @@ -639,6 +663,7 @@ SUSPEND TABLE TABLES TABLESPACE +TABLE_NAME TEMPORARY TEMPTABLE TERMINATED{s}BY @@ -700,6 +725,7 @@ WHERE WHILE WITH({s}PARSER)? WORK +WRAPPER WRITE X509 XA diff --git a/Source/SPExportController.m b/Source/SPExportController.m index 1d70f88f..9081b942 100644 --- a/Source/SPExportController.m +++ b/Source/SPExportController.m @@ -282,10 +282,12 @@ static const NSString *SPTableViewDropColumnID = @"drop"; // Close the advanced options view if it's open [exportAdvancedOptionsView setHidden:YES]; [exportAdvancedOptionsViewButton setState:NSOffState]; + showAdvancedView = NO; // Close the customize filename view if it's open [exportCustomFilenameView setHidden:YES]; [exportCustomFilenameViewButton setState:NSOffState]; + showCustomFilenameView = NO; // If open close the advanced options view and custom filename view [self _resizeWindowForAdvancedOptionsViewByHeightDelta:0]; diff --git a/Source/SPExtendedTableInfo.m b/Source/SPExtendedTableInfo.m index a765f6df..6f039659 100644 --- a/Source/SPExtendedTableInfo.m +++ b/Source/SPExtendedTableInfo.m @@ -98,7 +98,7 @@ } // Alter table's storage type - [connection queryString:[NSString stringWithFormat:@"ALTER TABLE %@ TYPE = %@", [selectedTable backtickQuotedString], newType]]; + [connection queryString:[NSString stringWithFormat:@"ALTER TABLE %@ %@ = %@", [selectedTable backtickQuotedString], [[tableDocumentInstance serverSupport] engineTypeQueryName], newType]]; if ([connection getLastErrorID] == 0) { // Reload the table's data diff --git a/Source/SPFieldEditorController.m b/Source/SPFieldEditorController.m index 2e761f80..5075ffaf 100644 --- a/Source/SPFieldEditorController.m +++ b/Source/SPFieldEditorController.m @@ -917,7 +917,10 @@ */ - (id)previewPanel:(id)panel previewItemAtIndex:(NSInteger)index { - return [NSURL fileURLWithPath:tmpFileName]; + if(tmpFileName) + return [NSURL fileURLWithPath:tmpFileName]; + + return nil; } /** diff --git a/Source/SPHistoryController.m b/Source/SPHistoryController.m index 3fb0e100..107c24cb 100644 --- a/Source/SPHistoryController.m +++ b/Source/SPHistoryController.m @@ -269,6 +269,7 @@ NSIndexSet *contentSelectedIndexSet = [tableContentInstance selectedRowIndexes]; NSRect contentViewport = [tableContentInstance viewport]; NSDictionary *contentFilter = [tableContentInstance filterSettings]; + NSData *filterTableData = [tableContentInstance filterTableData]; if (!theDatabase) return; // If a table is selected, save state information @@ -283,6 +284,7 @@ if (contentSortCol) [contentState setObject:contentSortCol forKey:@"sortCol"]; if (contentSelectedIndexSet) [contentState setObject:contentSelectedIndexSet forKey:@"selection"]; if (contentFilter) [contentState setObject:contentFilter forKey:@"filter"]; + if (filterTableData) [contentState setObject:filterTableData forKey:@"filterTable"]; // Update the table content states with this information - used when switching tables to restore last used view. [tableContentStates setObject:contentState forKey:[NSString stringWithFormat:@"%@.%@", [theDatabase backtickQuotedString], [theTable backtickQuotedString]]]; diff --git a/Source/SPIndexesController.m b/Source/SPIndexesController.m index 27b4471d..49ac8594 100644 --- a/Source/SPIndexesController.m +++ b/Source/SPIndexesController.m @@ -39,7 +39,7 @@ static const NSString *SPNewIndexKeyBlockSize = @"IndexKeyBlockSize"; - (void)_reloadIndexedColumnsTableData; - (void)_addIndexUsingDetails:(NSDictionary *)indexDetails; -- (void)_removeIndexUsingDeatails:(NSDictionary *)indexDetails; +- (void)_removeIndexUsingDetails:(NSDictionary *)indexDetails; - (void)_resizeWindowForAdvancedOptionsViewByHeightDelta:(NSInteger)delta; @@ -122,18 +122,26 @@ static const NSString *SPNewIndexKeyBlockSize = @"IndexKeyBlockSize"; // Check whether a save of the current field row is required. if (![tableStructure saveRowOnDeselect]) return; - [indexTypePopUpButton insertItemWithTitle:@"PRIMARY KEY" atIndex:0]; + // Reset visibility of the primary key item + [[[indexTypePopUpButton menu] itemWithTag:SPPrimaryKeyMenuTag] setHidden:NO]; // Set sheet defaults - key type PRIMARY, key name PRIMARY and disabled - [indexTypePopUpButton selectItemAtIndex:0]; + [indexTypePopUpButton selectItemWithTag:SPPrimaryKeyMenuTag]; [indexNameTextField setEnabled:NO]; [indexNameTextField setStringValue:@"PRIMARY"]; - - // If the table is of type MyISAM and Spatial extension support is available, add the SPATIAL type + + // Remove any existing SPATIAL menu item + if ([indexTypePopUpButton indexOfItemWithTag:SPSpatialMenuTag] != -1) + [indexTypePopUpButton removeItemAtIndex:[indexTypePopUpButton indexOfItemWithTag:SPSpatialMenuTag]]; + + // If the table is of type MyISAM and Spatial extension support is available, (re-)add the SPATIAL type NSString *engine = [[tableData statusValues] objectForKey:@"Engine"]; if ([engine isEqualToString:@"MyISAM"] && [[dbDocument serverSupport] supportsSpatialExtensions]) { - [indexTypePopUpButton addItemWithTitle:@"SPATIAL"]; + NSMenuItem *spatialMenuItem = [[[NSMenuItem alloc] init] autorelease]; + [spatialMenuItem setTitle:NSLocalizedString(@"SPATIAL", @"Spatial index menu item title")]; + [spatialMenuItem setTag:SPSpatialMenuTag]; + [[indexTypePopUpButton menu] addItem:spatialMenuItem]; } // Check to see whether a primary key already exists for the table, and if so select INDEX instead @@ -156,11 +164,11 @@ static const NSString *SPNewIndexKeyBlockSize = @"IndexKeyBlockSize"; if (isPrimaryKey || hasCompositePrimaryKey) { - // Remove primary key option - [indexTypePopUpButton removeItemAtIndex:0]; + // Hide primary key option + [[[indexTypePopUpButton menu] itemWithTag:SPPrimaryKeyMenuTag] setHidden:YES]; // Select INDEX type - [indexTypePopUpButton selectItemAtIndex:0]; + [indexTypePopUpButton selectItemWithTag:SPIndexMenuTag]; [indexNameTextField setEnabled:YES]; [indexNameTextField setStringValue:@""]; @@ -188,8 +196,11 @@ static const NSString *SPNewIndexKeyBlockSize = @"IndexKeyBlockSize"; break; } } + + // If no initial field has been selected yet - all fields are indexed - add the first field. + if (!initialField) initialField = [fields objectAtIndex:0]; - if (initialField) [indexedFieldNames release], initialField = nil; + if (indexedFieldNames) [indexedFieldNames release], indexedFieldNames = nil; // Reset the indexed columns [indexedFields removeAllObjects]; @@ -275,11 +286,12 @@ static const NSString *SPNewIndexKeyBlockSize = @"IndexKeyBlockSize"; */ - (IBAction)chooseIndexType:(id)sender { - NSString *indexType = [indexTypePopUpButton titleOfSelectedItem]; + NSInteger *indexType = [[indexTypePopUpButton selectedItem] tag]; - if ([indexType isEqualToString:@"PRIMARY KEY"] ) { + if (indexType == SPPrimaryKeyMenuTag) { [indexNameTextField setEnabled:NO]; [indexNameTextField setStringValue:@"PRIMARY"]; + [indexStorageTypePopUpButton setEnabled:NO]; } else { [indexNameTextField setEnabled:YES]; @@ -289,7 +301,7 @@ static const NSString *SPNewIndexKeyBlockSize = @"IndexKeyBlockSize"; } // Specifiying an index storage type (i.e. HASH or BTREE) is not permitted with SPATIAL indexes - [indexStorageTypePopUpButton setEnabled:(![indexType isEqualToString:@"SPATIAL"])]; + [indexStorageTypePopUpButton setEnabled:(indexType != SPSpatialMenuTag)]; } } @@ -301,6 +313,7 @@ static const NSString *SPNewIndexKeyBlockSize = @"IndexKeyBlockSize"; // Close the advanced options view if it's open [indexAdvancedOptionsView setHidden:YES]; [indexAdvancedOptionsViewButton setState:NSOffState]; + showAdvancedView = NO; // Hide the size column [indexSizeTableColumn setHidden:YES]; @@ -532,14 +545,30 @@ static const NSString *SPNewIndexKeyBlockSize = @"IndexKeyBlockSize"; [indexDetails setObject:indexedFields forKey:SPNewIndexIndexedColumns]; [indexDetails setObject:[indexNameTextField stringValue] forKey:SPNewIndexIndexName]; - [indexDetails setObject:[indexTypePopUpButton titleOfSelectedItem] forKey:SPNewIndexIndexType]; + switch ([[indexTypePopUpButton selectedItem] tag]) { + case SPPrimaryKeyMenuTag: + [indexDetails setObject:@"PRIMARY KEY" forKey:SPNewIndexIndexType]; + break; + case SPIndexMenuTag: + [indexDetails setObject:@"INDEX" forKey:SPNewIndexIndexType]; + break; + case SPUniqueMenuTag: + [indexDetails setObject:@"UNIQUE" forKey:SPNewIndexIndexType]; + break; + case SPFullTextMenuTag: + [indexDetails setObject:@"FULLTEXT" forKey:SPNewIndexIndexType]; + break; + case SPSpatialMenuTag: + [indexDetails setObject:@"SPATIAL" forKey:SPNewIndexIndexType]; + break; + } // If there is a key block size set it means the database version supports it if ([[indexKeyBlockSizeTextField stringValue] length]) { [indexDetails setObject:[NSNumber numberWithInteger:[indexKeyBlockSizeTextField integerValue]] forKey:SPNewIndexKeyBlockSize]; } - if (([indexStorageTypePopUpButton indexOfSelectedItem] > 0) && (![[indexTypePopUpButton titleOfSelectedItem] isEqualToString:@"SPATIAL"])) { + if (([indexStorageTypePopUpButton indexOfSelectedItem] > 0) && ([[indexTypePopUpButton selectedItem] tag] != SPSpatialMenuTag)) { [indexDetails setObject:[indexStorageTypePopUpButton titleOfSelectedItem] forKey:SPNewIndexStorageType]; } @@ -572,12 +601,12 @@ static const NSString *SPNewIndexKeyBlockSize = @"IndexKeyBlockSize"; [indexDetails setObject:[NSNumber numberWithBool:[contextInfo hasSuffix:@"AndForeignKey"]] forKey:@"RemoveForeignKey"]; if ([NSThread isMainThread]) { - [NSThread detachNewThreadSelector:@selector(_removeIndexUsingDeatails:) toTarget:self withObject:indexDetails]; + [NSThread detachNewThreadSelector:@selector(_removeIndexUsingDetails:) toTarget:self withObject:indexDetails]; [dbDocument enableTaskCancellationWithTitle:NSLocalizedString(@"Cancel", @"cancel button") callbackObject:self callbackFunction:NULL]; } else { - [self _removeIndexUsingDeatails:indexDetails]; + [self _removeIndexUsingDetails:indexDetails]; } } } @@ -729,14 +758,14 @@ static const NSString *SPNewIndexKeyBlockSize = @"IndexKeyBlockSize"; [query appendString:indexName]; } - // Add the columns - [query appendFormat:@" (%@)", [tempIndexedColumns componentsJoinedByCommas]]; - // If supplied specify the index's storage type if (indexStorageType) { [query appendString:@" USING "]; [query appendString:indexStorageType]; } + + // Add the columns + [query appendFormat:@" (%@)", [tempIndexedColumns componentsJoinedByCommas]]; // If supplied specify the index's key block size if (indexKeyBlockSize) { @@ -776,7 +805,7 @@ static const NSString *SPNewIndexKeyBlockSize = @"IndexKeyBlockSize"; * * @param indexDetails A dictionary containing the details of the index to be removed */ -- (void)_removeIndexUsingDeatails:(NSDictionary *)indexDetails +- (void)_removeIndexUsingDetails:(NSDictionary *)indexDetails { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; diff --git a/Source/SPNarrowDownCompletion.h b/Source/SPNarrowDownCompletion.h index 7b33dea0..192bbf38 100644 --- a/Source/SPNarrowDownCompletion.h +++ b/Source/SPNarrowDownCompletion.h @@ -55,6 +55,7 @@ NSRange theCharRange; NSRange theParseRange; NSString *theDbName; + NSString *theAliasName; NSTimer *stateTimer; NSArray *syncArrowImages; @@ -78,7 +79,7 @@ dictMode:(BOOL)mode dbMode:(BOOL)theDbMode tabTriggerMode:(BOOL)tabTriggerMode fuzzySearch:(BOOL)fuzzySearch backtickMode:(NSInteger)theBackTickMode withDbName:(NSString*)dbName withTableName:(NSString*)tableName selectedDb:(NSString*)selectedDb caretMovedLeft:(BOOL)caretMovedLeft autoComplete:(BOOL)autoComplete oneColumn:(BOOL)oneColumn - isQueryingDBStructure:(BOOL)isQueryingDBStructure; + alias:(NSString*)anAlias isQueryingDBStructure:(BOOL)isQueryingDBStructure; - (void)setCaretPos:(NSPoint)aPos; - (void)insert_text:(NSString* )aString; - (void)insertAutocompletePlaceholder; diff --git a/Source/SPNarrowDownCompletion.m b/Source/SPNarrowDownCompletion.m index f86c66c5..abc00d9a 100644 --- a/Source/SPNarrowDownCompletion.m +++ b/Source/SPNarrowDownCompletion.m @@ -215,7 +215,7 @@ dictMode:(BOOL)mode dbMode:(BOOL)theDbMode tabTriggerMode:(BOOL)tabTriggerMode fuzzySearch:(BOOL)fuzzySearch backtickMode:(NSInteger)theBackTickMode withDbName:(NSString*)dbName withTableName:(NSString*)tableName selectedDb:(NSString*)selectedDb caretMovedLeft:(BOOL)caretMovedLeft autoComplete:(BOOL)autoComplete oneColumn:(BOOL)oneColumn - isQueryingDBStructure:(BOOL)isQueryingDBStructure + alias:(NSString*)anAlias isQueryingDBStructure:(BOOL)isQueryingDBStructure { if(self = [self init]) { @@ -228,6 +228,7 @@ autoCompletionMode = autoComplete; + theAliasName = anAlias; oneColumnMode = oneColumn; isQueryingDatabaseStructure = isQueryingDBStructure; @@ -953,8 +954,10 @@ NSString* toInsert = [curMatch substringFromIndex:[originalFilterString length]]; theCharRange.length += [toInsert length] - currentAutocompleteLength; theParseRange.length += [toInsert length]; + [theView breakUndoCoalescing]; [theView insertText:[toInsert lowercaseString]]; + autocompletePlaceholderWasInserted = YES; // Restore the text selection location, and clearly mark the autosuggested text @@ -970,8 +973,14 @@ { if (!autocompletePlaceholderWasInserted) return; + [theView breakUndoCoalescing]; + if (useFastMethod) { - [theView setSelectedRange:theCharRange]; + if(backtickMode) { + NSRange r = NSMakeRange(theCharRange.location+1,theCharRange.length); + [theView setSelectedRange:r]; + } else + [theView setSelectedRange:theCharRange]; [theView insertText:originalFilterString]; } else { NSRange attributeResultRange = NSMakeRange(0, 0); @@ -1015,12 +1024,30 @@ if(NSMaxRange(theCharRange) > [[theView string] length]) theCharRange = NSIntersectionRange(NSMakeRange(0,[[theView string] length]), theCharRange); + [theView breakUndoCoalescing]; + NSRange r = [theView selectedRange]; if(r.length) [theView setSelectedRange:r]; - else - [theView setSelectedRange:theCharRange]; + else { + if(backtickMode == 100) { + NSString *replaceString = [[theView string] substringWithRange:theCharRange]; + BOOL nextCharIsBacktick = ([replaceString hasSuffix:@"`"]); + if(theCharRange.length == 1) nextCharIsBacktick = NO; + if(!nextCharIsBacktick) { + if([replaceString hasPrefix:@"`"]) + [theView setSelectedRange:NSMakeRange(theCharRange.location, theCharRange.length+2)]; + else + [theView setSelectedRange:theCharRange]; + } else { + [theView setSelectedRange:theCharRange]; + } + backtickMode = 0; + } else + [theView setSelectedRange:theCharRange]; + } + [theView breakUndoCoalescing]; [theView insertText:aString]; // If completion string contains backticks move caret out of the backticks @@ -1052,10 +1079,9 @@ NSString* candidateMatch = [selectedItem objectForKey:@"match"] ?: [selectedItem objectForKey:@"display"]; if([selectedItem objectForKey:@"isRef"] && ([[NSApp currentEvent] modifierFlags] & (NSShiftKeyMask)) - && [[selectedItem objectForKey:@"path"] length]) { + && [[selectedItem objectForKey:@"path"] length] && theAliasName == nil) { NSString *path = [[[selectedItem objectForKey:@"path"] componentsSeparatedByString:SPUniqueSchemaDelimiter] componentsJoinedByPeriodAndBacktickQuotedAndIgnoreFirst]; - // Check if path's db name is the current selected db name NSRange r = [path rangeOfString:[currentDb backtickQuotedString] options:NSCaseInsensitiveSearch range:NSMakeRange(0, [[currentDb backtickQuotedString] length])]; theCharRange = theParseRange; @@ -1068,7 +1094,7 @@ } else { // Is completion string a schema name for current connection if([selectedItem objectForKey:@"isRef"]) { - backtickMode = 0; // suppress move the caret one step rightwards + backtickMode = 100; // suppress move the caret one step rightwards [self insert_text:[candidateMatch backtickQuotedString]]; } else { [self insert_text:candidateMatch]; diff --git a/Source/SPPreferencesUpgrade.m b/Source/SPPreferencesUpgrade.m index 6f95057b..51bcd1b4 100644 --- a/Source/SPPreferencesUpgrade.m +++ b/Source/SPPreferencesUpgrade.m @@ -48,7 +48,7 @@ void SPApplyRevisionChanges(void) // Get the current revision if ([prefs objectForKey:@"lastUsedVersion"]) recordedVersionNumber = [[prefs objectForKey:@"lastUsedVersion"] integerValue]; if ([prefs objectForKey:SPLastUsedVersion]) recordedVersionNumber = [[prefs objectForKey:SPLastUsedVersion] integerValue]; - + // Skip processing if the current version matches or is less than recorded version if (currentVersionNumber <= recordedVersionNumber) return; @@ -57,7 +57,10 @@ void SPApplyRevisionChanges(void) [prefs setObject:[NSNumber numberWithInteger:currentVersionNumber] forKey:SPLastUsedVersion]; return; } - + + // Inform SPAppController to check installed default Bundles for available updates + [prefs setObject:[NSNumber numberWithBool:YES] forKey:@"doBundleUpdate"]; + // For versions prior to r336 (0.9.4), where column widths have been saved, walk through them and remove // any table widths set to 15 or less (fix for mangled columns caused by Issue #140) if (recordedVersionNumber < 336 && [prefs objectForKey:SPTableColumnWidths] != nil) { diff --git a/Source/SPQueryFavoriteManager.m b/Source/SPQueryFavoriteManager.m index 609b083e..5e270a07 100644 --- a/Source/SPQueryFavoriteManager.m +++ b/Source/SPQueryFavoriteManager.m @@ -465,6 +465,7 @@ */ - (BOOL)tableView:(NSTableView *)aTableView shouldSelectRow:(NSInteger)rowIndex { + if (rowIndex == -1) return YES; return ([[favorites objectAtIndex:rowIndex] objectForKey:@"headerOfFileURL"]) ? NO : YES; } diff --git a/Source/SPSSHTunnel.m b/Source/SPSSHTunnel.m index 76508304..0d4b5993 100644 --- a/Source/SPSSHTunnel.m +++ b/Source/SPSSHTunnel.m @@ -328,8 +328,8 @@ [taskEnvironment setObject:tunnelConnectionVerifyHash forKey:@"SP_CONNECTION_VERIFY_HASH"]; if (passwordInKeychain) { [taskEnvironment setObject:[[NSNumber numberWithInteger:SPSSHPasswordUsesKeychain] stringValue] forKey:@"SP_PASSWORD_METHOD"]; - [taskEnvironment setObject:keychainName forKey:@"SP_KEYCHAIN_ITEM_NAME"]; - [taskEnvironment setObject:keychainAccount forKey:@"SP_KEYCHAIN_ITEM_ACCOUNT"]; + [taskEnvironment setObject:[keychainName stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] forKey:@"SP_KEYCHAIN_ITEM_NAME"]; + [taskEnvironment setObject:[keychainAccount stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] forKey:@"SP_KEYCHAIN_ITEM_ACCOUNT"]; } else if (password) { [taskEnvironment setObject:[[NSNumber numberWithInteger:SPSSHPasswordAsksUI] stringValue] forKey:@"SP_PASSWORD_METHOD"]; } else { diff --git a/Source/SPServerSupport.h b/Source/SPServerSupport.h index fc16b426..bf18a8e8 100644 --- a/Source/SPServerSupport.h +++ b/Source/SPServerSupport.h @@ -65,6 +65,7 @@ BOOL supportsShowPrivileges; // Storage engines + NSString *engineTypeQueryName; BOOL supportsInformationSchemaEngines; BOOL supportsPre41StorageEngines; BOOL supportsBlackholeStorageEngine; @@ -172,6 +173,11 @@ @property (readonly) BOOL supportsShowPrivileges; /** + * @property engineTypeQueryName Returns the appropriate query part for specifying table engine - ENGINE or TYPE + */ +@property (readonly) NSString *engineTypeQueryName; + +/** * @property supportsInformationSchemaEngines Indicates if the server supports the information_schema.engines table */ @property (readonly) BOOL supportsInformationSchemaEngines; diff --git a/Source/SPServerSupport.m b/Source/SPServerSupport.m index 0b3ba79d..6f8213d6 100644 --- a/Source/SPServerSupport.m +++ b/Source/SPServerSupport.m @@ -54,6 +54,7 @@ @synthesize supportsFullDropUser; @synthesize supportsUserMaxVars; @synthesize supportsShowPrivileges; +@synthesize engineTypeQueryName; @synthesize supportsInformationSchemaEngines; @synthesize supportsPre41StorageEngines; @synthesize supportsBlackholeStorageEngine; @@ -150,7 +151,10 @@ // The SHOW PRIVILEGES statement wasn't added until MySQL 4.1.0 supportsShowPrivileges = [self isEqualToOrGreaterThanMajorVersion:4 minor:1 release:0]; - + + // MySQL 4.0.18+ and 4.1.2+ changed the TYPE option to ENGINE, but 4.x supports both + engineTypeQueryName = [self isEqualToOrGreaterThanMajorVersion:5 minor:0 release:0]?@"ENGINE":@"TYPE"; + // Before MySQL 4.1 the MEMORY engine was known as HEAP and the ISAM engine was available supportsPre41StorageEngines = (![self isEqualToOrGreaterThanMajorVersion:4 minor:1 release:0]); @@ -245,6 +249,7 @@ supportsFullDropUser = NO; supportsUserMaxVars = NO; supportsShowPrivileges = NO; + engineTypeQueryName = @"ENGINE"; supportsInformationSchemaEngines = NO; supportsPre41StorageEngines = NO; supportsBlackholeStorageEngine = NO; diff --git a/Source/SPTableContent.h b/Source/SPTableContent.h index e3fd8a53..0b33146b 100644 --- a/Source/SPTableContent.h +++ b/Source/SPTableContent.h @@ -226,12 +226,14 @@ - (void) setFiltersToRestore:(NSDictionary *)filterSettings; - (void) storeCurrentDetailsForRestoration; - (void) clearDetailsToRestore; +- (void) setFilterTableData:(NSData*)arcData; +- (NSData*) filterTableData; - (NSString *)escapeFilterArgument:(NSString *)argument againstClause:(NSString *)clause; - (void)openContentFilterManager; - (void)makeContentFilterHaveFocus; -- (NSArray*)fieldEditStatusForRow:(NSInteger)rowIndex andColumn:(NSInteger)columnIndex; +- (NSArray*)fieldEditStatusForRow:(NSInteger)rowIndex andColumn:(NSNumber *)columnIndex; - (void)updateFilterTableClause:(id)currentValue; - (NSString*)escapeFilterTableDefaultOperator:(NSString*)anOperator; diff --git a/Source/SPTableContent.m b/Source/SPTableContent.m index 8e2bde89..b136d90a 100644 --- a/Source/SPTableContent.m +++ b/Source/SPTableContent.m @@ -1604,6 +1604,12 @@ column = NSArrayObjectAtIndex(dataColumns, i); if ([column objectForKey:@"default"] == nil || [column objectForKey:@"default"] == [NSNull null]) { [newRow addObject:[NSNull null]]; + } else if ([[column objectForKey:@"default"] isEqualToString:@""] + && ![[column objectForKey:@"null"] boolValue] + && ([[column objectForKey:@"typegrouping"] isEqualToString:@"float"] + || [[column objectForKey:@"typegrouping"] isEqualToString:@"integer"])) + { + [newRow addObject:@"0"]; } else { [newRow addObject:[column objectForKey:@"default"]]; } @@ -2394,9 +2400,11 @@ NSMutableArray *rowFieldsToSave = [[NSMutableArray alloc] initWithCapacity:[dataColumns count]]; NSMutableArray *rowValuesToSave = [[NSMutableArray alloc] initWithCapacity:[dataColumns count]]; NSInteger i; + NSDictionary *fieldDefinition; id rowObject; for (i = 0; i < [dataColumns count]; i++) { rowObject = [tableValues cellDataAtRow:currentlyEditingRow column:i]; + fieldDefinition = NSArrayObjectAtIndex(dataColumns, i); // Skip "not loaded" cells entirely - these only occur when editing tables when the // preference setting is enabled, and don't need to be saved back to the table. @@ -2408,13 +2416,13 @@ // Prepare to derive the value to save NSString *fieldValue; - NSString *fieldTypeGroup = [NSArrayObjectAtIndex(dataColumns, i) objectForKey:@"typegrouping"]; + NSString *fieldTypeGroup = [fieldDefinition objectForKey:@"typegrouping"]; // Use NULL when the user has entered the nullValue string defined in the preferences, // or when a numeric field is empty. if ([rowObject isNSNull] || (([fieldTypeGroup isEqualToString:@"float"] || [fieldTypeGroup isEqualToString:@"integer"]) - && [[rowObject description] isEqualToString:@""])) + && [[rowObject description] isEqualToString:@""] && [[fieldDefinition objectForKey:@"null"] boolValue])) { fieldValue = @"NULL"; @@ -2449,7 +2457,7 @@ } // Store the key and value in the ordered arrays for saving. - [rowFieldsToSave addObject:[NSArrayObjectAtIndex(dataColumns, i) objectForKey:@"name"]]; + [rowFieldsToSave addObject:[fieldDefinition objectForKey:@"name"]]; [rowValuesToSave addObject:fieldValue]; } @@ -2490,10 +2498,19 @@ } else { NSBeep(); } - [tableValues replaceRowAtIndex:currentlyEditingRow withRowContents:oldRow]; + + // If creating a new row, remove the row; otherwise revert the row contents + if (isEditingNewRow) { + tableRowsCount--; + [tableValues removeRowAtIndex:currentlyEditingRow]; + [self updateCountText]; + isEditingNewRow = NO; + } else { + [tableValues replaceRowAtIndex:currentlyEditingRow withRowContents:oldRow]; + } isEditingRow = NO; - isEditingNewRow = NO; currentlyEditingRow = -1; + [tableContentView reloadData]; [[SPQueryController sharedQueryController] showErrorInConsole:NSLocalizedString(@"/* WARNING: No rows have been affected */\n", @"warning shown in the console when no rows have been affected after writing to the db") connection:[tableDocumentInstance name]]; return YES; @@ -2784,7 +2801,7 @@ * -2 for other errors * and the used WHERE clause to identify */ -- (NSArray*)fieldEditStatusForRow:(NSInteger)rowIndex andColumn:(NSInteger)columnIndex +- (NSArray*)fieldEditStatusForRow:(NSInteger)rowIndex andColumn:(NSNumber *)columnIndex { NSDictionary *columnDefinition = nil; @@ -2919,10 +2936,12 @@ NSInteger row = -1; NSInteger column = -1; + NSInteger editedColumn = -1; if(contextInfo) { row = [[contextInfo objectForKey:@"row"] integerValue]; column = [[contextInfo objectForKey:@"column"] integerValue]; + editedColumn = [[contextInfo objectForKey:@"editedColumn"] integerValue]; } if (data && contextInfo) { @@ -2958,8 +2977,8 @@ [[tableDocumentInstance parentWindow] makeFirstResponder:tableContentView]; - if(row > -1 && column > -1) - [tableContentView editColumn:column row:row withEvent:nil select:YES]; + if(row > -1 && editedColumn > -1) + [tableContentView editColumn:editedColumn row:row withEvent:nil select:YES]; } #pragma mark - @@ -3278,6 +3297,25 @@ [self setFiltersToRestore:nil]; } +- (void) setFilterTableData:(NSData*)arcData +{ + if(!arcData) return; + NSDictionary *filterData = [NSUnarchiver unarchiveObjectWithData:arcData]; + [filterTableData removeAllObjects]; + [filterTableData addEntriesFromDictionary:filterData]; + [filterTableWindow makeKeyAndOrderFront:nil]; + // [filterTableView reloadData]; +} + +- (NSData*) filterTableData +{ + if(![filterTableWindow isVisible]) return nil; + + [filterTableView deselectAll:nil]; + + return [NSArchiver archivedDataWithRootObject:filterTableData]; +} + #pragma mark - #pragma mark Table drawing and editing @@ -3494,8 +3532,9 @@ return c; } else return NSArrayObjectAtIndex([[filterTableData objectForKey:[NSNumber numberWithInteger:rowIndex]] objectForKey:@"filter"], [[aTableColumn identifier] integerValue]-1); - else + else { return NSArrayObjectAtIndex([[filterTableData objectForKey:[aTableColumn identifier]] objectForKey:@"filter"], rowIndex); + } } else if(aTableView == tableContentView) { @@ -4050,6 +4089,12 @@ if ([cellValue isNSNull]) cellValue = [NSString stringWithString:[prefs objectForKey:SPNullValue]]; + NSInteger editedColumn = 0; + for(NSTableColumn* col in [tableContentView tableColumns]) { + if([[col identifier] isEqualToNumber:[aTableColumn identifier]]) break; + editedColumn++; + } + [fieldEditor editWithObject:cellValue fieldName:[[aTableColumn headerCell] stringValue] usingEncoding:[mySQLConnection stringEncoding] @@ -4060,6 +4105,7 @@ contextInfo:[NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInteger:rowIndex], @"row", [aTableColumn identifier], @"column", + [NSNumber numberWithInteger:editedColumn], @"editedColumn", [NSNumber numberWithBool:isFieldEditable], @"isFieldEditable", nil]]; @@ -4444,6 +4490,8 @@ NSString *re1 = @"^\\s*(<[=>]?|>=?|!?=|≠|≤|≥)\\s*(.*?)\\s*$"; NSString *re2 = @"^\\s*(.*)\\s+(.*?)\\s*$"; NSCharacterSet *whiteSpaceCharSet = [NSCharacterSet whitespaceAndNewlineCharacterSet]; + NSInteger editedRow = [filterTableView editedRow]; + if(currentValue == filterTableGearLookAllFields) { numberOfRows = 1; @@ -4477,7 +4525,7 @@ } // Take value from currently edited table cell } else if([currentValue isKindOfClass:[NSString class]]){ - if(index == [filterTableView editedColumn] && i == [filterTableView editedRow]) + if(i == editedRow && index == [[NSArrayObjectAtIndex([filterTableView tableColumns], [filterTableView editedColumn]) identifier] integerValue]) filterCell = (NSString*)currentValue; else filterCell = NSArrayObjectAtIndex([filterCellData objectForKey:@"filter"], i); diff --git a/Source/SPTableStructure.m b/Source/SPTableStructure.m index f57a881f..1cd5ca4e 100644 --- a/Source/SPTableStructure.m +++ b/Source/SPTableStructure.m @@ -758,13 +758,7 @@ */ - (void)setAutoIncrementTo:(NSString*)valueAsString { - NSString *selTable = nil; - - // if selectedTable is nil try to get the name from SPTablesList - if (selectedTable == nil || ![selectedTable length]) - selTable = [tablesListInstance tableName]; - else - selTable = [NSString stringWithString:selectedTable]; + NSString *selTable = [tablesListInstance tableName]; if (selTable == nil || ![selTable length]) return; @@ -1219,8 +1213,18 @@ alertSheetOpened = NO; if(contextInfo && [contextInfo isEqualToString:@"autoincrementindex"]) { - if(returnCode) { - autoIncrementIndex = [chooseKeyButton titleOfSelectedItem]; + if (returnCode) { + switch ([[chooseKeyButton selectedItem] tag]) { + case SPPrimaryKeyMenuTag: + autoIncrementIndex = @"PRIMARY KEY"; + break; + case SPIndexMenuTag: + autoIncrementIndex = @"INDEX"; + break; + case SPUniqueMenuTag: + autoIncrementIndex = @"UNIQUE"; + break; + } } else { autoIncrementIndex = nil; if([tableSourceView selectedRow] > -1 && [extraFieldSuggestions count]) diff --git a/Source/SPTableStructureDelegate.m b/Source/SPTableStructureDelegate.m index d63064e2..17ad817d 100644 --- a/Source/SPTableStructureDelegate.m +++ b/Source/SPTableStructureDelegate.m @@ -116,9 +116,10 @@ isCurrentExtraAutoIncrement = [[[anObject stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] uppercaseString] isEqualToString:@"AUTO_INCREMENT"]; if(isCurrentExtraAutoIncrement) { [currentRow setObject:[NSNumber numberWithInteger:0] forKey:@"null"]; + // Asks the user to add an index to query if AUTO_INCREMENT is set and field isn't indexed if ((![currentRow objectForKey:@"Key"] || [[currentRow objectForKey:@"Key"] isEqualToString:@""])) { - [chooseKeyButton selectItemAtIndex:0]; + [chooseKeyButton selectItemWithTag:SPPrimaryKeyMenuTag]; [NSApp beginSheet:keySheet modalForWindow:[tableDocumentInstance parentWindow] modalDelegate:self diff --git a/Source/SPTablesList.h b/Source/SPTablesList.h index e53fb178..7729ed1d 100644 --- a/Source/SPTablesList.h +++ b/Source/SPTablesList.h @@ -103,6 +103,9 @@ BOOL tableListContainsViews; BOOL alertSheetOpened; + + NSFont *smallSystemFont; + } // IBAction methods diff --git a/Source/SPTablesList.m b/Source/SPTablesList.m index da787a6d..a0a9bf92 100644 --- a/Source/SPTablesList.m +++ b/Source/SPTablesList.m @@ -1477,34 +1477,51 @@ /** * Table view delegate method */ -- (void)tableView:(NSTableView *)aTableView willDisplayCell:(id)aCell forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex +- (void)tableView:(NSTableView *)aTableView willDisplayCell:(ImageAndTextCell*)aCell forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex { - if (rowIndex > 0 && rowIndex < [filteredTableTypes count] - && [[aTableColumn identifier] isEqualToString:@"tables"]) { - if(![[filteredTables objectAtIndex:rowIndex] isKindOfClass:[NSString class]]) { - [(ImageAndTextCell*)aCell setImage:nil]; - [(ImageAndTextCell*)aCell setIndentationLevel:0]; - } - else if ([[filteredTableTypes objectAtIndex:rowIndex] integerValue] == SPTableTypeView) { - [(ImageAndTextCell*)aCell setImage:[NSImage imageNamed:@"table-view-small"]]; - } else if ([[filteredTableTypes objectAtIndex:rowIndex] integerValue] == SPTableTypeTable) { - [(ImageAndTextCell*)aCell setImage:[NSImage imageNamed:@"table-small"]]; - } else if ([[filteredTableTypes objectAtIndex:rowIndex] integerValue] == SPTableTypeProc) { - [(ImageAndTextCell*)aCell setImage:[NSImage imageNamed:@"proc-small"]]; - } else if ([[filteredTableTypes objectAtIndex:rowIndex] integerValue] == SPTableTypeFunc) { - [(ImageAndTextCell*)aCell setImage:[NSImage imageNamed:@"func-small"]]; + if (rowIndex > 0 && rowIndex < [filteredTableTypes count] && [[aTableColumn identifier] isEqualToString:@"tables"]) { + + id item = NSArrayObjectAtIndex(filteredTables, rowIndex); + + if(![item isKindOfClass:[NSString class]]) { + [aCell setImage:nil]; + [aCell setIndentationLevel:0]; + return; } - if ([[filteredTableTypes objectAtIndex:rowIndex] integerValue] == SPTableTypeNone) { - [(ImageAndTextCell*)aCell setImage:nil]; - [(ImageAndTextCell*)aCell setIndentationLevel:0]; - } else { - [(ImageAndTextCell*)aCell setIndentationLevel:1]; - [(ImageAndTextCell*)aCell setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; + switch([NSArrayObjectAtIndex(filteredTableTypes, rowIndex) integerValue]) { + case SPTableTypeView: + [aCell setImage:[NSImage imageNamed:@"table-view-small"]]; + [aCell setIndentationLevel:1]; + [aCell setFont:smallSystemFont]; + break; + case SPTableTypeTable: + [aCell setImage:[NSImage imageNamed:@"table-small"]]; + [aCell setIndentationLevel:1]; + [aCell setFont:smallSystemFont]; + break; + case SPTableTypeProc: + [aCell setImage:[NSImage imageNamed:@"proc-small"]]; + [aCell setIndentationLevel:1]; + [aCell setFont:smallSystemFont]; + break; + case SPTableTypeFunc: + [aCell setImage:[NSImage imageNamed:@"func-small"]]; + [aCell setIndentationLevel:1]; + [aCell setFont:smallSystemFont]; + break; + case SPTableTypeNone: + [aCell setImage:nil]; + [aCell setIndentationLevel:0]; + break; + default: + [aCell setIndentationLevel:1]; + [aCell setFont:smallSystemFont]; } + } else { - [(ImageAndTextCell*)aCell setImage:nil]; - [(ImageAndTextCell*)aCell setIndentationLevel:0]; + [aCell setImage:nil]; + [aCell setIndentationLevel:0]; } } @@ -1775,6 +1792,7 @@ selectedTableType = SPTableTypeNone; selectedTableName = nil; [tables addObject:NSLocalizedString(@"TABLES",@"header for table list")]; + smallSystemFont = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]; } return self; @@ -2007,7 +2025,7 @@ // If there is a type selected other than the default we must specify it in CREATE TABLE statement if ([tableTypeButton indexOfSelectedItem] > 0) { - engineStatement = [NSString stringWithFormat:@"ENGINE = %@", [tableType backtickQuotedString]]; + engineStatement = [NSString stringWithFormat:@"%@ = %@", [[tableDocumentInstance serverSupport] engineTypeQueryName], [tableType backtickQuotedString]]; } NSString *createStatement = [NSString stringWithFormat:@"CREATE TABLE %@ (%@) %@ %@", [tableName backtickQuotedString], ([tableType isEqualToString:@"CSV"]) ? @"id INT NOT NULL" : @"id INT", charSetStatement, engineStatement]; diff --git a/Source/SPTextView.h b/Source/SPTextView.h index 797fff34..b15030b4 100644 --- a/Source/SPTextView.h +++ b/Source/SPTextView.h @@ -107,6 +107,7 @@ - (BOOL) isNextCharMarkedBy:(id)attribute withValue:(id)aValue; - (BOOL) areAdjacentCharsLinked; - (BOOL) isCaretAdjacentToAlphanumCharWithInsertionOf:(unichar)aChar; +- (BOOL) isCaretAtIndentPositionIgnoreLineStart:(BOOL)ignoreLineStart; - (BOOL) wrapSelectionWithPrefix:(NSString *)prefix suffix:(NSString *)suffix; - (BOOL) shiftSelectionRight; - (BOOL) shiftSelectionLeft; diff --git a/Source/SPTextView.m b/Source/SPTextView.m index af4356a2..82bfdedf 100644 --- a/Source/SPTextView.m +++ b/Source/SPTextView.m @@ -144,7 +144,6 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) [self setAutohelp:[prefs boolForKey:SPCustomQueryUpdateAutoHelp]]; [self setAutouppercaseKeywords:[prefs boolForKey:SPCustomQueryAutoUppercaseKeywords]]; [self setCompletionWasReinvokedAutomatically:NO]; - // Re-define tab stops for a better editing [self setTabStops]; @@ -153,9 +152,8 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) [[self layoutManager] setBackgroundLayoutEnabled:NO]; // add NSViewBoundsDidChangeNotification to scrollView - [[scrollView contentView] setPostsBoundsChangedNotifications:YES]; - NSNotificationCenter *aNotificationCenter = [NSNotificationCenter defaultCenter]; - [aNotificationCenter addObserver:self selector:@selector(boundsDidChangeNotification:) name:@"NSViewBoundsDidChangeNotification" object:[scrollView contentView]]; + [scrollView setPostsBoundsChangedNotifications:YES]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(boundsDidChangeNotification:) name:NSViewBoundsDidChangeNotification object:[scrollView contentView]]; [self setQueryHiliteColor:[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPCustomQueryEditorHighlightQueryColor]]]; [self setQueryEditorBackgroundColor:[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPCustomQueryEditorBackgroundColor]]]; @@ -775,6 +773,7 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) object:nil]; // Check for table name aliases + NSString *alias = nil; if(dbBrowseMode && tableDocumentInstance && customQueryInstance) { NSString *theDb = (dbName == nil) ? [NSString stringWithString:currentDb] : [NSString stringWithString:dbName]; NSString *connectionID = [tableDocumentInstance connectionID]; @@ -790,7 +789,7 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) for(NSString* m in matches) { NSRange aliasRange = [m rangeOfRegex:re capture:1L]; if(aliasRange.length) { - NSString *alias = [[m substringWithRange:aliasRange] stringByReplacingOccurrencesOfString:@"``" withString:@"`"]; + alias = [[m substringWithRange:aliasRange] stringByReplacingOccurrencesOfString:@"``" withString:@"`"]; // If alias refers to db.table split and check it if([alias rangeOfString:@"."].length) { NSRange dbRange = [alias rangeOfRegex:@"^`?(.*?)`?\\." capture:1L]; @@ -819,6 +818,7 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) } 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 @@ -839,24 +839,19 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) caretMovedLeft:caretMovedLeft autoComplete:autoCompleteMode oneColumn:isDictMode + alias:alias isQueryingDBStructure:[mySQLConnection isQueryingDatabaseStructure]]; completionParseRangeLocation = parseRange.location; //Get the NSPoint of the first character of the current word - NSRange glyphRange = [[self layoutManager] glyphRangeForCharacterRange:NSMakeRange(completionRange.location,0) actualCharacterRange:NULL]; + NSRange glyphRange = [[self layoutManager] glyphRangeForCharacterRange:NSMakeRange(completionRange.location,1) actualCharacterRange:NULL]; NSRect boundingRect = [[self layoutManager] boundingRectForGlyphRange:glyphRange inTextContainer:[self textContainer]]; - boundingRect = [self convertRect: boundingRect toView: NULL]; - NSPoint pos = [[self window] convertBaseToScreen: NSMakePoint(boundingRect.origin.x + boundingRect.size.width,boundingRect.origin.y + boundingRect.size.height)]; - - // TODO: check if needed - // if(filter) - // pos.x -= [filter sizeWithAttributes:[NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName]].width; - - // Adjust list location to be under the current word or insertion point + boundingRect = [self convertRect:boundingRect toView:nil]; + NSPoint pos = [[self window] convertBaseToScreen:NSMakePoint(boundingRect.origin.x + boundingRect.size.width,boundingRect.origin.y + boundingRect.size.height)]; pos.y -= [[self font] pointSize]*1.25; - [completionPopup setCaretPos:pos]; + [completionPopup orderFront:self]; [completionPopup insertAutocompletePlaceholder]; @@ -924,6 +919,24 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) } /** + * Checks if all the characters left from the caret are white spaces or caret is at the line begin. + */ +- (BOOL) isCaretAtIndentPositionIgnoreLineStart:(BOOL)ignoreLineStart +{ + NSString *textViewString = [[self textStorage] string]; + NSUInteger caretPosition = [self selectedRange].location; + NSUInteger currentLineStartPosition = [textViewString lineRangeForRange:NSMakeRange(caretPosition, 0)].location; + + // Check if caret is at the beginning of a line + // - used for deleteBackward: to allow to delete leading \n + if(!ignoreLineStart && caretPosition == currentLineStartPosition) + return NO; + + NSString *lineHeadToCaret = [textViewString substringWithRange:NSMakeRange(currentLineStartPosition, caretPosition-currentLineStartPosition)]; + return (![lineHeadToCaret length] || [lineHeadToCaret isMatchedByRegex:@"^\\s+$"]); +} + +/** * Checks if the caret is wrapped by auto-paired characters. * e.g. [| := caret]: "|" */ @@ -1112,27 +1125,39 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) { NSString *textViewString = [[self textStorage] string]; NSRange currentLineRange; - - if ([self selectedRange].location == NSNotFound || ![self isEditable]) return NO; + NSRange selectedRange = [self selectedRange]; + + if (selectedRange.location == NSNotFound || ![self isEditable]) return NO; + + NSString *indentString = @"\t"; + if ([prefs boolForKey:SPCustomQuerySoftIndent]) { + NSUInteger numberOfSpaces = [prefs integerForKey:SPCustomQuerySoftIndentWidth]; + if(numberOfSpaces < 1) numberOfSpaces = 1; + if(numberOfSpaces > 32) numberOfSpaces = 32; + NSMutableString *spaces = [NSMutableString string]; + for(NSInteger i = 0; i < numberOfSpaces; i++) + [spaces appendString:@" "]; + indentString = [NSString stringWithString:spaces]; + } // Indent the currently selected line if the caret is within a single line - if ([self selectedRange].length == 0) { + if (selectedRange.length == 0) { // Extract the current line range based on the text caret - currentLineRange = [textViewString lineRangeForRange:[self selectedRange]]; + currentLineRange = [textViewString lineRangeForRange:selectedRange]; // Register the indent for undo - [self shouldChangeTextInRange:NSMakeRange(currentLineRange.location, 0) replacementString:@"\t"]; + [self shouldChangeTextInRange:NSMakeRange(currentLineRange.location, 0) replacementString:indentString]; // Insert the new tab - [self replaceCharactersInRange:NSMakeRange(currentLineRange.location, 0) withString:@"\t"]; + [self replaceCharactersInRange:NSMakeRange(currentLineRange.location, 0) withString:indentString]; return YES; } // Otherwise, something is selected - NSRange firstLineRange = [textViewString lineRangeForRange:NSMakeRange([self selectedRange].location,0)]; - NSUInteger lastLineMaxRange = NSMaxRange([textViewString lineRangeForRange:NSMakeRange(NSMaxRange([self selectedRange])-1,0)]); + NSRange firstLineRange = [textViewString lineRangeForRange:NSMakeRange(selectedRange.location,0)]; + NSUInteger lastLineMaxRange = NSMaxRange([textViewString lineRangeForRange:NSMakeRange(NSMaxRange(selectedRange)-1,0)]); // Expand selection for first and last line to begin and end resp. but not the last line ending NSRange blockRange = NSMakeRange(firstLineRange.location, lastLineMaxRange - firstLineRange.location); @@ -1143,13 +1168,13 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) NSString *newString; // check for line ending if([textViewString characterAtIndex:NSMaxRange(firstLineRange)-1] == '\r') - newString = [[NSString stringWithString:@"\t"] stringByAppendingString: + newString = [indentString stringByAppendingString: [[textViewString substringWithRange:blockRange] - stringByReplacingOccurrencesOfString:@"\r" withString:@"\r\t"]]; + stringByReplacingOccurrencesOfString:@"\r" withString:[NSString stringWithFormat:@"\r%@", indentString]]]; else - newString = [[NSString stringWithString:@"\t"] stringByAppendingString: + newString = [indentString stringByAppendingString: [[textViewString substringWithRange:blockRange] - stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"]]; + stringByReplacingOccurrencesOfString:@"\n" withString:[NSString stringWithFormat:@"\n%@", indentString]]]; // Register the indent for undo [self shouldChangeTextInRange:blockRange replacementString:newString]; @@ -1190,11 +1215,30 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) || ([textViewString characterAtIndex:currentLineRange.location] != '\t' && [textViewString characterAtIndex:currentLineRange.location] != ' ')) return NO; + NSRange replaceRange; + + // Check for soft indention + NSUInteger indentStringLength = 1; + if ([prefs boolForKey:SPCustomQuerySoftIndent]) { + NSUInteger numberOfSpaces = [prefs integerForKey:SPCustomQuerySoftIndentWidth]; + if(numberOfSpaces < 1) numberOfSpaces = 1; + if(numberOfSpaces > 32) numberOfSpaces = 32; + indentStringLength = numberOfSpaces; + replaceRange = NSIntersectionRange(NSMakeRange(currentLineRange.location, indentStringLength), NSMakeRange(0,[[self string] length])); + // Correct length for only white spaces + NSString *possibleIndentString = [[[self textStorage] string] substringWithRange:replaceRange]; + NSUInteger numberOfLeadingWhiteSpaces = [possibleIndentString rangeOfRegex:@"^(\\s*)" capture:1L].length; + if(numberOfLeadingWhiteSpaces == NSNotFound) numberOfLeadingWhiteSpaces = 0; + replaceRange = NSMakeRange(currentLineRange.location, numberOfLeadingWhiteSpaces); + } else { + replaceRange = NSMakeRange(currentLineRange.location, indentStringLength); + } + // Register the undent for undo - [self shouldChangeTextInRange:NSMakeRange(currentLineRange.location, 1) replacementString:@""]; + [self shouldChangeTextInRange:replaceRange replacementString:@""]; // Remove the tab - [self replaceCharactersInRange:NSMakeRange(currentLineRange.location, 1) withString:@""]; + [self replaceCharactersInRange:replaceRange withString:@""]; return YES; } @@ -1208,25 +1252,36 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) if([textViewString characterAtIndex:NSMaxRange(blockRange)-1] == '\n' || [textViewString characterAtIndex:NSMaxRange(blockRange)-1] == '\r') blockRange.length--; + // Check for soft or hard indention + NSString *indentString = @"\t"; + NSUInteger indentStringLength = 1; + if ([prefs boolForKey:SPCustomQuerySoftIndent]) { + indentStringLength = [prefs integerForKey:SPCustomQuerySoftIndentWidth]; + if(indentStringLength < 1) indentStringLength = 1; + if(indentStringLength > 32) indentStringLength = 32; + NSMutableString *spaces = [NSMutableString string]; + for(NSInteger i = 0; i < indentStringLength; i++) + [spaces appendString:@" "]; + indentString = [NSString stringWithString:spaces]; + } + // Check if blockRange starts with SPACE or TAB // (this also catches the first line of the entire text buffer or // if only one line is selected) NSInteger leading = 0; if([textViewString characterAtIndex:blockRange.location] == ' ' || [textViewString characterAtIndex:blockRange.location] == '\t') - leading++; + leading += indentStringLength; // Replace \n[ \t] by \n of all lines in blockRange NSString *newString; // check for line ending if([textViewString characterAtIndex:NSMaxRange(firstLineRange)-1] == '\r') - newString = [[[textViewString substringWithRange:NSMakeRange(blockRange.location+leading, blockRange.length-leading)] - stringByReplacingOccurrencesOfString:@"\r\t" withString:@"\r"] - stringByReplacingOccurrencesOfString:@"\r " withString:@"\r"]; + newString = [[textViewString substringWithRange:NSMakeRange(blockRange.location+leading, blockRange.length-leading)] + stringByReplacingOccurrencesOfString:[NSString stringWithFormat:@"\r%@", indentString] withString:@"\r"]; else - newString = [[[textViewString substringWithRange:NSMakeRange(blockRange.location+leading, blockRange.length-leading)] - stringByReplacingOccurrencesOfString:@"\n\t" withString:@"\n"] - stringByReplacingOccurrencesOfString:@"\n " withString:@"\n"]; + newString = [[textViewString substringWithRange:NSMakeRange(blockRange.location+leading, blockRange.length-leading)] + stringByReplacingOccurrencesOfString:[NSString stringWithFormat:@"\n%@", indentString] withString:@"\n"]; // Register the unindent for undo [self shouldChangeTextInRange:blockRange replacementString:newString]; @@ -1407,10 +1462,11 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) caretMovedLeft:NO autoComplete:NO oneColumn:NO + alias:nil isQueryingDBStructure:NO]; //Get the NSPoint of the first character of the current word - NSRange glyphRange = [[self layoutManager] glyphRangeForCharacterRange:NSMakeRange(aRange.location,0) actualCharacterRange:NULL]; + NSRange glyphRange = [[self layoutManager] glyphRangeForCharacterRange:NSMakeRange(aRange.location,1) actualCharacterRange:NULL]; NSRect boundingRect = [[self layoutManager] boundingRectForGlyphRange:glyphRange inTextContainer:[self textContainer]]; boundingRect = [self convertRect: boundingRect toView: NULL]; NSPoint pos = [[self window] convertBaseToScreen: NSMakePoint(boundingRect.origin.x + boundingRect.size.width,boundingRect.origin.y + boundingRect.size.height)]; @@ -1563,10 +1619,11 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) caretMovedLeft:NO autoComplete:NO oneColumn:YES + alias:nil isQueryingDBStructure:NO]; //Get the NSPoint of the first character of the current word - NSRange glyphRange = [[self layoutManager] glyphRangeForCharacterRange:NSMakeRange(r2.location,0) actualCharacterRange:NULL]; + NSRange glyphRange = [[self layoutManager] glyphRangeForCharacterRange:NSMakeRange(r2.location,1) actualCharacterRange:NULL]; NSRect boundingRect = [[self layoutManager] boundingRectForGlyphRange:glyphRange inTextContainer:[self textContainer]]; boundingRect = [self convertRect: boundingRect toView: NULL]; NSPoint pos = [[self window] convertBaseToScreen: NSMakePoint(boundingRect.origin.x + boundingRect.size.width,boundingRect.origin.y + boundingRect.size.height)]; @@ -2033,6 +2090,7 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) } // Check for {SHIFT}TAB to try to insert query favorite via TAB trigger if SPTextView belongs to SPCustomQuery + // and TAB as soft indention if ([theEvent keyCode] == 48 && [self isEditable] && [[self delegate] isKindOfClass:[SPCustomQuery class]]){ NSRange targetRange = [self getRangeForCurrentWord]; NSString *tabTrigger = [[self string] substringWithRange:targetRange]; @@ -2079,13 +2137,19 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) } // Check if tab trigger is defined; if so insert it, otherwise pass through event - if(snippetControlCounter < 0 && [tableDocumentInstance fileURL]) { + if(snippetControlCounter < 0 && [tabTrigger length] && [tableDocumentInstance fileURL]) { NSArray *snippets = [[SPQueryController sharedQueryController] queryFavoritesForFileURL:[tableDocumentInstance fileURL] andTabTrigger:tabTrigger includeGlobals:YES]; if([snippets count] > 0 && [(NSString*)[(NSDictionary*)[snippets objectAtIndex:0] objectForKey:@"query"] length]) { [self insertAsSnippet:[(NSDictionary*)[snippets objectAtIndex:0] objectForKey:@"query"] atRange:targetRange]; return; } } + + // Check for TAB as indention for current line, i.e. left of the caret there are only white spaces + // but only if Soft Indent is set + if([prefs boolForKey:SPCustomQuerySoftIndent] && [self isCaretAtIndentPositionIgnoreLineStart:YES]) { + if([self shiftSelectionRight]) return; + } } // Note: switch(insertedCharacter) {} does not work instead use charactersIgnoringModifiers @@ -2277,7 +2341,14 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) - (void)moveWordRight:(id)sender { [super moveWordRight:sender]; - while([self selectedRange].location < [[[self textStorage] string] length] && [[[self textStorage] string] characterAtIndex:[self selectedRange].location] == '.') + NSCharacterSet *whiteSet = [NSCharacterSet whitespaceAndNewlineCharacterSet]; + while([self selectedRange].location < [[[self textStorage] string] length] + && ([[[self textStorage] string] characterAtIndex:[self selectedRange].location] == '.' + || ( + [[[self textStorage] string] characterAtIndex:[self selectedRange].location-1] == '.' + && ![whiteSet characterIsMember:[[[self textStorage] string] characterAtIndex:[self selectedRange].location]] + ) + )) [super moveWordRight:sender]; } @@ -2298,22 +2369,41 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) - (void)moveWordRightAndModifySelection:(id)sender { [super moveWordRightAndModifySelection:sender]; - while(NSMaxRange([self selectedRange]) < [[[self textStorage] string] length] && [[[self textStorage] string] characterAtIndex:NSMaxRange([self selectedRange])] == '.') + NSCharacterSet *whiteSet = [NSCharacterSet whitespaceAndNewlineCharacterSet]; + while(NSMaxRange([self selectedRange]) < [[[self textStorage] string] length] + && ([[[self textStorage] string] characterAtIndex:NSMaxRange([self selectedRange])] == '.' + || ( + [[[self textStorage] string] characterAtIndex:NSMaxRange([self selectedRange])-1] == '.' + && ![whiteSet characterIsMember:[[[self textStorage] string] characterAtIndex:NSMaxRange([self selectedRange])]] + ) + )) [super moveWordRightAndModifySelection:sender]; } - (void) deleteBackward:(id)sender { - // If the caret is currently inside a marked auto-pair, delete the characters on both sides - // of the caret. NSRange currentRange = [self selectedRange]; - if (currentRange.length == 0 && currentRange.location > 0 && [self areAdjacentCharsLinked]) - [self setSelectedRange:NSMakeRange(currentRange.location - 1,2)]; - // Avoid auto-uppercasing if resulting word would be a SQL keyword; - // e.g. type inta| and deleteBackward: - delBackwardsWasPressed = YES; + if (currentRange.length == 0) { + + // If the caret is currently inside a marked auto-pair, delete the characters on both sides + // of the caret. + if (currentRange.location > 0 && [self areAdjacentCharsLinked]) { + [self setSelectedRange:NSMakeRange(currentRange.location - 1,2)]; + // Avoid auto-uppercasing if resulting word would be a SQL keyword; + // e.g. type inta| and deleteBackward: + delBackwardsWasPressed = YES; + } + + // Remove soft indent if active and left from caret are only white spaces + else if ([prefs boolForKey:SPCustomQuerySoftIndent] && [self isCaretAtIndentPositionIgnoreLineStart:NO]) + { + [self shiftSelectionLeft]; + return; + } + + } [super deleteBackward:sender]; @@ -2365,6 +2455,18 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) // Return to avoid the original implementation, preventing double linebreaks return; } + + // Remove soft indent if active and left from caret are only white spaces + if (aSelector == @selector(deleteForward:) + && ![self selectedRange].length + && [prefs boolForKey:SPCustomQuerySoftIndent] + && [self isCaretAtIndentPositionIgnoreLineStart:YES] + && [self selectedRange].location < [[self string] length] && [[self string] characterAtIndex:[self selectedRange].location] == ' ') + { + [self shiftSelectionLeft]; + return; + } + [super doCommandBySelector:aSelector]; } @@ -3015,9 +3117,9 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) /** * Scrollview delegate after the textView's view port was changed. - * Manily used to update the syntax highlighting for a large text size. + * Manily used to update the syntax highlighting for a large text size and line numbering rendering. */ -- (void) boundsDidChangeNotification:(NSNotification *)notification +- (void)boundsDidChangeNotification:(NSNotification *)notification { // Invoke syntax highlighting if text view port was changed for large text if(startListeningToBoundChanges && [[self string] length] > SP_TEXT_SIZE_TRIGGER_FOR_PARTLY_PARSING) @@ -3029,6 +3131,8 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) if(![[self textStorage] changeInLength]) [self performSelector:@selector(doSyntaxHighlighting) withObject:nil afterDelay:0.4]; } + // else + // [scrollView displayRect:[scrollView visibleRect]]; } @@ -3057,7 +3161,7 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) } // Start autocompletion if enabled - if([[NSApp keyWindow] firstResponder] == self && [prefs boolForKey:SPCustomQueryAutoComplete] && !completionIsOpen && editedMask != 1 && [textStore editedRange].length) + if([[NSApp keyWindow] firstResponder] == self && [prefs boolForKey:SPCustomQueryAutoComplete] && !completionIsOpen && editedMask != 1 && [textStore changeInLength] == 1) [self performSelector:@selector(doAutoCompletion) withObject:nil afterDelay:[[prefs valueForKey:SPCustomQueryAutoCompleteDelay] doubleValue]]; // Cancel calling doSyntaxHighlighting for large text @@ -3069,6 +3173,8 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) // Do syntax highlighting/re-calculate snippet ranges only if the user really changed the text if(editedMask != 1) { + [customQueryInstance setTextViewWasChanged:YES]; + // Re-calculate snippet ranges if snippet session is active if(snippetControlCounter > -1 && !snippetWasJustInserted && !isProcessingMirroredSnippets) { // Remove any fully nested snippets relative to the current snippet which was edited @@ -3132,6 +3238,7 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) [self doSyntaxHighlighting]; } else { + [customQueryInstance setTextViewWasChanged:NO]; textBufferSizeIncreased = NO; } diff --git a/Source/SPTextViewAdditions.m b/Source/SPTextViewAdditions.m index 2e1f8f9b..9cefa758 100644 --- a/Source/SPTextViewAdditions.m +++ b/Source/SPTextViewAdditions.m @@ -41,41 +41,34 @@ if (curRange.length) return curRange; - NSUInteger curLocation = curRange.location; + NSInteger curLocation = curRange.location; + NSInteger start = curLocation; + NSInteger end = curLocation; + NSUInteger strLen = [[self string] length]; + + NSMutableCharacterSet *wordCharSet = [NSMutableCharacterSet alphanumericCharacterSet]; + [wordCharSet addCharactersInString:@"_."]; + [wordCharSet removeCharactersInString:@"`"]; + + if(start) { + start--; + while([wordCharSet characterIsMember:[[self string] characterAtIndex:start]]) { + start--; + if(start < 0) break; + } + start++; + } - [self moveWordLeft:self]; - [self moveWordRightAndModifySelection:self]; - - NSUInteger newStartRange = [self selectedRange].location; - NSUInteger newEndRange = newStartRange + [self selectedRange].length; - - // if current location does not intersect with found range - // then caret is at the begin of a word -> change strategy - if(curLocation < newStartRange || curLocation > newEndRange) - { - [self setSelectedRange:curRange]; - [self moveWordRight:self]; - [self moveWordLeftAndModifySelection:self]; - newStartRange = [self selectedRange].location; - newEndRange = newStartRange + [self selectedRange].length; + while(end < strLen && [wordCharSet characterIsMember:[[self string] characterAtIndex:end]]) { + end++; } - - // how many space in front of the selection - NSInteger bias = [self selectedRange].length - [[[[self string] substringWithRange:[self selectedRange]] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] length]; - [self setSelectedRange:NSMakeRange([self selectedRange].location+bias, [self selectedRange].length-bias)]; - newStartRange += bias; - newEndRange -= bias; - - // is caret inside the selection still? - if(curLocation < newStartRange || curLocation > newEndRange - || [[[self string] substringWithRange:[self selectedRange]] rangeOfCharacterFromSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]].location != NSNotFound) - [self setSelectedRange:curRange]; - - NSRange wordRange = [self selectedRange]; - - [self setSelectedRange:curRange]; - + + NSRange wordRange = NSMakeRange(start, end-start); + if(wordRange.length && [[self string] characterAtIndex:NSMaxRange(wordRange)-1] == '.') + wordRange.length--; + return(wordRange); + } /* @@ -534,7 +527,7 @@ [[NSFileManager defaultManager] removeItemAtPath:bundleInputFilePath error:nil]; - BOOL selfIsQueryEditor = ([[[self class] description] isEqualToString:@"SPTextView"] && [self respondsToSelector:@selector(currentQueryRange)]) ; + BOOL selfIsQueryEditor = ([[[self class] description] isEqualToString:@"SPTextView"] && [[self delegate] respondsToSelector:@selector(currentQueryRange)]); if([cmdData objectForKey:SPBundleFileInputSourceKey]) inputAction = [[cmdData objectForKey:SPBundleFileInputSourceKey] lowercaseString]; @@ -562,7 +555,7 @@ replaceRange = currentLineRange; else if([inputFallBackAction isEqualToString:SPBundleInputSourceCurrentQuery]) replaceRange = currentQueryRange; - else if([inputAction isEqualToString:SPBundleInputSourceEntireContent]) + else if([inputFallBackAction isEqualToString:SPBundleInputSourceEntireContent]) replaceRange = NSMakeRange(0,[[self string] length]); } else { replaceRange = currentSelectionRange; @@ -622,8 +615,11 @@ if(currentLineRange.length) [env setObject:[[self string] substringWithRange:currentLineRange] forKey:SPBundleShellVariableCurrentLine]; + [env setObject:NSStringFromRange(replaceRange) forKey:SPBundleShellVariableSelectedTextRange]; + NSError *inputFileError = nil; NSString *input = [NSString stringWithString:[[self string] substringWithRange:replaceRange]]; + [input writeToFile:bundleInputFilePath atomically:YES encoding:NSUTF8StringEncoding @@ -739,8 +735,9 @@ } else if([action isEqualToString:SPBundleOutputActionReplaceSelection]) { - [self shouldChangeTextInRange:replaceRange replacementString:output]; - [self replaceCharactersInRange:replaceRange withString:output]; + NSRange safeRange = NSIntersectionRange(replaceRange, NSMakeRange(0, [[self string] length])); + [self shouldChangeTextInRange:safeRange replacementString:output]; + [self replaceCharactersInRange:safeRange withString:output]; } } else { diff --git a/Source/SPTooltip.m b/Source/SPTooltip.m index e1cccbf9..b0344d52 100644 --- a/Source/SPTooltip.m +++ b/Source/SPTooltip.m @@ -257,24 +257,20 @@ static CGFloat slow_in_out (CGFloat t) id fr = [[NSApp keyWindow] firstResponder]; //If first responder is a textview return the caret position - if([fr respondsToSelector:@selector(getRangeForCurrentWord)] ) { - NSRange range = NSMakeRange([fr selectedRange].location,0); + if(([fr isMemberOfClass:[NSTextView class]] && [fr alignment] == NSLeftTextAlignment) || [[[fr class] description] isEqualToString:@"SPTextView"]) { + NSRange range = NSMakeRange([fr selectedRange].location,1); NSRange glyphRange = [[fr layoutManager] glyphRangeForCharacterRange:range actualCharacterRange:NULL]; NSRect boundingRect = [[fr layoutManager] boundingRectForGlyphRange:glyphRange inTextContainer:[fr textContainer]]; - boundingRect = [fr convertRect: boundingRect toView: NULL]; + boundingRect = [fr convertRect: boundingRect toView:NULL]; pos = [[fr window] convertBaseToScreen: NSMakePoint(boundingRect.origin.x + boundingRect.size.width,boundingRect.origin.y + boundingRect.size.height)]; NSFont* font = [fr font]; - pos.y -= [font pointSize]*1.3; + if(font) pos.y -= [font pointSize]*1.3; return pos; - // Otherwise return the upper left corner of the current keyWindow + // Otherwise return mouse location } else { pos = [NSEvent mouseLocation]; pos.y -= 16; return pos; - // pos = [[NSApp keyWindow] frame].origin; - // pos.x += 5; - // pos.y += [[NSApp keyWindow] frame].size.height - 23; - // return pos; } } diff --git a/Source/SPUserManager.h b/Source/SPUserManager.h index c4eff098..8bb4e4d2 100644 --- a/Source/SPUserManager.h +++ b/Source/SPUserManager.h @@ -59,6 +59,9 @@ IBOutlet NSTextField *userNameTextField; + IBOutlet NSWindow *errorsSheet; + IBOutlet NSTextView *errorsTextView; + IBOutlet BWAnchoredButtonBar *splitViewButtonBar; NSMutableArray *schemas; @@ -67,6 +70,9 @@ NSArray *treeSortDescriptors; NSSortDescriptor *treeSortDescriptor; + + BOOL isSaving; + NSMutableString *errorsString; } @property (nonatomic, retain) MCPConnection *mySqlConnection; @@ -93,6 +99,7 @@ - (IBAction)doApply:(id)sender; - (IBAction)checkAllPrivileges:(id)sender; - (IBAction)uncheckAllPrivileges:(id)sender; +- (IBAction)closeErrorsSheet:(id)sender; // Schema Privieges - (IBAction)addSchemaPriv:(id)sender; diff --git a/Source/SPUserManager.m b/Source/SPUserManager.m index b8d0e826..9118d8a9 100644 --- a/Source/SPUserManager.m +++ b/Source/SPUserManager.m @@ -91,6 +91,7 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; schemas = [[NSMutableArray alloc] init]; availablePrivs = [[NSMutableArray alloc] init]; grantedSchemaPrivs = [[NSMutableArray alloc] init]; + isSaving = NO; } return self; @@ -112,7 +113,11 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; // Set the button delegate [splitViewButtonBar setSplitViewDelegate:self]; - + + // Set schema table double-click actions + [grantedTableView setDoubleAction:@selector(doubleClickSchemaPriv:)]; + [availableTableView setDoubleAction:@selector(doubleClickSchemaPriv:)]; + [self _initializeUsers]; [self _initializeSchemaPrivs]; @@ -363,6 +368,10 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; { // Assumes that the child has already been initialized with values from the // global user table. + + // Set an originalhost key on the child to allow the tracking of edits + [child setPrimitiveValue:[child valueForKey:@"host"] forKey:@"originalhost"]; + // Select rows from the db table that contains schema privs for each user/host NSString *queryString = [NSString stringWithFormat:@"SELECT * from mysql.db d WHERE d.user = %@ and d.host = %@", [[[child parent] valueForKey:@"user"] tickQuotedString], [[child valueForKey:@"host"] tickQuotedString]]; @@ -597,21 +606,29 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; - (IBAction)doApply:(id)sender { NSError *error = nil; + errorsString = [[NSMutableString alloc] init]; //Change the first responder to end editing in any field [[self window] makeFirstResponder:self]; + isSaving = YES; [[self managedObjectContext] save:&error]; - - if (error != nil) { - [[NSApplication sharedApplication] presentError:error]; - } - else { - // Close sheet - [self.mySqlConnection queryString:@"FLUSH PRIVILEGES"]; - [NSApp endSheet:[self window] returnCode:0]; - [[self window] orderOut:self]; + isSaving = NO; + if (error != nil) [errorsString appendString:[error localizedDescription]]; + + [self.mySqlConnection queryString:@"FLUSH PRIVILEGES"]; + + // Display any errors + if ([errorsString length]) { + [errorsTextView setString:errorsString]; + [NSApp beginSheet:errorsSheet modalForWindow:[NSApp keyWindow] modalDelegate:nil didEndSelector:NULL contextInfo:nil]; + [errorsString release]; + return; } + + // Otherwise, close the sheet + [NSApp endSheet:[self window] returnCode:0]; + [[self window] orderOut:self]; } /** @@ -779,6 +796,23 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; } /** + * Move double-clicked rows across to the other table, using the + * appropriate methods. + */ +- (IBAction)doubleClickSchemaPriv:(id)sender +{ + + // Ignore double-clicked header cells + if ([sender clickedRow] == -1) return; + + if (sender == availableTableView) { + [self addSchemaPriv:sender]; + } else { + [self removeSchemaPriv:sender]; + } +} + +/** * Refreshes the current list of users. */ - (IBAction)refresh:(id)sender @@ -934,12 +968,25 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; } } +/** + * Closes the supplied sheet, before closing the master window. + */ +- (IBAction)closeErrorsSheet:(id)sender +{ + [NSApp endSheet:[sender window] returnCode:[sender tag]]; + [[sender window] orderOut:self]; + + // Close the window + [NSApp endSheet:[self window] returnCode:0]; + [[self window] orderOut:self]; +} + #pragma mark - #pragma mark Notifications /** * This notification is called when the managedObjectContext save happens. - * This takes the inserted, updated, and deleted arrays and applys them to + * This takes the inserted, updated, and deleted arrays and applies them to * the database. */ - (void)contextDidSave:(NSNotification *)notification @@ -995,7 +1042,7 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; NSString *renameUserStatement = [NSString stringWithFormat: @"RENAME USER %@@%@ TO %@@%@", [[user valueForKey:@"originaluser"] tickQuotedString], - [[child host] tickQuotedString], + [([child valueForKey:@"originalhost"]?[child valueForKey:@"originalhost"]:[child host]) tickQuotedString], [[user valueForKey:@"user"] tickQuotedString], [[child host] tickQuotedString]]; @@ -1020,6 +1067,19 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; } } else { + + // If the hostname has changed, remane the detail before editing details. + if (![[user valueForKey:@"host"] isEqualToString:[user valueForKey:@"originalhost"]]) { + NSString *renameUserStatement = [NSString stringWithFormat: + @"RENAME USER %@@%@ TO %@@%@", + [[[user parent] valueForKey:@"originaluser"] tickQuotedString], + [[user valueForKey:@"originalhost"] tickQuotedString], + [[[user parent] valueForKey:@"user"] tickQuotedString], + [[user valueForKey:@"host"] tickQuotedString]]; + + [self.mySqlConnection queryString:renameUserStatement]; + } + if ([serverSupport supportsUserMaxVars]) [self updateResourcesForUser:user]; [self grantPrivilegesToUser:user]; @@ -1366,11 +1426,14 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; - (BOOL)_checkAndDisplayMySqlError { if ([self.mySqlConnection queryErrored]) { - - SPBeginAlertSheet(NSLocalizedString(@"An error occurred", @"mysql error occurred message"), - NSLocalizedString(@"OK", @"OK button"), nil, nil, [self window], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"An error occurred whilst trying to perform the operation.\n\nMySQL said: %@", @"mysql error occurred informative message"), [self.mySqlConnection getLastErrorMessage]]); - + if (isSaving) { + [errorsString appendFormat:@"%@\n", [self.mySqlConnection getLastErrorMessage]]; + } else { + SPBeginAlertSheet(NSLocalizedString(@"An error occurred", @"mysql error occurred message"), + NSLocalizedString(@"OK", @"OK button"), nil, nil, [self window], self, nil, nil, + [NSString stringWithFormat:NSLocalizedString(@"An error occurred whilst trying to perform the operation.\n\nMySQL said: %@", @"mysql error occurred informative message"), [self.mySqlConnection getLastErrorMessage]]); + } + return NO; } @@ -1470,7 +1533,7 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; */ - (CGFloat)splitView:(NSSplitView *)sender constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)offset { - return (proposedMax - 555); + return (proposedMax - 620); } /** diff --git a/Source/SPWindow.h b/Source/SPWindow.h new file mode 100644 index 00000000..369ce769 --- /dev/null +++ b/Source/SPWindow.h @@ -0,0 +1,32 @@ +// +// $Id$ +// +// SPWindow.h +// sequel-pro +// +// Created by Rowan Beentje on January 23, 2011 +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// More info at <http://code.google.com/p/sequel-pro/> + +#import <Cocoa/Cocoa.h> + + +@interface SPWindow : NSWindow { + +} + +@end diff --git a/Source/SPWindow.m b/Source/SPWindow.m new file mode 100644 index 00000000..58f38d0b --- /dev/null +++ b/Source/SPWindow.m @@ -0,0 +1,112 @@ +// +// $Id$ +// +// SPWindow.m +// sequel-pro +// +// Created by Rowan Beentje on January 23, 2011 +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// More info at <http://code.google.com/p/sequel-pro/> + +#import "SPWindow.h" + + +@implementation SPWindow + +#pragma mark - +#pragma mark Keyboard shortcut additions + +/** + * While keyboard shortcuts are an easy way to apply code app-wide, alternate menu + * items only collapse if the unmodified key matches; this method allows keyboard + * shortcuts without menu equivalents for a window, or the use of different base shortcuts. + */ +- (void) sendEvent:(NSEvent *)theEvent +{ + + if ([theEvent type] == NSKeyDown && [[theEvent characters] length]) { + + unichar theCharacter = [[theEvent charactersIgnoringModifiers] characterAtIndex:0]; + + // ⌃⎋ sends a right-click to order front the context menu under the first responder's visible Rect + if ([theEvent keyCode] == 53 && (([theEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask) == (NSAlternateKeyMask))) { + + id firstResponder = [[NSApp keyWindow] firstResponder]; + + if(firstResponder && [firstResponder respondsToSelector:@selector(menuForEvent:)]) { + + NSRect theRect = [firstResponder visibleRect]; + NSPoint loc = theRect.origin; + loc.y += theRect.size.height+5; + loc = [firstResponder convertPoint:loc toView:nil]; + NSEvent *anEvent = [NSEvent + mouseEventWithType:NSRightMouseDown + location:loc + modifierFlags:0 + timestamp:1 + windowNumber:[self windowNumber] + context:[NSGraphicsContext currentContext] + eventNumber:0 + clickCount:1 + pressure:0.0]; + + [NSMenu popUpContextMenu:[firstResponder menuForEvent:theEvent] withEvent:anEvent forView:firstResponder]; + + return; + + } + + } + + switch (theCharacter) { + + // Alternate keys for switching tabs - ⇧⌘[ and ⇧⌘]. These seem to be standards on some apps, + // including Apple applications under some circumstances + case '}': + if (([theEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask) == (NSCommandKeyMask | NSShiftKeyMask)) + { + return [[self windowController] selectNextDocumentTab:self]; + } + break; + case '{': + if (([theEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask) == (NSCommandKeyMask | NSShiftKeyMask)) + { + return [[self windowController] selectPreviousDocumentTab:self]; + } + break; + + // Also support ⌥⌘← and ⌥⌘→, used in other applications, for maximum compatibility + case NSRightArrowFunctionKey: + if (([theEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask) == (NSCommandKeyMask | NSAlternateKeyMask | NSNumericPadKeyMask | NSFunctionKeyMask)) + { + return [[self windowController] selectNextDocumentTab:self]; + } + break; + case NSLeftArrowFunctionKey: + if (([theEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask) == (NSCommandKeyMask | NSAlternateKeyMask | NSNumericPadKeyMask | NSFunctionKeyMask)) + { + return [[self windowController] selectPreviousDocumentTab:self]; + } + break; + } + } + + [super sendEvent:theEvent]; +} + + +@end diff --git a/Source/SequelProTunnelAssistant.m b/Source/SequelProTunnelAssistant.m index bbf21a3c..eb16ef5e 100644 --- a/Source/SequelProTunnelAssistant.m +++ b/Source/SequelProTunnelAssistant.m @@ -72,8 +72,8 @@ int main(int argc, const char *argv[]) // request the password if ([[environment objectForKey:@"SP_PASSWORD_METHOD"] integerValue] == SPSSHPasswordUsesKeychain) { SPKeychain *keychain; - NSString *keychainName = [environment objectForKey:@"SP_KEYCHAIN_ITEM_NAME"]; - NSString *keychainAccount = [environment objectForKey:@"SP_KEYCHAIN_ITEM_ACCOUNT"]; + NSString *keychainName = [[environment objectForKey:@"SP_KEYCHAIN_ITEM_NAME"] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + NSString *keychainAccount = [[environment objectForKey:@"SP_KEYCHAIN_ITEM_ACCOUNT"] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; if (!keychainName || !keychainAccount) { NSLog(@"SSH Tunnel: keychain authentication specified but insufficient internal details supplied"); @@ -82,17 +82,16 @@ int main(int argc, const char *argv[]) } keychain = [[SPKeychain alloc] init]; - if (![keychain passwordExistsForName:keychainName account:keychainAccount]) { - NSLog(@"SSH Tunnel: specified keychain password not found"); + if ([keychain passwordExistsForName:keychainName account:keychainAccount]) { + printf("%s\n", [[keychain getPasswordForName:keychainName account:keychainAccount] UTF8String]); [keychain release]; [pool release]; - return 1; + return 0; } - printf("%s\n", [[keychain getPasswordForName:keychainName account:keychainAccount] UTF8String]); - [keychain release]; - [pool release]; - return 0; + // If retrieving the password failed, log an error and fall back to requesting from the GUI + NSLog(@"SSH Tunnel: specified keychain password not found"); + argument = [NSString stringWithFormat:NSLocalizedString(@"The SSH password could not be loaded from the keychain; please enter the SSH password for %@:", @"Prompt for SSH password when keychain fetch failed"), connectionName]; } // If the password method is set to request the password from the tunnel instance, do so. @@ -113,15 +112,15 @@ int main(int argc, const char *argv[]) } password = [sequelProTunnel getPasswordWithVerificationHash:verificationHash]; - if (!password) { - NSLog(@"SSH Tunnel: unable to successfully request password from Sequel Pro for internal authentication"); + if (password) { + printf("%s\n", [password UTF8String]); [pool release]; - return 1; + return 0; } - - printf("%s\n", [password UTF8String]); - [pool release]; - return 0; + + // If retrieving the password failed, log an error and fall back to requesting from the GUI + NSLog(@"SSH Tunnel: unable to successfully request password from Sequel Pro for internal authentication"); + argument = [NSString stringWithFormat:NSLocalizedString(@"The SSH password could not be loaded; please enter the SSH password for %@:", @"Prompt for SSH password when direct fetch failed"), connectionName]; } } |