aboutsummaryrefslogtreecommitdiffstats
path: root/Source
diff options
context:
space:
mode:
Diffstat (limited to 'Source')
-rw-r--r--Source/SPAppController.m416
-rw-r--r--Source/SPBundleEditorController.h7
-rw-r--r--Source/SPBundleEditorController.m283
-rw-r--r--Source/SPConnectionController.h2
-rw-r--r--Source/SPConnectionController.m7
-rw-r--r--Source/SPConnectionControllerDelegate.m27
-rw-r--r--Source/SPConnectionHandler.m17
-rw-r--r--Source/SPConstants.h11
-rw-r--r--Source/SPConstants.m10
-rw-r--r--Source/SPCopyTable.m7
-rw-r--r--Source/SPDatabaseDocument.m18
-rw-r--r--Source/SPExtendedTableInfo.m11
-rw-r--r--Source/SPIndexesController.m54
-rw-r--r--Source/SPLogger.m5
-rw-r--r--Source/SPStringAdditions.m6
-rw-r--r--Source/SPTableContent.m29
-rw-r--r--Source/SPTextView.m8
-rw-r--r--Source/SPTextViewAdditions.m7
-rw-r--r--Source/SPUserManager.m114
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:&currentPath];
@@ -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