diff options
Diffstat (limited to 'Source')
-rw-r--r-- | Source/SPAppController.h | 1 | ||||
-rw-r--r-- | Source/SPAppController.m | 39 | ||||
-rw-r--r-- | Source/SPDatabaseDocument.h | 7 | ||||
-rw-r--r-- | Source/SPDatabaseDocument.m | 1264 | ||||
-rw-r--r-- | Source/SPTableContent.m | 4 | ||||
-rw-r--r-- | Source/SPTablesList.h | 5 | ||||
-rw-r--r-- | Source/SPTablesList.m | 69 |
7 files changed, 804 insertions, 585 deletions
diff --git a/Source/SPAppController.h b/Source/SPAppController.h index 3c18911e..35c9ee63 100644 --- a/Source/SPAppController.h +++ b/Source/SPAppController.h @@ -54,6 +54,7 @@ // Window management - (IBAction)newWindow:(id)sender; - (IBAction)newTab:(id)sender; +- (IBAction)duplicateTab:(id)sender; - (NSWindow *) frontDocumentWindow; // IBAction methods diff --git a/Source/SPAppController.m b/Source/SPAppController.m index 534b3741..c5704f80 100644 --- a/Source/SPAppController.m +++ b/Source/SPAppController.m @@ -124,10 +124,14 @@ return NO; } - if([menuItem action] == @selector(newTab:)) + if ([menuItem action] == @selector(newTab:)) { return ([[self frontDocumentWindow] attachedSheet] == nil); } + if ([menuItem action] == @selector(duplicateTab:)) + { + return ([[self frontDocument] getConnection] != nil); + } return YES; } @@ -319,7 +323,7 @@ [frontController addNewConnection:self]; } - [[self frontDocument] initWithConnectionFile:filename]; + [[self frontDocument] setStateFromConnectionFile:filename]; } else if([[[filename pathExtension] lowercaseString] isEqualToString:[SPBundleFileExtension lowercaseString]]) { @@ -418,7 +422,7 @@ [newWindowController addNewConnection:self]; [[self frontDocument] setIsSavedInBundle:isBundleFile]; - [[self frontDocument] initWithConnectionFile:fileName]; + [[self frontDocument] setStateFromConnectionFile:fileName]; } } else { @@ -657,6 +661,35 @@ } /** + * Duplicate the current connection tab + */ +- (IBAction)duplicateTab:(id)sender +{ + SPDatabaseDocument *theFrontDocument = [self frontDocument]; + if (!theFrontDocument) return [self newTab:sender]; + + // Add a new tab to the window + if ([[self frontDocumentWindow] isMiniaturized]) [[self frontDocumentWindow] deminiaturize:self]; + [[[self frontDocumentWindow] windowController] addNewConnection:self]; + + // Get the state of the previously-frontmost document + NSDictionary *allStateDetails = [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithBool:YES], @"connection", + [NSNumber numberWithBool:YES], @"history", + [NSNumber numberWithBool:YES], @"session", + [NSNumber numberWithBool:YES], @"query", + [NSNumber numberWithBool:YES], @"password", + nil]; + NSMutableDictionary *theFrontState = [NSMutableDictionary dictionaryWithDictionary:[theFrontDocument stateIncludingDetails:allStateDetails]]; + + // Ensure it's set to autoconnect + [theFrontState setObject:[NSNumber numberWithBool:YES] forKey:@"auto_connect"]; + + // Set the connection on the new tab + [[self frontDocument] setState:theFrontState]; +} + +/** * Retrieve the frontmost document window; returns nil if not found. */ - (NSWindow *) frontDocumentWindow diff --git a/Source/SPDatabaseDocument.h b/Source/SPDatabaseDocument.h index 9563fd58..e4d12346 100644 --- a/Source/SPDatabaseDocument.h +++ b/Source/SPDatabaseDocument.h @@ -215,7 +215,6 @@ - (BOOL)couldCommitCurrentViewActions; - (void)initQueryEditorWithString:(NSString *)query; -- (void)initWithConnectionFile:(NSString *)path; // Connection callback and methods - (void)setConnection:(MCPConnection *)theConnection; @@ -322,6 +321,7 @@ // Menu methods - (BOOL)validateMenuItem:(NSMenuItem *)menuItem; +- (IBAction)openDatabaseInNewTab:(id)sender; - (IBAction)saveConnectionSheet:(id)sender; - (IBAction)import:(id)sender; - (IBAction)importFromClipboard:(id)sender; @@ -357,4 +357,9 @@ - (void)handleSchemeCommand:(NSDictionary*)commandDict; - (NSDictionary*)shellVariables; +// State saving and setting +- (NSDictionary *) stateIncludingDetails:(NSDictionary *)detailsToReturn; +- (BOOL)setState:(NSDictionary *)stateDetails; +- (void)setStateFromConnectionFile:(NSString *)path; + @end diff --git a/Source/SPDatabaseDocument.m b/Source/SPDatabaseDocument.m index 4e26a8c4..4626f031 100644 --- a/Source/SPDatabaseDocument.m +++ b/Source/SPDatabaseDocument.m @@ -246,403 +246,6 @@ } /** - * Initialise the document with the connection file at the supplied path. - */ -- (void)initWithConnectionFile:(NSString *)path -{ - NSError *readError = nil; - NSString *convError = nil; - NSPropertyListFormat format; - - NSString *encryptpw = nil; - NSDictionary *data = nil; - NSDictionary *connection = nil; - NSDictionary *spf = nil; - - NSInteger connectionType = -1; - - [self updateWindowTitle:self]; - - // Clean fields - [connectionController setName:@""]; - [connectionController setUser:@""]; - [connectionController setHost:@""]; - [connectionController setPort:@""]; - [connectionController setSocket:@""]; - [connectionController setUseSSL:NSOffState]; - [connectionController setSslKeyFileLocationEnabled:NSOffState]; - [connectionController setSslKeyFileLocation:nil]; - [connectionController setSslCertificateFileLocationEnabled:NSOffState]; - [connectionController setSslCertificateFileLocation:nil]; - [connectionController setSslCACertFileLocationEnabled:NSOffState]; - [connectionController setSslCACertFileLocation:nil]; - [connectionController setSshHost:@""]; - [connectionController setSshUser:@""]; - [connectionController setSshKeyLocationEnabled:NSOffState]; - [connectionController setSshKeyLocation:nil]; - [connectionController setSshPort:@""]; - [connectionController setDatabase:@""]; - [connectionController setPassword:nil]; - [connectionController setSshPassword:nil]; - - // Deselect all favorites - [[connectionController valueForKeyPath:@"favoritesTable"] deselectAll:connectionController]; - // Suppress the possibility to choose an other connection from the favorites - // if a connection should initialized by SPF file. Otherwise it could happen - // that the SPF file runs out of sync. - [[connectionController valueForKeyPath:@"favoritesTable"] setEnabled:NO]; - - - NSData *pData = [NSData dataWithContentsOfFile:path options:NSUncachedRead error:&readError]; - - spf = [[NSPropertyListSerialization propertyListFromData:pData - mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&convError] retain]; - - if(!spf || readError != nil || [convError length] || !(format == NSPropertyListXMLFormat_v1_0 || format == NSPropertyListBinaryFormat_v1_0)) { - NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while reading connection data file", @"error while reading connection data file")] - defaultButton:NSLocalizedString(@"OK", @"OK button") - alternateButton:nil - otherButton:nil - informativeTextWithFormat:NSLocalizedString(@"Connection data file couldn't be read.", @"error while reading connection data file")]; - - [alert setAlertStyle:NSCriticalAlertStyle]; - [alert runModal]; - if (spf) [spf release]; - [self closeAndDisconnect]; - return; - } - - // For dispatching later - if(![[spf objectForKey:@"format"] isEqualToString:@"connection"]) { - NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Warning", @"warning")] - defaultButton:NSLocalizedString(@"OK", @"OK button") - alternateButton:nil - otherButton:nil - informativeTextWithFormat:[NSString stringWithFormat:NSLocalizedString(@"The chosen file “%@” contains ‘%@’ data.", @"message while reading a spf file which matches non-supported formats."), path, [spf objectForKey:@"format"]]]; - - [alert setAlertStyle:NSWarningAlertStyle]; - [spf release]; - [self closeAndDisconnect]; - [alert runModal]; - return; - } - - if(![spf objectForKey:@"data"]) { - NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while reading connection data file", @"error while reading connection data file")] - defaultButton:NSLocalizedString(@"OK", @"OK button") - alternateButton:nil - otherButton:nil - informativeTextWithFormat:NSLocalizedString(@"No data found.", @"no data found")]; - - [alert setAlertStyle:NSCriticalAlertStyle]; - [alert runModal]; - [spf release]; - [self closeAndDisconnect]; - return; - } - - // Ask for a password if SPF file passwords were encrypted as sheet - if([spf objectForKey:@"encrypted"] && [[spf valueForKey:@"encrypted"] boolValue]) { - - if([self isSaveInBundle] && [[[NSApp delegate] spfSessionDocData] objectForKey:@"e_string"]) { - encryptpw = [[[NSApp delegate] spfSessionDocData] objectForKey:@"e_string"]; - } else { - [inputTextWindowHeader setStringValue:NSLocalizedString(@"Connection file is encrypted", @"Connection file is encrypted")]; - [inputTextWindowMessage setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Please enter the password for ‘%@’:", @"Please enter the password"), ([self isSaveInBundle]) ? [[[[NSApp delegate] sessionURL] absoluteString] lastPathComponent] : [path lastPathComponent]]]; - [inputTextWindowSecureTextField setStringValue:@""]; - [inputTextWindowSecureTextField selectText:nil]; - - [NSApp beginSheet:inputTextWindow modalForWindow:parentWindow modalDelegate:self didEndSelector:nil contextInfo:nil]; - - // wait for encryption password - NSModalSession session = [NSApp beginModalSessionForWindow:inputTextWindow]; - for (;;) { - - // Execute code on DefaultRunLoop - [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode - beforeDate:[NSDate distantFuture]]; - - // Break the run loop if editSheet was closed - if ([NSApp runModalSession:session] != NSRunContinuesResponse - || ![inputTextWindow isVisible]) - break; - - // Execute code on DefaultRunLoop - [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode - beforeDate:[NSDate distantFuture]]; - - } - [NSApp endModalSession:session]; - [inputTextWindow orderOut:nil]; - [NSApp endSheet:inputTextWindow]; - - if(passwordSheetReturnCode) { - encryptpw = [inputTextWindowSecureTextField stringValue]; - if([self isSaveInBundle]) { - NSMutableDictionary *spfSessionData = [NSMutableDictionary dictionary]; - [spfSessionData addEntriesFromDictionary:[[NSApp delegate] spfSessionDocData]]; - [spfSessionData setObject:encryptpw forKey:@"e_string"]; - [[NSApp delegate] setSpfSessionDocData:spfSessionData]; - } - } else { - [self closeAndDisconnect]; - [spf release]; - return; - } - } - } - - if([[spf objectForKey:@"data"] isKindOfClass:[NSDictionary class]]) - data = [NSDictionary dictionaryWithDictionary:[spf objectForKey:@"data"]]; - else if([[spf objectForKey:@"data"] isKindOfClass:[NSData class]]) { - NSData *decryptdata = nil; - decryptdata = [[[NSMutableData alloc] initWithData:[(NSData *)[spf objectForKey:@"data"] dataDecryptedWithPassword:encryptpw]] autorelease]; - if(decryptdata != nil && [decryptdata length]) { - NSKeyedUnarchiver *unarchiver = [[[NSKeyedUnarchiver alloc] initForReadingWithData:decryptdata] autorelease]; - data = (NSDictionary *)[unarchiver decodeObjectForKey:@"data"]; - [unarchiver finishDecoding]; - } - if(data == nil) { - NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while reading connection data file", @"error while reading connection data file")] - defaultButton:NSLocalizedString(@"OK", @"OK button") - alternateButton:nil - otherButton:nil - informativeTextWithFormat:NSLocalizedString(@"Wrong data format or password.", @"wrong data format or password")]; - - [alert setAlertStyle:NSCriticalAlertStyle]; - [alert runModal]; - [self closeAndDisconnect]; - [spf release]; - return; - } - } - - if(data == nil) { - NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while reading connection data file", @"error while reading connection data file")] - defaultButton:NSLocalizedString(@"OK", @"OK button") - alternateButton:nil - otherButton:nil - informativeTextWithFormat:NSLocalizedString(@"Wrong data format.", @"wrong data format")]; - - [alert setAlertStyle:NSCriticalAlertStyle]; - [alert runModal]; - [self closeAndDisconnect]; - [spf release]; - return; - } - - - if(![data objectForKey:@"connection"]) { - NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while reading connection data file", @"error while reading connection data file")] - defaultButton:NSLocalizedString(@"OK", @"OK button") - alternateButton:nil - otherButton:nil - informativeTextWithFormat:NSLocalizedString(@"No connection data found.", @"no connection data found")]; - - [alert setAlertStyle:NSCriticalAlertStyle]; - [alert runModal]; - [self closeAndDisconnect]; - [spf release]; - return; - } - - [spfDocData setObject:[NSNumber numberWithBool:NO] forKey:@"encrypted"]; - if(encryptpw != nil) { - [spfDocData setObject:[NSNumber numberWithBool:YES] forKey:@"encrypted"]; - [spfDocData setObject:encryptpw forKey:@"e_string"]; - } - encryptpw = nil; - - connection = [NSDictionary dictionaryWithDictionary:[data objectForKey:@"connection"]]; - - if([connection objectForKey:@"type"]) { - if([[connection objectForKey:@"type"] isEqualToString:@"SPTCPIPConnection"]) - connectionType = SPTCPIPConnection; - else if([[connection objectForKey:@"type"] isEqualToString:@"SPSocketConnection"]) - connectionType = SPSocketConnection; - else if([[connection objectForKey:@"type"] isEqualToString:@"SPSSHTunnelConnection"]) - connectionType = SPSSHTunnelConnection; - else - connectionType = SPTCPIPConnection; - - [connectionController setType:connectionType]; - [connectionController resizeTabViewToConnectionType:connectionType animating:NO]; - } - - if([connection objectForKey:@"name"]) - [connectionController setName:[connection objectForKey:@"name"]]; - if([connection objectForKey:@"user"]) - [connectionController setUser:[connection objectForKey:@"user"]]; - if([connection objectForKey:@"host"]) - [connectionController setHost:[connection objectForKey:@"host"]]; - if([connection objectForKey:@"port"]) - [connectionController setPort:[NSString stringWithFormat:@"%ld", (long)[[connection objectForKey:@"port"] integerValue]]]; - - if([connection objectForKey:@"useSSL"]) - [connectionController setUseSSL:[[connection objectForKey:@"useSSL"] intValue]]; - if([connection objectForKey:@"sslKeyFileLocationEnabled"]) - [connectionController setSslKeyFileLocationEnabled:[[connection objectForKey:@"sslKeyFileLocationEnabled"] intValue]]; - if([connection objectForKey:@"sslKeyFileLocation"]) - [connectionController setSslKeyFileLocation:[connection objectForKey:@"sslKeyFileLocation"]]; - if([connection objectForKey:@"sslCertificateFileLocationEnabled"]) - [connectionController setSslCertificateFileLocationEnabled:[[connection objectForKey:@"sslCertificateFileLocationEnabled"] intValue]]; - if([connection objectForKey:@"sslCertificateFileLocation"]) - [connectionController setSslCertificateFileLocation:[connection objectForKey:@"sslCertificateFileLocation"]]; - if([connection objectForKey:@"sslCACertFileLocationEnabled"]) - [connectionController setSslCACertFileLocationEnabled:[[connection objectForKey:@"sslCACertFileLocationEnabled"] intValue]]; - if([connection objectForKey:@"sslCACertFileLocation"]) - [connectionController setSslCACertFileLocation:[connection objectForKey:@"sslCACertFileLocation"]]; - - if([connection objectForKey:@"kcid"] && [(NSString *)[connection objectForKey:@"kcid"] length]) - [self setKeychainID:[connection objectForKey:@"kcid"]]; - - // Set password - if not in SPF file try to get it via the KeyChain - if([connection objectForKey:@"password"]) - [connectionController setPassword:[connection objectForKey:@"password"]]; - else { - NSString *pw = [self keychainPasswordForConnection:nil]; - if(pw) - [connectionController setPassword:pw]; - } - - if(connectionType == SPSocketConnection && [connection objectForKey:@"socket"]) - [connectionController setSocket:[connection objectForKey:@"socket"]]; - - if(connectionType == SPSSHTunnelConnection) { - if([connection objectForKey:@"ssh_host"]) - [connectionController setSshHost:[connection objectForKey:@"ssh_host"]]; - if([connection objectForKey:@"ssh_user"]) - [connectionController setSshUser:[connection objectForKey:@"ssh_user"]]; - if([connection objectForKey:@"ssh_keyLocationEnabled"]) - [connectionController setSshKeyLocationEnabled:[[connection objectForKey:@"ssh_keyLocationEnabled"] intValue]]; - if([connection objectForKey:@"ssh_keyLocation"]) - [connectionController setSshKeyLocation:[connection objectForKey:@"ssh_keyLocation"]]; - if([connection objectForKey:@"ssh_port"]) - [connectionController setSshPort:[NSString stringWithFormat:@"%ld", (long)[[connection objectForKey:@"ssh_port"] integerValue]]]; - - // Set ssh password - if not in SPF file try to get it via the KeyChain - if([connection objectForKey:@"ssh_password"]) - [connectionController setSshPassword:[connection objectForKey:@"ssh_password"]]; - else { - NSString *sshpw = [self keychainPasswordForSSHConnection:nil]; - if(sshpw) - [connectionController setSshPassword:sshpw]; - } - - } - - if([connection objectForKey:@"database"]) - [connectionController setDatabase:[connection objectForKey:@"database"]]; - - if([data objectForKey:@"session"]) { - spfSession = [[NSDictionary dictionaryWithDictionary:[data objectForKey:@"session"]] retain]; - [spfDocData setObject:[NSNumber numberWithBool:YES] forKey:@"include_session"]; - } - - if(![self isSaveInBundle]) { - [self setFileURL:[NSURL fileURLWithPath:path]]; - [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL:[NSURL fileURLWithPath:path]]; - } - - if([spf objectForKey:SPQueryFavorites]) - [spfPreferences setObject:[spf objectForKey:SPQueryFavorites] forKey:SPQueryFavorites]; - if([spf objectForKey:SPQueryHistory]) - [spfPreferences setObject:[spf objectForKey:SPQueryHistory] forKey:SPQueryHistory]; - if([spf objectForKey:SPContentFilters]) - [spfPreferences setObject:[spf objectForKey:SPContentFilters] forKey:SPContentFilters]; - - [spfDocData setObject:[NSNumber numberWithBool:([connection objectForKey:@"password"]) ? YES : NO] forKey:@"save_password"]; - - [spfDocData setObject:[NSNumber numberWithBool:NO] forKey:@"auto_connect"]; - - [connectionController updateSSLInterface:self]; - - if([spf objectForKey:@"auto_connect"] && [[spf valueForKey:@"auto_connect"] boolValue]) { - [spfDocData setObject:[NSNumber numberWithBool:YES] forKey:@"auto_connect"]; - [connectionController initiateConnection:self]; - } - [spf release]; -} - -/** - * Restore session from SPF file if given - */ -- (void)restoreSession -{ - NSAutoreleasePool *taskPool = [[NSAutoreleasePool alloc] init]; - - // Check and set the table - NSArray *tables = [tablesListInstance tables]; - - BOOL isSelectedTableDefined = YES; - - if([tables indexOfObject:[spfSession objectForKey:@"table"]] == NSNotFound) { - isSelectedTableDefined = NO; - } - - // Restore toolbar setting - if([spfSession objectForKey:@"isToolbarVisible"]) - [mainToolbar setVisible:[[spfSession objectForKey:@"isToolbarVisible"] boolValue]]; - - // Reset database view encoding if differs from default - if([spfSession objectForKey:@"connectionEncoding"] && ![[mySQLConnection encoding] isEqualToString:[spfSession objectForKey:@"connectionEncoding"]]) - [self setConnectionEncoding:[spfSession objectForKey:@"connectionEncoding"] reloadingViews:YES]; - - if(isSelectedTableDefined) { - // Set table content details for restore - if([spfSession objectForKey:@"contentSortCol"]) - [tableContentInstance setSortColumnNameToRestore:[spfSession objectForKey:@"contentSortCol"] isAscending:[[spfSession objectForKey:@"contentSortCol"] boolValue]]; - if([spfSession objectForKey:@"contentPageNumber"]) - [tableContentInstance setPageToRestore:[[spfSession objectForKey:@"pageNumber"] integerValue]]; - if([spfSession objectForKey:@"contentViewport"]) - [tableContentInstance setViewportToRestore:NSRectFromString([spfSession objectForKey:@"contentViewport"])]; - if([spfSession objectForKey:@"contentFilter"]) - [tableContentInstance setFiltersToRestore:[spfSession objectForKey:@"contentFilter"]]; - - // Select table - [tablesListInstance selectTableAtIndex:[NSNumber numberWithInteger:[tables indexOfObject:[spfSession objectForKey:@"table"]]]]; - - // TODO up to now it doesn't work - if([spfSession objectForKey:@"contentSelectedIndexSet"]) { - NSMutableIndexSet *anIndexSet = [NSMutableIndexSet indexSet]; - NSArray *items = [spfSession objectForKey:@"contentSelectedIndexSet"]; - NSUInteger i; - for(i=0; i<[items count]; i++) - [anIndexSet addIndex:(NSUInteger)NSArrayObjectAtIndex(items, i)]; - - [tableContentInstance setSelectedRowIndexesToRestore:anIndexSet]; - } - - [[tablesListInstance valueForKeyPath:@"tablesListView"] scrollRowToVisible:[tables indexOfObject:[spfSession objectForKey:@"selectedTable"]]]; - - } - - // Select view - if([[spfSession objectForKey:@"view"] isEqualToString:@"SP_VIEW_STRUCTURE"]) - [self viewStructure:self]; - else if([[spfSession objectForKey:@"view"] isEqualToString:@"SP_VIEW_CONTENT"]) - [self viewContent:self]; - else if([[spfSession objectForKey:@"view"] isEqualToString:@"SP_VIEW_CUSTOMQUERY"]) - [self viewQuery:self]; - else if([[spfSession objectForKey:@"view"] isEqualToString:@"SP_VIEW_STATUS"]) - [self viewStatus:self]; - else if([[spfSession objectForKey:@"view"] isEqualToString:@"SP_VIEW_RELATIONS"]) - [self viewRelations:self]; - else if([[spfSession objectForKey:@"view"] isEqualToString:@"SP_VIEW_TRIGGERS"]) - [self viewTriggers:self]; - - [self updateWindowTitle:self]; - - // dealloc spfSession data - [spfSession release]; - spfSession = nil; - - // End the task - [self endTask]; - [taskPool drain]; -} - -/** * Set the return code for entering the encryption passowrd sheet */ - (IBAction)closePasswordSheet:(id)sender @@ -2556,7 +2159,7 @@ { [[NSApp delegate] newWindow:self]; SPDatabaseDocument *newTableDocument = [[NSApp delegate] frontDocument]; - [newTableDocument initWithConnectionFile:[[self fileURL] path]]; + [newTableDocument setStateFromConnectionFile:[[self fileURL] path]]; } /** @@ -3297,182 +2900,72 @@ } - NSString *aString; - - NSMutableDictionary *spfdata = [NSMutableDictionary dictionary]; - NSMutableDictionary *connection = [NSMutableDictionary dictionary]; - NSMutableDictionary *session = nil; - NSMutableDictionary *data = [NSMutableDictionary dictionary]; + // Set up the dictionary to save to file, together with a data store + NSMutableDictionary *spfStructure = [NSMutableDictionary dictionary]; + NSMutableDictionary *spfData = [NSMutableDictionary dictionary]; - NSIndexSet *contentSelectedIndexSet = [tableContentInstance selectedRowIndexes]; - - [spfdata setObject:[NSNumber numberWithInteger:1] forKey:@"version"]; - [spfdata setObject:@"connection" forKey:@"format"]; - [spfdata setObject:@"mysql" forKey:@"rdbms_type"]; + // Add basic details + [spfStructure setObject:[NSNumber numberWithInteger:1] forKey:@"version"]; + [spfStructure setObject:@"connection" forKey:@"format"]; + [spfStructure setObject:@"mysql" forKey:@"rdbms_type"]; if([self mySQLVersion]) - [spfdata setObject:[self mySQLVersion] forKey:@"rdbms_version"]; - - // Store the preferences - take them from the current document URL to catch renaming - [spfdata setObject:[[SPQueryController sharedQueryController] favoritesForFileURL:[self fileURL]] forKey:SPQueryFavorites]; - [spfdata setObject:[[SPQueryController sharedQueryController] historyForFileURL:[self fileURL]] forKey:SPQueryHistory]; - [spfdata setObject:[[SPQueryController sharedQueryController] contentFilterForFileURL:[self fileURL]] forKey:SPContentFilters]; - - [spfdata setObject:[spfDocData_temp objectForKey:@"encrypted"] forKey:@"encrypted"]; - - [spfdata setObject:[spfDocData_temp objectForKey:@"auto_connect"] forKey:@"auto_connect"]; - - if([[self keyChainID] length]) - [connection setObject:[self keyChainID] forKey:@"kcid"]; - [connection setObject:[self name] forKey:@"name"]; - [connection setObject:[self host] forKey:@"host"]; - [connection setObject:[self user] forKey:@"user"]; - - [connection setObject:[NSNumber numberWithInt:[connectionController useSSL]] forKey:@"useSSL"]; - [connection setObject:[NSNumber numberWithInt:[connectionController sslKeyFileLocationEnabled]] forKey:@"sslKeyFileLocationEnabled"]; - [connection setObject:[connectionController sslKeyFileLocation] forKey:@"sslKeyFileLocation"]; - [connection setObject:[NSNumber numberWithInt:[connectionController sslCertificateFileLocationEnabled]] forKey:@"sslCertificateFileLocationEnabled"]; - [connection setObject:[connectionController sslCertificateFileLocation] forKey:@"sslCertificateFileLocation"]; - [connection setObject:[NSNumber numberWithInt:[connectionController sslCACertFileLocationEnabled]] forKey:@"sslCACertFileLocationEnabled"]; - [connection setObject:[connectionController sslCACertFileLocation] forKey:@"sslCACertFileLocation"]; + [spfStructure setObject:[self mySQLVersion] forKey:@"rdbms_version"]; - switch([connectionController type]) { - case SPTCPIPConnection: - aString = @"SPTCPIPConnection"; - break; - case SPSocketConnection: - aString = @"SPSocketConnection"; - if ([connectionController socket] && [[connectionController socket] length]) [connection setObject:[connectionController socket] forKey:@"socket"]; - break; - case SPSSHTunnelConnection: - aString = @"SPSSHTunnelConnection"; - [connection setObject:[connectionController sshHost] forKey:@"ssh_host"]; - [connection setObject:[connectionController sshUser] forKey:@"ssh_user"]; - [connection setObject:[NSNumber numberWithInt:[connectionController sshKeyLocationEnabled]] forKey:@"ssh_keyLocationEnabled"]; - [connection setObject:[connectionController sshKeyLocation] forKey:@"ssh_keyLocation"]; - if([connectionController sshPort] && [[connectionController sshPort] length]) - [connection setObject:[NSNumber numberWithInteger:[[connectionController sshPort] integerValue]] forKey:@"ssh_port"]; - break; - default: - aString = @"SPTCPIPConnection"; - } - [connection setObject:aString forKey:@"type"]; + // Add auto-connect if appropriate + [spfStructure setObject:[spfDocData_temp objectForKey:@"auto_connect"] forKey:@"auto_connect"]; - if([[spfDocData_temp objectForKey:@"save_password"] boolValue]) { - - NSString *pw = [self keychainPasswordForConnection:nil]; - if(![pw length]) pw = [connectionController password]; - if (pw) - [connection setObject:pw forKey:@"password"]; - else - [connection setObject:@"" forKey:@"password"]; - - if([connectionController type] == SPSSHTunnelConnection) { - NSString *sshpw = [self keychainPasswordForSSHConnection:nil]; - if(![sshpw length]) sshpw = [connectionController sshPassword]; - if (sshpw) - [connection setObject:sshpw forKey:@"ssh_password"]; - else - [connection setObject:@"" forKey:@"ssh_password"]; - } - } - - if([connectionController port] && [[connectionController port] length]) - [connection setObject:[NSNumber numberWithInteger:[[connectionController port] integerValue]] forKey:@"port"]; - - if([[self database] length]) - [connection setObject:[self database] forKey:@"database"]; + // Set up the document details to store + NSMutableDictionary *stateDetailsToSave = [NSMutableDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithBool:YES], @"connection", + [NSNumber numberWithBool:YES], @"history", + nil]; // Include session data like selected table, view etc. ? - if([[spfDocData_temp objectForKey:@"include_session"] boolValue]) { - - session = [NSMutableDictionary dictionary]; - - if([[self table] length]) - [session setObject:[self table] forKey:@"table"]; - if([tableContentInstance sortColumnName]) - [session setObject:[tableContentInstance sortColumnName] forKey:@"contentSortCol"]; - - switch([spHistoryControllerInstance currentlySelectedView]){ - case SPTableViewStructure: - aString = @"SP_VIEW_STRUCTURE"; - break; - case SPTableViewContent: - aString = @"SP_VIEW_CONTENT"; - break; - case SPTableViewCustomQuery: - aString = @"SP_VIEW_CUSTOMQUERY"; - break; - case SPTableViewStatus: - aString = @"SP_VIEW_STATUS"; - break; - case SPTableViewRelations: - aString = @"SP_VIEW_RELATIONS"; - break; - case SPTableViewTriggers: - aString = @"SP_VIEW_TRIGGERS"; - break; - default: - aString = @"SP_VIEW_STRUCTURE"; - } - [session setObject:aString forKey:@"view"]; - - [session setObject:[NSNumber numberWithBool:[[parentWindow toolbar] isVisible]] forKey:@"isToolbarVisible"]; - [session setObject:[mySQLConnection encoding] forKey:@"connectionEncoding"]; - - [session setObject:[NSNumber numberWithBool:[tableContentInstance sortColumnIsAscending]] forKey:@"contentSortColIsAsc"]; - [session setObject:[NSNumber numberWithInteger:[tableContentInstance pageNumber]] forKey:@"contentPageNumber"]; - [session setObject:[NSNumber numberWithFloat:[tableContentInstance tablesListWidth]] forKey:@"windowVerticalDividerPosition"]; - [session setObject:NSStringFromRect([tableContentInstance viewport]) forKey:@"contentViewport"]; - if([tableContentInstance filterSettings]) - [session setObject:[tableContentInstance filterSettings] forKey:@"contentFilter"]; - - if (contentSelectedIndexSet && [contentSelectedIndexSet count]) { - NSMutableArray *indices = [NSMutableArray array]; - NSUInteger indexBuffer[[contentSelectedIndexSet count]]; - NSUInteger limit = [contentSelectedIndexSet getIndexes:indexBuffer maxCount:[contentSelectedIndexSet count] inIndexRange:NULL]; - NSUInteger idx; - for (idx = 0; idx < limit; idx++) { - [indices addObject:[NSNumber numberWithInteger:indexBuffer[idx]]]; - } - [session setObject:indices forKey:@"contentSelectedIndexSet"]; - } - } - - if([[spfDocData_temp objectForKey:@"save_editor_content"] boolValue]) { - if(session == nil) - session = [NSMutableDictionary dictionary]; - - if([[[[customQueryInstance valueForKeyPath:@"textView"] textStorage] string] length] > 50000) - [session setObject:[[[[[customQueryInstance valueForKeyPath:@"textView"] textStorage] string] dataUsingEncoding:NSUTF8StringEncoding] compress] forKey:@"queries"]; - else - [session setObject:[[[customQueryInstance valueForKeyPath:@"textView"] textStorage] string] forKey:@"queries"]; - } - - [data setObject:connection forKey:@"connection"]; - if(session != nil) - [data setObject:session forKey:@"session"]; - - if(![[spfDocData_temp objectForKey:@"encrypted"] boolValue]) { - [spfdata setObject:data forKey:@"data"]; + if ([[spfDocData_temp objectForKey:@"include_session"] boolValue]) + [stateDetailsToSave setObject:[NSNumber numberWithBool:YES] forKey:@"session"]; + + // Include the query editor contents if asked to + if ([[spfDocData_temp objectForKey:@"save_editor_content"] boolValue]) { + [stateDetailsToSave setObject:[NSNumber numberWithBool:YES] forKey:@"query"]; + [stateDetailsToSave setObject:[NSNumber numberWithBool:YES] forKey:@"enablecompression"]; + } + + // Add passwords if asked to + if ([[spfDocData_temp objectForKey:@"save_password"] boolValue]) + [stateDetailsToSave setObject:[NSNumber numberWithBool:YES] forKey:@"password"]; + + // Retrieve details and add to the appropriate dictionaries + NSMutableDictionary *stateDetails = [NSMutableDictionary dictionaryWithDictionary:[self stateIncludingDetails:stateDetailsToSave]]; + [spfStructure setObject:[stateDetails objectForKey:SPQueryFavorites] forKey:SPQueryFavorites]; + [spfStructure setObject:[stateDetails objectForKey:SPQueryHistory] forKey:SPQueryHistory]; + [spfStructure setObject:[stateDetails objectForKey:SPContentFilters] forKey:SPContentFilters]; + [stateDetails removeObjectsForKeys:[NSArray arrayWithObjects:SPQueryFavorites, SPQueryHistory, SPContentFilters, nil]]; + [spfData addEntriesFromDictionary:stateDetails]; + + // Determine whether to use encryption when adding the data + [spfStructure setObject:[spfDocData_temp objectForKey:@"encrypted"] forKey:@"encrypted"]; + if (![[spfDocData_temp objectForKey:@"encrypted"] boolValue]) { + [spfStructure setObject:spfData forKey:@"data"]; } else { - NSMutableData *encryptdata = [[[NSMutableData alloc] init] autorelease]; - NSKeyedArchiver *archiver = [[[NSKeyedArchiver alloc] initForWritingWithMutableData:encryptdata] autorelease]; - [archiver encodeObject:data forKey:@"data"]; + NSMutableData *dataToEncrypt = [[[NSMutableData alloc] init] autorelease]; + NSKeyedArchiver *archiver = [[[NSKeyedArchiver alloc] initForWritingWithMutableData:dataToEncrypt] autorelease]; + [archiver encodeObject:spfData forKey:@"data"]; [archiver finishEncoding]; - [spfdata setObject:[encryptdata dataEncryptedWithPassword:[spfDocData_temp objectForKey:@"e_string"]] forKey:@"data"]; + [spfStructure setObject:[dataToEncrypt dataEncryptedWithPassword:[spfDocData_temp objectForKey:@"e_string"]] forKey:@"data"]; } + // Convert to plist NSString *err = nil; - NSData *plist = [NSPropertyListSerialization dataFromPropertyList:spfdata - format:NSPropertyListXMLFormat_v1_0 - errorDescription:&err]; + NSData *plist = [NSPropertyListSerialization dataFromPropertyList:spfStructure + format:NSPropertyListXMLFormat_v1_0 + errorDescription:&err]; - if(err != nil) { + if (err != nil) { NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while converting connection data", @"error while converting connection data")] defaultButton:NSLocalizedString(@"OK", @"OK button") alternateButton:nil - otherButton:nil - informativeTextWithFormat:err]; + otherButton:nil + informativeTextWithFormat:err]; [alert setAlertStyle:NSCriticalAlertStyle]; [alert runModal]; @@ -3481,18 +2974,18 @@ NSError *error = nil; [plist writeToFile:fileName options:NSAtomicWrite error:&error]; - if(error != nil){ + if (error != nil){ NSAlert *errorAlert = [NSAlert alertWithError:error]; [errorAlert runModal]; return NO; } - if(contextInfo == nil) { + if (contextInfo == nil) { // Register and update query favorites, content filter, and history for the (new) file URL NSMutableDictionary *preferences = [[NSMutableDictionary alloc] init]; - [preferences setObject:[spfdata objectForKey:SPQueryHistory] forKey:SPQueryHistory]; - [preferences setObject:[spfdata objectForKey:SPQueryFavorites] forKey:SPQueryFavorites]; - [preferences setObject:[spfdata objectForKey:SPContentFilters] forKey:SPContentFilters]; + [preferences setObject:[spfStructure objectForKey:SPQueryHistory] forKey:SPQueryHistory]; + [preferences setObject:[spfStructure objectForKey:SPQueryFavorites] forKey:SPQueryFavorites]; + [preferences setObject:[spfStructure objectForKey:SPContentFilters] forKey:SPContentFilters]; [[SPQueryController sharedQueryController] registerDocumentWithFileURL:[NSURL fileURLWithPath:fileName] andContextInfo:preferences]; [self setFileURL:[NSURL fileURLWithPath:fileName]]; @@ -3512,6 +3005,35 @@ } /** + * Open the currently selected database in a new tab, clearing any table selection. + */ +- (IBAction)openDatabaseInNewTab:(id)sender +{ + + // Add a new tab to the window + [[parentWindow windowController] addNewConnection:self]; + + // Get the current state + NSDictionary *allStateDetails = [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithBool:YES], @"connection", + [NSNumber numberWithBool:YES], @"history", + [NSNumber numberWithBool:YES], @"session", + [NSNumber numberWithBool:YES], @"query", + [NSNumber numberWithBool:YES], @"password", + nil]; + NSMutableDictionary *currentState = [NSMutableDictionary dictionaryWithDictionary:[self stateIncludingDetails:allStateDetails]]; + + // Ensure it's set to autoconnect, and clear the table + [currentState setObject:[NSNumber numberWithBool:YES] forKey:@"auto_connect"]; + NSMutableDictionary *sessionDict = [NSMutableDictionary dictionaryWithDictionary:[currentState objectForKey:@"session"]]; + [sessionDict removeObjectForKey:@"table"]; + [currentState setObject:sessionDict forKey:@"session"]; + + // Set the connection on the new tab + [[[NSApp delegate] frontDocument] setState:currentState]; +} + +/** * Passes the request to the dataImport object */ - (IBAction)import:(id)sender @@ -3594,10 +3116,11 @@ } } - if ([menuItem action] == @selector(import:) || - [menuItem action] == @selector(removeDatabase:) || - [menuItem action] == @selector(copyDatabase:) || - [menuItem action] == @selector(renameDatabase:) || + if ([menuItem action] == @selector(import:) || + [menuItem action] == @selector(removeDatabase:) || + [menuItem action] == @selector(copyDatabase:) || + [menuItem action] == @selector(renameDatabase:) || + [menuItem action] == @selector(openDatabaseInNewTab:) || [menuItem action] == @selector(refreshTables:)) { return ([self database] != nil); @@ -4355,6 +3878,611 @@ } #pragma mark - +#pragma mark State saving and setting + +/** + * Retrieve the current database document state for saving. A supplied dictionary + * determines the level of detail that is required, with the following optional keys: + * - connection: Connection settings (with keychain references where available) and database + * - password: Whether to include passwords in the returned connection details + * - session: Selected table and view, together with content view filter, sort, scroll position + * - history: query history, per-doc query favourites, and per-doc content filters + * - query: custom query editor content + * - enablecompression: large (>50k) custom query editor contents will be stored as compressed data + * If none of these are supplied, nil will be returned. + */ +- (NSDictionary *) stateIncludingDetails:(NSDictionary *)detailsToReturn +{ + BOOL returnConnection = [[detailsToReturn objectForKey:@"connection"] boolValue]; + BOOL includePasswords = [[detailsToReturn objectForKey:@"password"] boolValue]; + BOOL returnSession = [[detailsToReturn objectForKey:@"session"] boolValue]; + BOOL returnHistory = [[detailsToReturn objectForKey:@"history"] boolValue]; + BOOL returnQuery = [[detailsToReturn objectForKey:@"query"] boolValue]; + + if (!returnConnection && !returnSession && !returnHistory && !returnQuery) return nil; + NSMutableDictionary *stateDetails = [NSMutableDictionary dictionary]; + + // Add connection details + if (returnConnection) { + NSMutableDictionary *connection = [NSMutableDictionary dictionary]; + + [connection setObject:@"mysql" forKey:@"rdbms_type"]; + + NSString *connectionType; + switch ([connectionController type]) { + case SPTCPIPConnection: + connectionType = @"SPTCPIPConnection"; + break; + case SPSocketConnection: + connectionType = @"SPSocketConnection"; + if ([connectionController socket] && [[connectionController socket] length]) [connection setObject:[connectionController socket] forKey:@"socket"]; + break; + case SPSSHTunnelConnection: + connectionType = @"SPSSHTunnelConnection"; + [connection setObject:[connectionController sshHost] forKey:@"ssh_host"]; + [connection setObject:[connectionController sshUser] forKey:@"ssh_user"]; + [connection setObject:[NSNumber numberWithInt:[connectionController sshKeyLocationEnabled]] forKey:@"ssh_keyLocationEnabled"]; + [connection setObject:[connectionController sshKeyLocation] forKey:@"ssh_keyLocation"]; + if ([connectionController sshPort] && [[connectionController sshPort] length]) + [connection setObject:[NSNumber numberWithInteger:[[connectionController sshPort] integerValue]] forKey:@"ssh_port"]; + break; + default: + connectionType = @"SPTCPIPConnection"; + } + [connection setObject:connectionType forKey:@"type"]; + + if ([[self keyChainID] length]) [connection setObject:[self keyChainID] forKey:@"kcid"]; + [connection setObject:[self name] forKey:@"name"]; + [connection setObject:[self host] forKey:@"host"]; + [connection setObject:[self user] forKey:@"user"]; + if([connectionController port] && [[connectionController port] length]) + [connection setObject:[NSNumber numberWithInteger:[[connectionController port] integerValue]] forKey:@"port"]; + if([[self database] length]) + [connection setObject:[self database] forKey:@"database"]; + + if (includePasswords) { + NSString *pw = [self keychainPasswordForConnection:nil]; + if (![pw length]) pw = [connectionController password]; + if (pw) + [connection setObject:pw forKey:@"password"]; + else + [connection setObject:@"" forKey:@"password"]; + + if ([connectionController type] == SPSSHTunnelConnection) { + NSString *sshpw = [self keychainPasswordForSSHConnection:nil]; + if(![sshpw length]) sshpw = [connectionController sshPassword]; + if (sshpw) + [connection setObject:sshpw forKey:@"ssh_password"]; + else + [connection setObject:@"" forKey:@"ssh_password"]; + } + } + + [connection setObject:[NSNumber numberWithInt:[connectionController useSSL]] forKey:@"useSSL"]; + [connection setObject:[NSNumber numberWithInt:[connectionController sslKeyFileLocationEnabled]] forKey:@"sslKeyFileLocationEnabled"]; + [connection setObject:[connectionController sslKeyFileLocation] forKey:@"sslKeyFileLocation"]; + [connection setObject:[NSNumber numberWithInt:[connectionController sslCertificateFileLocationEnabled]] forKey:@"sslCertificateFileLocationEnabled"]; + [connection setObject:[connectionController sslCertificateFileLocation] forKey:@"sslCertificateFileLocation"]; + [connection setObject:[NSNumber numberWithInt:[connectionController sslCACertFileLocationEnabled]] forKey:@"sslCACertFileLocationEnabled"]; + [connection setObject:[connectionController sslCACertFileLocation] forKey:@"sslCACertFileLocation"]; + + [stateDetails setObject:[NSDictionary dictionaryWithDictionary:connection] forKey:@"connection"]; + } + + // Add document-specific saved settings + if (returnHistory) { + [stateDetails setObject:[[SPQueryController sharedQueryController] favoritesForFileURL:[self fileURL]] forKey:SPQueryFavorites]; + [stateDetails setObject:[[SPQueryController sharedQueryController] historyForFileURL:[self fileURL]] forKey:SPQueryHistory]; + [stateDetails setObject:[[SPQueryController sharedQueryController] contentFilterForFileURL:[self fileURL]] forKey:SPContentFilters]; + } + + // Set up a session state dictionary for either state or custom query + NSMutableDictionary *sessionState = [NSMutableDictionary dictionary]; + + // Store session state if appropriate + if (returnSession) { + + if ([[self table] length]) + [sessionState setObject:[self table] forKey:@"table"]; + + NSString *currentlySelectedViewName; + switch ([spHistoryControllerInstance currentlySelectedView]) { + case SPTableViewStructure: + currentlySelectedViewName = @"SP_VIEW_STRUCTURE"; + break; + case SPTableViewContent: + currentlySelectedViewName = @"SP_VIEW_CONTENT"; + break; + case SPTableViewCustomQuery: + currentlySelectedViewName = @"SP_VIEW_CUSTOMQUERY"; + break; + case SPTableViewStatus: + currentlySelectedViewName = @"SP_VIEW_STATUS"; + break; + case SPTableViewRelations: + currentlySelectedViewName = @"SP_VIEW_RELATIONS"; + break; + case SPTableViewTriggers: + currentlySelectedViewName = @"SP_VIEW_TRIGGERS"; + break; + default: + currentlySelectedViewName = @"SP_VIEW_STRUCTURE"; + } + [sessionState setObject:currentlySelectedViewName forKey:@"view"]; + + [sessionState setObject:[mySQLConnection encoding] forKey:@"connectionEncoding"]; + + [sessionState setObject:[NSNumber numberWithBool:[[parentWindow toolbar] isVisible]] forKey:@"isToolbarVisible"]; + [sessionState setObject:[NSNumber numberWithFloat:[tableContentInstance tablesListWidth]] forKey:@"windowVerticalDividerPosition"]; + + if ([tableContentInstance sortColumnName]) + [sessionState setObject:[tableContentInstance sortColumnName] forKey:@"contentSortCol"]; + [sessionState setObject:[NSNumber numberWithBool:[tableContentInstance sortColumnIsAscending]] forKey:@"contentSortColIsAsc"]; + [sessionState setObject:[NSNumber numberWithInteger:[tableContentInstance pageNumber]] forKey:@"contentPageNumber"]; + [sessionState setObject:NSStringFromRect([tableContentInstance viewport]) forKey:@"contentViewport"]; + if ([tableContentInstance filterSettings]) + [sessionState setObject:[tableContentInstance filterSettings] forKey:@"contentFilter"]; + + NSIndexSet *contentSelectedIndexSet = [tableContentInstance selectedRowIndexes]; + if (contentSelectedIndexSet && [contentSelectedIndexSet count]) { + NSMutableArray *indices = [NSMutableArray array]; + NSUInteger indexBuffer[[contentSelectedIndexSet count]]; + NSUInteger limit = [contentSelectedIndexSet getIndexes:indexBuffer maxCount:[contentSelectedIndexSet count] inIndexRange:NULL]; + NSUInteger idx; + for (idx = 0; idx < limit; idx++) { + [indices addObject:[NSNumber numberWithInteger:indexBuffer[idx]]]; + } + [sessionState setObject:indices forKey:@"contentSelectedIndexSet"]; + } + } + + // Add the custom query editor content if appropriate + if (returnQuery) { + NSString *queryString = [[[customQueryInstance valueForKeyPath:@"textView"] textStorage] string]; + if ([[detailsToReturn objectForKey:@"enablecompression"] boolValue] && [queryString length] > 50000) { + [sessionState setObject:[[queryString dataUsingEncoding:NSUTF8StringEncoding] compress] forKey:@"queries"]; + } else { + [sessionState setObject:queryString forKey:@"queries"]; + } + } + + // Store the session state dictionary if either state or custom queries were saved + if ([sessionState count]) + [stateDetails setObject:[NSDictionary dictionaryWithDictionary:sessionState] forKey:@"session"]; + + return stateDetails; +} + +/** + * Set the state of the document to the supplied dictionary, which should + * at least contain a "connection" dictionary of details. + * Returns whether the state was set successfully. + */ +- (BOOL)setState:(NSDictionary *)stateDetails +{ + NSDictionary *connection = nil; + NSInteger connectionType = -1; + + // If this document already has a connection, don't proceed. + if (mySQLConnection) return NO; + + // Load the connection data from the state dictionary + connection = [NSDictionary dictionaryWithDictionary:[stateDetails objectForKey:@"connection"]]; + if (!connection) return NO; + + [self updateWindowTitle:self]; + + // Deselect all favorites on the connection controller + [[connectionController valueForKeyPath:@"favoritesTable"] deselectAll:connectionController]; + + // Suppress the possibility to choose an other connection from the favorites + // if a connection should initialized by SPF file. Otherwise it could happen + // that the SPF file runs out of sync. + [[connectionController valueForKeyPath:@"favoritesTable"] setEnabled:NO]; + + // Ensure the connection controller is set to a blank slate + [connectionController setName:@""]; + [connectionController setUser:@""]; + [connectionController setHost:@""]; + [connectionController setPort:@""]; + [connectionController setSocket:@""]; + [connectionController setUseSSL:NSOffState]; + [connectionController setSslKeyFileLocationEnabled:NSOffState]; + [connectionController setSslKeyFileLocation:nil]; + [connectionController setSslCertificateFileLocationEnabled:NSOffState]; + [connectionController setSslCertificateFileLocation:nil]; + [connectionController setSslCACertFileLocationEnabled:NSOffState]; + [connectionController setSslCACertFileLocation:nil]; + [connectionController setSshHost:@""]; + [connectionController setSshUser:@""]; + [connectionController setSshKeyLocationEnabled:NSOffState]; + [connectionController setSshKeyLocation:nil]; + [connectionController setSshPort:@""]; + [connectionController setDatabase:@""]; + [connectionController setPassword:nil]; + [connectionController setSshPassword:nil]; + + // Set the correct connection type + if ([connection objectForKey:@"type"]) { + if ([[connection objectForKey:@"type"] isEqualToString:@"SPTCPIPConnection"]) + connectionType = SPTCPIPConnection; + else if ([[connection objectForKey:@"type"] isEqualToString:@"SPSocketConnection"]) + connectionType = SPSocketConnection; + else if ([[connection objectForKey:@"type"] isEqualToString:@"SPSSHTunnelConnection"]) + connectionType = SPSSHTunnelConnection; + else + connectionType = SPTCPIPConnection; + + [connectionController setType:connectionType]; + [connectionController resizeTabViewToConnectionType:connectionType animating:NO]; + } + + // Set basic details + if ([connection objectForKey:@"name"]) + [connectionController setName:[connection objectForKey:@"name"]]; + if ([connection objectForKey:@"user"]) + [connectionController setUser:[connection objectForKey:@"user"]]; + if ([connection objectForKey:@"host"]) + [connectionController setHost:[connection objectForKey:@"host"]]; + if ([connection objectForKey:@"port"]) + [connectionController setPort:[NSString stringWithFormat:@"%ld", (long)[[connection objectForKey:@"port"] integerValue]]]; + + // Set SSL details + if ([connection objectForKey:@"useSSL"]) + [connectionController setUseSSL:[[connection objectForKey:@"useSSL"] intValue]]; + if ([connection objectForKey:@"sslKeyFileLocationEnabled"]) + [connectionController setSslKeyFileLocationEnabled:[[connection objectForKey:@"sslKeyFileLocationEnabled"] intValue]]; + if ([connection objectForKey:@"sslKeyFileLocation"]) + [connectionController setSslKeyFileLocation:[connection objectForKey:@"sslKeyFileLocation"]]; + if ([connection objectForKey:@"sslCertificateFileLocationEnabled"]) + [connectionController setSslCertificateFileLocationEnabled:[[connection objectForKey:@"sslCertificateFileLocationEnabled"] intValue]]; + if ([connection objectForKey:@"sslCertificateFileLocation"]) + [connectionController setSslCertificateFileLocation:[connection objectForKey:@"sslCertificateFileLocation"]]; + if ([connection objectForKey:@"sslCACertFileLocationEnabled"]) + [connectionController setSslCACertFileLocationEnabled:[[connection objectForKey:@"sslCACertFileLocationEnabled"] intValue]]; + if ([connection objectForKey:@"sslCACertFileLocation"]) + [connectionController setSslCACertFileLocation:[connection objectForKey:@"sslCACertFileLocation"]]; + + // Set the keychain details if available + if ([connection objectForKey:@"kcid"] && [(NSString *)[connection objectForKey:@"kcid"] length]) + [self setKeychainID:[connection objectForKey:@"kcid"]]; + + // Set password - if not in SPF file try to get it via the KeyChain + if ([connection objectForKey:@"password"]) + [connectionController setPassword:[connection objectForKey:@"password"]]; + else { + NSString *pw = [self keychainPasswordForConnection:nil]; + if (pw) + [connectionController setPassword:pw]; + } + + // Set the socket details, whether or not the type is a socket + if ([connection objectForKey:@"socket"]) + [connectionController setSocket:[connection objectForKey:@"socket"]]; + + // Set SSH details if available, whether or not the SSH type is currently active (to allow fallback on failure) + if ([connection objectForKey:@"ssh_host"]) + [connectionController setSshHost:[connection objectForKey:@"ssh_host"]]; + if ([connection objectForKey:@"ssh_user"]) + [connectionController setSshUser:[connection objectForKey:@"ssh_user"]]; + if ([connection objectForKey:@"ssh_keyLocationEnabled"]) + [connectionController setSshKeyLocationEnabled:[[connection objectForKey:@"ssh_keyLocationEnabled"] intValue]]; + if ([connection objectForKey:@"ssh_keyLocation"]) + [connectionController setSshKeyLocation:[connection objectForKey:@"ssh_keyLocation"]]; + if ([connection objectForKey:@"ssh_port"]) + [connectionController setSshPort:[NSString stringWithFormat:@"%ld", (long)[[connection objectForKey:@"ssh_port"] integerValue]]]; + + // Set the SSH password - if not in SPF file try to get it via the KeyChain + if ([connection objectForKey:@"ssh_password"]) + [connectionController setSshPassword:[connection objectForKey:@"ssh_password"]]; + else { + NSString *sshpw = [self keychainPasswordForSSHConnection:nil]; + if(sshpw) + [connectionController setSshPassword:sshpw]; + } + + // Restore the selected database if saved + if ([connection objectForKey:@"database"]) + [connectionController setDatabase:[connection objectForKey:@"database"]]; + + // Store session details - if provided - for later setting once the connection is established + if ([stateDetails objectForKey:@"session"]) { + spfSession = [[NSDictionary dictionaryWithDictionary:[stateDetails objectForKey:@"session"]] retain]; + } + + // Restore favourites and history + if ([stateDetails objectForKey:SPQueryFavorites]) + [spfPreferences setObject:[stateDetails objectForKey:SPQueryFavorites] forKey:SPQueryFavorites]; + if ([stateDetails objectForKey:SPQueryHistory]) + [spfPreferences setObject:[stateDetails objectForKey:SPQueryHistory] forKey:SPQueryHistory]; + if ([stateDetails objectForKey:SPContentFilters]) + [spfPreferences setObject:[stateDetails objectForKey:SPContentFilters] forKey:SPContentFilters]; + + [connectionController updateSSLInterface:self]; + + // Autoconnect if appropriate + if ([stateDetails objectForKey:@"auto_connect"] && [[stateDetails valueForKey:@"auto_connect"] boolValue]) { + [connectionController initiateConnection:self]; + } +} + +/** + * Initialise the document with the connection file at the supplied path. + */ +- (void)setStateFromConnectionFile:(NSString *)path +{ + NSError *readError = nil; + NSString *convError = nil; + NSPropertyListFormat format; + + NSString *encryptpw = nil; + NSMutableDictionary *data = nil; + NSDictionary *spf = nil; + + + // Read the property list data, and unserialize it. + NSData *pData = [NSData dataWithContentsOfFile:path options:NSUncachedRead error:&readError]; + + spf = [[NSPropertyListSerialization propertyListFromData:pData + mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&convError] retain]; + + if (!spf || readError != nil || [convError length] || !(format == NSPropertyListXMLFormat_v1_0 || format == NSPropertyListBinaryFormat_v1_0)) { + NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while reading connection data file", @"error while reading connection data file")] + defaultButton:NSLocalizedString(@"OK", @"OK button") + alternateButton:nil + otherButton:nil + informativeTextWithFormat:NSLocalizedString(@"Connection data file couldn't be read.", @"error while reading connection data file")]; + + [alert setAlertStyle:NSCriticalAlertStyle]; + [alert runModal]; + if (spf) [spf release]; + [self closeAndDisconnect]; + return; + } + + // If the .spf format is unhandled, error. + if (![[spf objectForKey:@"format"] isEqualToString:@"connection"]) { + NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Warning", @"warning")] + defaultButton:NSLocalizedString(@"OK", @"OK button") + alternateButton:nil + otherButton:nil + informativeTextWithFormat:[NSString stringWithFormat:NSLocalizedString(@"The chosen file “%@” contains ‘%@’ data.", @"message while reading a spf file which matches non-supported formats."), path, [spf objectForKey:@"format"]]]; + + [alert setAlertStyle:NSWarningAlertStyle]; + [spf release]; + [self closeAndDisconnect]; + [alert runModal]; + return; + } + + // Error if the expected data source wasn't present in the file + if (![spf objectForKey:@"data"]) { + NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while reading connection data file", @"error while reading connection data file")] + defaultButton:NSLocalizedString(@"OK", @"OK button") + alternateButton:nil + otherButton:nil + informativeTextWithFormat:NSLocalizedString(@"No data found.", @"no data found")]; + + [alert setAlertStyle:NSCriticalAlertStyle]; + [alert runModal]; + [spf release]; + [self closeAndDisconnect]; + return; + } + + // Ask for a password if SPF file passwords were encrypted, via a sheet + if ([spf objectForKey:@"encrypted"] && [[spf valueForKey:@"encrypted"] boolValue]) { + if([self isSaveInBundle] && [[[NSApp delegate] spfSessionDocData] objectForKey:@"e_string"]) { + encryptpw = [[[NSApp delegate] spfSessionDocData] objectForKey:@"e_string"]; + } else { + [inputTextWindowHeader setStringValue:NSLocalizedString(@"Connection file is encrypted", @"Connection file is encrypted")]; + [inputTextWindowMessage setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Please enter the password for ‘%@’:", @"Please enter the password"), ([self isSaveInBundle]) ? [[[[NSApp delegate] sessionURL] absoluteString] lastPathComponent] : [path lastPathComponent]]]; + [inputTextWindowSecureTextField setStringValue:@""]; + [inputTextWindowSecureTextField selectText:nil]; + + [NSApp beginSheet:inputTextWindow modalForWindow:parentWindow modalDelegate:self didEndSelector:nil contextInfo:nil]; + + // wait for encryption password + NSModalSession session = [NSApp beginModalSessionForWindow:inputTextWindow]; + for (;;) { + + // Execute code on DefaultRunLoop + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode + beforeDate:[NSDate distantFuture]]; + + // Break the run loop if editSheet was closed + if ([NSApp runModalSession:session] != NSRunContinuesResponse + || ![inputTextWindow isVisible]) + break; + + // Execute code on DefaultRunLoop + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode + beforeDate:[NSDate distantFuture]]; + + } + [NSApp endModalSession:session]; + [inputTextWindow orderOut:nil]; + [NSApp endSheet:inputTextWindow]; + + if (passwordSheetReturnCode) { + encryptpw = [inputTextWindowSecureTextField stringValue]; + if ([self isSaveInBundle]) { + NSMutableDictionary *spfSessionData = [NSMutableDictionary dictionary]; + [spfSessionData addEntriesFromDictionary:[[NSApp delegate] spfSessionDocData]]; + [spfSessionData setObject:encryptpw forKey:@"e_string"]; + [[NSApp delegate] setSpfSessionDocData:spfSessionData]; + } + } else { + [self closeAndDisconnect]; + [spf release]; + return; + } + } + } + + if ([[spf objectForKey:@"data"] isKindOfClass:[NSDictionary class]]) + data = [NSMutableDictionary dictionaryWithDictionary:[spf objectForKey:@"data"]]; + else if ([[spf objectForKey:@"data"] isKindOfClass:[NSData class]]) { + NSData *decryptdata = nil; + decryptdata = [[[NSMutableData alloc] initWithData:[(NSData *)[spf objectForKey:@"data"] dataDecryptedWithPassword:encryptpw]] autorelease]; + if (decryptdata != nil && [decryptdata length]) { + NSKeyedUnarchiver *unarchiver = [[[NSKeyedUnarchiver alloc] initForReadingWithData:decryptdata] autorelease]; + data = [NSMutableDictionary dictionaryWithDictionary:(NSDictionary *)[unarchiver decodeObjectForKey:@"data"]]; + [unarchiver finishDecoding]; + } + if (data == nil) { + NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while reading connection data file", @"error while reading connection data file")] + defaultButton:NSLocalizedString(@"OK", @"OK button") + alternateButton:nil + otherButton:nil + informativeTextWithFormat:NSLocalizedString(@"Wrong data format or password.", @"wrong data format or password")]; + + [alert setAlertStyle:NSCriticalAlertStyle]; + [alert runModal]; + [self closeAndDisconnect]; + [spf release]; + return; + } + } + + // Ensure the data was read correctly, and has connection details + if (!data || ![data objectForKey:@"connection"]) { + NSString *informativeText; + if (!data) { + informativeText = NSLocalizedString(@"Wrong data format.", @"wrong data format"); + } else { + informativeText = NSLocalizedString(@"No connection data found.", @"no connection data found"); + } + NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while reading connection data file", @"error while reading connection data file")] + defaultButton:NSLocalizedString(@"OK", @"OK button") + alternateButton:nil + otherButton:nil + informativeTextWithFormat:informativeText]; + + [alert setAlertStyle:NSCriticalAlertStyle]; + [alert runModal]; + [self closeAndDisconnect]; + [spf release]; + return; + } + + // Move favourites and history into the data dictionary to pass to setState: + [data setObject:[spf objectForKey:SPQueryFavorites] forKey:SPQueryFavorites]; + [data setObject:[spf objectForKey:SPQueryHistory] forKey:SPQueryHistory]; + [data setObject:[spf objectForKey:SPContentFilters] forKey:SPContentFilters]; + + // Ensure the encryption status is stored in the spfDocData store for future saves + [spfDocData setObject:[NSNumber numberWithBool:NO] forKey:@"encrypted"]; + if (encryptpw != nil) { + [spfDocData setObject:[NSNumber numberWithBool:YES] forKey:@"encrypted"]; + [spfDocData setObject:encryptpw forKey:@"e_string"]; + } + encryptpw = nil; + + // If session data is available, ensure it is marked for save + if ([data objectForKey:@"session"]) { + [spfDocData setObject:[NSNumber numberWithBool:YES] forKey:@"include_session"]; + } + + if (![self isSaveInBundle]) { + [self setFileURL:[NSURL fileURLWithPath:path]]; + [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL:[NSURL fileURLWithPath:path]]; + } + + [spfDocData setObject:[NSNumber numberWithBool:([[data objectForKey:@"connection"] objectForKey:@"password"]) ? YES : NO] forKey:@"save_password"]; + + [spfDocData setObject:[NSNumber numberWithBool:NO] forKey:@"auto_connect"]; + + if([spf objectForKey:@"auto_connect"] && [[spf valueForKey:@"auto_connect"] boolValue]) { + [spfDocData setObject:[NSNumber numberWithBool:YES] forKey:@"auto_connect"]; + [data setObject:[NSNumber numberWithBool:YES] forKey:@"auto_connect"]; + } + + // Set the state dictionary, triggering an autoconnect if appropriate + [self setState:data]; + + [spf release]; +} + +/** + * Restore session from SPF file if given + */ +- (void)restoreSession +{ + NSAutoreleasePool *taskPool = [[NSAutoreleasePool alloc] init]; + + // Check and set the table + NSArray *tables = [tablesListInstance tables]; + + BOOL isSelectedTableDefined = YES; + + if([tables indexOfObject:[spfSession objectForKey:@"table"]] == NSNotFound) { + isSelectedTableDefined = NO; + } + + // Restore toolbar setting + if([spfSession objectForKey:@"isToolbarVisible"]) + [mainToolbar setVisible:[[spfSession objectForKey:@"isToolbarVisible"] boolValue]]; + + // Reset database view encoding if differs from default + if([spfSession objectForKey:@"connectionEncoding"] && ![[mySQLConnection encoding] isEqualToString:[spfSession objectForKey:@"connectionEncoding"]]) + [self setConnectionEncoding:[spfSession objectForKey:@"connectionEncoding"] reloadingViews:YES]; + + if(isSelectedTableDefined) { + // Set table content details for restore + if([spfSession objectForKey:@"contentSortCol"]) + [tableContentInstance setSortColumnNameToRestore:[spfSession objectForKey:@"contentSortCol"] isAscending:[[spfSession objectForKey:@"contentSortColIsAsc"] boolValue]]; + if([spfSession objectForKey:@"contentPageNumber"]) + [tableContentInstance setPageToRestore:[[spfSession objectForKey:@"pageNumber"] integerValue]]; + if([spfSession objectForKey:@"contentViewport"]) + [tableContentInstance setViewportToRestore:NSRectFromString([spfSession objectForKey:@"contentViewport"])]; + if([spfSession objectForKey:@"contentFilter"]) + [tableContentInstance setFiltersToRestore:[spfSession objectForKey:@"contentFilter"]]; + + // Select table + [tablesListInstance selectTableAtIndex:[NSNumber numberWithInteger:[tables indexOfObject:[spfSession objectForKey:@"table"]]]]; + + // Restore table selection indexes + if([spfSession objectForKey:@"contentSelectedIndexSet"]) { + NSMutableIndexSet *anIndexSet = [NSMutableIndexSet indexSet]; + NSArray *items = [spfSession objectForKey:@"contentSelectedIndexSet"]; + NSUInteger i; + for(i=0; i<[items count]; i++) + [anIndexSet addIndex:[NSArrayObjectAtIndex(items, i) integerValue]]; + + [tableContentInstance setSelectedRowIndexesToRestore:anIndexSet]; + } + + [[tablesListInstance valueForKeyPath:@"tablesListView"] scrollRowToVisible:[tables indexOfObject:[spfSession objectForKey:@"selectedTable"]]]; + + } + + // Select view + if([[spfSession objectForKey:@"view"] isEqualToString:@"SP_VIEW_STRUCTURE"]) + [self viewStructure:self]; + else if([[spfSession objectForKey:@"view"] isEqualToString:@"SP_VIEW_CONTENT"]) + [self viewContent:self]; + else if([[spfSession objectForKey:@"view"] isEqualToString:@"SP_VIEW_CUSTOMQUERY"]) + [self viewQuery:self]; + else if([[spfSession objectForKey:@"view"] isEqualToString:@"SP_VIEW_STATUS"]) + [self viewStatus:self]; + else if([[spfSession objectForKey:@"view"] isEqualToString:@"SP_VIEW_RELATIONS"]) + [self viewRelations:self]; + else if([[spfSession objectForKey:@"view"] isEqualToString:@"SP_VIEW_TRIGGERS"]) + [self viewTriggers:self]; + + [self updateWindowTitle:self]; + + // dealloc spfSession data + [spfSession release]; + spfSession = nil; + + // End the task + [self endTask]; + [taskPool drain]; +} + +#pragma mark - #pragma mark Connection controller delegate methods /** diff --git a/Source/SPTableContent.m b/Source/SPTableContent.m index 2f0cec21..5b7f4a4b 100644 --- a/Source/SPTableContent.m +++ b/Source/SPTableContent.m @@ -284,6 +284,10 @@ BOOL enableInteraction = ![[tableDocumentInstance selectedToolbarItemIdentifier] isEqualToString:SPMainToolbarTableContent] || ![tableDocumentInstance isWorking]; if (!tableDetails) { + + // If no table is currently selected, no action required - return. + if (!selectedTable) return; + newTableName = nil; } else { newTableName = [tableDetails objectForKey:@"name"]; diff --git a/Source/SPTablesList.h b/Source/SPTablesList.h index 56711217..8487358d 100644 --- a/Source/SPTablesList.h +++ b/Source/SPTablesList.h @@ -65,7 +65,6 @@ IBOutlet id toolbarReloadButton; IBOutlet id addTableButton; IBOutlet id truncateTableButton; - IBOutlet id truncateTableContextButton; IBOutlet NSSplitView *tableListSplitView; IBOutlet NSSplitView *tableListFilterSplitView; IBOutlet NSButton *tableInfoCollapseButton; @@ -76,6 +75,7 @@ IBOutlet NSMenuItem *removeTableMenuItem; IBOutlet NSMenuItem *duplicateTableMenuItem; IBOutlet NSMenuItem *renameTableMenuItem; + IBOutlet NSMenuItem *openTableInNewTabMenuItem; IBOutlet NSMenuItem *separatorTableMenuItem; IBOutlet NSMenuItem *showCreateSyntaxMenuItem; IBOutlet NSMenuItem *separatorTableMenuItem2; @@ -85,7 +85,9 @@ // Table list context menu items IBOutlet NSMenuItem *removeTableContextMenuItem; IBOutlet NSMenuItem *duplicateTableContextMenuItem; + IBOutlet NSMenuItem *truncateTableContextMenuItem; IBOutlet NSMenuItem *renameTableContextMenuItem; + IBOutlet NSMenuItem *openTableInNewTabContextMenuItem; IBOutlet NSMenuItem *separatorTableContextMenuItem; IBOutlet NSMenuItem *showCreateSyntaxContextMenuItem; IBOutlet NSMenuItem *separatorTableContextMenuItem2; @@ -111,6 +113,7 @@ - (IBAction)copyTable:(id)sender; - (IBAction)renameTable:(id)sender; - (IBAction)truncateTable:(id)sender; +- (IBAction)openTableInNewTab:(id)sender; - (IBAction)togglePaneCollapse:(id)sender; // Additional methods diff --git a/Source/SPTablesList.m b/Source/SPTablesList.m index 6fdca8c0..41a528d3 100644 --- a/Source/SPTablesList.m +++ b/Source/SPTablesList.m @@ -558,6 +558,32 @@ } /** + * Open the table in a new tab. + */ +- (IBAction)openTableInNewTab:(id)sender +{ + + // Add a new tab to the window + [[[tableDocumentInstance parentWindow] windowController] addNewConnection:self]; + + // Get the state of the document + NSDictionary *allStateDetails = [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithBool:YES], @"connection", + [NSNumber numberWithBool:YES], @"history", + [NSNumber numberWithBool:YES], @"session", + [NSNumber numberWithBool:YES], @"query", + [NSNumber numberWithBool:YES], @"password", + nil]; + NSMutableDictionary *documentState = [NSMutableDictionary dictionaryWithDictionary:[tableDocumentInstance stateIncludingDetails:allStateDetails]]; + + // Ensure it's set to autoconnect + [documentState setObject:[NSNumber numberWithBool:YES] forKey:@"auto_connect"]; + + // Set the connection on the new tab + [[[NSApp delegate] frontDocument] setState:documentState]; +} + +/** * Toggle whether the splitview is collapsed. */ - (IBAction)togglePaneCollapse:(id)sender @@ -701,27 +727,27 @@ [removeTableMenuItem setTitle:NSLocalizedString(@"Delete Tables", @"delete tables menu title")]; [truncateTableButton setTitle:NSLocalizedString(@"Truncate Tables", @"truncate tables menu item")]; [removeTableContextMenuItem setTitle:NSLocalizedString(@"Delete Tables", @"delete tables menu title")]; - [truncateTableContextButton setTitle:NSLocalizedString(@"Truncate Tables", @"truncate tables menu item")]; + [truncateTableContextMenuItem setTitle:NSLocalizedString(@"Truncate Tables", @"truncate tables menu item")]; [truncateTableButton setHidden:NO]; - [truncateTableContextButton setHidden:NO]; + [truncateTableContextMenuItem setHidden:NO]; break; case SPTableTypeView: [removeTableMenuItem setTitle:NSLocalizedString(@"Delete Views", @"delete views menu title")]; [removeTableContextMenuItem setTitle:NSLocalizedString(@"Delete Views", @"delete views menu title")]; [truncateTableButton setHidden:YES]; - [truncateTableContextButton setHidden:YES]; + [truncateTableContextMenuItem setHidden:YES]; break; case SPTableTypeProc: [removeTableMenuItem setTitle:NSLocalizedString(@"Delete Procedures", @"delete procedures menu title")]; [removeTableContextMenuItem setTitle:NSLocalizedString(@"Delete Procedures", @"delete procedures menu title")]; [truncateTableButton setHidden:YES]; - [truncateTableContextButton setHidden:YES]; + [truncateTableContextMenuItem setHidden:YES]; break; case SPTableTypeFunc: [removeTableMenuItem setTitle:NSLocalizedString(@"Delete Functions", @"delete functions menu title")]; [removeTableContextMenuItem setTitle:NSLocalizedString(@"Delete Functions", @"delete functions menu title")]; [truncateTableButton setHidden:YES]; - [truncateTableContextButton setHidden:YES]; + [truncateTableContextMenuItem setHidden:YES]; break; } @@ -729,12 +755,14 @@ [removeTableMenuItem setTitle:NSLocalizedString(@"Delete Items", @"delete items menu title")]; [removeTableContextMenuItem setTitle:NSLocalizedString(@"Delete Items", @"delete items menu title")]; [truncateTableButton setHidden:YES]; - [truncateTableContextButton setHidden:YES]; + [truncateTableContextMenuItem setHidden:YES]; } + } // Context menu [renameTableContextMenuItem setHidden:YES]; + [openTableInNewTabContextMenuItem setHidden:YES]; [duplicateTableContextMenuItem setHidden:YES]; [separatorTableContextMenuItem setHidden:YES]; [separatorTableContextMenuItem2 setHidden:NO]; @@ -743,6 +771,7 @@ // 'Gear' menu [renameTableMenuItem setHidden:YES]; + [openTableInNewTabMenuItem setHidden:YES]; [duplicateTableMenuItem setHidden:YES]; [separatorTableMenuItem setHidden:YES]; [separatorTableMenuItem2 setHidden:NO]; @@ -828,6 +857,8 @@ [duplicateTableMenuItem setTitle:NSLocalizedString(@"Duplicate View...", @"duplicate view menu title")]; [truncateTableButton setHidden:YES]; [removeTableMenuItem setTitle:NSLocalizedString(@"Delete View", @"delete view menu title")]; + [openTableInNewTabMenuItem setHidden:NO]; + [openTableInNewTabMenuItem setTitle:NSLocalizedString(@"Open View in New Tab", @"open view in new table title")]; [showCreateSyntaxMenuItem setHidden:NO]; [showCreateSyntaxMenuItem setTitle:NSLocalizedString(@"Show Create View Syntax...", @"show create view syntax menu item")]; @@ -835,8 +866,10 @@ [renameTableContextMenuItem setTitle:NSLocalizedString(@"Rename View...", @"rename view menu title")]; [duplicateTableContextMenuItem setHidden:NO]; [duplicateTableContextMenuItem setTitle:NSLocalizedString(@"Duplicate View...", @"duplicate view menu title")]; - [truncateTableContextButton setHidden:YES]; + [truncateTableContextMenuItem setHidden:YES]; [removeTableContextMenuItem setTitle:NSLocalizedString(@"Delete View", @"delete view menu title")]; + [openTableInNewTabContextMenuItem setHidden:NO]; + [openTableInNewTabContextMenuItem setTitle:NSLocalizedString(@"Open View in New Tab", @"open view in new table title")]; [showCreateSyntaxContextMenuItem setHidden:NO]; [showCreateSyntaxContextMenuItem setTitle:NSLocalizedString(@"Show Create View Syntax...", @"show create view syntax menu item")]; } @@ -865,6 +898,8 @@ [truncateTableButton setHidden:NO]; [truncateTableButton setTitle:NSLocalizedString(@"Truncate Table", @"truncate table menu title")]; [removeTableMenuItem setTitle:NSLocalizedString(@"Delete Table", @"delete table menu title")]; + [openTableInNewTabMenuItem setHidden:NO]; + [openTableInNewTabMenuItem setTitle:NSLocalizedString(@"Open Table in New Tab", @"open table in new table title")]; [showCreateSyntaxMenuItem setHidden:NO]; [showCreateSyntaxMenuItem setTitle:NSLocalizedString(@"Show Create Table Syntax...", @"show create table syntax menu item")]; @@ -872,9 +907,11 @@ [renameTableContextMenuItem setTitle:NSLocalizedString(@"Rename Table...", @"rename table menu title")]; [duplicateTableContextMenuItem setHidden:NO]; [duplicateTableContextMenuItem setTitle:NSLocalizedString(@"Duplicate Table...", @"duplicate table menu title")]; - [truncateTableContextButton setHidden:NO]; - [truncateTableContextButton setTitle:NSLocalizedString(@"Truncate Table", @"truncate table menu title")]; + [truncateTableContextMenuItem setHidden:NO]; + [truncateTableContextMenuItem setTitle:NSLocalizedString(@"Truncate Table", @"truncate table menu title")]; [removeTableContextMenuItem setTitle:NSLocalizedString(@"Delete Table", @"delete table menu title")]; + [openTableInNewTabContextMenuItem setHidden:NO]; + [openTableInNewTabContextMenuItem setTitle:NSLocalizedString(@"Open Table in New Tab", @"open table in new table title")]; [showCreateSyntaxContextMenuItem setHidden:NO]; [showCreateSyntaxContextMenuItem setTitle:NSLocalizedString(@"Show Create Table Syntax...", @"show create table syntax menu item")]; } @@ -896,6 +933,8 @@ [duplicateTableMenuItem setTitle:NSLocalizedString(@"Duplicate Procedure...", @"duplicate proc menu title")]; [truncateTableButton setHidden:YES]; [removeTableMenuItem setTitle:NSLocalizedString(@"Delete Procedure", @"delete proc menu title")]; + [openTableInNewTabMenuItem setHidden:NO]; + [openTableInNewTabMenuItem setTitle:NSLocalizedString(@"Open Procedure in New Tab", @"open procedure in new table title")]; [showCreateSyntaxMenuItem setHidden:NO]; [showCreateSyntaxMenuItem setTitle:NSLocalizedString(@"Show Create Procedure Syntax...", @"show create proc syntax menu item")]; @@ -903,8 +942,10 @@ [renameTableContextMenuItem setTitle:NSLocalizedString(@"Rename Procedure...", @"rename proc menu title")]; [duplicateTableContextMenuItem setHidden:NO]; [duplicateTableContextMenuItem setTitle:NSLocalizedString(@"Duplicate Procedure...", @"duplicate proc menu title")]; - [truncateTableContextButton setHidden:YES]; + [truncateTableContextMenuItem setHidden:YES]; [removeTableContextMenuItem setTitle:NSLocalizedString(@"Delete Procedure", @"delete proc menu title")]; + [openTableInNewTabContextMenuItem setHidden:NO]; + [openTableInNewTabContextMenuItem setTitle:NSLocalizedString(@"Open Procedure in New Tab", @"open procedure in new table title")]; [showCreateSyntaxContextMenuItem setHidden:NO]; [showCreateSyntaxContextMenuItem setTitle:NSLocalizedString(@"Show Create Procedure Syntax...", @"show create proc syntax menu item")]; } @@ -926,6 +967,8 @@ [duplicateTableMenuItem setTitle:NSLocalizedString(@"Duplicate Function...", @"duplicate func menu title")]; [truncateTableButton setHidden:YES]; [removeTableMenuItem setTitle:NSLocalizedString(@"Delete Function", @"delete func menu title")]; + [openTableInNewTabMenuItem setHidden:NO]; + [openTableInNewTabMenuItem setTitle:NSLocalizedString(@"Open Function in New Tab", @"open function in new table title")]; [showCreateSyntaxMenuItem setHidden:NO]; [showCreateSyntaxMenuItem setTitle:NSLocalizedString(@"Show Create Function Syntax...", @"show create func syntax menu item")]; @@ -933,8 +976,10 @@ [renameTableContextMenuItem setTitle:NSLocalizedString(@"Rename Function...", @"rename func menu title")]; [duplicateTableContextMenuItem setHidden:NO]; [duplicateTableContextMenuItem setTitle:NSLocalizedString(@"Duplicate Function...", @"duplicate func menu title")]; - [truncateTableContextButton setHidden:YES]; + [truncateTableContextMenuItem setHidden:YES]; [removeTableContextMenuItem setTitle:NSLocalizedString(@"Delete Function", @"delete func menu title")]; + [openTableInNewTabContextMenuItem setHidden:NO]; + [openTableInNewTabContextMenuItem setTitle:NSLocalizedString(@"Open Function in New Tab", @"open function in new table title")]; [showCreateSyntaxContextMenuItem setHidden:NO]; [showCreateSyntaxContextMenuItem setTitle:NSLocalizedString(@"Show Create Function Syntax...", @"show create func syntax menu item")]; } @@ -1506,7 +1551,7 @@ return ([tablesListView numberOfSelectedRows] > 0); } - if ([menuItem action] == @selector(renameTable:)) { + if ([menuItem action] == @selector(renameTable:) || [menuItem action] == @selector(openTableInNewTab:)) { return (([tablesListView numberOfSelectedRows] == 1) && [[self tableName] length]); } |