From fe9a592ca3be829570f5bd88efd5b427120556e5 Mon Sep 17 00:00:00 2001 From: rowanbeentje Date: Tue, 23 Nov 2010 01:47:52 +0000 Subject: - Abstract SPDatabaseDocument's Connection/.spf saving/loading functionality into state setting and getting functions, and refactor old commands to use them - Fix restoring of content sort column order and selected indexes - Add menu items for duplicating current tab (as an alternate), opening selected table in a new tab, or opening selected database in a new tab - Clean up file menu by making Connection/Session "Save as..." menu items alternates - Update localisable strings --- Source/SPDatabaseDocument.m | 3318 ++++++++++++++++++++++--------------------- 1 file changed, 1723 insertions(+), 1595 deletions(-) (limited to 'Source/SPDatabaseDocument.m') diff --git a/Source/SPDatabaseDocument.m b/Source/SPDatabaseDocument.m index 4e26a8c4..4626f031 100644 --- a/Source/SPDatabaseDocument.m +++ b/Source/SPDatabaseDocument.m @@ -246,846 +246,449 @@ } /** - * Initialise the document with the connection file at the supplied path. + * Set the return code for entering the encryption passowrd sheet */ -- (void)initWithConnectionFile:(NSString *)path +- (IBAction)closePasswordSheet:(id)sender { - 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]; + passwordSheetReturnCode = 0; + if([sender tag]) { + [NSApp stopModal]; + passwordSheetReturnCode = 1; + } + [NSApp abortModal]; +} - spf = [[NSPropertyListSerialization propertyListFromData:pData - mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&convError] retain]; +/** + * Go backward or forward in the history depending on the menu item selected. + */ +- (IBAction)backForwardInHistory:(id)sender +{ - 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")]; + // Ensure history navigation is permitted - trigger end editing and any required saves + if (![self couldCommitCurrentViewActions]) return; - [alert setAlertStyle:NSCriticalAlertStyle]; - [alert runModal]; - if (spf) [spf release]; - [self closeAndDisconnect]; - return; + switch ([sender tag]) + { + // Go backward + case 0: + [spHistoryControllerInstance goBackInHistory]; + break; + // Go forward + case 1: + [spHistoryControllerInstance goForwardInHistory]; + break; } +} - // 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"]]]; +#pragma mark - +#pragma mark Connection callback and methods - [alert setAlertStyle:NSWarningAlertStyle]; - [spf release]; - [self closeAndDisconnect]; - [alert runModal]; - return; - } +- (void)setConnection:(MCPConnection *)theConnection +{ + _isConnected = YES; + mySQLConnection = [theConnection retain]; + + // Now that we have a connection, determine what functionality the database supports. + // Note that this must be done before anything else as it's used by nearly all of the main controllers. + serverSupport = [[SPServerSupport alloc] initWithMajorVersion:[mySQLConnection serverMajorVersion] + minor:[mySQLConnection serverMinorVersion] + release:[mySQLConnection serverReleaseVersion]]; + + // Set the fileURL and init the preferences (query favs, filters, and history) if available for that URL + [self setFileURL:[[SPQueryController sharedQueryController] registerDocumentWithFileURL:[self fileURL] andContextInfo:spfPreferences]]; + + // ...but hide the icon while the document is temporary + if ([self isUntitled]) [[parentWindow standardWindowButton:NSWindowDocumentIconButton] setImage:nil]; - 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")]; + // Get the mysql version + mySQLVersion = [[NSString alloc] initWithString:[mySQLConnection serverVersionString]]; - [alert setAlertStyle:NSCriticalAlertStyle]; - [alert runModal]; - [spf release]; - [self closeAndDisconnect]; - return; + // Update the selected database if appropriate + if ([connectionController database] && ![[connectionController database] isEqualToString:@""]) { + if (selectedDatabase) [selectedDatabase release], selectedDatabase = nil; + selectedDatabase = [[NSString alloc] initWithString:[connectionController database]]; + [spHistoryControllerInstance updateHistoryEntries]; } - // 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]; + // Ensure the connection encoding is set to utf8 for database/table name retrieval + [mySQLConnection setEncoding:@"utf8"]; - // wait for encryption password - NSModalSession session = [NSApp beginModalSessionForWindow:inputTextWindow]; - for (;;) { + // Update the database list + [self setDatabases:self]; + + [chooseDatabaseButton setEnabled:!_isWorkingLevel]; - // Execute code on DefaultRunLoop - [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode - beforeDate:[NSDate distantFuture]]; + [databaseDataInstance setConnection:mySQLConnection]; + + // Pass the support class to the data instance + [databaseDataInstance setServerSupport:serverSupport]; + + // Set the connection on the tables list instance - this updates the table list while the connection + // is still UTF8 + [tablesListInstance setConnection:mySQLConnection]; - // Break the run loop if editSheet was closed - if ([NSApp runModalSession:session] != NSRunContinuesResponse - || ![inputTextWindow isVisible]) - break; + // Set the connection encoding if necessary + NSNumber *encodingType = [prefs objectForKey:SPDefaultEncoding]; + + if ([encodingType intValue] != SPEncodingAutodetect) { + [self setConnectionEncoding:[self mysqlEncodingFromEncodingTag:encodingType] reloadingViews:NO]; + } - // Execute code on DefaultRunLoop - [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode - beforeDate:[NSDate distantFuture]]; + // For each of the main controllers, assign the current connection + [tableSourceInstance setConnection:mySQLConnection]; + [tableContentInstance setConnection:mySQLConnection]; + [tableRelationsInstance setConnection:mySQLConnection]; + [tableTriggersInstance setConnection:mySQLConnection]; + [customQueryInstance setConnection:mySQLConnection]; + [tableDumpInstance setConnection:mySQLConnection]; + [exportControllerInstance setConnection:mySQLConnection]; + [tableDataInstance setConnection:mySQLConnection]; + [extendedTableInfoInstance setConnection:mySQLConnection]; + + // Set the custom query editor's MySQL version + [customQueryInstance setMySQLversion:mySQLVersion]; - } - [NSApp endModalSession:session]; - [inputTextWindow orderOut:nil]; - [NSApp endSheet:inputTextWindow]; + [self updateWindowTitle:self]; + + // Connected Growl notification + [[SPGrowlController sharedGrowlController] notifyWithTitle:@"Connected" + description:[NSString stringWithFormat:NSLocalizedString(@"Connected to %@",@"description for connected growl notification"), [parentWindow title]] + document:self + notificationName:@"Connected"]; - 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; - } + // Init Custom Query editor with the stored queries in a spf file if given. + [spfDocData setObject:[NSNumber numberWithBool:NO] forKey:@"save_editor_content"]; + + if (spfSession != nil && [spfSession objectForKey:@"queries"]) { + [spfDocData setObject:[NSNumber numberWithBool:YES] forKey:@"save_editor_content"]; + if ([[spfSession objectForKey:@"queries"] isKindOfClass:[NSData class]]) { + NSString *q = [[NSString alloc] initWithData:[[spfSession objectForKey:@"queries"] decompress] encoding:NSUTF8StringEncoding]; + [self initQueryEditorWithString:q]; + [q release]; } + else + [self initQueryEditorWithString:[spfSession objectForKey:@"queries"]]; } - 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; - } + // Insert queryEditorInitString into the Query Editor if defined + if (queryEditorInitString && [queryEditorInitString length]) { + [self viewQuery:self]; + [customQueryInstance doPerformLoadQueryService:queryEditorInitString]; + [queryEditorInitString release]; + queryEditorInitString = nil; } - 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; - } + // Set focus to table list filter field if visible + // otherwise set focus to Table List view + if ([[tablesListInstance tables] count] > 20) + [parentWindow makeFirstResponder:listFilterField]; + else + [parentWindow makeFirstResponder:[tablesListInstance valueForKeyPath:@"tablesListView"]]; + if (spfSession != nil) { - 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")]; + // Restore vertical split view divider for tables' list and right view (Structure, Content, etc.) + if([spfSession objectForKey:@"windowVerticalDividerPosition"]) + [contentViewSplitter setPosition:[[spfSession objectForKey:@"windowVerticalDividerPosition"] floatValue] ofDividerAtIndex:0]; - [alert setAlertStyle:NSCriticalAlertStyle]; - [alert runModal]; - [self closeAndDisconnect]; - [spf release]; - return; + // Start a task to restore the session details + [self startTaskWithDescription:NSLocalizedString(@"Restoring session...", @"Restoring session task description")]; + + if ([NSThread isMainThread]) + [NSThread detachNewThreadSelector:@selector(restoreSession) toTarget:self withObject:nil]; + else + [self restoreSession]; + } + else { + switch ([prefs integerForKey:SPDefaultViewMode] > 0 ? [prefs integerForKey:SPDefaultViewMode] : [prefs integerForKey:SPLastViewMode]) { + default: + case SPStructureViewMode: + [self viewStructure:self]; + break; + case SPContentViewMode: + [self viewContent:self]; + break; + case SPRelationsViewMode: + [self viewRelations:self]; + break; + case SPTableInfoViewMode: + [self viewStatus:self]; + break; + case SPQueryEditorViewMode: + [self viewQuery:self]; + break; + case SPTriggersViewMode: + [self viewTriggers:self]; + break; + } } - [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; + (void *)[self databaseEncoding]; +} - connection = [NSDictionary dictionaryWithDictionary:[data objectForKey:@"connection"]]; +/** + * Returns the current connection associated with this document. + * + * @return The document's connection + */ +- (MCPConnection *) getConnection +{ + return mySQLConnection; +} - 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; +/** + * Sets this connection's Keychain ID. + */ +- (void)setKeychainID:(NSString *)theID +{ + keyChainID = [[NSString stringWithString:theID] retain]; +} - [connectionController setType:connectionType]; - [connectionController resizeTabViewToConnectionType:connectionType animating:NO]; - } +#pragma mark - +#pragma mark Database methods - 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]]]; +/** + * sets up the database select toolbar item + */ +- (IBAction)setDatabases:(id)sender; +{ + if (!chooseDatabaseButton) return; - 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"]]; + [chooseDatabaseButton removeAllItems]; - if([connection objectForKey:@"kcid"] && [(NSString *)[connection objectForKey:@"kcid"] length]) - [self setKeychainID:[connection objectForKey:@"kcid"]]; + [chooseDatabaseButton addItemWithTitle:NSLocalizedString(@"Choose Database...", @"menu item for choose db")]; + [[chooseDatabaseButton menu] addItem:[NSMenuItem separatorItem]]; + [[chooseDatabaseButton menu] addItemWithTitle:NSLocalizedString(@"Add Database...", @"menu item to add db") action:@selector(addDatabase:) keyEquivalent:@""]; + [[chooseDatabaseButton menu] addItemWithTitle:NSLocalizedString(@"Refresh Databases", @"menu item to refresh databases") action:@selector(setDatabases:) keyEquivalent:@""]; + [[chooseDatabaseButton menu] addItem:[NSMenuItem separatorItem]]; - // 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]; - } + MCPResult *queryResult = [mySQLConnection listDBs]; - if(connectionType == SPSocketConnection && [connection objectForKey:@"socket"]) - [connectionController setSocket:[connection objectForKey:@"socket"]]; + if ([queryResult numOfRows]) [queryResult dataSeek:0]; + + if (allDatabases) [allDatabases release]; + if (allSystemDatabases) [allSystemDatabases release]; + + allDatabases = [[NSMutableArray alloc] initWithCapacity:[queryResult numOfRows]]; - 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"]]; + allSystemDatabases = [[NSMutableArray alloc] initWithCapacity:2]; + + for (NSInteger i = 0 ; i < [queryResult numOfRows] ; i++) + { + 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"]) { + [allSystemDatabases addObject:database]; + } else { - NSString *sshpw = [self keychainPasswordForSSHConnection:nil]; - if(sshpw) - [connectionController setSshPassword:sshpw]; + [allDatabases addObject:database]; } - } - 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"]; + // Add system databases + for (NSString *db in allSystemDatabases) + { + [chooseDatabaseButton addItemWithTitle:db]; } - - if(![self isSaveInBundle]) { - [self setFileURL:[NSURL fileURLWithPath:path]]; - [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL:[NSURL fileURLWithPath:path]]; + + // Add a separator between the system and user databases + if ([allSystemDatabases count] > 0) { + [[chooseDatabaseButton menu] addItem:[NSMenuItem separatorItem]]; } - 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]; + // Add user databases + for (NSString *db in allDatabases) + { + [chooseDatabaseButton addItemWithTitle:db]; } - [spf release]; + + (![self database]) ? [chooseDatabaseButton selectItemAtIndex:0] : [chooseDatabaseButton selectItemWithTitle:[self database]]; } /** - * Restore session from SPF file if given + * Selects the database choosen by the user, using a child task if necessary, + * and displaying errors in an alert sheet on failure. */ -- (void)restoreSession +- (IBAction)chooseDatabase:(id)sender { - 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; + if (![tablesListInstance selectionShouldChangeInTableView:nil]) { + [chooseDatabaseButton selectItemWithTitle:[self database]]; + return; } - // 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]; + if ( [chooseDatabaseButton indexOfSelectedItem] == 0 ) { + if ([self database]) { + [chooseDatabaseButton selectItemWithTitle:[self database]]; } - - [[tablesListInstance valueForKeyPath:@"tablesListView"] scrollRowToVisible:[tables indexOfObject:[spfSession objectForKey:@"selectedTable"]]]; - + return; } - // 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; + // Lock editability again if performing a task + if (_isWorkingLevel) databaseListIsSelectable = NO; - // End the task - [self endTask]; - [taskPool drain]; + // Select the database + [self selectDatabase:[chooseDatabaseButton titleOfSelectedItem] item:[self table]]; } /** - * Set the return code for entering the encryption passowrd sheet + * Select the specified database and, optionally, table. */ -- (IBAction)closePasswordSheet:(id)sender +- (void)selectDatabase:(NSString *)aDatabase item:(NSString *)anItem { - passwordSheetReturnCode = 0; - if([sender tag]) { - [NSApp stopModal]; - passwordSheetReturnCode = 1; + // Do not update the navigator since nothing is changed + [[SPNavigatorController sharedNavigatorController] setIgnoreUpdate:NO]; + + // If Navigator runs in syncMode let it follow the selection + if([[SPNavigatorController sharedNavigatorController] syncMode]) { + NSMutableString *schemaPath = [NSMutableString string]; + [schemaPath setString:[self connectionID]]; + if([chooseDatabaseButton titleOfSelectedItem] && [[chooseDatabaseButton titleOfSelectedItem] length]) { + [schemaPath appendString:SPUniqueSchemaDelimiter]; + [schemaPath appendString:[chooseDatabaseButton titleOfSelectedItem]]; + } + [[SPNavigatorController sharedNavigatorController] selectPath:schemaPath]; } - [NSApp abortModal]; -} -/** - * Go backward or forward in the history depending on the menu item selected. - */ -- (IBAction)backForwardInHistory:(id)sender -{ - - // Ensure history navigation is permitted - trigger end editing and any required saves - if (![self couldCommitCurrentViewActions]) return; - - switch ([sender tag]) - { - // Go backward - case 0: - [spHistoryControllerInstance goBackInHistory]; - break; - // Go forward - case 1: - [spHistoryControllerInstance goForwardInHistory]; - break; + // Start a task + [self startTaskWithDescription:[NSString stringWithFormat:NSLocalizedString(@"Loading database '%@'...", @"Loading database task string"), [chooseDatabaseButton titleOfSelectedItem]]]; + NSDictionary *selectionDetails = [NSDictionary dictionaryWithObjectsAndKeys: + aDatabase, @"database", + anItem, @"item", + nil]; + if ([NSThread isMainThread]) { + [NSThread detachNewThreadSelector:@selector(_selectDatabaseAndItem:) toTarget:self withObject:selectionDetails]; + } + else { + [self _selectDatabaseAndItem:selectionDetails]; } } -#pragma mark - -#pragma mark Connection callback and methods - -- (void)setConnection:(MCPConnection *)theConnection +/** + * opens the add-db sheet and creates the new db + */ +- (IBAction)addDatabase:(id)sender { - _isConnected = YES; - mySQLConnection = [theConnection retain]; + if (![tablesListInstance selectionShouldChangeInTableView:nil]) return; - // Now that we have a connection, determine what functionality the database supports. - // Note that this must be done before anything else as it's used by nearly all of the main controllers. - serverSupport = [[SPServerSupport alloc] initWithMajorVersion:[mySQLConnection serverMajorVersion] - minor:[mySQLConnection serverMinorVersion] - release:[mySQLConnection serverReleaseVersion]]; + [databaseNameField setStringValue:@""]; + + // Populate the database encoding popup button with a default menu item + [databaseEncodingButton removeAllItems]; + [databaseEncodingButton addItemWithTitle:@"Default"]; + + // Retrieve the server-supported encodings and add them to the menu + NSArray *encodings = [databaseDataInstance getDatabaseCharacterSetEncodings]; + NSString *utf8MenuItemTitle = nil; - // Set the fileURL and init the preferences (query favs, filters, and history) if available for that URL - [self setFileURL:[[SPQueryController sharedQueryController] registerDocumentWithFileURL:[self fileURL] andContextInfo:spfPreferences]]; + [databaseEncodingButton setEnabled:YES]; - // ...but hide the icon while the document is temporary - if ([self isUntitled]) [[parentWindow standardWindowButton:NSWindowDocumentIconButton] setImage:nil]; + if (([encodings count] > 0) && [serverSupport supportsPost41CharacterSetHandling]) { + [[databaseEncodingButton menu] addItem:[NSMenuItem separatorItem]]; + + for (NSDictionary *encoding in encodings) + { + NSString *menuItemTitle = (![encoding objectForKey:@"DESCRIPTION"]) ? [encoding objectForKey:@"CHARACTER_SET_NAME"] : [NSString stringWithFormat:@"%@ (%@)", [encoding objectForKey:@"DESCRIPTION"], [encoding objectForKey:@"CHARACTER_SET_NAME"]]; + [databaseEncodingButton addItemWithTitle:menuItemTitle]; - // Get the mysql version - mySQLVersion = [[NSString alloc] initWithString:[mySQLConnection serverVersionString]]; + // If the UTF8 entry has been encountered, store the title + if ([[encoding objectForKey:@"CHARACTER_SET_NAME"] isEqualToString:@"utf8"]) { + utf8MenuItemTitle = [NSString stringWithString:menuItemTitle]; + } + } - // Update the selected database if appropriate - if ([connectionController database] && ![[connectionController database] isEqualToString:@""]) { - if (selectedDatabase) [selectedDatabase release], selectedDatabase = nil; - selectedDatabase = [[NSString alloc] initWithString:[connectionController database]]; - [spHistoryControllerInstance updateHistoryEntries]; + // If a UTF8 entry was found, promote it to the top of the list + if (utf8MenuItemTitle) { + [[databaseEncodingButton menu] insertItem:[NSMenuItem separatorItem] atIndex:2]; + [databaseEncodingButton insertItemWithTitle:utf8MenuItemTitle atIndex:2]; + } + } + else { + [databaseEncodingButton setEnabled:NO]; } - - // Ensure the connection encoding is set to utf8 for database/table name retrieval - [mySQLConnection setEncoding:@"utf8"]; - - // Update the database list - [self setDatabases:self]; - [chooseDatabaseButton setEnabled:!_isWorkingLevel]; + [NSApp beginSheet:databaseSheet + modalForWindow:parentWindow + modalDelegate:self + didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) + contextInfo:@"addDatabase"]; +} - [databaseDataInstance setConnection:mySQLConnection]; - - // Pass the support class to the data instance - [databaseDataInstance setServerSupport:serverSupport]; - - // Set the connection on the tables list instance - this updates the table list while the connection - // is still UTF8 - [tablesListInstance setConnection:mySQLConnection]; - // Set the connection encoding if necessary - NSNumber *encodingType = [prefs objectForKey:SPDefaultEncoding]; +/** + * opens the copy database sheet and copies the databsae + */ +- (IBAction)copyDatabase:(id)sender +{ + if (![tablesListInstance selectionShouldChangeInTableView:nil]) return; - if ([encodingType intValue] != SPEncodingAutodetect) { - [self setConnectionEncoding:[self mysqlEncodingFromEncodingTag:encodingType] reloadingViews:NO]; - } - - // For each of the main controllers, assign the current connection - [tableSourceInstance setConnection:mySQLConnection]; - [tableContentInstance setConnection:mySQLConnection]; - [tableRelationsInstance setConnection:mySQLConnection]; - [tableTriggersInstance setConnection:mySQLConnection]; - [customQueryInstance setConnection:mySQLConnection]; - [tableDumpInstance setConnection:mySQLConnection]; - [exportControllerInstance setConnection:mySQLConnection]; - [tableDataInstance setConnection:mySQLConnection]; - [extendedTableInfoInstance setConnection:mySQLConnection]; + [databaseCopyNameField setStringValue:selectedDatabase]; + [copyDatabaseMessageField setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Duplicate database '%@' to:", @"duplicate database message"), selectedDatabase]]; - // Set the custom query editor's MySQL version - [customQueryInstance setMySQLversion:mySQLVersion]; + [NSApp beginSheet:databaseCopySheet + modalForWindow:parentWindow + modalDelegate:self + didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) + contextInfo:@"copyDatabase"]; +} - [self updateWindowTitle:self]; +/** + * opens the rename database sheet and renames the databsae + */ +- (IBAction)renameDatabase:(id)sender +{ + if (![tablesListInstance selectionShouldChangeInTableView:nil]) return; - // Connected Growl notification - [[SPGrowlController sharedGrowlController] notifyWithTitle:@"Connected" - description:[NSString stringWithFormat:NSLocalizedString(@"Connected to %@",@"description for connected growl notification"), [parentWindow title]] - document:self - notificationName:@"Connected"]; - - // Init Custom Query editor with the stored queries in a spf file if given. - [spfDocData setObject:[NSNumber numberWithBool:NO] forKey:@"save_editor_content"]; + [databaseRenameNameField setStringValue:selectedDatabase]; + [renameDatabaseMessageField setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Rename database '%@' to:", @"rename database message"), selectedDatabase]]; - if (spfSession != nil && [spfSession objectForKey:@"queries"]) { - [spfDocData setObject:[NSNumber numberWithBool:YES] forKey:@"save_editor_content"]; - if ([[spfSession objectForKey:@"queries"] isKindOfClass:[NSData class]]) { - NSString *q = [[NSString alloc] initWithData:[[spfSession objectForKey:@"queries"] decompress] encoding:NSUTF8StringEncoding]; - [self initQueryEditorWithString:q]; - [q release]; - } - else - [self initQueryEditorWithString:[spfSession objectForKey:@"queries"]]; - } + [NSApp beginSheet:databaseRenameSheet + modalForWindow:parentWindow + modalDelegate:self + didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) + contextInfo:@"renameDatabase"]; +} - // Insert queryEditorInitString into the Query Editor if defined - if (queryEditorInitString && [queryEditorInitString length]) { - [self viewQuery:self]; - [customQueryInstance doPerformLoadQueryService:queryEditorInitString]; - [queryEditorInitString release]; - queryEditorInitString = nil; - } +/** + * opens sheet to ask user if he really wants to delete the db + */ +- (IBAction)removeDatabase:(id)sender +{ + // No database selected, bail + if ([chooseDatabaseButton indexOfSelectedItem] == 0) return; - // Set focus to table list filter field if visible - // otherwise set focus to Table List view - if ([[tablesListInstance tables] count] > 20) - [parentWindow makeFirstResponder:listFilterField]; - else - [parentWindow makeFirstResponder:[tablesListInstance valueForKeyPath:@"tablesListView"]]; + if (![tablesListInstance selectionShouldChangeInTableView:nil]) return; - if (spfSession != nil) { + NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Delete database '%@'?", @"delete database message"), [self database]] + defaultButton:NSLocalizedString(@"Delete", @"delete button") + alternateButton:NSLocalizedString(@"Cancel", @"cancel button") + otherButton:nil + informativeTextWithFormat:[NSString stringWithFormat:NSLocalizedString(@"Are you sure you want to delete the database '%@'? This operation cannot be undone.", @"delete database informative message"), [self database]]]; - // Restore vertical split view divider for tables' list and right view (Structure, Content, etc.) - if([spfSession objectForKey:@"windowVerticalDividerPosition"]) - [contentViewSplitter setPosition:[[spfSession objectForKey:@"windowVerticalDividerPosition"] floatValue] ofDividerAtIndex:0]; + NSArray *buttons = [alert buttons]; - // Start a task to restore the session details - [self startTaskWithDescription:NSLocalizedString(@"Restoring session...", @"Restoring session task description")]; - - if ([NSThread isMainThread]) - [NSThread detachNewThreadSelector:@selector(restoreSession) toTarget:self withObject:nil]; - else - [self restoreSession]; - } - else { - switch ([prefs integerForKey:SPDefaultViewMode] > 0 ? [prefs integerForKey:SPDefaultViewMode] : [prefs integerForKey:SPLastViewMode]) { - default: - case SPStructureViewMode: - [self viewStructure:self]; - break; - case SPContentViewMode: - [self viewContent:self]; - break; - case SPRelationsViewMode: - [self viewRelations:self]; - break; - case SPTableInfoViewMode: - [self viewStatus:self]; - break; - case SPQueryEditorViewMode: - [self viewQuery:self]; - break; - case SPTriggersViewMode: - [self viewTriggers:self]; - break; - } - } + // Change the alert's cancel button to have the key equivalent of return + [[buttons objectAtIndex:0] setKeyEquivalent:@"d"]; + [[buttons objectAtIndex:0] setKeyEquivalentModifierMask:NSCommandKeyMask]; + [[buttons objectAtIndex:1] setKeyEquivalent:@"\r"]; - (void *)[self databaseEncoding]; + [alert setAlertStyle:NSCriticalAlertStyle]; + + [alert beginSheetModalForWindow:parentWindow modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:@"removeDatabase"]; } /** - * Returns the current connection associated with this document. - * - * @return The document's connection + * Refreshes the tables list by calling SPTablesList's updateTables. */ -- (MCPConnection *) getConnection +- (IBAction)refreshTables:(id)sender { - return mySQLConnection; + [tablesListInstance updateTables:self]; } /** - * Sets this connection's Keychain ID. - */ -- (void)setKeychainID:(NSString *)theID -{ - keyChainID = [[NSString stringWithString:theID] retain]; -} - -#pragma mark - -#pragma mark Database methods - -/** - * sets up the database select toolbar item - */ -- (IBAction)setDatabases:(id)sender; -{ - if (!chooseDatabaseButton) return; - - [chooseDatabaseButton removeAllItems]; - - [chooseDatabaseButton addItemWithTitle:NSLocalizedString(@"Choose Database...", @"menu item for choose db")]; - [[chooseDatabaseButton menu] addItem:[NSMenuItem separatorItem]]; - [[chooseDatabaseButton menu] addItemWithTitle:NSLocalizedString(@"Add Database...", @"menu item to add db") action:@selector(addDatabase:) keyEquivalent:@""]; - [[chooseDatabaseButton menu] addItemWithTitle:NSLocalizedString(@"Refresh Databases", @"menu item to refresh databases") action:@selector(setDatabases:) keyEquivalent:@""]; - [[chooseDatabaseButton menu] addItem:[NSMenuItem separatorItem]]; - - MCPResult *queryResult = [mySQLConnection listDBs]; - - if ([queryResult numOfRows]) [queryResult dataSeek:0]; - - if (allDatabases) [allDatabases release]; - if (allSystemDatabases) [allSystemDatabases release]; - - allDatabases = [[NSMutableArray alloc] initWithCapacity:[queryResult numOfRows]]; - - allSystemDatabases = [[NSMutableArray alloc] initWithCapacity:2]; - - for (NSInteger i = 0 ; i < [queryResult numOfRows] ; i++) - { - 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"]) { - [allSystemDatabases addObject:database]; - } - else { - [allDatabases addObject:database]; - } - } - - // Add system databases - for (NSString *db in allSystemDatabases) - { - [chooseDatabaseButton addItemWithTitle:db]; - } - - // Add a separator between the system and user databases - if ([allSystemDatabases count] > 0) { - [[chooseDatabaseButton menu] addItem:[NSMenuItem separatorItem]]; - } - - // Add user databases - for (NSString *db in allDatabases) - { - [chooseDatabaseButton addItemWithTitle:db]; - } - - (![self database]) ? [chooseDatabaseButton selectItemAtIndex:0] : [chooseDatabaseButton selectItemWithTitle:[self database]]; -} - -/** - * Selects the database choosen by the user, using a child task if necessary, - * and displaying errors in an alert sheet on failure. - */ -- (IBAction)chooseDatabase:(id)sender -{ - if (![tablesListInstance selectionShouldChangeInTableView:nil]) { - [chooseDatabaseButton selectItemWithTitle:[self database]]; - return; - } - - if ( [chooseDatabaseButton indexOfSelectedItem] == 0 ) { - if ([self database]) { - [chooseDatabaseButton selectItemWithTitle:[self database]]; - } - return; - } - - // Lock editability again if performing a task - if (_isWorkingLevel) databaseListIsSelectable = NO; - - // Select the database - [self selectDatabase:[chooseDatabaseButton titleOfSelectedItem] item:[self table]]; -} - -/** - * Select the specified database and, optionally, table. - */ -- (void)selectDatabase:(NSString *)aDatabase item:(NSString *)anItem -{ - // Do not update the navigator since nothing is changed - [[SPNavigatorController sharedNavigatorController] setIgnoreUpdate:NO]; - - // If Navigator runs in syncMode let it follow the selection - if([[SPNavigatorController sharedNavigatorController] syncMode]) { - NSMutableString *schemaPath = [NSMutableString string]; - [schemaPath setString:[self connectionID]]; - if([chooseDatabaseButton titleOfSelectedItem] && [[chooseDatabaseButton titleOfSelectedItem] length]) { - [schemaPath appendString:SPUniqueSchemaDelimiter]; - [schemaPath appendString:[chooseDatabaseButton titleOfSelectedItem]]; - } - [[SPNavigatorController sharedNavigatorController] selectPath:schemaPath]; - } - - // Start a task - [self startTaskWithDescription:[NSString stringWithFormat:NSLocalizedString(@"Loading database '%@'...", @"Loading database task string"), [chooseDatabaseButton titleOfSelectedItem]]]; - NSDictionary *selectionDetails = [NSDictionary dictionaryWithObjectsAndKeys: - aDatabase, @"database", - anItem, @"item", - nil]; - if ([NSThread isMainThread]) { - [NSThread detachNewThreadSelector:@selector(_selectDatabaseAndItem:) toTarget:self withObject:selectionDetails]; - } - else { - [self _selectDatabaseAndItem:selectionDetails]; - } -} - -/** - * opens the add-db sheet and creates the new db - */ -- (IBAction)addDatabase:(id)sender -{ - if (![tablesListInstance selectionShouldChangeInTableView:nil]) return; - - [databaseNameField setStringValue:@""]; - - // Populate the database encoding popup button with a default menu item - [databaseEncodingButton removeAllItems]; - [databaseEncodingButton addItemWithTitle:@"Default"]; - - // Retrieve the server-supported encodings and add them to the menu - NSArray *encodings = [databaseDataInstance getDatabaseCharacterSetEncodings]; - NSString *utf8MenuItemTitle = nil; - - [databaseEncodingButton setEnabled:YES]; - - if (([encodings count] > 0) && [serverSupport supportsPost41CharacterSetHandling]) { - [[databaseEncodingButton menu] addItem:[NSMenuItem separatorItem]]; - - for (NSDictionary *encoding in encodings) - { - NSString *menuItemTitle = (![encoding objectForKey:@"DESCRIPTION"]) ? [encoding objectForKey:@"CHARACTER_SET_NAME"] : [NSString stringWithFormat:@"%@ (%@)", [encoding objectForKey:@"DESCRIPTION"], [encoding objectForKey:@"CHARACTER_SET_NAME"]]; - [databaseEncodingButton addItemWithTitle:menuItemTitle]; - - // If the UTF8 entry has been encountered, store the title - if ([[encoding objectForKey:@"CHARACTER_SET_NAME"] isEqualToString:@"utf8"]) { - utf8MenuItemTitle = [NSString stringWithString:menuItemTitle]; - } - } - - // If a UTF8 entry was found, promote it to the top of the list - if (utf8MenuItemTitle) { - [[databaseEncodingButton menu] insertItem:[NSMenuItem separatorItem] atIndex:2]; - [databaseEncodingButton insertItemWithTitle:utf8MenuItemTitle atIndex:2]; - } - } - else { - [databaseEncodingButton setEnabled:NO]; - } - - [NSApp beginSheet:databaseSheet - modalForWindow:parentWindow - modalDelegate:self - didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) - contextInfo:@"addDatabase"]; -} - - -/** - * opens the copy database sheet and copies the databsae - */ -- (IBAction)copyDatabase:(id)sender -{ - if (![tablesListInstance selectionShouldChangeInTableView:nil]) return; - - [databaseCopyNameField setStringValue:selectedDatabase]; - [copyDatabaseMessageField setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Duplicate database '%@' to:", @"duplicate database message"), selectedDatabase]]; - - [NSApp beginSheet:databaseCopySheet - modalForWindow:parentWindow - modalDelegate:self - didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) - contextInfo:@"copyDatabase"]; -} - -/** - * opens the rename database sheet and renames the databsae - */ -- (IBAction)renameDatabase:(id)sender -{ - if (![tablesListInstance selectionShouldChangeInTableView:nil]) return; - - [databaseRenameNameField setStringValue:selectedDatabase]; - [renameDatabaseMessageField setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Rename database '%@' to:", @"rename database message"), selectedDatabase]]; - - [NSApp beginSheet:databaseRenameSheet - modalForWindow:parentWindow - modalDelegate:self - didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) - contextInfo:@"renameDatabase"]; -} - -/** - * opens sheet to ask user if he really wants to delete the db - */ -- (IBAction)removeDatabase:(id)sender -{ - // No database selected, bail - if ([chooseDatabaseButton indexOfSelectedItem] == 0) return; - - if (![tablesListInstance selectionShouldChangeInTableView:nil]) return; - - NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Delete database '%@'?", @"delete database message"), [self database]] - defaultButton:NSLocalizedString(@"Delete", @"delete button") - alternateButton:NSLocalizedString(@"Cancel", @"cancel button") - otherButton:nil - informativeTextWithFormat:[NSString stringWithFormat:NSLocalizedString(@"Are you sure you want to delete the database '%@'? This operation cannot be undone.", @"delete database informative message"), [self database]]]; - - NSArray *buttons = [alert buttons]; - - // Change the alert's cancel button to have the key equivalent of return - [[buttons objectAtIndex:0] setKeyEquivalent:@"d"]; - [[buttons objectAtIndex:0] setKeyEquivalentModifierMask:NSCommandKeyMask]; - [[buttons objectAtIndex:1] setKeyEquivalent:@"\r"]; - - [alert setAlertStyle:NSCriticalAlertStyle]; - - [alert beginSheetModalForWindow:parentWindow modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:@"removeDatabase"]; -} - -/** - * Refreshes the tables list by calling SPTablesList's updateTables. - */ -- (IBAction)refreshTables:(id)sender -{ - [tablesListInstance updateTables:self]; -} - -/** - * Displays the database server variables sheet. - */ -- (IBAction)showServerVariables:(id)sender + * Displays the database server variables sheet. + */ +- (IBAction)showServerVariables:(id)sender { if (!serverVariablesController) { serverVariablesController = [[SPServerVariablesController alloc] init]; @@ -2556,7 +2159,7 @@ { [[NSApp delegate] newWindow:self]; SPDatabaseDocument *newTableDocument = [[NSApp delegate] frontDocument]; - [newTableDocument initWithConnectionFile:[[self fileURL] path]]; + [newTableDocument setStateFromConnectionFile:[[self fileURL] path]]; } /** @@ -3291,1067 +2894,1592 @@ return NO; } - [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL:[NSURL fileURLWithPath:fileName]]; + [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL:[NSURL fileURLWithPath:fileName]]; + + return YES; + + } + + // Set up the dictionary to save to file, together with a data store + NSMutableDictionary *spfStructure = [NSMutableDictionary dictionary]; + NSMutableDictionary *spfData = [NSMutableDictionary dictionary]; + + // Add basic details + [spfStructure setObject:[NSNumber numberWithInteger:1] forKey:@"version"]; + [spfStructure setObject:@"connection" forKey:@"format"]; + [spfStructure setObject:@"mysql" forKey:@"rdbms_type"]; + if([self mySQLVersion]) + [spfStructure setObject:[self mySQLVersion] forKey:@"rdbms_version"]; + + // Add auto-connect if appropriate + [spfStructure setObject:[spfDocData_temp objectForKey:@"auto_connect"] forKey:@"auto_connect"]; + + // 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]) + [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 *dataToEncrypt = [[[NSMutableData alloc] init] autorelease]; + NSKeyedArchiver *archiver = [[[NSKeyedArchiver alloc] initForWritingWithMutableData:dataToEncrypt] autorelease]; + [archiver encodeObject:spfData forKey:@"data"]; + [archiver finishEncoding]; + [spfStructure setObject:[dataToEncrypt dataEncryptedWithPassword:[spfDocData_temp objectForKey:@"e_string"]] forKey:@"data"]; + } + + // Convert to plist + NSString *err = nil; + NSData *plist = [NSPropertyListSerialization dataFromPropertyList:spfStructure + format:NSPropertyListXMLFormat_v1_0 + errorDescription:&err]; + + 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]; + + [alert setAlertStyle:NSCriticalAlertStyle]; + [alert runModal]; + return NO; + } + + NSError *error = nil; + [plist writeToFile:fileName options:NSAtomicWrite error:&error]; + if (error != nil){ + NSAlert *errorAlert = [NSAlert alertWithError:error]; + [errorAlert runModal]; + return NO; + } + + if (contextInfo == nil) { + // Register and update query favorites, content filter, and history for the (new) file URL + NSMutableDictionary *preferences = [[NSMutableDictionary alloc] init]; + [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]]; + [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL:[NSURL fileURLWithPath:fileName]]; + + [self updateWindowTitle:self]; + + // Store doc data permanently + [spfDocData removeAllObjects]; + [spfDocData addEntriesFromDictionary:spfDocData_temp]; + + [preferences release]; + } + + return YES; + +} + +/** + * 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 +{ + [tableDumpInstance importFile]; +} + +/** + * Passes the request to the dataImport object + */ +- (IBAction)importFromClipboard:(id)sender +{ + [tableDumpInstance importFromClipboard]; +} + +/** + * Show the MySQL Help TOC of the current MySQL connection + * Invoked by the MainMenu > Help > MySQL Help + */ +- (IBAction)showMySQLHelp:(id)sender +{ + [customQueryInstance showHelpFor:SP_HELP_TOC_SEARCH_STRING addToHistory:YES calledByAutoHelp:NO]; + [[customQueryInstance helpWebViewWindow] makeKeyWindow]; +} + +/** + * Menu item validation. + */ +- (BOOL)validateMenuItem:(NSMenuItem *)menuItem +{ + if ([menuItem menu] == chooseDatabaseButton) { + return (_isConnected && databaseListIsSelectable); + } + + if (!_isConnected || _isWorkingLevel) { + return ([menuItem action] == @selector(newWindow:) || [menuItem action] == @selector(terminate:) || [menuItem action] == @selector(closeTab:)); + } + + if ([menuItem action] == @selector(openCurrentConnectionInNewWindow:)) + { + if([self isUntitled]) { + [menuItem setTitle:NSLocalizedString(@"Open in New Window", @"menu item open in new window")]; + return NO; + } else { + [menuItem setTitle:[NSString stringWithFormat:NSLocalizedString(@"Open “%@” in New Window", @"menu item open “%@” in new window"), [self displayName]]]; + return YES; + } + } + + // Data export + if ([menuItem action] == @selector(export:)) { + return (([self database] != nil) && ([[tablesListInstance tables] count] > 1)); + } + + // Selected tables data export + if ([menuItem action] == @selector(exportSelectedTablesAs:)) { + + NSInteger tag = [menuItem tag]; + NSInteger type = [tablesListInstance tableType]; + NSInteger numberOfSelectedItems = [[[tablesListInstance valueForKeyPath:@"tablesListView"] selectedRowIndexes] count]; + + BOOL enable = (([self database] != nil) && numberOfSelectedItems); + + // Enable all export formats if at least one table/view is selected + if (numberOfSelectedItems == 1) { + if (type == SPTableTypeTable || type == SPTableTypeView) { + return enable; + } + else if ((type == SPTableTypeProc) || (type == SPTableTypeFunc)) { + return (enable && (tag == SPSQLExport)); + } + } + else { + for (NSNumber *type in [tablesListInstance selectedTableTypes]) + { + if ([type intValue] == SPTableTypeTable || [type intValue] == SPTableTypeView) return enable; + } + + return (enable && (tag == SPSQLExport)); + } + } + + 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); + } + + if ([menuItem action] == @selector(importFromClipboard:)) + { + return ([self database] && [[NSPasteboard generalPasteboard] availableTypeFromArray:[NSArray arrayWithObjects:NSStringPboardType, nil]]) ? YES : NO; + + } + + // Change "Save Query/Queries" menu item title dynamically + // and disable it if no query in the editor + if ([menuItem action] == @selector(saveConnectionSheet:) && [menuItem tag] == 0) { + if([customQueryInstance numberOfQueries] < 1) { + [menuItem setTitle:NSLocalizedString(@"Save Query…", @"Save Query…")]; + return NO; + } + else if([customQueryInstance numberOfQueries] == 1) + [menuItem setTitle:NSLocalizedString(@"Save Query…", @"Save Query…")]; + else + [menuItem setTitle:NSLocalizedString(@"Save Queries…", @"Save Queries…")]; + + return YES; + } + + if ([menuItem action] == @selector(printDocument:)) { + return (([self database] != nil && [[tablesListInstance valueForKeyPath:@"tablesListView"] numberOfSelectedRows] == 1) || + // If Custom Query Tab is active the textView will handle printDocument by itself + // if it is first responder; otherwise allow to print the Query Result table even + // if no db/table is selected + [tableTabView indexOfTabViewItem:[tableTabView selectedTabViewItem]] == 2); + } + + if ([menuItem action] == @selector(chooseEncoding:)) { + return [self supportsEncoding]; + } + + if ([menuItem action] == @selector(analyzeTable:) || + [menuItem action] == @selector(optimizeTable:) || + [menuItem action] == @selector(repairTable:) || + [menuItem action] == @selector(flushTable:) || + [menuItem action] == @selector(checkTable:) || + [menuItem action] == @selector(checksumTable:) || + [menuItem action] == @selector(showCreateTableSyntax:) || + [menuItem action] == @selector(copyCreateTableSyntax:)) + { + return ([[[tablesListInstance valueForKeyPath:@"tablesListView"] selectedRowIndexes] count]) ? YES:NO; + } + + if ([menuItem action] == @selector(addConnectionToFavorites:)) { + return ([connectionController selectedFavorite] ? NO : YES); + } + + // Backward in history menu item + if (([menuItem action] == @selector(backForwardInHistory:)) && ([menuItem tag] == 0)) { + return (([[spHistoryControllerInstance history] count]) && ([spHistoryControllerInstance historyPosition] > 0)); + } + + // Forward in history menu item + if (([menuItem action] == @selector(backForwardInHistory:)) && ([menuItem tag] == 1)) { + return (([[spHistoryControllerInstance history] count]) && (([spHistoryControllerInstance historyPosition] + 1) < [[spHistoryControllerInstance history] count])); + } + + // Show/hide console + if ([menuItem action] == @selector(toggleConsole:)) { + [menuItem setTitle:([[[SPQueryController sharedQueryController] window] isVisible]) ? NSLocalizedString(@"Hide Console", @"hide console") : NSLocalizedString(@"Show Console", @"show console")]; + } + + // Clear console + if ([menuItem action] == @selector(clearConsole:)) { + return ([[SPQueryController sharedQueryController] consoleMessageCount] > 0); + } + + // Show/hide console + if ([menuItem action] == @selector(toggleNavigator:)) { + [menuItem setTitle:([[[SPNavigatorController sharedNavigatorController] window] isVisible]) ? NSLocalizedString(@"Hide Navigator", @"hide navigator") : NSLocalizedString(@"Show Navigator", @"show navigator")]; + } + + // Focus on table content filter + if ([menuItem action] == @selector(focusOnTableContentFilter:)) { + return ([self table] != nil && [[self table] isNotEqualTo:@""]); + } + + // Focus on table list or filter resp. + if ([menuItem action] == @selector(focusOnTableListFilter:)) { + + if([[tablesListInstance valueForKeyPath:@"tables"] count] > 20) + [menuItem setTitle:NSLocalizedString(@"Filter Tables", @"filter tables menu item")]; + else + [menuItem setTitle:NSLocalizedString(@"Change Focus to Table List", @"change focus to table list menu item")]; + + return ([[tablesListInstance valueForKeyPath:@"tables"] count] > 1); + } + + // If validation for the sort favorites tableview items reaches here then the preferences window isn't + // open return NO. + if (([menuItem action] == @selector(sortFavorites:)) || ([menuItem action] == @selector(reverseFavoritesSortOrder:))) { + return NO; + } + + // Default to YES for unhandled menus + return YES; +} + +/** + * Adds the current database connection details to the user's favorites if it doesn't already exist. + */ +- (IBAction)addConnectionToFavorites:(id)sender +{ + // Obviously don't add if it already exists. We shouldn't really need this as the menu item validation + // enables or disables the menu item based on the same method. Although to be safe do the check anyway + // as we don't know what's calling this method. + if ([connectionController selectedFavorite]) return; + + // Request the connection controller to add its details to favorites + [connectionController addFavorite:self]; +} + +/** + * Return YES if Custom Query is active. + */ +- (BOOL)isCustomQuerySelected +{ + return [[self selectedToolbarItemIdentifier] isEqualToString:SPMainToolbarCustomQuery]; +} + +/** + * Called when the NSSavePanel sheet ends. Writes the server variables to the selected file if required. + */ +- (void)savePanelDidEnd:(NSSavePanel *)sheet returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo +{ + if (returnCode == NSOKButton) { + if ([contextInfo isEqualToString:@"CreateSyntax"]) { + + NSString *createSyntax = [createTableSyntaxTextView string]; - return YES; + if ([createSyntax length] > 0) { + NSString *output = [NSString stringWithFormat:@"-- Create syntax for '%@'\n\n%@\n", [self table], createSyntax]; + [output writeToFile:[sheet filename] atomically:YES encoding:NSUTF8StringEncoding error:NULL]; + } + } } +} - NSString *aString; +/** + * Return the createTableSyntaxWindow + */ +- (NSWindow *)getCreateTableSyntaxWindow +{ + return createTableSyntaxWindow; +} - NSMutableDictionary *spfdata = [NSMutableDictionary dictionary]; - NSMutableDictionary *connection = [NSMutableDictionary dictionary]; - NSMutableDictionary *session = nil; - NSMutableDictionary *data = [NSMutableDictionary dictionary]; +#pragma mark - +#pragma mark Titlebar Methods - NSIndexSet *contentSelectedIndexSet = [tableContentInstance selectedRowIndexes]; +/** + * Update the window title. + */ +- (void) updateWindowTitle:(id)sender +{ + NSMutableString *tabTitle; + NSMutableString *windowTitle; + SPDatabaseDocument *frontTableDocument = [parentWindowController selectedTableDocument]; - [spfdata setObject:[NSNumber numberWithInteger:1] forKey:@"version"]; - [spfdata setObject:@"connection" forKey:@"format"]; - [spfdata setObject:@"mysql" forKey:@"rdbms_type"]; - if([self mySQLVersion]) - [spfdata setObject:[self mySQLVersion] forKey:@"rdbms_version"]; + // Determine name details + NSString *pathName = @""; + if ([[[self fileURL] path] length] && ![self isUntitled]) { + pathName = [NSString stringWithFormat:@"%@ — ", [[[self fileURL] path] lastPathComponent]]; + } + + if ([connectionController isConnecting]) { + windowTitle = NSLocalizedString(@"Connecting…", @"window title string indicating that sp is connecting"); + tabTitle = windowTitle; + } + else if (!_isConnected) { + windowTitle = [NSString stringWithFormat:@"%@%@", pathName, @"Sequel Pro"]; + tabTitle = windowTitle; + } + else { + windowTitle = [NSMutableString string]; + tabTitle = [NSMutableString string]; - // 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]; + // Add the path to the window title + [windowTitle appendString:pathName]; - [spfdata setObject:[spfDocData_temp objectForKey:@"encrypted"] forKey:@"encrypted"]; + // Add the MySQL version to the window title if enabled in prefs + if ([prefs boolForKey:SPDisplayServerVersionInWindowTitle]) [windowTitle appendFormat:@"(MySQL %@) ", mySQLVersion]; - [spfdata setObject:[spfDocData_temp objectForKey:@"auto_connect"] forKey:@"auto_connect"]; + // Add the name to the window + [windowTitle appendString:[self name]]; - 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"]; + // Also add to the non-front tabs if the host is different, not connected, or no db is selected + if ([[frontTableDocument name] isNotEqualTo:[self name]] || ![frontTableDocument getConnection] || ![self database]) { + [tabTitle appendString:[self name]]; + } - [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"]; + // If a database is selected, add to the window - and other tabs if host is the same but db different or table is not set + if ([self database]) { + [windowTitle appendFormat:@"/%@", [self database]]; + if (frontTableDocument == self + || ![frontTableDocument getConnection] + || [[frontTableDocument name] isNotEqualTo:[self name]] + || [[frontTableDocument database] isNotEqualTo:[self database]] + || ![[self table] length]) + { + if ([tabTitle length]) [tabTitle appendString:@"/"]; + [tabTitle appendString:[self database]]; + } + } - 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"; + // Add the table name if one is selected + if ([[self table] length]) { + [windowTitle appendFormat:@"/%@", [self table]]; + if ([tabTitle length]) [tabTitle appendString:@"/"]; + [tabTitle appendString:[self table]]; + } + } + + // Set the titles + [parentTabViewItem setLabel:tabTitle]; + if ([parentWindowController selectedTableDocument] == self) { + [parentWindow setTitle:windowTitle]; } - [connection setObject:aString forKey:@"type"]; - if([[spfDocData_temp objectForKey:@"save_password"] boolValue]) { + // If the sender wasn't the window controller, update other tabs in this window + // for shared pathname updates + if ([sender class] != [SPWindowController class]) [parentWindowController updateAllTabTitles:self]; +} + +/** + * Set the connection status icon in the titlebar + */ +- (void)setStatusIconToImageWithName:(NSString *)imageName +{ + NSString *imagePath = [[NSBundle mainBundle] pathForResource:imageName ofType:@"png"]; + if (!imagePath) return; - NSString *pw = [self keychainPasswordForConnection:nil]; - if(![pw length]) pw = [connectionController password]; - if (pw) - [connection setObject:pw forKey:@"password"]; - else - [connection setObject:@"" forKey:@"password"]; + NSImage *image = [[[NSImage alloc] initByReferencingFile:imagePath] autorelease]; + [titleImageView setImage:image]; +} - 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"]; - } - } +- (void)setTitlebarStatus:(NSString *)status +{ + [self clearStatusIcon]; + [titleStringView setStringValue:status]; +} - if([connectionController port] && [[connectionController port] length]) - [connection setObject:[NSNumber numberWithInteger:[[connectionController port] integerValue]] forKey:@"port"]; +/** + * Clear the connection status icon in the titlebar + */ +- (void)clearStatusIcon +{ + [titleImageView setImage:nil]; +} - if([[self database] length]) - [connection setObject:[self database] forKey:@"database"]; +#pragma mark - +#pragma mark Toolbar Methods - // Include session data like selected table, view etc. ? - if([[spfDocData_temp objectForKey:@"include_session"] boolValue]) { +/** + * set up the standard toolbar + */ +- (void)setupToolbar +{ + // create a new toolbar instance, and attach it to our document window + mainToolbar = [[NSToolbar alloc] initWithIdentifier:@"TableWindowToolbar"]; - session = [NSMutableDictionary dictionary]; + // set up toolbar properties + [mainToolbar setAllowsUserCustomization:YES]; + [mainToolbar setAutosavesConfiguration:YES]; + [mainToolbar setShowsBaselineSeparator:NO]; + [mainToolbar setDisplayMode:NSToolbarDisplayModeIconAndLabel]; - if([[self table] length]) - [session setObject:[self table] forKey:@"table"]; - if([tableContentInstance sortColumnName]) - [session setObject:[tableContentInstance sortColumnName] forKey:@"contentSortCol"]; + // set ourself as the delegate + [mainToolbar setDelegate:self]; - 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"]; + // update the toolbar item size + [self updateChooseDatabaseToolbarItemWidth]; - [session setObject:[NSNumber numberWithBool:[[parentWindow toolbar] isVisible]] forKey:@"isToolbarVisible"]; - [session setObject:[mySQLConnection encoding] forKey:@"connectionEncoding"]; + // The history controller needs to track toolbar item state - trigger setup. + [spHistoryControllerInstance setupInterface]; +} - [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"]; +/** + * Return the identifier for the currently selected toolbar item, or nil if none is selected. + */ +- (NSString *)selectedToolbarItemIdentifier; +{ + return [mainToolbar selectedItemIdentifier]; +} - 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"]; - } - } +/** + * toolbar delegate method + */ +- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)willBeInsertedIntoToolbar +{ + NSToolbarItem *toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier] autorelease]; - if([[spfDocData_temp objectForKey:@"save_editor_content"] boolValue]) { - if(session == nil) - session = [NSMutableDictionary dictionary]; + if ([itemIdentifier isEqualToString:SPMainToolbarDatabaseSelection]) { + [toolbarItem setLabel:NSLocalizedString(@"Select Database", @"toolbar item for selecting a db")]; + [toolbarItem setPaletteLabel:[toolbarItem label]]; + [toolbarItem setView:chooseDatabaseButton]; + [toolbarItem setMinSize:NSMakeSize(200,26)]; + [toolbarItem setMaxSize:NSMakeSize(200,32)]; + [chooseDatabaseButton setTarget:self]; + [chooseDatabaseButton setAction:@selector(chooseDatabase:)]; + [chooseDatabaseButton setEnabled:(_isConnected && !_isWorkingLevel)]; - 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"]; - } + if (willBeInsertedIntoToolbar) { + chooseDatabaseToolbarItem = toolbarItem; + [self updateChooseDatabaseToolbarItemWidth]; + } - [data setObject:connection forKey:@"connection"]; - if(session != nil) - [data setObject:session forKey:@"session"]; + } else if ([itemIdentifier isEqualToString:SPMainToolbarHistoryNavigation]) { + [toolbarItem setLabel:NSLocalizedString(@"Table History", @"toolbar item for navigation history")]; + [toolbarItem setPaletteLabel:[toolbarItem label]]; + [toolbarItem setView:historyControl]; - if(![[spfDocData_temp objectForKey:@"encrypted"] boolValue]) { - [spfdata setObject:data forKey:@"data"]; - } else { - NSMutableData *encryptdata = [[[NSMutableData alloc] init] autorelease]; - NSKeyedArchiver *archiver = [[[NSKeyedArchiver alloc] initForWritingWithMutableData:encryptdata] autorelease]; - [archiver encodeObject:data forKey:@"data"]; - [archiver finishEncoding]; - [spfdata setObject:[encryptdata dataEncryptedWithPassword:[spfDocData_temp objectForKey:@"e_string"]] forKey:@"data"]; - } + } else if ([itemIdentifier isEqualToString:SPMainToolbarShowConsole]) { + [toolbarItem setPaletteLabel:NSLocalizedString(@"Show Console", @"show console")]; + [toolbarItem setToolTip:NSLocalizedString(@"Show the console which shows all MySQL commands performed by Sequel Pro", @"tooltip for toolbar item for show console")]; - NSString *err = nil; - NSData *plist = [NSPropertyListSerialization dataFromPropertyList:spfdata - format:NSPropertyListXMLFormat_v1_0 - errorDescription:&err]; + [toolbarItem setLabel:NSLocalizedString(@"Console", @"Console")]; + [toolbarItem setImage:[NSImage imageNamed:@"hideconsole"]]; - 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]; + //set up the target action + [toolbarItem setTarget:self]; + [toolbarItem setAction:@selector(showConsole:)]; - [alert setAlertStyle:NSCriticalAlertStyle]; - [alert runModal]; - return NO; - } + } else if ([itemIdentifier isEqualToString:SPMainToolbarClearConsole]) { + //set the text label to be displayed in the toolbar and customization palette + [toolbarItem setLabel:NSLocalizedString(@"Clear Console", @"toolbar item for clear console")]; + [toolbarItem setPaletteLabel:NSLocalizedString(@"Clear Console", @"toolbar item for clear console")]; + //set up tooltip and image + [toolbarItem setToolTip:NSLocalizedString(@"Clear the console which shows all MySQL commands performed by Sequel Pro", @"tooltip for toolbar item for clear console")]; + [toolbarItem setImage:[NSImage imageNamed:@"clearconsole"]]; + //set up the target action + [toolbarItem setTarget:self]; + [toolbarItem setAction:@selector(clearConsole:)]; - NSError *error = nil; - [plist writeToFile:fileName options:NSAtomicWrite error:&error]; - if(error != nil){ - NSAlert *errorAlert = [NSAlert alertWithError:error]; - [errorAlert runModal]; - return NO; - } + } else if ([itemIdentifier isEqualToString:SPMainToolbarTableStructure]) { + [toolbarItem setLabel:NSLocalizedString(@"Structure", @"toolbar item label for switching to the Table Structure tab")]; + [toolbarItem setPaletteLabel:NSLocalizedString(@"Edit Table Structure", @"toolbar item label for switching to the Table Structure tab")]; + //set up tooltip and image + [toolbarItem setToolTip:NSLocalizedString(@"Switch to the Table Structure tab", @"tooltip for toolbar item for switching to the Table Structure tab")]; + [toolbarItem setImage:[NSImage imageNamed:@"toolbar-switch-to-structure"]]; + //set up the target action + [toolbarItem setTarget:self]; + [toolbarItem setAction:@selector(viewStructure:)]; - 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]; - [[SPQueryController sharedQueryController] registerDocumentWithFileURL:[NSURL fileURLWithPath:fileName] andContextInfo:preferences]; + } else if ([itemIdentifier isEqualToString:SPMainToolbarTableContent]) { + [toolbarItem setLabel:NSLocalizedString(@"Content", @"toolbar item label for switching to the Table Content tab")]; + [toolbarItem setPaletteLabel:NSLocalizedString(@"Browse & Edit Table Content", @"toolbar item label for switching to the Table Content tab")]; + //set up tooltip and image + [toolbarItem setToolTip:NSLocalizedString(@"Switch to the Table Content tab", @"tooltip for toolbar item for switching to the Table Content tab")]; + [toolbarItem setImage:[NSImage imageNamed:@"toolbar-switch-to-browse"]]; + //set up the target action + [toolbarItem setTarget:self]; + [toolbarItem setAction:@selector(viewContent:)]; - [self setFileURL:[NSURL fileURLWithPath:fileName]]; - [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL:[NSURL fileURLWithPath:fileName]]; + } else if ([itemIdentifier isEqualToString:SPMainToolbarCustomQuery]) { + [toolbarItem setLabel:NSLocalizedString(@"Query", @"toolbar item label for switching to the Run Query tab")]; + [toolbarItem setPaletteLabel:NSLocalizedString(@"Run Custom Query", @"toolbar item label for switching to the Run Query tab")]; + //set up tooltip and image + [toolbarItem setToolTip:NSLocalizedString(@"Switch to the Run Query tab", @"tooltip for toolbar item for switching to the Run Query tab")]; + [toolbarItem setImage:[NSImage imageNamed:@"toolbar-switch-to-sql"]]; + //set up the target action + [toolbarItem setTarget:self]; + [toolbarItem setAction:@selector(viewQuery:)]; - [self updateWindowTitle:self]; + } else if ([itemIdentifier isEqualToString:SPMainToolbarTableInfo]) { + [toolbarItem setLabel:NSLocalizedString(@"Table Info", @"toolbar item label for switching to the Table Info tab")]; + [toolbarItem setPaletteLabel:NSLocalizedString(@"Table Info", @"toolbar item label for switching to the Table Info tab")]; + //set up tooltip and image + [toolbarItem setToolTip:NSLocalizedString(@"Switch to the Table Info tab", @"tooltip for toolbar item for switching to the Table Info tab")]; + [toolbarItem setImage:[NSImage imageNamed:@"toolbar-switch-to-table-info"]]; + //set up the target action + [toolbarItem setTarget:self]; + [toolbarItem setAction:@selector(viewStatus:)]; - // Store doc data permanently - [spfDocData removeAllObjects]; - [spfDocData addEntriesFromDictionary:spfDocData_temp]; + } else if ([itemIdentifier isEqualToString:SPMainToolbarTableRelations]) { + [toolbarItem setLabel:NSLocalizedString(@"Relations", @"toolbar item label for switching to the Table Relations tab")]; + [toolbarItem setPaletteLabel:NSLocalizedString(@"Table Relations", @"toolbar item label for switching to the Table Relations tab")]; + //set up tooltip and image + [toolbarItem setToolTip:NSLocalizedString(@"Switch to the Table Relations tab", @"tooltip for toolbar item for switching to the Table Relations tab")]; + [toolbarItem setImage:[NSImage imageNamed:@"toolbar-switch-to-table-relations"]]; + //set up the target action + [toolbarItem setTarget:self]; + [toolbarItem setAction:@selector(viewRelations:)]; - [preferences release]; + } else if ([itemIdentifier isEqualToString:SPMainToolbarTableTriggers]) { + [toolbarItem setLabel:NSLocalizedString(@"Triggers", @"toolbar item label for switching to the Table Triggers tab")]; + [toolbarItem setPaletteLabel:NSLocalizedString(@"Table Triggers", @"toolbar item label for switching to the Table Triggers tab")]; + //set up tooltip and image + [toolbarItem setToolTip:NSLocalizedString(@"Switch to the Table Triggers tab", @"tooltip for toolbar item for switching to the Table Triggers tab")]; + [toolbarItem setImage:[NSImage imageNamed:@"toolbar-switch-to-table-triggers"]]; + //set up the target action + [toolbarItem setTarget:self]; + [toolbarItem setAction:@selector(viewTriggers:)]; + + } else if ([itemIdentifier isEqualToString:SPMainToolbarUserManager]) { + [toolbarItem setLabel:NSLocalizedString(@"Users", @"toolbar item label for switching to the User Manager tab")]; + [toolbarItem setPaletteLabel:NSLocalizedString(@"Users", @"toolbar item label for switching to the User Manager tab")]; + //set up tooltip and image + [toolbarItem setToolTip:NSLocalizedString(@"Switch to the User Manager tab", @"tooltip for toolbar item for switching to the User Manager tab")]; + [toolbarItem setImage:[NSImage imageNamed:NSImageNameEveryone]]; + //set up the target action + [toolbarItem setTarget:self]; + [toolbarItem setAction:@selector(showUserManager:)]; + + } else { + //itemIdentifier refered to a toolbar item that is not provided or supported by us or cocoa + toolbarItem = nil; } - return YES; - + return toolbarItem; } /** - * Passes the request to the dataImport object + * toolbar delegate method */ -- (IBAction)import:(id)sender +- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar*)toolbar { - [tableDumpInstance importFile]; + return [NSArray arrayWithObjects: + SPMainToolbarDatabaseSelection, + SPMainToolbarHistoryNavigation, + SPMainToolbarShowConsole, + SPMainToolbarClearConsole, + SPMainToolbarTableStructure, + SPMainToolbarTableContent, + SPMainToolbarCustomQuery, + SPMainToolbarTableInfo, + SPMainToolbarTableRelations, + SPMainToolbarTableTriggers, + SPMainToolbarUserManager, + NSToolbarCustomizeToolbarItemIdentifier, + NSToolbarFlexibleSpaceItemIdentifier, + NSToolbarSpaceItemIdentifier, + NSToolbarSeparatorItemIdentifier, + nil]; } /** - * Passes the request to the dataImport object + * toolbar delegate method */ -- (IBAction)importFromClipboard:(id)sender +- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar*)toolbar { - [tableDumpInstance importFromClipboard]; + return [NSArray arrayWithObjects: + SPMainToolbarDatabaseSelection, + SPMainToolbarTableStructure, + SPMainToolbarTableContent, + SPMainToolbarTableRelations, + SPMainToolbarTableInfo, + SPMainToolbarCustomQuery, + NSToolbarFlexibleSpaceItemIdentifier, + SPMainToolbarHistoryNavigation, + SPMainToolbarUserManager, + SPMainToolbarShowConsole, + nil]; } /** - * Show the MySQL Help TOC of the current MySQL connection - * Invoked by the MainMenu > Help > MySQL Help + * toolbar delegate method */ -- (IBAction)showMySQLHelp:(id)sender +- (NSArray *)toolbarSelectableItemIdentifiers:(NSToolbar *)toolbar { - [customQueryInstance showHelpFor:SP_HELP_TOC_SEARCH_STRING addToHistory:YES calledByAutoHelp:NO]; - [[customQueryInstance helpWebViewWindow] makeKeyWindow]; + return [NSArray arrayWithObjects: + SPMainToolbarTableStructure, + SPMainToolbarTableContent, + SPMainToolbarCustomQuery, + SPMainToolbarTableInfo, + SPMainToolbarTableRelations, + SPMainToolbarTableTriggers, + nil]; + } /** - * Menu item validation. + * Validates the toolbar items */ -- (BOOL)validateMenuItem:(NSMenuItem *)menuItem +- (BOOL)validateToolbarItem:(NSToolbarItem *)toolbarItem { - if ([menuItem menu] == chooseDatabaseButton) { - return (_isConnected && databaseListIsSelectable); - } + if (!_isConnected || _isWorkingLevel) return NO; - if (!_isConnected || _isWorkingLevel) { - return ([menuItem action] == @selector(newWindow:) || [menuItem action] == @selector(terminate:) || [menuItem action] == @selector(closeTab:)); - } + NSString *identifier = [toolbarItem itemIdentifier]; - if ([menuItem action] == @selector(openCurrentConnectionInNewWindow:)) - { - if([self isUntitled]) { - [menuItem setTitle:NSLocalizedString(@"Open in New Window", @"menu item open in new window")]; - return NO; + // Show console item + if ([identifier isEqualToString:SPMainToolbarShowConsole]) { + if ([[[SPQueryController sharedQueryController] window] isVisible]) { + [toolbarItem setImage:[NSImage imageNamed:@"showconsole"]]; } else { - [menuItem setTitle:[NSString stringWithFormat:NSLocalizedString(@"Open “%@” in New Window", @"menu item open “%@” in new window"), [self displayName]]]; - return YES; - } - } - - // Data export - if ([menuItem action] == @selector(export:)) { - return (([self database] != nil) && ([[tablesListInstance tables] count] > 1)); - } - - // Selected tables data export - if ([menuItem action] == @selector(exportSelectedTablesAs:)) { - - NSInteger tag = [menuItem tag]; - NSInteger type = [tablesListInstance tableType]; - NSInteger numberOfSelectedItems = [[[tablesListInstance valueForKeyPath:@"tablesListView"] selectedRowIndexes] count]; - - BOOL enable = (([self database] != nil) && numberOfSelectedItems); - - // Enable all export formats if at least one table/view is selected - if (numberOfSelectedItems == 1) { - if (type == SPTableTypeTable || type == SPTableTypeView) { - return enable; - } - else if ((type == SPTableTypeProc) || (type == SPTableTypeFunc)) { - return (enable && (tag == SPSQLExport)); - } - } - else { - for (NSNumber *type in [tablesListInstance selectedTableTypes]) - { - if ([type intValue] == SPTableTypeTable || [type intValue] == SPTableTypeView) return enable; - } - - return (enable && (tag == SPSQLExport)); + [toolbarItem setImage:[NSImage imageNamed:@"hideconsole"]]; } - } - - if ([menuItem action] == @selector(import:) || - [menuItem action] == @selector(removeDatabase:) || - [menuItem action] == @selector(copyDatabase:) || - [menuItem action] == @selector(renameDatabase:) || - [menuItem action] == @selector(refreshTables:)) - { - return ([self database] != nil); - } - - if ([menuItem action] == @selector(importFromClipboard:)) - { - return ([self database] && [[NSPasteboard generalPasteboard] availableTypeFromArray:[NSArray arrayWithObjects:NSStringPboardType, nil]]) ? YES : NO; - - } - - // Change "Save Query/Queries" menu item title dynamically - // and disable it if no query in the editor - if ([menuItem action] == @selector(saveConnectionSheet:) && [menuItem tag] == 0) { - if([customQueryInstance numberOfQueries] < 1) { - [menuItem setTitle:NSLocalizedString(@"Save Query…", @"Save Query…")]; + if ([[[SPQueryController sharedQueryController] window] isKeyWindow]) { return NO; - } - else if([customQueryInstance numberOfQueries] == 1) - [menuItem setTitle:NSLocalizedString(@"Save Query…", @"Save Query…")]; - else - [menuItem setTitle:NSLocalizedString(@"Save Queries…", @"Save Queries…")]; - - return YES; - } - - if ([menuItem action] == @selector(printDocument:)) { - return (([self database] != nil && [[tablesListInstance valueForKeyPath:@"tablesListView"] numberOfSelectedRows] == 1) || - // If Custom Query Tab is active the textView will handle printDocument by itself - // if it is first responder; otherwise allow to print the Query Result table even - // if no db/table is selected - [tableTabView indexOfTabViewItem:[tableTabView selectedTabViewItem]] == 2); - } - - if ([menuItem action] == @selector(chooseEncoding:)) { - return [self supportsEncoding]; - } - - if ([menuItem action] == @selector(analyzeTable:) || - [menuItem action] == @selector(optimizeTable:) || - [menuItem action] == @selector(repairTable:) || - [menuItem action] == @selector(flushTable:) || - [menuItem action] == @selector(checkTable:) || - [menuItem action] == @selector(checksumTable:) || - [menuItem action] == @selector(showCreateTableSyntax:) || - [menuItem action] == @selector(copyCreateTableSyntax:)) - { - return ([[[tablesListInstance valueForKeyPath:@"tablesListView"] selectedRowIndexes] count]) ? YES:NO; - } - - if ([menuItem action] == @selector(addConnectionToFavorites:)) { - return ([connectionController selectedFavorite] ? NO : YES); - } - - // Backward in history menu item - if (([menuItem action] == @selector(backForwardInHistory:)) && ([menuItem tag] == 0)) { - return (([[spHistoryControllerInstance history] count]) && ([spHistoryControllerInstance historyPosition] > 0)); - } - - // Forward in history menu item - if (([menuItem action] == @selector(backForwardInHistory:)) && ([menuItem tag] == 1)) { - return (([[spHistoryControllerInstance history] count]) && (([spHistoryControllerInstance historyPosition] + 1) < [[spHistoryControllerInstance history] count])); - } - - // Show/hide console - if ([menuItem action] == @selector(toggleConsole:)) { - [menuItem setTitle:([[[SPQueryController sharedQueryController] window] isVisible]) ? NSLocalizedString(@"Hide Console", @"hide console") : NSLocalizedString(@"Show Console", @"show console")]; - } - - // Clear console - if ([menuItem action] == @selector(clearConsole:)) { - return ([[SPQueryController sharedQueryController] consoleMessageCount] > 0); - } - - // Show/hide console - if ([menuItem action] == @selector(toggleNavigator:)) { - [menuItem setTitle:([[[SPNavigatorController sharedNavigatorController] window] isVisible]) ? NSLocalizedString(@"Hide Navigator", @"hide navigator") : NSLocalizedString(@"Show Navigator", @"show navigator")]; + } else { + return YES; + } } - - // Focus on table content filter - if ([menuItem action] == @selector(focusOnTableContentFilter:)) { - return ([self table] != nil && [[self table] isNotEqualTo:@""]); + + // Clear console item + if ([identifier isEqualToString:SPMainToolbarClearConsole]) { + return ([[SPQueryController sharedQueryController] consoleMessageCount] > 0); } - // Focus on table list or filter resp. - if ([menuItem action] == @selector(focusOnTableListFilter:)) { - - if([[tablesListInstance valueForKeyPath:@"tables"] count] > 20) - [menuItem setTitle:NSLocalizedString(@"Filter Tables", @"filter tables menu item")]; - else - [menuItem setTitle:NSLocalizedString(@"Change Focus to Table List", @"change focus to table list menu item")]; - - return ([[tablesListInstance valueForKeyPath:@"tables"] count] > 1); + if (![identifier isEqualToString:SPMainToolbarCustomQuery] && ![identifier isEqualToString:SPMainToolbarUserManager]) { + return (([tablesListInstance tableType] == SPTableTypeTable) || + ([tablesListInstance tableType] == SPTableTypeView)); } - - // If validation for the sort favorites tableview items reaches here then the preferences window isn't - // open return NO. - if (([menuItem action] == @selector(sortFavorites:)) || ([menuItem action] == @selector(reverseFavoritesSortOrder:))) { - return NO; + + return YES; +} + +#pragma mark - +#pragma mark Tab methods + +/** + * Make this document's window frontmost in the application, + * and ensure this tab is selected. + */ +- (void)makeKeyDocument +{ + [[[self parentWindow] onMainThread] makeKeyAndOrderFront:self]; + [[[[self parentTabViewItem] onMainThread] tabView] selectTabViewItemWithIdentifier:self]; +} + +/** + * Invoked to determine whether the parent tab is allowed to close + */ +- (BOOL)parentTabShouldClose +{ + // If no connection is available, always return YES. Covers initial setup and disconnections. + if(!_isConnected) return YES; + + // If tasks are active, return NO to allow tasks to complete + if (_isWorkingLevel) return NO; + + // If the table list considers itself to be working, return NO. This catches open alerts, and + // edits in progress in various views. + if ( ![tablesListInstance selectionShouldChangeInTableView:nil] ) return NO; + + // Auto-save spf file based connection and return whether the save was successful + if([self fileURL] && [[[self fileURL] path] length] && ![self isUntitled]) { + BOOL isSaved = [self saveDocumentWithFilePath:nil inBackground:YES onlyPreferences:YES contextInfo:nil]; + if(isSaved) + [[SPQueryController sharedQueryController] removeRegisteredDocumentWithFileURL:[self fileURL]]; + return isSaved; } - // Default to YES for unhandled menus + [[SPNavigatorController sharedNavigatorController] performSelectorOnMainThread:@selector(removeConnection:) withObject:[self connectionID] waitUntilDone:YES]; + + // Return YES by default return YES; } /** - * Adds the current database connection details to the user's favorites if it doesn't already exist. + * Invoked when the parent tab is about to close */ -- (IBAction)addConnectionToFavorites:(id)sender +- (void)parentTabDidClose { - // Obviously don't add if it already exists. We shouldn't really need this as the menu item validation - // enables or disables the menu item based on the same method. Although to be safe do the check anyway - // as we don't know what's calling this method. - if ([connectionController selectedFavorite]) return; - // Request the connection controller to add its details to favorites - [connectionController addFavorite:self]; + // Cancel autocompletion trigger + if([prefs boolForKey:SPCustomQueryAutoComplete]) + [NSObject cancelPreviousPerformRequestsWithTarget:[customQueryInstance valueForKeyPath:@"textView"] + selector:@selector(doAutoCompletion) + object:nil]; + if([prefs boolForKey:SPCustomQueryUpdateAutoHelp]) + [NSObject cancelPreviousPerformRequestsWithTarget:[customQueryInstance valueForKeyPath:@"textView"] + selector:@selector(autoHelp) + object:nil]; + + + [mySQLConnection setDelegate:nil]; + if (_isConnected) [self closeConnection]; + else [connectionController cancelConnection]; + if ([[[SPQueryController sharedQueryController] window] isVisible]) [self toggleConsole:self]; + [createTableSyntaxWindow orderOut:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [self setParentWindow:nil]; } /** - * Return YES if Custom Query is active. + * Invoked when the parent tab is currently the active tab in the + * window, but is being switched away from, to allow cleaning up + * details in the window. */ -- (BOOL)isCustomQuerySelected +- (void)willResignActiveTabInWindow { - return [[self selectedToolbarItemIdentifier] isEqualToString:SPMainToolbarCustomQuery]; + + // Remove the icon accessory view from the title bar + [titleAccessoryView removeFromSuperview]; + + // Remove the task progress window + [parentWindow removeChildWindow:taskProgressWindow]; + [taskProgressWindow orderOut:self]; } /** - * Called when the NSSavePanel sheet ends. Writes the server variables to the selected file if required. + * Invoked when the parent tab became the active tab in the window, + * to allow the window to reflect the contents of this view. */ -- (void)savePanelDidEnd:(NSSavePanel *)sheet returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo +- (void)didBecomeActiveTabInWindow { - if (returnCode == NSOKButton) { - if ([contextInfo isEqualToString:@"CreateSyntax"]) { - NSString *createSyntax = [createTableSyntaxTextView string]; + // Update the toolbar + BOOL toolbarVisible = ![parentWindow toolbar] || [[parentWindow toolbar] isVisible]; + [parentWindow setToolbar:mainToolbar]; + [[parentWindow toolbar] setVisible:toolbarVisible]; - if ([createSyntax length] > 0) { - NSString *output = [NSString stringWithFormat:@"-- Create syntax for '%@'\n\n%@\n", [self table], createSyntax]; + // Update the window's title and represented document + [self updateWindowTitle:self]; + if (spfFileURL && [spfFileURL isFileURL]) + [parentWindow setRepresentedURL:spfFileURL]; + else + [parentWindow setRepresentedURL:nil]; - [output writeToFile:[sheet filename] atomically:YES encoding:NSUTF8StringEncoding error:NULL]; + // Add the icon accessory view to the title bar + NSView *windowFrame = [[parentWindow contentView] superview]; + NSRect av = [titleAccessoryView frame]; + NSRect initialAccessoryViewFrame = NSMakeRect( + [windowFrame frame].size.width - av.size.width - 30, + [windowFrame frame].size.height - av.size.height, + av.size.width, + av.size.height); + [titleAccessoryView setFrame:initialAccessoryViewFrame]; + [windowFrame addSubview:titleAccessoryView]; + + // Add the progress window to this window + [self centerTaskWindow]; + [taskProgressWindow orderFront:self]; + [parentWindow addChildWindow:taskProgressWindow ordered:NSWindowAbove]; +} + +/** + * Invoked when the parent tab became the key tab in the application; + * the selected tab in the frontmost window. + */ +- (void)tabDidBecomeKey +{ + // Synchronize Navigator with current active document if Navigator runs in syncMode + if([[SPNavigatorController sharedNavigatorController] syncMode] && [self connectionID] && ![[self connectionID] isEqualToString:@"_"]) { + NSMutableString *schemaPath = [NSMutableString string]; + [schemaPath setString:[self connectionID]]; + if([self database] && [[self database] length]) { + [schemaPath appendString:SPUniqueSchemaDelimiter]; + [schemaPath appendString:[self database]]; + if([self table] && [[self table] length]) { + [schemaPath appendString:SPUniqueSchemaDelimiter]; + [schemaPath appendString:[self table]]; } } + [[SPNavigatorController sharedNavigatorController] selectPath:schemaPath]; } } /** - * Return the createTableSyntaxWindow + * Invoked when the document window is resized */ -- (NSWindow *)getCreateTableSyntaxWindow +- (void)tabDidResize { - return createTableSyntaxWindow; -} -#pragma mark - -#pragma mark Titlebar Methods + // If the task interface is visible, and this tab is frontmost, re-center the task child window + if (_isWorkingLevel && [parentWindowController selectedTableDocument] == self) [self centerTaskWindow]; +} /** - * Update the window title. + * Set the parent window */ -- (void) updateWindowTitle:(id)sender +- (void)setParentWindow:(NSWindow *)aWindow { - NSMutableString *tabTitle; - NSMutableString *windowTitle; - SPDatabaseDocument *frontTableDocument = [parentWindowController selectedTableDocument]; - - // Determine name details - NSString *pathName = @""; - if ([[[self fileURL] path] length] && ![self isUntitled]) { - pathName = [NSString stringWithFormat:@"%@ — ", [[[self fileURL] path] lastPathComponent]]; - } - - if ([connectionController isConnecting]) { - windowTitle = NSLocalizedString(@"Connecting…", @"window title string indicating that sp is connecting"); - tabTitle = windowTitle; + // If the window is being set for the first time - connection controller is visible - update focus + if (!parentWindow && !mySQLConnection) { + [aWindow makeFirstResponder:[connectionController valueForKey:@"favoritesTable"]]; + [connectionController performSelector:@selector(updateFavoriteSelection:) withObject:self afterDelay:0.0]; } - else if (!_isConnected) { - windowTitle = [NSString stringWithFormat:@"%@%@", pathName, @"Sequel Pro"]; - tabTitle = windowTitle; - } - else { - windowTitle = [NSMutableString string]; - tabTitle = [NSMutableString string]; - - // Add the path to the window title - [windowTitle appendString:pathName]; - // Add the MySQL version to the window title if enabled in prefs - if ([prefs boolForKey:SPDisplayServerVersionInWindowTitle]) [windowTitle appendFormat:@"(MySQL %@) ", mySQLVersion]; - - // Add the name to the window - [windowTitle appendString:[self name]]; + parentWindow = aWindow; + SPSSHTunnel *currentTunnel = [connectionController valueForKeyPath:@"sshTunnel"]; + if (currentTunnel) [currentTunnel setParentWindow:parentWindow]; +} - // Also add to the non-front tabs if the host is different, not connected, or no db is selected - if ([[frontTableDocument name] isNotEqualTo:[self name]] || ![frontTableDocument getConnection] || ![self database]) { - [tabTitle appendString:[self name]]; - } +/** + * Return the parent window + */ +- (NSWindow *)parentWindow +{ + return parentWindow; +} - // If a database is selected, add to the window - and other tabs if host is the same but db different or table is not set - if ([self database]) { - [windowTitle appendFormat:@"/%@", [self database]]; - if (frontTableDocument == self - || ![frontTableDocument getConnection] - || [[frontTableDocument name] isNotEqualTo:[self name]] - || [[frontTableDocument database] isNotEqualTo:[self database]] - || ![[self table] length]) - { - if ([tabTitle length]) [tabTitle appendString:@"/"]; - [tabTitle appendString:[self database]]; - } - } +#pragma mark - +#pragma mark NSDocument compatibility - // Add the table name if one is selected - if ([[self table] length]) { - [windowTitle appendFormat:@"/%@", [self table]]; - if ([tabTitle length]) [tabTitle appendString:@"/"]; - [tabTitle appendString:[self table]]; - } - } - - // Set the titles - [parentTabViewItem setLabel:tabTitle]; +/** + * Set the NSURL for a .spf file for this connection instance. + */ +- (void)setFileURL:(NSURL *)theURL +{ + if (spfFileURL) [spfFileURL release], spfFileURL = nil; + spfFileURL = [theURL retain]; if ([parentWindowController selectedTableDocument] == self) { - [parentWindow setTitle:windowTitle]; + if (spfFileURL && [spfFileURL isFileURL]) + [parentWindow setRepresentedURL:spfFileURL]; + else + [parentWindow setRepresentedURL:nil]; } - - // If the sender wasn't the window controller, update other tabs in this window - // for shared pathname updates - if ([sender class] != [SPWindowController class]) [parentWindowController updateAllTabTitles:self]; } /** - * Set the connection status icon in the titlebar + * Retrieve the NSURL for the .spf file for this connection instance (if any) */ -- (void)setStatusIconToImageWithName:(NSString *)imageName +- (NSURL *)fileURL { - NSString *imagePath = [[NSBundle mainBundle] pathForResource:imageName ofType:@"png"]; - if (!imagePath) return; + return [[spfFileURL copy] autorelease]; +} - NSImage *image = [[[NSImage alloc] initByReferencingFile:imagePath] autorelease]; - [titleImageView setImage:image]; +/** + * Invoked if user chose "Save" from 'Do you want save changes you made...' sheet + * which is called automatically if [self isDocumentEdited] == YES and user wanted to close an Untitled doc. + */ +- (BOOL)writeSafelyToURL:(NSURL *)absoluteURL ofType:(NSString *)typeName forSaveOperation:(NSSaveOperationType)saveOperation error:(NSError **)outError +{ + if(saveOperation == NSSaveOperation) { + // Dummy error to avoid crashes after Canceling the Save Panel + if (outError) *outError = [NSError errorWithDomain:@"SP_DOMAIN" code:1000 userInfo:nil]; + [self saveConnectionSheet:nil]; + return NO; + } + return YES; } -- (void)setTitlebarStatus:(NSString *)status +/** + * Shows "save?" dialog when closing the document if the an Untitled doc has doc-based query favorites or content filters. + */ +- (BOOL)isDocumentEdited { - [self clearStatusIcon]; - [titleStringView setStringValue:status]; + return ([self fileURL] && [[[self fileURL] path] length] && [self isUntitled] && ([[[SPQueryController sharedQueryController] favoritesForFileURL:[self fileURL]] count] + || [[[[SPQueryController sharedQueryController] contentFilterForFileURL:[self fileURL]] objectForKey:@"number"] count] + || [[[[SPQueryController sharedQueryController] contentFilterForFileURL:[self fileURL]] objectForKey:@"date"] count] + || [[[[SPQueryController sharedQueryController] contentFilterForFileURL:[self fileURL]] objectForKey:@"string"] count]) + ); } /** - * Clear the connection status icon in the titlebar + * The window title for this document. */ -- (void)clearStatusIcon +- (NSString *)displayName { - [titleImageView setImage:nil]; + if (!_isConnected) { + return [NSString stringWithFormat:@"%@%@", + ([[[self fileURL] path] length] && ![self isUntitled]) ? [NSString stringWithFormat:@"%@ — ",[[[self fileURL] path] lastPathComponent]] : @"", @"Sequel Pro"]; + + } + return [[[self fileURL] path] lastPathComponent]; } #pragma mark - -#pragma mark Toolbar Methods +#pragma mark State saving and setting /** - * set up the standard toolbar + * 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. */ -- (void)setupToolbar +- (NSDictionary *) stateIncludingDetails:(NSDictionary *)detailsToReturn { - // create a new toolbar instance, and attach it to our document window - mainToolbar = [[NSToolbar alloc] initWithIdentifier:@"TableWindowToolbar"]; + 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]; - // set up toolbar properties - [mainToolbar setAllowsUserCustomization:YES]; - [mainToolbar setAutosavesConfiguration:YES]; - [mainToolbar setShowsBaselineSeparator:NO]; - [mainToolbar setDisplayMode:NSToolbarDisplayModeIconAndLabel]; + if (!returnConnection && !returnSession && !returnHistory && !returnQuery) return nil; + NSMutableDictionary *stateDetails = [NSMutableDictionary dictionary]; - // set ourself as the delegate - [mainToolbar setDelegate:self]; + // Add connection details + if (returnConnection) { + NSMutableDictionary *connection = [NSMutableDictionary dictionary]; - // update the toolbar item size - [self updateChooseDatabaseToolbarItemWidth]; + [connection setObject:@"mysql" forKey:@"rdbms_type"]; - // The history controller needs to track toolbar item state - trigger setup. - [spHistoryControllerInstance setupInterface]; -} + 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"]; + } + } -/** - * Return the identifier for the currently selected toolbar item, or nil if none is selected. - */ -- (NSString *)selectedToolbarItemIdentifier; -{ - return [mainToolbar selectedItemIdentifier]; + [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; } /** - * toolbar delegate method + * 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. */ -- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)willBeInsertedIntoToolbar +- (BOOL)setState:(NSDictionary *)stateDetails { - NSToolbarItem *toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier] autorelease]; + NSDictionary *connection = nil; + NSInteger connectionType = -1; - if ([itemIdentifier isEqualToString:SPMainToolbarDatabaseSelection]) { - [toolbarItem setLabel:NSLocalizedString(@"Select Database", @"toolbar item for selecting a db")]; - [toolbarItem setPaletteLabel:[toolbarItem label]]; - [toolbarItem setView:chooseDatabaseButton]; - [toolbarItem setMinSize:NSMakeSize(200,26)]; - [toolbarItem setMaxSize:NSMakeSize(200,32)]; - [chooseDatabaseButton setTarget:self]; - [chooseDatabaseButton setAction:@selector(chooseDatabase:)]; - [chooseDatabaseButton setEnabled:(_isConnected && !_isWorkingLevel)]; + // If this document already has a connection, don't proceed. + if (mySQLConnection) return NO; - if (willBeInsertedIntoToolbar) { - chooseDatabaseToolbarItem = toolbarItem; - [self updateChooseDatabaseToolbarItemWidth]; - } + // Load the connection data from the state dictionary + connection = [NSDictionary dictionaryWithDictionary:[stateDetails objectForKey:@"connection"]]; + if (!connection) return NO; - } else if ([itemIdentifier isEqualToString:SPMainToolbarHistoryNavigation]) { - [toolbarItem setLabel:NSLocalizedString(@"Table History", @"toolbar item for navigation history")]; - [toolbarItem setPaletteLabel:[toolbarItem label]]; - [toolbarItem setView:historyControl]; + [self updateWindowTitle:self]; - } else if ([itemIdentifier isEqualToString:SPMainToolbarShowConsole]) { - [toolbarItem setPaletteLabel:NSLocalizedString(@"Show Console", @"show console")]; - [toolbarItem setToolTip:NSLocalizedString(@"Show the console which shows all MySQL commands performed by Sequel Pro", @"tooltip for toolbar item for show console")]; + // Deselect all favorites on the connection controller + [[connectionController valueForKeyPath:@"favoritesTable"] deselectAll:connectionController]; - [toolbarItem setLabel:NSLocalizedString(@"Console", @"Console")]; - [toolbarItem setImage:[NSImage imageNamed:@"hideconsole"]]; + // 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]; - //set up the target action - [toolbarItem setTarget:self]; - [toolbarItem setAction:@selector(showConsole:)]; + // 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]; - } else if ([itemIdentifier isEqualToString:SPMainToolbarClearConsole]) { - //set the text label to be displayed in the toolbar and customization palette - [toolbarItem setLabel:NSLocalizedString(@"Clear Console", @"toolbar item for clear console")]; - [toolbarItem setPaletteLabel:NSLocalizedString(@"Clear Console", @"toolbar item for clear console")]; - //set up tooltip and image - [toolbarItem setToolTip:NSLocalizedString(@"Clear the console which shows all MySQL commands performed by Sequel Pro", @"tooltip for toolbar item for clear console")]; - [toolbarItem setImage:[NSImage imageNamed:@"clearconsole"]]; - //set up the target action - [toolbarItem setTarget:self]; - [toolbarItem setAction:@selector(clearConsole:)]; + // 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; - } else if ([itemIdentifier isEqualToString:SPMainToolbarTableStructure]) { - [toolbarItem setLabel:NSLocalizedString(@"Structure", @"toolbar item label for switching to the Table Structure tab")]; - [toolbarItem setPaletteLabel:NSLocalizedString(@"Edit Table Structure", @"toolbar item label for switching to the Table Structure tab")]; - //set up tooltip and image - [toolbarItem setToolTip:NSLocalizedString(@"Switch to the Table Structure tab", @"tooltip for toolbar item for switching to the Table Structure tab")]; - [toolbarItem setImage:[NSImage imageNamed:@"toolbar-switch-to-structure"]]; - //set up the target action - [toolbarItem setTarget:self]; - [toolbarItem setAction:@selector(viewStructure:)]; + [connectionController setType:connectionType]; + [connectionController resizeTabViewToConnectionType:connectionType animating:NO]; + } - } else if ([itemIdentifier isEqualToString:SPMainToolbarTableContent]) { - [toolbarItem setLabel:NSLocalizedString(@"Content", @"toolbar item label for switching to the Table Content tab")]; - [toolbarItem setPaletteLabel:NSLocalizedString(@"Browse & Edit Table Content", @"toolbar item label for switching to the Table Content tab")]; - //set up tooltip and image - [toolbarItem setToolTip:NSLocalizedString(@"Switch to the Table Content tab", @"tooltip for toolbar item for switching to the Table Content tab")]; - [toolbarItem setImage:[NSImage imageNamed:@"toolbar-switch-to-browse"]]; - //set up the target action - [toolbarItem setTarget:self]; - [toolbarItem setAction:@selector(viewContent:)]; + // 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]]]; - } else if ([itemIdentifier isEqualToString:SPMainToolbarCustomQuery]) { - [toolbarItem setLabel:NSLocalizedString(@"Query", @"toolbar item label for switching to the Run Query tab")]; - [toolbarItem setPaletteLabel:NSLocalizedString(@"Run Custom Query", @"toolbar item label for switching to the Run Query tab")]; - //set up tooltip and image - [toolbarItem setToolTip:NSLocalizedString(@"Switch to the Run Query tab", @"tooltip for toolbar item for switching to the Run Query tab")]; - [toolbarItem setImage:[NSImage imageNamed:@"toolbar-switch-to-sql"]]; - //set up the target action - [toolbarItem setTarget:self]; - [toolbarItem setAction:@selector(viewQuery:)]; + // 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"]]; - } else if ([itemIdentifier isEqualToString:SPMainToolbarTableInfo]) { - [toolbarItem setLabel:NSLocalizedString(@"Table Info", @"toolbar item label for switching to the Table Info tab")]; - [toolbarItem setPaletteLabel:NSLocalizedString(@"Table Info", @"toolbar item label for switching to the Table Info tab")]; - //set up tooltip and image - [toolbarItem setToolTip:NSLocalizedString(@"Switch to the Table Info tab", @"tooltip for toolbar item for switching to the Table Info tab")]; - [toolbarItem setImage:[NSImage imageNamed:@"toolbar-switch-to-table-info"]]; - //set up the target action - [toolbarItem setTarget:self]; - [toolbarItem setAction:@selector(viewStatus:)]; + // 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]; + } - } else if ([itemIdentifier isEqualToString:SPMainToolbarTableRelations]) { - [toolbarItem setLabel:NSLocalizedString(@"Relations", @"toolbar item label for switching to the Table Relations tab")]; - [toolbarItem setPaletteLabel:NSLocalizedString(@"Table Relations", @"toolbar item label for switching to the Table Relations tab")]; - //set up tooltip and image - [toolbarItem setToolTip:NSLocalizedString(@"Switch to the Table Relations tab", @"tooltip for toolbar item for switching to the Table Relations tab")]; - [toolbarItem setImage:[NSImage imageNamed:@"toolbar-switch-to-table-relations"]]; - //set up the target action - [toolbarItem setTarget:self]; - [toolbarItem setAction:@selector(viewRelations:)]; + // Set the socket details, whether or not the type is a socket + if ([connection objectForKey:@"socket"]) + [connectionController setSocket:[connection objectForKey:@"socket"]]; - } else if ([itemIdentifier isEqualToString:SPMainToolbarTableTriggers]) { - [toolbarItem setLabel:NSLocalizedString(@"Triggers", @"toolbar item label for switching to the Table Triggers tab")]; - [toolbarItem setPaletteLabel:NSLocalizedString(@"Table Triggers", @"toolbar item label for switching to the Table Triggers tab")]; - //set up tooltip and image - [toolbarItem setToolTip:NSLocalizedString(@"Switch to the Table Triggers tab", @"tooltip for toolbar item for switching to the Table Triggers tab")]; - [toolbarItem setImage:[NSImage imageNamed:@"toolbar-switch-to-table-triggers"]]; - //set up the target action - [toolbarItem setTarget:self]; - [toolbarItem setAction:@selector(viewTriggers:)]; - - } else if ([itemIdentifier isEqualToString:SPMainToolbarUserManager]) { - [toolbarItem setLabel:NSLocalizedString(@"Users", @"toolbar item label for switching to the User Manager tab")]; - [toolbarItem setPaletteLabel:NSLocalizedString(@"Users", @"toolbar item label for switching to the User Manager tab")]; - //set up tooltip and image - [toolbarItem setToolTip:NSLocalizedString(@"Switch to the User Manager tab", @"tooltip for toolbar item for switching to the User Manager tab")]; - [toolbarItem setImage:[NSImage imageNamed:NSImageNameEveryone]]; - //set up the target action - [toolbarItem setTarget:self]; - [toolbarItem setAction:@selector(showUserManager:)]; - - } else { - //itemIdentifier refered to a toolbar item that is not provided or supported by us or cocoa - toolbarItem = nil; + // 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]; } - return toolbarItem; -} + // Restore the selected database if saved + if ([connection objectForKey:@"database"]) + [connectionController setDatabase:[connection objectForKey:@"database"]]; -/** - * toolbar delegate method - */ -- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar*)toolbar -{ - return [NSArray arrayWithObjects: - SPMainToolbarDatabaseSelection, - SPMainToolbarHistoryNavigation, - SPMainToolbarShowConsole, - SPMainToolbarClearConsole, - SPMainToolbarTableStructure, - SPMainToolbarTableContent, - SPMainToolbarCustomQuery, - SPMainToolbarTableInfo, - SPMainToolbarTableRelations, - SPMainToolbarTableTriggers, - SPMainToolbarUserManager, - NSToolbarCustomizeToolbarItemIdentifier, - NSToolbarFlexibleSpaceItemIdentifier, - NSToolbarSpaceItemIdentifier, - NSToolbarSeparatorItemIdentifier, - nil]; -} + // Store session details - if provided - for later setting once the connection is established + if ([stateDetails objectForKey:@"session"]) { + spfSession = [[NSDictionary dictionaryWithDictionary:[stateDetails objectForKey:@"session"]] retain]; + } -/** - * toolbar delegate method - */ -- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar*)toolbar -{ - return [NSArray arrayWithObjects: - SPMainToolbarDatabaseSelection, - SPMainToolbarTableStructure, - SPMainToolbarTableContent, - SPMainToolbarTableRelations, - SPMainToolbarTableInfo, - SPMainToolbarCustomQuery, - NSToolbarFlexibleSpaceItemIdentifier, - SPMainToolbarHistoryNavigation, - SPMainToolbarUserManager, - SPMainToolbarShowConsole, - nil]; -} + // 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]; -/** - * toolbar delegate method - */ -- (NSArray *)toolbarSelectableItemIdentifiers:(NSToolbar *)toolbar -{ - return [NSArray arrayWithObjects: - SPMainToolbarTableStructure, - SPMainToolbarTableContent, - SPMainToolbarCustomQuery, - SPMainToolbarTableInfo, - SPMainToolbarTableRelations, - SPMainToolbarTableTriggers, - nil]; + [connectionController updateSSLInterface:self]; + // Autoconnect if appropriate + if ([stateDetails objectForKey:@"auto_connect"] && [[stateDetails valueForKey:@"auto_connect"] boolValue]) { + [connectionController initiateConnection:self]; + } } /** - * Validates the toolbar items + * Initialise the document with the connection file at the supplied path. */ -- (BOOL)validateToolbarItem:(NSToolbarItem *)toolbarItem +- (void)setStateFromConnectionFile:(NSString *)path { - if (!_isConnected || _isWorkingLevel) return NO; + NSError *readError = nil; + NSString *convError = nil; + NSPropertyListFormat format; - NSString *identifier = [toolbarItem itemIdentifier]; + NSString *encryptpw = nil; + NSMutableDictionary *data = nil; + NSDictionary *spf = nil; - // Show console item - if ([identifier isEqualToString:SPMainToolbarShowConsole]) { - if ([[[SPQueryController sharedQueryController] window] isVisible]) { - [toolbarItem setImage:[NSImage imageNamed:@"showconsole"]]; - } else { - [toolbarItem setImage:[NSImage imageNamed:@"hideconsole"]]; - } - if ([[[SPQueryController sharedQueryController] window] isKeyWindow]) { - return NO; - } else { - return YES; - } + + // 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; } - // Clear console item - if ([identifier isEqualToString:SPMainToolbarClearConsole]) { - return ([[SPQueryController sharedQueryController] consoleMessageCount] > 0); + // 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; } - if (![identifier isEqualToString:SPMainToolbarCustomQuery] && ![identifier isEqualToString:SPMainToolbarUserManager]) { - return (([tablesListInstance tableType] == SPTableTypeTable) || - ([tablesListInstance tableType] == SPTableTypeView)); + // 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; } - return YES; -} + // 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]; -#pragma mark - -#pragma mark Tab methods + [NSApp beginSheet:inputTextWindow modalForWindow:parentWindow modalDelegate:self didEndSelector:nil contextInfo:nil]; -/** - * Make this document's window frontmost in the application, - * and ensure this tab is selected. - */ -- (void)makeKeyDocument -{ - [[[self parentWindow] onMainThread] makeKeyAndOrderFront:self]; - [[[[self parentTabViewItem] onMainThread] tabView] selectTabViewItemWithIdentifier:self]; -} + // wait for encryption password + NSModalSession session = [NSApp beginModalSessionForWindow:inputTextWindow]; + for (;;) { -/** - * Invoked to determine whether the parent tab is allowed to close - */ -- (BOOL)parentTabShouldClose -{ - // If no connection is available, always return YES. Covers initial setup and disconnections. - if(!_isConnected) return YES; + // Execute code on DefaultRunLoop + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode + beforeDate:[NSDate distantFuture]]; - // If tasks are active, return NO to allow tasks to complete - if (_isWorkingLevel) return NO; + // Break the run loop if editSheet was closed + if ([NSApp runModalSession:session] != NSRunContinuesResponse + || ![inputTextWindow isVisible]) + break; - // If the table list considers itself to be working, return NO. This catches open alerts, and - // edits in progress in various views. - if ( ![tablesListInstance selectionShouldChangeInTableView:nil] ) return NO; + // 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]; - // Auto-save spf file based connection and return whether the save was successful - if([self fileURL] && [[[self fileURL] path] length] && ![self isUntitled]) { - BOOL isSaved = [self saveDocumentWithFilePath:nil inBackground:YES onlyPreferences:YES contextInfo:nil]; - if(isSaved) - [[SPQueryController sharedQueryController] removeRegisteredDocumentWithFileURL:[self fileURL]]; - return isSaved; + [alert setAlertStyle:NSCriticalAlertStyle]; + [alert runModal]; + [self closeAndDisconnect]; + [spf release]; + return; } - [[SPNavigatorController sharedNavigatorController] performSelectorOnMainThread:@selector(removeConnection:) withObject:[self connectionID] waitUntilDone:YES]; + // 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]; - // Return YES by default - return YES; -} + // 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; -/** - * Invoked when the parent tab is about to close - */ -- (void)parentTabDidClose -{ + // If session data is available, ensure it is marked for save + if ([data objectForKey:@"session"]) { + [spfDocData setObject:[NSNumber numberWithBool:YES] forKey:@"include_session"]; + } - // Cancel autocompletion trigger - if([prefs boolForKey:SPCustomQueryAutoComplete]) - [NSObject cancelPreviousPerformRequestsWithTarget:[customQueryInstance valueForKeyPath:@"textView"] - selector:@selector(doAutoCompletion) - object:nil]; - if([prefs boolForKey:SPCustomQueryUpdateAutoHelp]) - [NSObject cancelPreviousPerformRequestsWithTarget:[customQueryInstance valueForKeyPath:@"textView"] - selector:@selector(autoHelp) - object:nil]; + 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"]; - [mySQLConnection setDelegate:nil]; - if (_isConnected) [self closeConnection]; - else [connectionController cancelConnection]; - if ([[[SPQueryController sharedQueryController] window] isVisible]) [self toggleConsole:self]; - [createTableSyntaxWindow orderOut:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self]; - [self setParentWindow:nil]; -} + [spfDocData setObject:[NSNumber numberWithBool:NO] forKey:@"auto_connect"]; -/** - * Invoked when the parent tab is currently the active tab in the - * window, but is being switched away from, to allow cleaning up - * details in the window. - */ -- (void)willResignActiveTabInWindow -{ + 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"]; + } - // Remove the icon accessory view from the title bar - [titleAccessoryView removeFromSuperview]; + // Set the state dictionary, triggering an autoconnect if appropriate + [self setState:data]; - // Remove the task progress window - [parentWindow removeChildWindow:taskProgressWindow]; - [taskProgressWindow orderOut:self]; + [spf release]; } /** - * Invoked when the parent tab became the active tab in the window, - * to allow the window to reflect the contents of this view. + * Restore session from SPF file if given */ -- (void)didBecomeActiveTabInWindow +- (void)restoreSession { + NSAutoreleasePool *taskPool = [[NSAutoreleasePool alloc] init]; - // Update the toolbar - BOOL toolbarVisible = ![parentWindow toolbar] || [[parentWindow toolbar] isVisible]; - [parentWindow setToolbar:mainToolbar]; - [[parentWindow toolbar] setVisible:toolbarVisible]; - - // Update the window's title and represented document - [self updateWindowTitle:self]; - if (spfFileURL && [spfFileURL isFileURL]) - [parentWindow setRepresentedURL:spfFileURL]; - else - [parentWindow setRepresentedURL:nil]; - - // Add the icon accessory view to the title bar - NSView *windowFrame = [[parentWindow contentView] superview]; - NSRect av = [titleAccessoryView frame]; - NSRect initialAccessoryViewFrame = NSMakeRect( - [windowFrame frame].size.width - av.size.width - 30, - [windowFrame frame].size.height - av.size.height, - av.size.width, - av.size.height); - [titleAccessoryView setFrame:initialAccessoryViewFrame]; - [windowFrame addSubview:titleAccessoryView]; + // Check and set the table + NSArray *tables = [tablesListInstance tables]; - // Add the progress window to this window - [self centerTaskWindow]; - [taskProgressWindow orderFront:self]; - [parentWindow addChildWindow:taskProgressWindow ordered:NSWindowAbove]; -} + BOOL isSelectedTableDefined = YES; -/** - * Invoked when the parent tab became the key tab in the application; - * the selected tab in the frontmost window. - */ -- (void)tabDidBecomeKey -{ - // Synchronize Navigator with current active document if Navigator runs in syncMode - if([[SPNavigatorController sharedNavigatorController] syncMode] && [self connectionID] && ![[self connectionID] isEqualToString:@"_"]) { - NSMutableString *schemaPath = [NSMutableString string]; - [schemaPath setString:[self connectionID]]; - if([self database] && [[self database] length]) { - [schemaPath appendString:SPUniqueSchemaDelimiter]; - [schemaPath appendString:[self database]]; - if([self table] && [[self table] length]) { - [schemaPath appendString:SPUniqueSchemaDelimiter]; - [schemaPath appendString:[self table]]; - } - } - [[SPNavigatorController sharedNavigatorController] selectPath:schemaPath]; + if([tables indexOfObject:[spfSession objectForKey:@"table"]] == NSNotFound) { + isSelectedTableDefined = NO; } -} - -/** - * Invoked when the document window is resized - */ -- (void)tabDidResize -{ - // If the task interface is visible, and this tab is frontmost, re-center the task child window - if (_isWorkingLevel && [parentWindowController selectedTableDocument] == self) [self centerTaskWindow]; -} + // Restore toolbar setting + if([spfSession objectForKey:@"isToolbarVisible"]) + [mainToolbar setVisible:[[spfSession objectForKey:@"isToolbarVisible"] boolValue]]; -/** - * Set the parent window - */ -- (void)setParentWindow:(NSWindow *)aWindow -{ - // If the window is being set for the first time - connection controller is visible - update focus - if (!parentWindow && !mySQLConnection) { - [aWindow makeFirstResponder:[connectionController valueForKey:@"favoritesTable"]]; - [connectionController performSelector:@selector(updateFavoriteSelection:) withObject:self afterDelay:0.0]; - } + // 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]; - parentWindow = aWindow; - SPSSHTunnel *currentTunnel = [connectionController valueForKeyPath:@"sshTunnel"]; - if (currentTunnel) [currentTunnel setParentWindow:parentWindow]; -} + 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"]]; -/** - * Return the parent window - */ -- (NSWindow *)parentWindow -{ - return parentWindow; -} + // Select table + [tablesListInstance selectTableAtIndex:[NSNumber numberWithInteger:[tables indexOfObject:[spfSession objectForKey:@"table"]]]]; -#pragma mark - -#pragma mark NSDocument compatibility + // 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]]; -/** - * Set the NSURL for a .spf file for this connection instance. - */ -- (void)setFileURL:(NSURL *)theURL -{ - if (spfFileURL) [spfFileURL release], spfFileURL = nil; - spfFileURL = [theURL retain]; - if ([parentWindowController selectedTableDocument] == self) { - if (spfFileURL && [spfFileURL isFileURL]) - [parentWindow setRepresentedURL:spfFileURL]; - else - [parentWindow setRepresentedURL:nil]; - } -} + [tableContentInstance setSelectedRowIndexesToRestore:anIndexSet]; + } -/** - * Retrieve the NSURL for the .spf file for this connection instance (if any) - */ -- (NSURL *)fileURL -{ - return [[spfFileURL copy] autorelease]; -} + [[tablesListInstance valueForKeyPath:@"tablesListView"] scrollRowToVisible:[tables indexOfObject:[spfSession objectForKey:@"selectedTable"]]]; -/** - * Invoked if user chose "Save" from 'Do you want save changes you made...' sheet - * which is called automatically if [self isDocumentEdited] == YES and user wanted to close an Untitled doc. - */ -- (BOOL)writeSafelyToURL:(NSURL *)absoluteURL ofType:(NSString *)typeName forSaveOperation:(NSSaveOperationType)saveOperation error:(NSError **)outError -{ - if(saveOperation == NSSaveOperation) { - // Dummy error to avoid crashes after Canceling the Save Panel - if (outError) *outError = [NSError errorWithDomain:@"SP_DOMAIN" code:1000 userInfo:nil]; - [self saveConnectionSheet:nil]; - return NO; } - return YES; -} -/** - * Shows "save?" dialog when closing the document if the an Untitled doc has doc-based query favorites or content filters. - */ -- (BOOL)isDocumentEdited -{ - return ([self fileURL] && [[[self fileURL] path] length] && [self isUntitled] && ([[[SPQueryController sharedQueryController] favoritesForFileURL:[self fileURL]] count] - || [[[[SPQueryController sharedQueryController] contentFilterForFileURL:[self fileURL]] objectForKey:@"number"] count] - || [[[[SPQueryController sharedQueryController] contentFilterForFileURL:[self fileURL]] objectForKey:@"date"] count] - || [[[[SPQueryController sharedQueryController] contentFilterForFileURL:[self fileURL]] objectForKey:@"string"] count]) - ); -} + // 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]; -/** - * The window title for this document. - */ -- (NSString *)displayName -{ - if (!_isConnected) { - return [NSString stringWithFormat:@"%@%@", - ([[[self fileURL] path] length] && ![self isUntitled]) ? [NSString stringWithFormat:@"%@ — ",[[[self fileURL] path] lastPathComponent]] : @"", @"Sequel Pro"]; + [self updateWindowTitle:self]; - } - return [[[self fileURL] path] lastPathComponent]; + // dealloc spfSession data + [spfSession release]; + spfSession = nil; + + // End the task + [self endTask]; + [taskPool drain]; } #pragma mark - -- cgit v1.2.3 ref='#n4321'>4321 4322 4323 4324 4325 4326 4327 4328 4329 4330 4331 4332 4333 4334 4335 4336 4337 4338 4339 4340 4341 4342 4343 4344 4345 4346 4347 4348 4349 4350 4351 4352 4353 4354 4355 4356 4357 4358 4359 4360 4361 4362 4363 4364 4365 4366 4367 4368 4369 4370 4371 4372 4373 4374 4375 4376 4377 4378 4379 4380 4381 4382 4383 4384 4385 4386 4387 4388 4389 4390 4391 4392 4393 4394 4395 4396 4397 4398 4399 4400 4401 4402 4403 4404 4405 4406 4407 4408 4409 4410 4411 4412 4413 4414 4415 4416 4417 4418 4419 4420 4421 4422 4423 4424 4425 4426 4427 4428 4429 4430 4431 4432 4433 4434 4435 4436 4437 4438 4439 4440 4441 4442 4443 4444 4445 4446 4447 4448 4449 4450 4451 4452 4453 4454 4455 4456 4457 4458 4459 4460 4461 4462 4463 4464 4465 4466 4467 4468 4469 4470 4471 4472 4473 4474 4475 4476 4477 4478 4479 4480 4481 4482 4483 4484 4485 4486 4487 4488 4489 4490 4491 4492 4493 4494 4495 4496 4497 4498 4499 4500 4501 4502 4503 4504 4505 4506 4507 4508 4509 4510 4511 4512 4513 4514 4515 4516 4517 4518 4519 4520 4521 4522 4523 4524 4525 4526 4527 4528 4529 4530 4531 4532 4533 4534 4535 4536 4537 4538 4539 4540 4541 4542 4543 4544 4545 4546 4547 4548 4549 4550 4551 4552 4553 4554 4555 4556 4557 4558 4559 4560 4561 4562 4563 4564 4565 4566 4567 4568 4569 4570 4571 4572 4573 4574 4575 4576 4577 4578 4579 4580 4581 4582 4583 4584 4585 4586 4587 4588 4589 4590 4591 4592 4593 4594 4595 4596 4597 4598 4599 4600 4601 4602 4603 4604 4605 4606 4607 4608 4609 4610 4611 4612 4613 4614 4615 4616 4617 4618 4619 4620 4621 4622 4623 4624 4625 4626 4627 4628 4629 4630 4631 4632 4633 4634 4635 4636 4637 4638 4639 4640 4641 4642 4643 4644 4645 4646 4647 4648 4649 4650 4651 4652 4653 4654 4655 4656 4657 4658 4659 4660 4661 4662 4663 4664 4665 4666 4667 4668 4669 4670 4671 4672 4673 4674 4675 4676 4677 4678 4679 4680 4681 4682 4683 4684 4685 4686 4687 4688 4689 4690 4691 4692 4693 4694 4695 4696 4697 4698 4699 4700 4701 4702 4703 4704 4705 4706 4707 4708 4709 4710 4711 4712 4713 4714 4715 4716 4717 4718 4719 4720 4721 4722 4723 4724 4725 4726 4727 4728 4729 4730 4731 4732 4733 4734 4735 4736 4737 4738 4739 4740 4741 4742 4743 4744 4745 4746 4747 4748 4749 4750 4751 4752 4753 4754 4755 4756 4757 4758 4759 4760 4761 4762 4763 4764 4765 4766 4767 4768 4769 4770 4771 4772 4773 4774 4775 4776 4777 4778 4779 4780 4781 4782 4783 4784 4785 4786 4787 4788 4789 4790 4791 4792 4793 4794 4795 4796 4797 4798 4799 4800 4801 4802 4803 4804 4805 4806 4807 4808 4809 4810 4811 4812 4813 4814 4815 4816 4817 4818 4819 4820 4821 4822 4823 4824 4825 4826 4827 4828 4829 4830 4831 4832 4833 4834 4835 4836 4837 4838 4839 4840 4841 4842 4843 4844 4845 4846 4847 4848 4849 4850 4851 4852 4853 4854 4855 4856 4857 4858 4859 4860 4861 4862 4863 4864 4865 4866 4867 4868 4869 4870 4871 4872 4873 4874 4875 4876 4877 4878 4879 4880 4881 4882 4883 4884 4885 4886 4887 4888 4889 4890 4891 4892 4893 4894 4895 4896 4897 4898 4899 4900 4901 4902 4903 4904 4905 4906 4907 4908 4909 4910 4911