diff options
Diffstat (limited to 'Source')
-rw-r--r-- | Source/SPAppController.m | 416 | ||||
-rw-r--r-- | Source/SPBundleEditorController.h | 7 | ||||
-rw-r--r-- | Source/SPBundleEditorController.m | 283 | ||||
-rw-r--r-- | Source/SPConnectionController.h | 2 | ||||
-rw-r--r-- | Source/SPConnectionController.m | 7 | ||||
-rw-r--r-- | Source/SPConnectionControllerDelegate.m | 27 | ||||
-rw-r--r-- | Source/SPConnectionHandler.m | 17 | ||||
-rw-r--r-- | Source/SPConstants.h | 11 | ||||
-rw-r--r-- | Source/SPConstants.m | 10 | ||||
-rw-r--r-- | Source/SPCopyTable.m | 7 | ||||
-rw-r--r-- | Source/SPDatabaseDocument.m | 18 | ||||
-rw-r--r-- | Source/SPExtendedTableInfo.m | 11 | ||||
-rw-r--r-- | Source/SPIndexesController.m | 54 | ||||
-rw-r--r-- | Source/SPLogger.m | 5 | ||||
-rw-r--r-- | Source/SPStringAdditions.m | 6 | ||||
-rw-r--r-- | Source/SPTableContent.m | 29 | ||||
-rw-r--r-- | Source/SPTextView.m | 8 | ||||
-rw-r--r-- | Source/SPTextViewAdditions.m | 7 | ||||
-rw-r--r-- | Source/SPUserManager.m | 114 |
19 files changed, 729 insertions, 310 deletions
diff --git a/Source/SPAppController.m b/Source/SPAppController.m index 879ea116..d89f0cce 100644 --- a/Source/SPAppController.m +++ b/Source/SPAppController.m @@ -528,11 +528,11 @@ } else { // Check for installed UUIDs if(![cmdData objectForKey:SPBundleFileUUIDKey]) { - NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while installing bundle file", @"error while installing bundle file")] - defaultButton:NSLocalizedString(@"OK", @"OK button") + NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while installing Bundle", @"Open Files : Bundle : UUID : Error dialog title")] + defaultButton:NSLocalizedString(@"OK", @"Open Files : Bundle : UUID : OK button") alternateButton:nil otherButton:nil - informativeTextWithFormat:[NSString stringWithFormat:NSLocalizedString(@"The bundle ‘%@’ has no UUID which is necessary to identify installed bundles.", @"the bundle ‘%@’ has no UUID which is necessary to identify installed bundles."), [filename lastPathComponent]]]; + informativeTextWithFormat:[NSString stringWithFormat:NSLocalizedString(@"The Bundle ‘%@’ has no UUID which is necessary to identify installed Bundles.", @"Open Files : Bundle: UUID : UUID-Attribute is missing in bundle's command.plist file"), [filename lastPathComponent]]]; [alert setAlertStyle:NSCriticalAlertStyle]; [alert runModal]; @@ -540,21 +540,21 @@ return; } if([[installedBundleUUIDs allKeys] containsObject:[cmdData objectForKey:SPBundleFileUUIDKey]]) { - NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Installing bundle file", @"installing bundle file")] - defaultButton:NSLocalizedString(@"Update", @"Update button") - alternateButton:NSLocalizedString(@"Cancel", @"Cancel button") + 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") + alternateButton:NSLocalizedString(@"Cancel", @"Open Files : Bundle : Already-Installed : Cancel button") otherButton:nil - informativeTextWithFormat:[NSString stringWithFormat:NSLocalizedString(@"A bundle ‘%@’ is already installed. Do you want to update it?", @"a bundle ‘%@’ is already installed. do you want to update it?"), [[installedBundleUUIDs objectForKey:[cmdData objectForKey:SPBundleFileUUIDKey]] objectForKey:@"name"]]]; + informativeTextWithFormat:[NSString stringWithFormat:NSLocalizedString(@"A Bundle ‘%@’ is already installed. Do you want to update it?", @"Open Files : Bundle : Already-Installed : 'Update Bundle' question dialog message"), [[installedBundleUUIDs objectForKey:[cmdData objectForKey:SPBundleFileUUIDKey]] objectForKey:@"name"]]]; [alert setAlertStyle:NSCriticalAlertStyle]; 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'", infoPath]; + NSString *moveToTrashCommand = [NSString stringWithFormat:@"osascript -e 'tell application \"Finder\" to move (POSIX file \"%@\") to the trash'", newPath]; [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") + 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"]] + defaultButton:NSLocalizedString(@"OK", @"Open Files : Bundle : Already-Installed : Delete-Old-Error : OK button") alternateButton:nil otherButton:nil informativeTextWithFormat:[error localizedDescription]]; @@ -590,11 +590,11 @@ [self reloadBundles:self]; } else { - NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while installing bundle file", @"error while installing bundle file")] - defaultButton:NSLocalizedString(@"OK", @"OK button") + NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while installing Bundle", @"Open Files : Bundle : Install-Error : error dialog title")] + defaultButton:NSLocalizedString(@"OK", @"Open Files : Bundle : Install-Error : OK button") alternateButton:nil otherButton:nil - informativeTextWithFormat:[NSString stringWithFormat:NSLocalizedString(@"The bundle ‘%@’ already exists.", @"the bundle ‘%@’ already exists."), [filename lastPathComponent]]]; + informativeTextWithFormat:[NSString stringWithFormat:NSLocalizedString(@"The Bundle ‘%@’ already exists.", @"Open Files : Bundle : Install-Error : Destination path already exists error dialog message"), [filename lastPathComponent]]]; [alert setAlertStyle:NSCriticalAlertStyle]; [alert runModal]; @@ -635,6 +635,31 @@ else parameter = [NSArray array]; + + // 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]; + NSString *result = @""; + NSString *status = @"0"; + if([parameter count]) { + NSInteger idx = [SPChooseMenuItemDialog withItems:parameter atPosition:[NSEvent mouseLocation]]; + if(idx > -1) { + result = [parameter objectAtIndex:idx]; + } + } + if(![status writeToFile:statusFileName atomically:YES encoding:NSUTF8StringEncoding error:nil]) { + 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")); + } + [result writeToFile:resultFileName atomically:YES encoding:NSUTF8StringEncoding error:nil]; + return; + } + + NSString *activeProcessID = [[[[self frontDocumentWindow] delegate] selectedTableDocument] processID]; SPDatabaseDocument *processDocument = nil; @@ -769,6 +794,8 @@ [env setObject:[infoPath stringByDeletingLastPathComponent] forKey:SPBundleShellVariableBundlePath]; [env setObject:bundleInputFilePath forKey:SPBundleShellVariableInputFilePath]; [env setObject:SPBundleScopeGeneral forKey:SPBundleShellVariableBundleScope]; + [env setObject:SPURLSchemeQueryResultPathHeader forKey:SPBundleShellVariableQueryResultFile]; + [env setObject:SPURLSchemeQueryResultStatusPathHeader forKey:SPBundleShellVariableQueryResultStatusFile]; NSString *input = @""; NSError *inputFileError = nil; @@ -798,7 +825,9 @@ [[NSFileManager defaultManager] removeItemAtPath:bundleInputFilePath error:nil]; - NSString *action = [[cmdData objectForKey:SPBundleFileOutputActionKey] lowercaseString]; + NSString *action = SPBundleOutputActionNone; + if([cmdData objectForKey:SPBundleFileOutputActionKey] && [[cmdData objectForKey:SPBundleFileOutputActionKey] length]) + action = [[cmdData objectForKey:SPBundleFileOutputActionKey] lowercaseString]; // Redirect due exit code if(err != nil) { @@ -837,8 +866,7 @@ } if(err == nil && output) { - if([cmdData objectForKey:SPBundleFileOutputActionKey] && [[cmdData objectForKey:SPBundleFileOutputActionKey] length] - && ![[cmdData objectForKey:SPBundleFileOutputActionKey] isEqualToString:SPBundleOutputActionNone]) { + if(![action isEqualToString:SPBundleOutputActionNone]) { NSPoint pos = [NSEvent mouseLocation]; pos.y -= 16; @@ -1318,118 +1346,302 @@ // Clean menu [menu compatibleRemoveAllItems]; - NSString *bundlePath = [[NSFileManager defaultManager] applicationSupportDirectoryForSubDirectory:SPBundleSupportFolder createIfNotExists:NO error:nil]; + NSArray *bundlePaths = [NSArray arrayWithObjects: + ([[NSFileManager defaultManager] applicationSupportDirectoryForSubDirectory:SPBundleSupportFolder createIfNotExists:NO error:nil])?:@"", + [NSString stringWithFormat:@"%@/Contents/SharedSupport/Default Bundles", [[NSBundle mainBundle] bundlePath]], + nil]; - if(bundlePath) { - NSError *error = nil; - NSArray *foundBundles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:bundlePath error:&error]; - if (foundBundles && [foundBundles count] && error == nil) { - for(NSString* bundle in foundBundles) { - if(![[[bundle pathExtension] lowercaseString] isEqualToString:[SPUserBundleFileExtension lowercaseString]]) continue; + BOOL processDefaultBundles = NO; + NSFileManager *fm = [NSFileManager defaultManager]; + + NSArray *deletedDefaultBundles; + NSMutableArray *updatedDefaultBundles = [NSMutableArray array]; + if([[NSUserDefaults standardUserDefaults] objectForKey:SPBundleDeletedDefaultBundlesKey]) { + deletedDefaultBundles = [[[NSUserDefaults standardUserDefaults] objectForKey:SPBundleDeletedDefaultBundlesKey] retain]; + } else { + deletedDefaultBundles = [[NSArray array] retain]; + } + if([[NSUserDefaults standardUserDefaults] objectForKey:SPBundleUpdatedDefaultBundlesKey]) { + [updatedDefaultBundles setArray:[[NSUserDefaults standardUserDefaults] objectForKey:SPBundleUpdatedDefaultBundlesKey]]; + } - foundInstalledBundles = YES; + NSMutableString *infoAboutUpdatedDefaultBundles = [NSMutableString string]; - NSError *readError = nil; - NSString *convError = nil; - NSPropertyListFormat format; - NSDictionary *cmdData = nil; - NSString *infoPath = [NSString stringWithFormat:@"%@/%@/%@", bundlePath, bundle, SPBundleFileName]; - NSData *pData = [NSData dataWithContentsOfFile:infoPath options:NSUncachedRead error:&readError]; + for(NSString* bundlePath in bundlePaths) { + if([bundlePath length]) { - cmdData = [[NSPropertyListSerialization propertyListFromData:pData - mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&convError] retain]; + NSError *error = nil; + NSArray *foundBundles = [fm contentsOfDirectoryAtPath:bundlePath error:&error]; + if (foundBundles && [foundBundles count] && error == nil) { - if(!cmdData || readError != nil || [convError length] || !(format == NSPropertyListXMLFormat_v1_0 || format == NSPropertyListBinaryFormat_v1_0)) { - NSLog(@"“%@/%@” file couldn't be read.", bundle, SPBundleFileName); - NSBeep(); - if (cmdData) [cmdData release]; - } else { - if((![cmdData objectForKey:SPBundleFileDisabledKey] || ![[cmdData objectForKey:SPBundleFileDisabledKey] intValue]) && [cmdData objectForKey:SPBundleFileNameKey] && [[cmdData objectForKey:SPBundleFileNameKey] length] && [cmdData objectForKey:SPBundleFileScopeKey]) - { + for(NSString* bundle in foundBundles) { + if(![[[bundle pathExtension] lowercaseString] isEqualToString:[SPUserBundleFileExtension lowercaseString]]) continue; + + foundInstalledBundles = YES; + + NSError *readError = nil; + NSString *convError = nil; + NSPropertyListFormat format; + NSDictionary *cmdData = nil; + 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]; + + if(!cmdData || readError != nil || [convError length] || !(format == NSPropertyListXMLFormat_v1_0 || format == NSPropertyListBinaryFormat_v1_0)) { + + NSLog(@"“%@” file couldn't be read.", infoPath); + NSBeep(); + + } 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(bundleWasDeleted) continue; + + // 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; + } + + } + + // Remove item from update list which will be updated in the Prefs + [updatedDefaultBundles removeObject:[cmdData objectForKey:SPBundleFileUUIDKey]]; + + } + + if (cmdData) [cmdData release]; + + } else { + continue; + } + } + + BOOL isDir; + NSString *newInfoPath = [NSString stringWithFormat:@"%@/%@/%@", [bundlePaths objectAtIndex:0], bundle, SPBundleFileName]; + NSString *orgPath = [NSString stringWithFormat:@"%@/%@", [bundlePaths objectAtIndex:1], bundle]; + NSString *newPath = [NSString stringWithFormat:@"%@/%@", [bundlePaths objectAtIndex:0], bundle]; + if([fm fileExistsAtPath:newPath isDirectory:&isDir] && isDir) + newPath = [NSString stringWithFormat:@"%@_%ld", newPath, (NSUInteger)(random() % 35000)]; + NSError *error = nil; + [fm copyItemAtPath:orgPath toPath:newPath error:&error]; + if(error != nil) { + NSBeep(); + NSLog(@"Default Bundle “%@” couldn't be copied to '%@'", bundle, newInfoPath); + continue; + } + infoPath = [NSString stringWithString:newInfoPath]; - NSArray *scopes = [[cmdData objectForKey:SPBundleFileScopeKey] componentsSeparatedByString:@" "]; - for(NSString *scope in scopes) { - 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]; + } + + [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; } - if([cmdData objectForKey:SPBundleFileCategoryKey] && [[cmdData objectForKey:SPBundleFileCategoryKey] length] && ![[bundleCategories objectForKey:scope] containsObject:[cmdData objectForKey:SPBundleFileCategoryKey]]) - [[bundleCategories objectForKey:scope] addObject:[cmdData objectForKey:SPBundleFileCategoryKey]]; - } + NSArray *scopes = [[cmdData objectForKey:SPBundleFileScopeKey] componentsSeparatedByString:@" "]; + for(NSString *scope in scopes) { - NSMutableDictionary *aDict = [NSMutableDictionary dictionary]; - [aDict setObject:[cmdData objectForKey:SPBundleFileNameKey] forKey:SPBundleInternLabelKey]; - [aDict setObject:infoPath forKey:SPBundleInternPathToFileKey]; + 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]; + } - // 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] && ![[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: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]]; + 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]]; + } + + [aDict setObject:[NSArray arrayWithObjects:theChar, [NSNumber numberWithInteger:mask], nil] forKey:SPBundleInternKeyEquivalentKey]; } - [aDict setObject:[NSArray arrayWithObjects:theChar, [NSNumber numberWithInteger:mask], nil] forKey:SPBundleInternKeyEquivalentKey]; - } + if([cmdData objectForKey:SPBundleFileTooltipKey] && [[cmdData objectForKey:SPBundleFileTooltipKey] length]) + [aDict setObject:[cmdData objectForKey:SPBundleFileTooltipKey] forKey:SPBundleFileTooltipKey]; - if([cmdData objectForKey:SPBundleFileUUIDKey] && [[cmdData objectForKey:SPBundleFileUUIDKey] length]) - [installedBundleUUIDs setObject:[NSDictionary dictionaryWithObjectsAndKeys: - [NSString stringWithFormat:@"%@ (%@)", bundle, [cmdData objectForKey:SPBundleFileNameKey]], @"name", - infoPath, @"path", nil] forKey:[cmdData objectForKey:SPBundleFileUUIDKey]]; + if([cmdData objectForKey:SPBundleFileCategoryKey] && [[cmdData objectForKey:SPBundleFileCategoryKey] length]) + [aDict setObject:[cmdData objectForKey:SPBundleFileCategoryKey] forKey:SPBundleFileCategoryKey]; - if([cmdData objectForKey:SPBundleFileTooltipKey] && [[cmdData objectForKey:SPBundleFileTooltipKey] length]) - [aDict setObject:[cmdData objectForKey:SPBundleFileTooltipKey] forKey:SPBundleFileTooltipKey]; + if([cmdData objectForKey:SPBundleFileKeyEquivalentKey] && [[cmdData objectForKey:SPBundleFileKeyEquivalentKey] length]) + [aDict setObject:[cmdData objectForKey:SPBundleFileKeyEquivalentKey] forKey:@"key"]; - if([cmdData objectForKey:SPBundleFileCategoryKey] && [[cmdData objectForKey:SPBundleFileCategoryKey] length]) - [aDict setObject:[cmdData objectForKey:SPBundleFileCategoryKey] forKey:SPBundleFileCategoryKey]; + for(NSString* scope in scopes) + [[bundleItems objectForKey:scope] addObject:aDict]; - if([cmdData objectForKey:SPBundleFileKeyEquivalentKey] && [[cmdData objectForKey:SPBundleFileKeyEquivalentKey] length]) - [aDict setObject:[cmdData objectForKey:SPBundleFileKeyEquivalentKey] forKey:@"key"]; + } + + if (cmdData) [cmdData release]; - for(NSString* scope in scopes) - [[bundleItems objectForKey:scope] addObject:aDict]; } - if (cmdData) [cmdData release]; } - } - NSSortDescriptor *sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:SPBundleInternLabelKey ascending:YES] autorelease]; - for(NSString* scope in [bundleItems allKeys]) { - [[bundleItems objectForKey:scope] sortUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]]; - [[bundleCategories objectForKey:scope] sortUsingSelector:@selector(compare:)]; + NSSortDescriptor *sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:SPBundleInternLabelKey ascending:YES] autorelease]; + for(NSString* scope in [bundleItems allKeys]) { + [[bundleItems objectForKey:scope] sortUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]]; + [[bundleCategories objectForKey:scope] sortUsingSelector:@selector(compare:)]; + } } } + processDefaultBundles = YES; + } + + [deletedDefaultBundles release]; + + // Synchronize updated Bundles + [[NSUserDefaults standardUserDefaults] setObject:updatedDefaultBundles forKey:SPBundleUpdatedDefaultBundlesKey]; + + // Inform user about default Bundle updates which were modified by the user and re-run Reload Bundles + if([infoAboutUpdatedDefaultBundles length]) { + NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Default Bundles Update", @"default bundles update") + defaultButton:NSLocalizedString(@"OK", @"OK button") + alternateButton:nil + otherButton:nil + informativeTextWithFormat:[NSString stringWithFormat:NSLocalizedString(@"The following default Bundles were updated:\n%@\nYour modifications were stored as “(user)”.", @"the following default bundles were updated:\n%@\nyour modifications were stored as “(user)”."), infoAboutUpdatedDefaultBundles]]; + + [alert runModal]; + [self reloadBundles:nil]; + return; } - // Rebuild Bundles main menu item + // === Rebuild Bundles main menu item === // Add default menu items NSMenuItem *anItem; diff --git a/Source/SPBundleEditorController.h b/Source/SPBundleEditorController.h index d586e5bc..ac4c9e6b 100644 --- a/Source/SPBundleEditorController.h +++ b/Source/SPBundleEditorController.h @@ -65,6 +65,9 @@ IBOutlet NSScrollView *commandScrollView; IBOutlet NSScrollView *descriptionScrollView; + IBOutlet id undeleteSheet; + IBOutlet NSTableView *undeleteTableView; + IBOutlet NSTreeController *commandBundleTreeController; NSMutableArray *touchedBundleArray; NSMutableDictionary *commandBundleTree; @@ -108,6 +111,8 @@ NSArray *shellVariableSuggestions; + NSMutableArray *deletedDefaultBundles; + } - (IBAction)inputPopupButtonChanged:(id)sender; @@ -126,6 +131,8 @@ - (IBAction)reloadBundles:(id)sender; - (IBAction)metaButtonChanged:(id)sender; - (IBAction)performClose:(id)sender; +- (IBAction)undeleteDefaultBundles:(id)sender; +- (IBAction)closeUndeleteDefaultBundlesSheet:(id)sender; - (BOOL)saveBundle:(NSDictionary*)bundle atPath:(NSString*)aPath; - (BOOL)cancelRowEditing; diff --git a/Source/SPBundleEditorController.m b/Source/SPBundleEditorController.m index 32f5b581..de779e67 100644 --- a/Source/SPBundleEditorController.m +++ b/Source/SPBundleEditorController.m @@ -33,6 +33,11 @@ #define kGeneralScopeArrayIndex 2 #define kDisabledScopeTag 10 +#define SP_BUNDLEEDITOR_SCOPE_INPUTFIELD_STRING NSLocalizedString(@"Input Field", @"Bundle Editor : Scope dropdown : 'input field' item") +#define SP_BUNDLEEDITOR_SCOPE_DATATABLE_STRING NSLocalizedString(@"Data Table", @"Bundle Editor : Scope dropdown : 'data table' item") +#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") + @interface SPBundleEditorController (PrivateAPI) - (void)_updateBundleDataView; @@ -62,6 +67,7 @@ draggedFilePath = nil; oldBundleName = nil; isTableCellEditing = NO; + deletedDefaultBundles = [[NSMutableArray alloc] initWithCapacity:1]; } return self; @@ -96,6 +102,7 @@ [withBlobDataTableArray release]; [shellVariableSuggestions release]; + [deletedDefaultBundles release]; if(touchedBundleArray) [touchedBundleArray release], touchedBundleArray = nil; if(commandBundleTree) [commandBundleTree release], commandBundleTree = nil; @@ -120,10 +127,10 @@ sortDescriptor = [[NSSortDescriptor alloc] initWithKey:kBundleNameKey ascending:YES selector:@selector(localizedCompare:)]; [commandBundleTree setObject:[NSMutableArray array] forKey:kChildrenKey]; - [commandBundleTree setObject:@"BUNDLES" forKey:kBundleNameKey]; - [[commandBundleTree objectForKey:kChildrenKey] addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:[NSMutableArray array], kChildrenKey, NSLocalizedString(@"Input Field", @"input field scope menu label"), kBundleNameKey, nil]]; - [[commandBundleTree objectForKey:kChildrenKey] addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:[NSMutableArray array], kChildrenKey, NSLocalizedString(@"Data Table", @"data table scope menu label"), kBundleNameKey, nil]]; - [[commandBundleTree objectForKey:kChildrenKey] addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:[NSMutableArray array], kChildrenKey, NSLocalizedString(@"General", @"general scope menu label"), kBundleNameKey, nil]]; + [commandBundleTree setObject:NSLocalizedString(@"BUNDLES",@"Bundle Editor : Outline View : 'BUNDLES' item") forKey:kBundleNameKey]; + [[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]]; // Init all needed menus inputGeneralScopePopUpMenu = [[NSMenu alloc] initWithTitle:@""]; @@ -166,60 +173,60 @@ NSDictionary *menuItemTitles = [NSDictionary dictionaryWithObjects: [NSArray arrayWithObjects: - NSLocalizedString(@"None", @"none menu item label"), - - NSLocalizedString(@"None", @"none menu item label"), - NSLocalizedString(@"Selected Text", @"selected text menu item label"), - NSLocalizedString(@"Entire Content", @"entire content menu item label"), - - NSLocalizedString(@"None", @"none menu item label"), - NSLocalizedString(@"Selected Rows (TSV)", @"selected rows (TSV) menu item label"), - NSLocalizedString(@"Selected Rows (CSV)", @"selected rows (CSV) menu item label"), - NSLocalizedString(@"Selected Rows (SQL)", @"selected rows (SQL) menu item label"), - NSLocalizedString(@"Table Content (TSV)", @"table content (TSV) menu item label"), - NSLocalizedString(@"Table Content (CSV)", @"table content (CSV) menu item label"), - NSLocalizedString(@"Table Content (SQL)", @"table content (SQL) menu item label"), - - NSLocalizedString(@"None", @"none menu item label"), - NSLocalizedString(@"Insert as Text", @"insert as text item label"), - NSLocalizedString(@"Insert as Snippet", @"insert as snippet item label"), - NSLocalizedString(@"Replace Selection", @"replace selection item label"), - NSLocalizedString(@"Replace Entire Content", @"replace entire content item label"), - NSLocalizedString(@"Show as Text Tooltip", @"show as text tooltip item label"), - NSLocalizedString(@"Show as HTML Tooltip", @"show as html tooltip item label"), - NSLocalizedString(@"Show as HTML", @"show as html item label"), - - NSLocalizedString(@"None", @"none menu item label"), - NSLocalizedString(@"Show as Text Tooltip", @"show as text tooltip item label"), - NSLocalizedString(@"Show as HTML Tooltip", @"show as html tooltip item label"), - NSLocalizedString(@"Show as HTML", @"show as html item label"), - - NSLocalizedString(@"None", @"none menu item label"), - NSLocalizedString(@"Show as Text Tooltip", @"show as text tooltip item label"), - NSLocalizedString(@"Show as HTML Tooltip", @"show as html tooltip item label"), - NSLocalizedString(@"Show as HTML", @"show as html item label"), - - NSLocalizedString(@"None", @"none menu item label"), - NSLocalizedString(@"Current Word", @"current word item label"), - NSLocalizedString(@"Current Line", @"current line item label"), - NSLocalizedString(@"Current Query", @"current query item label"), - NSLocalizedString(@"Entire Content", @"entire content item label"), - - NSLocalizedString(@"None", @"none menu item label"), - - NSLocalizedString(@"None", @"none menu item label"), - NSLocalizedString(@"Database changed", @"database changed item label"), - NSLocalizedString(@"Table changed", @"table changed item label"), - NSLocalizedString(@"Table Row changed", @"table row changed item label"), - - NSLocalizedString(@"None", @"none menu item label"), - NSLocalizedString(@"Database changed", @"database changed item label"), - NSLocalizedString(@"Table changed", @"table changed item label"), - - NSLocalizedString(@"exclude BLOB", @"exclude BLOB item label"), - NSLocalizedString(@"include BLOB", @"include BLOB item label"), - NSLocalizedString(@"save BLOB as image file", @"save BLOB as image file item label"), - NSLocalizedString(@"save BLOB as dat file", @"save BLOB as dat file item label"), + NSLocalizedString(@"None", @"Bundle Editor : Scope=General : Input source dropdown: 'None' item"), + + NSLocalizedString(@"None", @"Bundle Editor : Scope=Field : Input source dropdown: 'None' item"), + NSLocalizedString(@"Selected Text", @"Bundle Editor : Scope=Field : Input source dropdown: 'selected text' item"), + NSLocalizedString(@"Entire Content", @"Bundle Editor : Scope=Field : Input source dropdown: 'entire content' item"), + + NSLocalizedString(@"None", @"Bundle Editor : Scope=Data-Table : Input source dropdown: 'none' item"), + NSLocalizedString(@"Selected Rows (TSV)", @"Bundle Editor : Scope=Data-Table : Input source dropdown: 'selected rows as tab-separated' item"), + NSLocalizedString(@"Selected Rows (CSV)", @"Bundle Editor : Scope=Data-Table : Input source dropdown: 'selected rows as comma-separated' item"), + NSLocalizedString(@"Selected Rows (SQL)", @"Bundle Editor : Scope=Data-Table : Input source dropdown: 'selected rows as SQL' item"), + NSLocalizedString(@"Table Content (TSV)", @"Bundle Editor : Scope=Data-Table : Input source dropdown: 'table content as tab-separated' item"), + NSLocalizedString(@"Table Content (CSV)", @"Bundle Editor : Scope=Data-Table : Input source dropdown: 'table content as comma-separated' item"), + NSLocalizedString(@"Table Content (SQL)", @"Bundle Editor : Scope=Data-Table : Input source dropdown: 'table content as SQL' item"), + + NSLocalizedString(@"None", @"Bundle Editor : Scope=Field : Output dropdown : 'none' item"), + NSLocalizedString(@"Insert as Text", @"Bundle Editor : Scope=Field : Output dropdown : 'insert as text' item"), + NSLocalizedString(@"Insert as Snippet", @"Bundle Editor : Scope=Field : Output dropdown : 'insert as snippet' item"), + NSLocalizedString(@"Replace Selection", @"Bundle Editor : Scope=Field : Output dropdown : 'replace selection' item"), + NSLocalizedString(@"Replace Entire Content", @"Bundle Editor : Scope=Field : Output dropdown : 'replace entire content' item"), + NSLocalizedString(@"Show as Text Tooltip", @"Bundle Editor : Scope=Field : Output dropdown : 'show as text tooltip' item"), + NSLocalizedString(@"Show as HTML Tooltip", @"Bundle Editor : Scope=Field : Output dropdown : 'show as html tooltip' item"), + NSLocalizedString(@"Show as HTML", @"Bundle Editor : Scope=Field : Output dropdown : 'show as html' item"), + + NSLocalizedString(@"None", @"Bundle Editor : Scope=General : Output dropdown : 'none' item"), + NSLocalizedString(@"Show as Text Tooltip", @"Bundle Editor : Scope=General : Output dropdown : 'show as text tooltip' item"), + NSLocalizedString(@"Show as HTML Tooltip", @"Bundle Editor : Scope=General : Output dropdown : 'show as html tooltip' item"), + NSLocalizedString(@"Show as HTML", @"Bundle Editor : Scope=General : Output dropdown : 'show as html' item"), + + NSLocalizedString(@"None", @"Bundle Editor : Scope=Data-Table : Output dropdown : 'none' item"), + NSLocalizedString(@"Show as Text Tooltip", @"Bundle Editor : Scope=Data-Table : Output dropdown : 'show as text tooltip' item"), + NSLocalizedString(@"Show as HTML Tooltip", @"Bundle Editor : Scope=Data-Table : Output dropdown : 'show as html tooltip' item"), + NSLocalizedString(@"Show as HTML", @"Bundle Editor : Scope=Data-Table : Output dropdown : 'show as html' item"), + + NSLocalizedString(@"None", @"Bundle Editor : Fallback Input source dropdown : 'none' item"), + NSLocalizedString(@"Current Word", @"Bundle Editor : Fallback Input source dropdown : 'current word' item"), + NSLocalizedString(@"Current Line", @"Bundle Editor : Fallback Input source dropdown : 'current line' item"), + NSLocalizedString(@"Current Query", @"Bundle Editor : Fallback Input source dropdown : 'current query' item"), + NSLocalizedString(@"Entire Content", @"Bundle Editor : Fallback Input source dropdown : 'entire content' item"), + + NSLocalizedString(@"None", @"Bundle Editor : Scope=Field : Trigger dropdown : 'none' item"), + + NSLocalizedString(@"None", @"Bundle Editor : Scope=Data-Table : Trigger dropdown : 'none' item"), + NSLocalizedString(@"Database changed", @"Bundle Editor : Scope=Data-Table : Trigger dropdown : 'database changed' item"), + NSLocalizedString(@"Table changed", @"Bundle Editor : Scope=Data-Table : Trigger dropdown : 'table changed' item"), + NSLocalizedString(@"Table Row changed", @"Bundle Editor : Scope=Data-Table : Trigger dropdown : 'table row changed' item"), + + NSLocalizedString(@"None", @"Bundle Editor : Scope=General : Trigger dropdown : 'none' item"), + NSLocalizedString(@"Database changed", @"Bundle Editor : Scope=General : Trigger dropdown : 'database changed' item"), + NSLocalizedString(@"Table changed", @"Bundle Editor : Scope=General : Trigger dropdown : 'table changed' item"), + + NSLocalizedString(@"exclude BLOB", @"Bundle Editor : BLOB dropdown : 'exclude BLOB' item"), + NSLocalizedString(@"include BLOB", @"Bundle Editor : BLOB dropdown : 'include BLOB' item"), + NSLocalizedString(@"save BLOB as image file", @"Bundle Editor : BLOB dropdown : 'save BLOB as image file' item"), + NSLocalizedString(@"save BLOB as dat file", @"Bundle Editor : BLOB dropdown : 'save BLOB as dat file' item"), nil] forKeys:allPopupScopeItems]; @@ -285,15 +292,15 @@ [anItem release]; [inputGeneralScopePopUpMenu compatibleRemoveAllItems]; - anItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"General", @"general scope menu label") action:@selector(scopeButtonChanged:) keyEquivalent:@""]; + anItem = [[NSMenuItem alloc] initWithTitle:SP_BUNDLEEDITOR_SCOPE_GENERAL_STRING action:@selector(scopeButtonChanged:) keyEquivalent:@""]; [anItem setTag:kGeneralScopeArrayIndex]; [inputGeneralScopePopUpMenu addItem:anItem]; [anItem release]; - anItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Input Field", @"input field scope menu label") action:@selector(scopeButtonChanged:) keyEquivalent:@""]; + anItem = [[NSMenuItem alloc] initWithTitle:SP_BUNDLEEDITOR_SCOPE_INPUTFIELD_STRING action:@selector(scopeButtonChanged:) keyEquivalent:@""]; [anItem setTag:kInputFieldScopeArrayIndex]; [inputGeneralScopePopUpMenu addItem:anItem]; [anItem release]; - anItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Data Table", @"data table scope menu label") action:@selector(scopeButtonChanged:) keyEquivalent:@""]; + anItem = [[NSMenuItem alloc] initWithTitle:SP_BUNDLEEDITOR_SCOPE_DATATABLE_STRING action:@selector(scopeButtonChanged:) keyEquivalent:@""]; [anItem setTag:kDataTableScopeArrayIndex]; [inputGeneralScopePopUpMenu addItem:anItem]; [anItem release]; @@ -316,9 +323,9 @@ SPBundleShellVariableExitNone, SPBundleShellVariableExitReplaceContent, SPBundleShellVariableExitReplaceSelection, - SPBundleShellVariableExitInsertAsHTML, - SPBundleShellVariableExitInsertAsHTMLTooltip, - SPBundleShellVariableExitInsertAsTextTooltip, + SPBundleShellVariableExitShowAsHTML, + SPBundleShellVariableExitShowAsHTMLTooltip, + SPBundleShellVariableExitShowAsTextTooltip, SPBundleShellVariableInputFilePath, SPBundleShellVariableInputTableMetaData, SPBundleShellVariableBundlePath, @@ -350,6 +357,10 @@ nil ] retain]; + if([[NSUserDefaults standardUserDefaults] objectForKey:SPBundleDeletedDefaultBundlesKey]) { + [deletedDefaultBundles setArray:[[NSUserDefaults standardUserDefaults] objectForKey:SPBundleDeletedDefaultBundlesKey]]; + } + [self _initTree]; } @@ -601,11 +612,11 @@ [commandsOutlineView reloadData]; - NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Error", @"error") - defaultButton:NSLocalizedString(@"OK", @"OK button") + NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Error", @"Bundle Editor : Copy-Command-Error : error dialog title") + defaultButton:NSLocalizedString(@"OK", @"Bundle Editor : Copy-Command-Error : OK button") alternateButton:nil otherButton:nil - informativeTextWithFormat:NSLocalizedString(@"Error while duplicating Bundle content.", @"error while duplicating Bundle content")]; + informativeTextWithFormat:NSLocalizedString(@"Error while duplicating Bundle content.", @"Bundle Editor : Copy-Command-Error : Copying failed error message")]; [alert setAlertStyle:NSCriticalAlertStyle]; [alert runModal]; @@ -615,6 +626,8 @@ } [bundle setObject:newFileName forKey:kBundleNameKey]; + [self saveBundle:bundle atPath:nil]; + // Insert duplicate below selected one NSUInteger *currentPath[[currentIndexPath length]]; [currentIndexPath getIndexes:¤tPath]; @@ -709,11 +722,11 @@ [commandsOutlineView abortEditing]; - NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Remove selected Bundle?", @"remove selected bundle message") - defaultButton:NSLocalizedString(@"Remove", @"remove button") - alternateButton:NSLocalizedString(@"Cancel", @"cancel button") + NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Remove selected Bundle?", @"Bundle Editor : Remove-Bundle: remove dialog title") + defaultButton:NSLocalizedString(@"Remove", @"Bundle Editor : Remove-Bundle: remove button") + alternateButton:NSLocalizedString(@"Cancel", @"Bundle Editor : Remove-Bundle: cancel button") otherButton:nil - informativeTextWithFormat:NSLocalizedString(@"Are you sure you want to move the selected Bundle to the Trash and remove them respectively?", @"move to trash and remove resp the selected bundle informative message")]; + informativeTextWithFormat:NSLocalizedString(@"Are you sure you want to move the selected Bundle to the Trash and remove them respectively?", @"Bundle Editor : Remove-Bundle: remove dialog message")]; [alert setAlertStyle:NSCriticalAlertStyle]; @@ -763,7 +776,7 @@ */ - (IBAction)showHelp:(id)sender { - [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:NSLocalizedString(@"http://www.sequelpro.com/docs/Bundle_Editor", @"Localized help page for bundle editor - do not localize if no translated webpage is available")]]; + [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:SPLOCALIZEDURL_BUNDLEEDITORHELP]]; } /** @@ -798,6 +811,27 @@ [self close]; } +- (IBAction)undeleteDefaultBundles:(id)sender +{ + [NSApp beginSheet:undeleteSheet + modalForWindow:[self window] + modalDelegate:self + didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) + contextInfo:@"undeleteSelectedDefaultBundles"]; +} + +- (IBAction)closeUndeleteDefaultBundlesSheet:(id)sender +{ + + [NSApp endSheet:[sender window] returnCode:[sender tag]]; + + if ([sender respondsToSelector:@selector(orderOut:)]) + [sender orderOut:nil]; + else if ([sender respondsToSelector:@selector(window)]) + [[sender window] orderOut:nil]; + +} + /** * Save all touched bundles to disk and close the Bundle Editor window */ @@ -843,8 +877,8 @@ for(id item in allBundles) { if(![self saveBundle:item atPath:nil]) { closeMe = NO; - NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while saving “%@”.", @"error while saving “%@”"), [item objectForKey:kBundleNameKey]] - defaultButton:NSLocalizedString(@"OK", @"OK button") + NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while saving “%@”.", @"Bundle Editor : Save-and-Close-Error : error dialog title"), [item objectForKey:kBundleNameKey]] + defaultButton:NSLocalizedString(@"OK", @"Bundle Editor : Save-and-Close-Error : OK button") alternateButton:nil otherButton:nil informativeTextWithFormat:@""]; @@ -871,6 +905,7 @@ NSFileManager *fm = [NSFileManager defaultManager]; BOOL isDir = NO; + BOOL isNewBundle = NO; // If passed aPath is nil construct the path from bundle's bundleName. // aPath is mainly used for dragging a bundle from table view. @@ -888,6 +923,7 @@ if(![fm createDirectoryAtPath:aPath withIntermediateDirectories:YES attributes:nil error:nil]) return NO; isDir = YES; + isNewBundle = YES; } // If aPath exists but it's not a folder bail out @@ -908,6 +944,30 @@ kBundleNameKey, nil]]; + + if(!isNewBundle) { + NSError *readError = nil; + NSString *convError = nil; + NSPropertyListFormat format; + NSDictionary *cmdData = nil; + NSData *pData = [NSData dataWithContentsOfFile:cmdFilePath 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.", cmdFilePath); + NSBeep(); + if (cmdData) [cmdData release]; + return NO; + } else { + // Check for changes and return if no changes are found + if([[saveDict description] isEqualToString:[cmdData description]]) + return YES; + if([cmdData objectForKey:SPBundleFileIsDefaultBundleKey]) + [saveDict setObject:[NSNumber numberWithBool:YES] forKey:SPBundleFileDefaultBundleWasModifiedKey]; + } + if (cmdData) [cmdData release]; + } + // Remove a given old command.plist file [fm removeItemAtPath:cmdFilePath error:nil]; [saveDict writeToFile:cmdFilePath atomically:YES]; @@ -949,8 +1009,8 @@ NSString *moveToTrashCommand = [NSString stringWithFormat:@"osascript -e 'tell application \"Finder\" to move (POSIX file \"%@\") to the trash'", thePath]; [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"), thePath] - defaultButton:NSLocalizedString(@"OK", @"OK button") + NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while moving “%@” to Trash.", @"Bundle Editor : Trash-Bundle(s)-Error : error dialog title"), thePath] + defaultButton:NSLocalizedString(@"OK", @"Bundle Editor : Trash-Bundle(s)-Error : OK button") alternateButton:nil otherButton:nil informativeTextWithFormat:[error localizedDescription]]; @@ -960,6 +1020,10 @@ deletionSuccessfully = NO; break; } + if([obj objectForKey:SPBundleFileIsDefaultBundleKey]) { + [deletedDefaultBundles addObject:[NSArray arrayWithObjects:[obj objectForKey:SPBundleFileUUIDKey], [obj objectForKey:SPBundleFileNameKey], nil]]; + [[NSUserDefaults standardUserDefaults] setObject:deletedDefaultBundles forKey:SPBundleDeletedDefaultBundlesKey]; + } [commandsOutlineView reloadData]; } } @@ -978,7 +1042,8 @@ [addButton setEnabled:([[commandBundleTreeController selectionIndexPath] length] > 1)]; } - } else if([contextInfo isEqualToString:@"saveBundle"]) { + } + else if([contextInfo isEqualToString:@"saveBundle"]) { if (returnCode == NSOKButton) { id aBundle = [self _currentSelectedObject]; @@ -998,8 +1063,8 @@ } if(!copyingWasSuccessful || ![self saveBundle:aBundle atPath:savePath]) { - NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Error while saving the Bundle.", @"error while saving a Bundle") - defaultButton:NSLocalizedString(@"OK", @"OK button") + NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Error while saving the Bundle.", @"Bundle Editor : Save-Bundle-Error : error dialog title") + defaultButton:NSLocalizedString(@"OK", @"Bundle Editor : Save-Bundle-Error : OK button") alternateButton:nil otherButton:nil informativeTextWithFormat:@""]; @@ -1009,6 +1074,28 @@ } } } + else if([contextInfo isEqualToString:@"undeleteSelectedDefaultBundles"]) { + if(returnCode == 1) { + + NSIndexSet *selectedRows = [undeleteTableView selectedRowIndexes]; + + if(![selectedRows count]) return; + + NSInteger rowIndex; + NSMutableArray *stillUndeletedBundles = [NSMutableArray array]; + for(rowIndex = 0; rowIndex < [deletedDefaultBundles count]; rowIndex++) { + if(![selectedRows containsIndex:rowIndex]) + [stillUndeletedBundles addObject:[deletedDefaultBundles objectAtIndex:rowIndex]]; + } + [deletedDefaultBundles setArray:stillUndeletedBundles]; + [undeleteTableView reloadData]; + [[NSUserDefaults standardUserDefaults] setObject:stillUndeletedBundles forKey:SPBundleDeletedDefaultBundlesKey]; + [[NSUserDefaults standardUserDefaults] synchronize]; + [[NSApp delegate] reloadBundles:nil]; + [self reloadBundles:self]; + + } + } } @@ -1096,6 +1183,24 @@ } #pragma mark - +#pragma mark TableView delegates + +- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView +{ + return [deletedDefaultBundles count]; +} + +- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex +{ + return [[deletedDefaultBundles objectAtIndex:rowIndex] objectAtIndex:1]; +} + +- (BOOL)tableView:(NSTableView *)aTableView shouldEditTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex +{ + return NO; +} + +#pragma mark - #pragma mark outline delegates - (BOOL)outlineView:(id)outlineView isItemExpandable:(id)item @@ -1201,7 +1306,7 @@ } - (NSString *)outlineView:(NSOutlineView *)outlineView toolTipForCell:(NSCell *)cell rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)tc item:(id)item mouseLocation:(NSPoint)mouseLocation{ - if([outlineView levelForItem:item] == 0) return NSLocalizedString(@"Installed Bundles", @"Installed Bundles"); + if([outlineView levelForItem:item] == 0) return NSLocalizedString(@"Installed Bundles", @"Bundle Editor : Outline View : 'BUNDLES' item : tooltip"); if([outlineView levelForItem:item] == 1) { NSString *bName = [[item representedObject] objectForKey:kBundleNameKey]; NSUInteger k = 0; @@ -1218,13 +1323,13 @@ } switch([self _scopeIndexForArrangedScopeIndex:k]) { case kInputFieldScopeArrayIndex: - return NSLocalizedString(@"Input Field Scope\ncommands will run on each text input field", @"Input Field Scope\ncommands will run on each text input field tooltip"); + return NSLocalizedString(@"Input Field Scope\ncommands will run on each text input field", @"Bundle Editor : Outline View : 'Input Field' item : tooltip"); break; case kDataTableScopeArrayIndex: - return NSLocalizedString(@"Data Table Scope\ncommands will run on the Content and Query data tables", @"Data Table Scope\ncommands will run on the Content and Query data tables tooltip"); + return NSLocalizedString(@"Data Table Scope\ncommands will run on the Content and Query data tables", @"Bundle Editor : Outline View : 'Data Table' item : tooltip"); break; case kGeneralScopeArrayIndex: - return NSLocalizedString(@"General Scope\ncommands will run application-wide", @"General Scope\ncommands will run application-wide tooltip"); + return NSLocalizedString(@"General Scope\ncommands will run application-wide", @"Bundle Editor : Outline View : 'General' item : tooltip"); break; default: return @""; @@ -1232,25 +1337,25 @@ } if([outlineView levelForItem:item] == 2) { if([[item representedObject] objectForKey:kChildrenKey]) { - return [NSString stringWithFormat:@"“%@” %@", [[item representedObject] objectForKey:kBundleNameKey], NSLocalizedString(@"submenu label", @"submenu label")]; + return [NSString stringWithFormat:NSLocalizedString(@"Bundles in category “%@”",@"Bundle Editor : Outline View : Menu Category item : tooltip"), [[item representedObject] objectForKey:kBundleNameKey]]; } else { if([[item representedObject] objectForKey:SPBundleFileTooltipKey] && [[[item representedObject] objectForKey:SPBundleFileTooltipKey] length]) return [[item representedObject] objectForKey:SPBundleFileTooltipKey]; else - return [NSString stringWithFormat:@"“%@” Bundle", [[item representedObject] objectForKey:kBundleNameKey]]; + return [NSString stringWithFormat:SP_BUNDLEEDITOR_OUTLINE_BUNDLE_TOOLTIP_STRING, [[item representedObject] objectForKey:kBundleNameKey]]; } } if([outlineView levelForItem:item] == 3) { if([[item representedObject] objectForKey:SPBundleFileTooltipKey] && [[[item representedObject] objectForKey:SPBundleFileTooltipKey] length]) return [[item representedObject] objectForKey:SPBundleFileTooltipKey]; else - return [NSString stringWithFormat:@"“%@” Bundle", [[item representedObject] objectForKey:kBundleNameKey]]; + return [NSString stringWithFormat:SP_BUNDLEEDITOR_OUTLINE_BUNDLE_TOOLTIP_STRING, [[item representedObject] objectForKey:kBundleNameKey]]; } return @""; } #pragma mark - -#pragma mark TableView delegate +#pragma mark TableView (outline) delegate /** * Traps enter and esc and edit/cancel without entering next row @@ -1386,6 +1491,10 @@ return ([[commandBundleTreeController selectedObjects] count] == 1 && ![[[commandBundleTreeController selectedObjects] objectAtIndex:0] objectForKey:kChildrenKey]); } + if ( action == @selector(undeleteDefaultBundles:) ) { + return ([deletedDefaultBundles count]) ? YES : NO; + } + return YES; } diff --git a/Source/SPConnectionController.h b/Source/SPConnectionController.h index 2cd4d170..73a7ef77 100644 --- a/Source/SPConnectionController.h +++ b/Source/SPConnectionController.h @@ -38,7 +38,7 @@ @interface NSObject (BWAnchoredButtonBar) -- (void)setSplitViewDelegate:(id)splitViewDelegate; +- (NSRect)splitView:(NSSplitView *)splitView additionalEffectiveRectOfDividerAtIndex:(NSInteger)dividerIndex; @end diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index 4bafafbf..2bad361f 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -133,8 +133,8 @@ static const NSString *SPExportFavoritesFilename = @"SequelProFavorites.plist"; [databaseConnectionView setHidden:YES]; [connectionView setFrame:[databaseConnectionView frame]]; [databaseConnectionSuperview addSubview:connectionView]; - [connectionSplitView setPosition:[[dbDocument valueForKey:@"dbTablesTableView"] frame].size.width-6 ofDividerAtIndex:0]; - [connectionSplitViewButtonBar setSplitViewDelegate:self]; + [connectionSplitView setPosition:[[dbDocument valueForKey:@"dbTablesTableView"] frame].size.width ofDividerAtIndex:0]; + [connectionSplitView setDelegate:self]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(scrollViewFrameChanged:) name:NSViewFrameDidChangeNotification object:nil]; @@ -278,6 +278,9 @@ static const NSString *SPExportFavoritesFilename = @"SequelProFavorites.plist"; isConnecting = YES; cancellingConnection = NO; + // Disable the favorites outline view to prevent further connections attempts + [favoritesOutlineView setEnabled:NO]; + [addToFavoritesButton setHidden:YES]; [addToFavoritesButton display]; [helpButton setHidden:YES]; diff --git a/Source/SPConnectionControllerDelegate.m b/Source/SPConnectionControllerDelegate.m index c0b86c0f..449a2cb2 100644 --- a/Source/SPConnectionControllerDelegate.m +++ b/Source/SPConnectionControllerDelegate.m @@ -35,33 +35,18 @@ #pragma mark - #pragma mark SplitView delegate methods -/** - * When the split view is resized, trigger a resize in the hidden table - * width as well, to keep the connection view and connected view in synch. - * Use this rather than splitViewDidResizeSubviews: as the latter is not - * forwarded by the BWAnchoredButtonBar. - */ -- (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex -{ - [databaseConnectionView setPosition:[[[connectionSplitView subviews] objectAtIndex:0] frame].size.width ofDividerAtIndex:0]; - - return proposedPosition; -} - -/** - * Return the maximum possible size of the splitview. - */ -- (CGFloat)splitView:(NSSplitView *)sender constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)offset +- (NSRect)splitView:(NSSplitView *)splitView additionalEffectiveRectOfDividerAtIndex:(NSInteger)dividerIndex { - return (proposedMax - 445); + return [connectionSplitViewButtonBar splitView:splitView additionalEffectiveRectOfDividerAtIndex:dividerIndex]; } /** - * Return the minimum possible size of the splitview. + * When the split view is resized, trigger a resize in the hidden table + * width as well, to keep the connection view and connected view in sync. */ -- (CGFloat)splitView:(NSSplitView *)sender constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)offset +- (void)splitViewDidResizeSubviews:(NSNotification *)notification { - return (proposedMin + 80); + [databaseConnectionView setPosition:[[[connectionSplitView subviews] objectAtIndex:0] frame].size.width ofDividerAtIndex:0]; } #pragma mark - diff --git a/Source/SPConnectionHandler.m b/Source/SPConnectionHandler.m index 6a6bc3d6..6d777ff5 100644 --- a/Source/SPConnectionHandler.m +++ b/Source/SPConnectionHandler.m @@ -32,17 +32,8 @@ * Set up the MySQL connection, either through a successful tunnel or directly in the background. */ - (void)initiateMySQLConnection -{ - // Disable the favorites table view to prevent further connections attempts - [favoritesOutlineView setEnabled:NO]; - - if (sshTunnel) { - [progressIndicatorText setStringValue:NSLocalizedString(@"MySQL connecting...", @"MySQL connecting very short status message")]; - } - else { - [progressIndicatorText setStringValue:NSLocalizedString(@"Connecting...", @"Generic connecting very short status message")]; - } - +{ + [progressIndicatorText setStringValue:(sshTunnel) ? NSLocalizedString(@"MySQL connecting...", @"MySQL connecting very short status message") : NSLocalizedString(@"Connecting...", @"Generic connecting very short status message")]; [progressIndicatorText display]; [connectButton setTitle:NSLocalizedString(@"Cancel", @"cancel button")]; @@ -295,7 +286,9 @@ certificateAuthorityCertificatePath:[self sslCACertFileLocationEnabled] ? [self [dbDocument setTitlebarStatus:NSLocalizedString(@"SSH Disconnected", @"SSH disconnected titlebar marker")]; [self failConnectionWithTitle:NSLocalizedString(@"SSH connection failed!", @"SSH connection failed title") errorMessage:[theTunnel lastError] detail:[sshTunnel debugMessages]]; - } + + [self _restoreConnectionInterface]; + } else if (newState == PROXY_STATE_CONNECTED) { [dbDocument setTitlebarStatus:NSLocalizedString(@"SSH Connected", @"SSH connected titlebar marker")]; diff --git a/Source/SPConstants.h b/Source/SPConstants.h index 2a35ab60..c5a932ea 100644 --- a/Source/SPConstants.h +++ b/Source/SPConstants.h @@ -211,6 +211,7 @@ typedef enum #define SPLOCALIZEDURL_KEYBOARDSHORTCUTS NSLocalizedString(@"http://www.sequelpro.com/docs/Keyboard_Shortcuts", @"Localized keyboard shortcuts page - do not localize if no translated webpage is available") #define SPLOCALIZEDURL_CONNECTIONHELP NSLocalizedString(@"http://www.sequelpro.com/docs/Getting_Connected", @"Localized connection help page - do not localize if no translated webpage is available") #define SPLOCALIZEDURL_TRANSLATIONFEEDBACK NSLocalizedString(@"http://dev.sequelpro.com/translate/feedback", @"Localized translation feedback page - do not localize if no translated webpage is available") +#define SPLOCALIZEDURL_BUNDLEEDITORHELP NSLocalizedString(@"http://www.sequelpro.com/docs/Bundle_Editor", @"Localized help page for bundle editor - do not localize if no translated webpage is available") // Long running notification time for Growl messages extern const CGFloat SPLongRunningNotificationTime; @@ -367,6 +368,8 @@ extern NSString *SPUniqueSchemaDelimiter; extern NSString *SPLastImportIntoNewTableEncoding; extern NSString *SPLastImportIntoNewTableType; extern NSString *SPGlobalValueHistory; +extern NSString *SPBundleDeletedDefaultBundlesKey; +extern NSString *SPBundleUpdatedDefaultBundlesKey; // URLs extern NSString *SPDonationsURL; @@ -475,6 +478,8 @@ extern NSString *SPBundleFileUUIDKey; extern NSString *SPBundleFileDescriptionKey; extern NSString *SPBundleFileTriggerKey; extern NSString *SPBundleFileWithBlobKey; +extern NSString *SPBundleFileIsDefaultBundleKey; +extern NSString *SPBundleFileDefaultBundleWasModifiedKey; extern NSString *SPBundleInternLabelKey; extern NSString *SPBundleInternPathToFileKey; extern NSString *SPBundleInternKeyEquivalentKey; @@ -518,9 +523,9 @@ extern NSString *SPBundleShellVariableExitReplaceSelection; extern NSString *SPBundleShellVariableExitReplaceContent; extern NSString *SPBundleShellVariableExitInsertAsText; extern NSString *SPBundleShellVariableExitInsertAsSnippet; -extern NSString *SPBundleShellVariableExitInsertAsHTML; -extern NSString *SPBundleShellVariableExitInsertAsTextTooltip; -extern NSString *SPBundleShellVariableExitInsertAsHTMLTooltip; +extern NSString *SPBundleShellVariableExitShowAsHTML; +extern NSString *SPBundleShellVariableExitShowAsTextTooltip; +extern NSString *SPBundleShellVariableExitShowAsHTMLTooltip; extern NSString *SPBundleShellVariableCurrentHost; extern NSString *SPBundleShellVariableCurrentUser; extern NSString *SPBundleShellVariableCurrentPort; diff --git a/Source/SPConstants.m b/Source/SPConstants.m index f919e880..c7ac921d 100644 --- a/Source/SPConstants.m +++ b/Source/SPConstants.m @@ -179,6 +179,8 @@ NSString *SPUniqueSchemaDelimiter = @""; // U+FFF8 NSString *SPLastImportIntoNewTableEncoding = @"LastImportIntoNewTableEncoding"; NSString *SPLastImportIntoNewTableType = @"LastImportIntoNewTableType"; NSString *SPGlobalValueHistory = @"GlobalValueHistory"; +NSString *SPBundleDeletedDefaultBundlesKey = @"deletedDefaultBundles"; +NSString *SPBundleUpdatedDefaultBundlesKey = @"updatedDefaultBundles"; // URLs NSString *SPDonationsURL = @"http://www.sequelpro.com/donate.html"; @@ -287,6 +289,8 @@ NSString *SPBundleFileUUIDKey = @"uuid"; NSString *SPBundleFileDescriptionKey = @"description"; NSString *SPBundleFileTriggerKey = @"trigger"; NSString *SPBundleFileWithBlobKey = @"withblob"; +NSString *SPBundleFileIsDefaultBundleKey = @"isDefaultBundle"; +NSString *SPBundleFileDefaultBundleWasModifiedKey = @"defaultBundleWasModified"; NSString *SPBundleInternLabelKey = @"label"; NSString *SPBundleInternPathToFileKey = @"path"; NSString *SPBundleInternKeyEquivalentKey = @"keyEquivalent"; @@ -317,11 +321,11 @@ NSString *SPBundleShellVariableCurrentUser = @"SP_CURRENT_USER" NSString *SPBundleShellVariableCurrentWord = @"SP_CURRENT_WORD"; NSString *SPBundleShellVariableDatabaseEncoding = @"SP_DATABASE_ENCODING"; NSString *SPBundleShellVariableDataTableSource = @"SP_DATA_TABLE_SOURCE"; -NSString *SPBundleShellVariableExitInsertAsHTML = @"SP_BUNDLE_EXIT_SHOW_AS_HTML"; -NSString *SPBundleShellVariableExitInsertAsHTMLTooltip = @"SP_BUNDLE_EXIT_SHOW_AS_HTML_TOOLTIP"; +NSString *SPBundleShellVariableExitShowAsHTML = @"SP_BUNDLE_EXIT_SHOW_AS_HTML"; +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 *SPBundleShellVariableExitInsertAsTextTooltip = @"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"; diff --git a/Source/SPCopyTable.m b/Source/SPCopyTable.m index fad9332e..827672b5 100644 --- a/Source/SPCopyTable.m +++ b/Source/SPCopyTable.m @@ -1276,7 +1276,9 @@ NSInteger kBlobAsImageFile = 4; [[NSFileManager defaultManager] removeItemAtPath:bundleInputFilePath error:nil]; - NSString *action = [[cmdData objectForKey:SPBundleFileOutputActionKey] lowercaseString]; + NSString *action = SPBundleOutputActionNone; + if([cmdData objectForKey:SPBundleFileOutputActionKey] && [[cmdData objectForKey:SPBundleFileOutputActionKey] length]) + action = [[cmdData objectForKey:SPBundleFileOutputActionKey] lowercaseString]; // Redirect due exit code if(err != nil) { @@ -1315,8 +1317,7 @@ NSInteger kBlobAsImageFile = 4; } if(err == nil && output) { - if([cmdData objectForKey:SPBundleFileOutputActionKey] && [[cmdData objectForKey:SPBundleFileOutputActionKey] length] - && ![[cmdData objectForKey:SPBundleFileOutputActionKey] isEqualToString:SPBundleOutputActionNone]) { + if(![action isEqualToString:SPBundleOutputActionNone]) { NSPoint pos = [NSEvent mouseLocation]; pos.y -= 16; diff --git a/Source/SPDatabaseDocument.m b/Source/SPDatabaseDocument.m index 096974ba..787817bc 100644 --- a/Source/SPDatabaseDocument.m +++ b/Source/SPDatabaseDocument.m @@ -493,7 +493,8 @@ YY_BUFFER_STATE yy_scan_string (const char *); NSString *database = NSArrayObjectAtIndex([queryResult fetchRowAsArray], 0); // If the database is either information_schema or mysql then it is classed as a system table - if ([database isEqualToString:@"information_schema"] || [database isEqualToString:@"mysql"]) { + // 5.5.3+ performance_schema + if ([database isEqualToString:@"information_schema"] || [database isEqualToString:@"mysql"] || [database isEqualToString:@"performance_schema"]) { [allSystemDatabases addObject:database]; } else { @@ -5266,16 +5267,6 @@ YY_BUFFER_STATE yy_scan_string (const char *); #pragma mark - #pragma mark SplitView delegate methods -- (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)dividerIndex -{ - - // Limit the right view of DBViewSplitter in order to avoid GUI element overlapping - if(splitView == contentViewSplitter) return proposedMax - 495; - - return proposedMax; - -} - /** * tells the splitView that it can collapse views */ @@ -5295,9 +5286,8 @@ YY_BUFFER_STATE yy_scan_string (const char *); - (NSRect)splitView:(NSSplitView *)splitView additionalEffectiveRectOfDividerAtIndex:(NSInteger)dividerIndex { - if (sidebarGrabber != nil) { - NSRect grabberBounds = [sidebarGrabber bounds]; - return [sidebarGrabber convertRect:NSMakeRect(grabberBounds.origin.x + (grabberBounds.size.width - 16), grabberBounds.origin.y, 16, grabberBounds.size.height) toView:splitView]; + if (splitView == contentViewSplitter && sidebarGrabber != nil) { + return [sidebarGrabber splitView:splitView additionalEffectiveRectOfDividerAtIndex:dividerIndex]; } else { return NSZeroRect; } diff --git a/Source/SPExtendedTableInfo.m b/Source/SPExtendedTableInfo.m index 1f67b95d..a765f6df 100644 --- a/Source/SPExtendedTableInfo.m +++ b/Source/SPExtendedTableInfo.m @@ -513,23 +513,24 @@ // If we are viewing tables in the information_schema database, then disable all controls that cause table // changes as these tables are not modifiable by anyone. - BOOL isInformationSchemaDb = [[tableDocumentInstance database] isEqualToString:@"information_schema"]; + //also affects mysql and performance_schema + BOOL isSystemSchemaDb = ([[tableDocumentInstance database] isEqualToString:@"information_schema"] || [[tableDocumentInstance database] isEqualToString:@"performance_schema"] || [[tableDocumentInstance database] isEqualToString:@"mysql"]); if ([[databaseDataInstance getDatabaseStorageEngines] count] && [statusFields objectForKey:@"Engine"]) { - [tableTypePopUpButton setEnabled:(!isInformationSchemaDb)]; + [tableTypePopUpButton setEnabled:(!isSystemSchemaDb)]; } if ([[databaseDataInstance getDatabaseCharacterSetEncodings] count] && [tableDataInstance tableEncoding]) { - [tableEncodingPopUpButton setEnabled:(!isInformationSchemaDb)]; + [tableEncodingPopUpButton setEnabled:(!isSystemSchemaDb)]; } if ([[databaseDataInstance getDatabaseCollationsForEncoding:[tableDataInstance tableEncoding]] count] && [statusFields objectForKey:@"Collation"]) { - [tableCollationPopUpButton setEnabled:(!isInformationSchemaDb)]; + [tableCollationPopUpButton setEnabled:(!isSystemSchemaDb)]; } - [tableCommentsTextView setEditable:(!isInformationSchemaDb)]; + [tableCommentsTextView setEditable:(!isSystemSchemaDb)]; } #pragma mark - diff --git a/Source/SPIndexesController.m b/Source/SPIndexesController.m index a19480e6..27b4471d 100644 --- a/Source/SPIndexesController.m +++ b/Source/SPIndexesController.m @@ -129,17 +129,32 @@ static const NSString *SPNewIndexKeyBlockSize = @"IndexKeyBlockSize"; [indexNameTextField setEnabled:NO]; [indexNameTextField setStringValue:@"PRIMARY"]; - // If the table is of type MyISAM and Spation extension support is available, add the SPATIAL type + // If the table is of type MyISAM and Spatial extension support is available, add the SPATIAL type NSString *engine = [[tableData statusValues] objectForKey:@"Engine"]; if ([engine isEqualToString:@"MyISAM"] && [[dbDocument serverSupport] supportsSpatialExtensions]) { [indexTypePopUpButton addItemWithTitle:@"SPATIAL"]; } - + // Check to see whether a primary key already exists for the table, and if so select INDEX instead for (NSDictionary *field in fields) { - if ([field objectForKey:@"isprimarykey"]) { + BOOL hasCompositePrimaryKey = NO; + BOOL isPrimaryKey = [field objectForKey:@"isprimarykey"]; + + // The 'isprimarykey' key of a field is only present for single column primary keys, not composite keys, + // so we need to check the indexes manually. + if (!isPrimaryKey) { + for (NSDictionary *index in indexes) + { + if ([[index objectForKey:@"Key_name"] isEqualToString:@"PRIMARY"]) { + hasCompositePrimaryKey = YES; + break; + } + } + } + + if (isPrimaryKey || hasCompositePrimaryKey) { // Remove primary key option [indexTypePopUpButton removeItemAtIndex:0]; @@ -154,10 +169,31 @@ static const NSString *SPNewIndexKeyBlockSize = @"IndexKeyBlockSize"; break; } } + + NSMutableArray *indexedFieldNames = [[NSMutableArray alloc] init]; + + // Build an array of all indexed column names + for (NSDictionary *index in indexes) + { + [indexedFieldNames addObject:[index objectForKey:@"Column_name"]]; + } + + NSDictionary *initialField = nil; + + // Select the first column as the initial field that doesn't already have an index + for (NSDictionary *field in fields) + { + if (![indexedFieldNames containsObject:[field objectForKey:@"name"]]) { + initialField = [[field mutableCopy] autorelease]; + break; + } + } + + if (initialField) [indexedFieldNames release], initialField = nil; // Reset the indexed columns [indexedFields removeAllObjects]; - [indexedFields addObject:[[[fields objectAtIndex:0] mutableCopy] autorelease]]; + [indexedFields addObject:initialField]; [indexedColumnsTableView reloadData]; @@ -393,15 +429,19 @@ static const NSString *SPNewIndexKeyBlockSize = @"IndexKeyBlockSize"; */ - (NSInteger)numberOfItemsInComboBoxCell:(NSComboBoxCell *)comboBoxCell { - return [fields count]; + return ([fields count] - [indexedFields count]); } /** * Returns the item to be displayed in the combo box cell as the supplied index. */ - (id)comboBoxCell:(NSComboBoxCell *)comboBoxCell objectValueForItemAtIndex:(NSInteger)index -{ - return [[fields objectAtIndex:index] objectForKey:@"name"]; +{ + NSMutableArray *availableFields = [fields mutableCopy]; + + [availableFields removeObjectsInArray:indexedFields]; + + return [[availableFields objectAtIndex:index] objectForKey:@"name"]; } #pragma mark - diff --git a/Source/SPLogger.m b/Source/SPLogger.m index 743b0fce..5736fde6 100644 --- a/Source/SPLogger.m +++ b/Source/SPLogger.m @@ -107,7 +107,10 @@ static SPLogger *logger = nil; NSString *logString = [[NSString alloc] initWithFormat:theString arguments:arguments]; va_end(arguments); - // Write the log line, forcing an immediate write to disk to ensure logging + // Write the log line, forcing an immediate write to disk to ensure logging, and + // synchronised to allow use across multiple executables or their frameworks. + [logFileHandle synchronizeFile]; + [logFileHandle seekToEndOfFile]; [logFileHandle writeData:[[NSString stringWithFormat:@"%@ %@\n", [[NSDate date] descriptionWithCalendarFormat:@"%H:%M:%S" timeZone:nil locale:[[NSUserDefaults standardUserDefaults] dictionaryRepresentation]], logString] dataUsingEncoding:NSUTF8StringEncoding]]; [logFileHandle synchronizeFile]; diff --git a/Source/SPStringAdditions.m b/Source/SPStringAdditions.m index b523907c..a8346399 100644 --- a/Source/SPStringAdditions.m +++ b/Source/SPStringAdditions.m @@ -526,9 +526,9 @@ [theEnv setObject:[NSNumber numberWithInteger:SPBundleRedirectActionReplaceContent] forKey:SPBundleShellVariableExitReplaceContent]; [theEnv setObject:[NSNumber numberWithInteger:SPBundleRedirectActionInsertAsText] forKey:SPBundleShellVariableExitInsertAsText]; [theEnv setObject:[NSNumber numberWithInteger:SPBundleRedirectActionInsertAsSnippet] forKey:SPBundleShellVariableExitInsertAsSnippet]; - [theEnv setObject:[NSNumber numberWithInteger:SPBundleRedirectActionShowAsHTML] forKey:SPBundleShellVariableExitInsertAsHTML]; - [theEnv setObject:[NSNumber numberWithInteger:SPBundleRedirectActionShowAsTextTooltip] forKey:SPBundleShellVariableExitInsertAsTextTooltip]; - [theEnv setObject:[NSNumber numberWithInteger:SPBundleRedirectActionShowAsHTMLTooltip] forKey:SPBundleShellVariableExitInsertAsHTMLTooltip]; + [theEnv setObject:[NSNumber numberWithInteger:SPBundleRedirectActionShowAsHTML] forKey:SPBundleShellVariableExitShowAsHTML]; + [theEnv setObject:[NSNumber numberWithInteger:SPBundleRedirectActionShowAsTextTooltip] forKey:SPBundleShellVariableExitShowAsTextTooltip]; + [theEnv setObject:[NSNumber numberWithInteger:SPBundleRedirectActionShowAsHTMLTooltip] forKey:SPBundleShellVariableExitShowAsHTMLTooltip]; // Create and set an unique process ID for each SPDatabaseDocument which has to passed // for each sequelpro:// scheme command as user to be able to identify the url scheme command. diff --git a/Source/SPTableContent.m b/Source/SPTableContent.m index 82282b5d..8e2bde89 100644 --- a/Source/SPTableContent.m +++ b/Source/SPTableContent.m @@ -4164,16 +4164,45 @@ return NO; } +// Set a minimum size for the filter text area - (CGFloat)splitView:(NSSplitView *)sender constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)offset { return (proposedMax - 180); } +// Set a minimum size for the field list and action area - (CGFloat)splitView:(NSSplitView *)sender constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)offset { return (proposedMin + 200); } +// Improve default resizing and resize only the filter text area by default +- (void)splitView:(NSSplitView *)sender resizeSubviewsWithOldSize:(NSSize)oldSize +{ + NSSize newSize = [sender frame].size; + NSView *leftView = [[sender subviews] objectAtIndex:0]; + NSView *rightView = [[sender subviews] objectAtIndex:1]; + float dividerThickness = [sender dividerThickness]; + NSRect leftFrame = [leftView frame]; + NSRect rightFrame = [rightView frame]; + + // Resize height of both views + leftFrame.size.height = newSize.height; + rightFrame.size.height = newSize.height; + + // Only resize the right view's width - unless the constraint has been reached + if (rightFrame.size.width > 180 || newSize.width > oldSize.width) { + rightFrame.size.width = newSize.width - leftFrame.size.width - dividerThickness; + } else { + leftFrame.size.width = newSize.width - rightFrame.size.width - dividerThickness; + } + rightFrame.origin.x = leftFrame.size.width + dividerThickness; + + [leftView setFrame:leftFrame]; + [rightView setFrame:rightFrame]; +} + + #pragma mark - #pragma mark Task interaction diff --git a/Source/SPTextView.m b/Source/SPTextView.m index ab37f464..af4356a2 100644 --- a/Source/SPTextView.m +++ b/Source/SPTextView.m @@ -351,8 +351,10 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) // Put information_schema and/or mysql db at the end if not selected + // 5.5.3+ also has performance_schema NSString* mysql_id = [NSString stringWithFormat:@"%@%@%@", connectionID, SPUniqueSchemaDelimiter, @"mysql"]; - NSString* inf_id = [NSString stringWithFormat:@"%@%@%@", connectionID, SPUniqueSchemaDelimiter, @"information_schema"]; + NSString* inf_id = [NSString stringWithFormat:@"%@%@%@", connectionID, SPUniqueSchemaDelimiter, @"information_schema"]; + NSString* perf_id = [NSString stringWithFormat:@"%@%@%@", connectionID, SPUniqueSchemaDelimiter, @"performance_schema"]; if(currentDb && ![currentDb isEqualToString:mysql_id] && [sortedDbs containsObject:mysql_id]) { [sortedDbs removeObject:mysql_id]; [sortedDbs addObject:mysql_id]; @@ -361,6 +363,10 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) [sortedDbs removeObject:inf_id]; [sortedDbs addObject:inf_id]; } + if(currentDb && ![currentDb isEqualToString:perf_id] && [sortedDbs containsObject:perf_id]) { + [sortedDbs removeObject:perf_id]; + [sortedDbs addObject:perf_id]; + } BOOL aTableNameExists = NO; if(!aDbName) { diff --git a/Source/SPTextViewAdditions.m b/Source/SPTextViewAdditions.m index f04c6684..2e1f8f9b 100644 --- a/Source/SPTextViewAdditions.m +++ b/Source/SPTextViewAdditions.m @@ -649,7 +649,9 @@ [[NSFileManager defaultManager] removeItemAtPath:bundleInputFilePath error:nil]; - NSString *action = [[cmdData objectForKey:SPBundleFileOutputActionKey] lowercaseString]; + NSString *action = SPBundleOutputActionNone; + if([cmdData objectForKey:SPBundleFileOutputActionKey] && [[cmdData objectForKey:SPBundleFileOutputActionKey] length]) + action = [[cmdData objectForKey:SPBundleFileOutputActionKey] lowercaseString]; // Redirect due exit code if(err != nil) { @@ -688,8 +690,7 @@ } if(err == nil && output) { - if([cmdData objectForKey:SPBundleFileOutputActionKey] && [[cmdData objectForKey:SPBundleFileOutputActionKey] length] - && ![[cmdData objectForKey:SPBundleFileOutputActionKey] isEqualToString:SPBundleOutputActionNone]) { + if(![action isEqualToString:SPBundleOutputActionNone]) { if([action isEqualToString:SPBundleOutputActionShowAsTextTooltip]) { [SPTooltip showWithObject:output]; diff --git a/Source/SPUserManager.m b/Source/SPUserManager.m index c545c6a8..b8d0e826 100644 --- a/Source/SPUserManager.m +++ b/Source/SPUserManager.m @@ -39,6 +39,8 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; - (void)_selectParentFromSelection; - (NSArray *)_fetchUserWithUserName:(NSString *)username; - (NSManagedObject *)_createNewSPUser; +- (void)_grantPrivileges:(NSArray *)thePrivileges onDatabase:(NSString *)aDatabase forUser:(NSString *)aUser host:(NSString *)aHost; +- (void)_revokePrivileges:(NSArray *)thePrivileges onDatabase:(NSString *)aDatabase forUser:(NSString *)aUser host:(NSString *)aHost; - (BOOL)_checkAndDisplayMySqlError; - (void)_clearData; - (void)_initializeChild:(NSManagedObject *)child withItem:(NSDictionary *)item; @@ -169,6 +171,9 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; while (privRow = [result fetchRowAsArray]) { privKey = [NSMutableString stringWithString:[[privRow objectAtIndex:0] lowercaseString]]; + + // Skip the special "Usage" key + if ([privKey isEqualToString:@"usage"]) continue; [privKey replaceOccurrencesOfString:@" " withString:@"_" options:NSLiteralSearch range:NSMakeRange(0, [privKey length])]; [privKey appendString:@"_priv"]; @@ -1167,30 +1172,10 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; } // Grant privileges - if ([grantPrivileges count] > 0) - { - NSString *grantStatement = [NSString stringWithFormat:@"GRANT %@ ON %@.* TO %@@%@", - [[grantPrivileges componentsJoinedByCommas] uppercaseString], - [dbName backtickQuotedString], - [[schemaPriv valueForKeyPath:@"user.parent.user"] tickQuotedString], - [[schemaPriv valueForKeyPath:@"user.host"] tickQuotedString]]; - - [self.mySqlConnection queryString:grantStatement]; - [self _checkAndDisplayMySqlError]; - } + [self _grantPrivileges:grantPrivileges onDatabase:dbName forUser:[schemaPriv valueForKeyPath:@"user.parent.user"] host:[schemaPriv valueForKeyPath:@"user.host"]]; // Revoke privileges - if ([revokePrivileges count] > 0) - { - NSString *revokeStatement = [NSString stringWithFormat:@"REVOKE %@ ON %@.* FROM %@@%@", - [[revokePrivileges componentsJoinedByCommas] uppercaseString], - [dbName backtickQuotedString], - [[schemaPriv valueForKeyPath:@"user.parent.user"] tickQuotedString], - [[schemaPriv valueForKeyPath:@"user.host"] tickQuotedString]]; - - [self.mySqlConnection queryString:revokeStatement]; - [self _checkAndDisplayMySqlError]; - } + [self _revokePrivileges:revokePrivileges onDatabase:dbName forUser:[schemaPriv valueForKeyPath:@"user.parent.user"] host:[schemaPriv valueForKeyPath:@"user.host"]]; return YES; } @@ -1245,28 +1230,10 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; } // Grant privileges - if ([grantPrivileges count] > 0) { - - NSString *grantStatement = [NSString stringWithFormat:@"GRANT %@ ON *.* TO %@@%@", - [[grantPrivileges componentsJoinedByCommas] uppercaseString], - [[[user parent] valueForKey:@"user"] tickQuotedString], - [[user valueForKey:@"host"] tickQuotedString]]; + [self _grantPrivileges:grantPrivileges onDatabase:nil forUser:[[user parent] valueForKey:@"user"] host:[user valueForKey:@"host"]]; - [self.mySqlConnection queryString:grantStatement]; - [self _checkAndDisplayMySqlError]; - } - // Revoke privileges - if ([revokePrivileges count] > 0) - { - NSString *revokeStatement = [NSString stringWithFormat:@"REVOKE %@ ON *.* FROM %@@%@", - [[revokePrivileges componentsJoinedByCommas] uppercaseString], - [[[user parent] valueForKey:@"user"] tickQuotedString], - [[user valueForKey:@"host"] tickQuotedString]]; - - [self.mySqlConnection queryString:revokeStatement]; - [self _checkAndDisplayMySqlError]; - } + [self _revokePrivileges:revokePrivileges onDatabase:nil forUser:[[user parent] valueForKey:@"user"] host:[user valueForKey:@"host"]]; } for (NSManagedObject *priv in [user valueForKey:@"schema_privileges"]) { @@ -1331,6 +1298,69 @@ static const NSString *SPTableViewNameColumnID = @"NameColumn"; } /** + * Grant the supplied privileges to the specified user and host + */ +- (void)_grantPrivileges:(NSArray *)thePrivileges onDatabase:(NSString *)aDatabase forUser:(NSString *)aUser host:(NSString *)aHost +{ + if (![thePrivileges count]) return; + + NSString *grantStatement; + + // Special case when all items are checked, to allow GRANT OPTION to work + if ([self.privsSupportedByServer count] == [thePrivileges count]) { + grantStatement = [NSString stringWithFormat:@"GRANT ALL ON %@.* TO %@@%@ WITH GRANT OPTION", + aDatabase?[aDatabase backtickQuotedString]:@"*", + [aUser tickQuotedString], + [aHost tickQuotedString]]; + } else { + grantStatement = [NSString stringWithFormat:@"GRANT %@ ON %@.* TO %@@%@", + [[thePrivileges componentsJoinedByCommas] uppercaseString], + aDatabase?[aDatabase backtickQuotedString]:@"*", + [aUser tickQuotedString], + [aHost tickQuotedString]]; + } + + [self.mySqlConnection queryString:grantStatement]; + [self _checkAndDisplayMySqlError]; +} + + +/** + * Revoke the supplied privileges from the specified user and host + */ +- (void)_revokePrivileges:(NSArray *)thePrivileges onDatabase:(NSString *)aDatabase forUser:(NSString *)aUser host:(NSString *)aHost +{ + if (![thePrivileges count]) return; + + NSString *revokeStatement; + + // Special case when all items are checked, to allow GRANT OPTION to work + if ([self.privsSupportedByServer count] == [thePrivileges count]) { + revokeStatement = [NSString stringWithFormat:@"REVOKE ALL PRIVILEGES ON %@.* FROM %@@%@", + aDatabase?[aDatabase backtickQuotedString]:@"*", + [aUser tickQuotedString], + [aHost tickQuotedString]]; + + [self.mySqlConnection queryString:revokeStatement]; + [self _checkAndDisplayMySqlError]; + + revokeStatement = [NSString stringWithFormat:@"REVOKE GRANT OPTION ON %@.* FROM %@@%@", + aDatabase?[aDatabase backtickQuotedString]:@"*", + [aUser tickQuotedString], + [aHost tickQuotedString]]; + } else { + revokeStatement = [NSString stringWithFormat:@"REVOKE %@ ON %@.* FROM %@@%@", + [[thePrivileges componentsJoinedByCommas] uppercaseString], + aDatabase?[aDatabase backtickQuotedString]:@"*", + [aUser tickQuotedString], + [aHost tickQuotedString]]; + } + + [self.mySqlConnection queryString:revokeStatement]; + [self _checkAndDisplayMySqlError]; +} + +/** * Displays an alert panel if there was an error condition on the MySQL connection. */ - (BOOL)_checkAndDisplayMySqlError |