aboutsummaryrefslogtreecommitdiffstats
path: root/Source/SPDatabaseDocument.m
diff options
context:
space:
mode:
Diffstat (limited to 'Source/SPDatabaseDocument.m')
-rw-r--r--Source/SPDatabaseDocument.m4496
1 files changed, 4496 insertions, 0 deletions
diff --git a/Source/SPDatabaseDocument.m b/Source/SPDatabaseDocument.m
new file mode 100644
index 00000000..2120a85d
--- /dev/null
+++ b/Source/SPDatabaseDocument.m
@@ -0,0 +1,4496 @@
+//
+// $Id$
+//
+// SPDatabaseDocument.m
+// sequel-pro
+//
+// Created by lorenz textor (lorenz@textor.ch) on Wed May 01 2002.
+// Copyright (c) 2002-2003 Lorenz Textor. All rights reserved.
+//
+// Forked by Abhi Beckert (abhibeckert.com) 2008-04-04
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//
+// More info at <http://code.google.com/p/sequel-pro/>
+
+#import "SPDatabaseDocument.h"
+#import "SPTablesList.h"
+#import "SPTableStructure.h"
+#import "SPTableContent.h"
+#import "CustomQuery.h"
+#import "TableDump.h"
+#import "ImageAndTextCell.h"
+#import "SPGrowlController.h"
+#import "SPExportController.h"
+#import "SPQueryController.h"
+#import "SPNavigatorController.h"
+#import "SPSQLParser.h"
+#import "SPTableData.h"
+#import "SPDatabaseData.h"
+#import "SPStringAdditions.h"
+#import "SPArrayAdditions.h"
+#import "SPDataAdditions.h"
+#import "SPAppController.h"
+#import "SPExtendedTableInfo.h"
+#import "SPConnectionController.h"
+#import "SPHistoryController.h"
+#import "SPPreferenceController.h"
+#import "SPUserManager.h"
+#import "SPEncodingPopupAccessory.h"
+#import "SPConstants.h"
+#import "YRKSpinningProgressIndicator.h"
+#import "SPProcessListController.h"
+#import "SPServerVariablesController.h"
+#import "SPAlertSheets.h"
+#import "SPConstants.h"
+#import "SPMainThreadTrampoline.h"
+#import "SPLogger.h"
+#import "SPDatabaseCopy.h"
+#import "SPTableCopy.h"
+#import "SPDatabaseRename.h"
+
+@interface SPDatabaseDocument (PrivateAPI)
+
+- (void)_addDatabase;
+- (void)_copyDatabase;
+- (void)_renameDatabase;
+- (void)_removeDatabase;
+- (void)_selectDatabaseAndItem:(NSDictionary *)selectionDetails;
+
+@end
+
+@implementation SPDatabaseDocument
+
+@synthesize parentWindowController;
+@synthesize parentTabViewItem;
+@synthesize isProcessing;
+
+- (id)init
+{
+
+ if ((self = [super init])) {
+
+ _mainNibLoaded = NO;
+ _encoding = [[NSString alloc] initWithString:@"utf8"];
+ _isConnected = NO;
+ _isWorkingLevel = 0;
+ databaseListIsSelectable = YES;
+ _queryMode = SPInterfaceQueryMode;
+ chooseDatabaseButton = nil;
+ chooseDatabaseToolbarItem = nil;
+ connectionController = nil;
+ selectedDatabase = nil;
+ mySQLConnection = nil;
+ mySQLVersion = nil;
+ allDatabases = nil;
+ allSystemDatabases = nil;
+ mainToolbar = nil;
+ parentWindow = nil;
+ isProcessing = NO;
+
+ printWebView = [[WebView alloc] init];
+ [printWebView setFrameLoadDelegate:self];
+
+ prefs = [NSUserDefaults standardUserDefaults];
+ queryEditorInitString = nil;
+
+ spfFileURL = nil;
+ spfSession = nil;
+ spfPreferences = [[NSMutableDictionary alloc] init];
+ spfDocData = [[NSMutableDictionary alloc] init];
+
+ titleAccessoryView = nil;
+ taskProgressWindow = nil;
+ taskDisplayIsIndeterminate = YES;
+ taskDisplayLastValue = 0;
+ taskProgressValue = 0;
+ taskProgressValueDisplayInterval = 1;
+ taskDrawTimer = nil;
+ taskFadeAnimator = nil;
+ taskCanBeCancelled = NO;
+ taskCancellationCallbackObject = nil;
+ taskCancellationCallbackSelector = NULL;
+
+ keyChainID = nil;
+ statusValues = nil;
+ printThread = nil;
+ nibObjectsToRelease = [[NSMutableArray alloc] init];
+
+ // As this object is not an NSWindowController subclass, top-level objects in loaded nibs aren't
+ // automatically released. Keep track of the top-level objects for release on dealloc.
+ NSArray *dbViewTopLevelObjects = nil;
+ NSNib *nibLoader = [[NSNib alloc] initWithNibNamed:@"DBView" bundle:[NSBundle mainBundle]];
+ [nibLoader instantiateNibWithOwner:self topLevelObjects:&dbViewTopLevelObjects];
+ [nibLoader release];
+ [nibObjectsToRelease addObjectsFromArray:dbViewTopLevelObjects];
+ }
+
+ return self;
+}
+
+- (void)awakeFromNib
+{
+ if (_mainNibLoaded) return;
+ _mainNibLoaded = YES;
+
+ // Set up the toolbar
+ [self setupToolbar];
+
+ // Set up the connection controller
+ connectionController = [[SPConnectionController alloc] initWithDocument:self];
+
+ // Set the connection controller's delegate
+ [connectionController setDelegate:self];
+
+ // Register observers for when the DisplayTableViewVerticalGridlines preference changes
+ [prefs addObserver:self forKeyPath:SPDisplayTableViewVerticalGridlines options:NSKeyValueObservingOptionNew context:NULL];
+ [prefs addObserver:tableSourceInstance forKeyPath:SPDisplayTableViewVerticalGridlines options:NSKeyValueObservingOptionNew context:NULL];
+ [prefs addObserver:tableContentInstance forKeyPath:SPDisplayTableViewVerticalGridlines options:NSKeyValueObservingOptionNew context:NULL];
+ [prefs addObserver:customQueryInstance forKeyPath:SPDisplayTableViewVerticalGridlines options:NSKeyValueObservingOptionNew context:NULL];
+ [prefs addObserver:tableRelationsInstance forKeyPath:SPDisplayTableViewVerticalGridlines options:NSKeyValueObservingOptionNew context:NULL];
+ [prefs addObserver:[SPQueryController sharedQueryController] forKeyPath:SPDisplayTableViewVerticalGridlines options:NSKeyValueObservingOptionNew context:NULL];
+
+ // Register observers for the when the UseMonospacedFonts preference changes
+ [prefs addObserver:tableSourceInstance forKeyPath:SPUseMonospacedFonts options:NSKeyValueObservingOptionNew context:NULL];
+ // [prefs addObserver:tableContentInstance forKeyPath:SPUseMonospacedFonts options:NSKeyValueObservingOptionNew context:NULL];
+ // [prefs addObserver:customQueryInstance forKeyPath:SPUseMonospacedFonts options:NSKeyValueObservingOptionNew context:NULL];
+ [prefs addObserver:[SPQueryController sharedQueryController] forKeyPath:SPUseMonospacedFonts options:NSKeyValueObservingOptionNew context:NULL];
+
+ [prefs addObserver:tableContentInstance forKeyPath:SPGlobalResultTableFont options:NSKeyValueObservingOptionNew context:NULL];
+
+ // Register observers for when the logging preference changes
+ [prefs addObserver:[SPQueryController sharedQueryController] forKeyPath:SPConsoleEnableLogging options:NSKeyValueObservingOptionNew context:NULL];
+
+ // Register a second observer for when the logging preference changes so we can tell the current connection about it
+ [prefs addObserver:self forKeyPath:SPConsoleEnableLogging options:NSKeyValueObservingOptionNew context:NULL];
+
+ // Register for notifications
+ //register for notifications
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willPerformQuery:)
+ name:@"SMySQLQueryWillBePerformed" object:self];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(hasPerformedQuery:)
+ name:@"SMySQLQueryHasBeenPerformed" object:self];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillTerminate:)
+ name:@"NSApplicationWillTerminateNotification" object:nil];
+
+ // Find the Database -> Database Encoding menu (it's not in our nib, so we can't use interface builder)
+ selectEncodingMenu = [[[[[NSApp mainMenu] itemWithTag:SPMainMenuDatabase] submenu] itemWithTag:1] submenu];
+
+ // Hide the tabs in the tab view (we only show them to allow switching tabs in interface builder)
+ [tableTabView setTabViewType:NSNoTabsNoBorder];
+
+ // Bind the background color of the create syntax text view to the users preference
+ [createTableSyntaxTextView setAllowsDocumentBackgroundColorChange:YES];
+
+ NSMutableDictionary *bindingOptions = [NSMutableDictionary dictionary];
+
+ [bindingOptions setObject:NSUnarchiveFromDataTransformerName forKey:@"NSValueTransformerName"];
+
+ [createTableSyntaxTextView bind:@"backgroundColor"
+ toObject:[NSUserDefaultsController sharedUserDefaultsController]
+ withKeyPath:@"values.CustomQueryEditorBackgroundColor"
+ options:bindingOptions];
+
+ // Load additional nibs, keeping track of the top-level objects to allow correct release
+ NSArray *connectionDialogTopLevelObjects = nil;
+ NSNib *nibLoader = [[NSNib alloc] initWithNibNamed:@"ConnectionErrorDialog" bundle:[NSBundle mainBundle]];
+ if (![nibLoader instantiateNibWithOwner:self topLevelObjects:&connectionDialogTopLevelObjects]) {
+ NSLog(@"Connection error dialog could not be loaded; connection failure handling will not function correctly.");
+ } else {
+ [nibObjectsToRelease addObjectsFromArray:connectionDialogTopLevelObjects];
+ }
+ [nibLoader release];
+ NSArray *progressIndicatorLayerTopLevelObjects = nil;
+ nibLoader = [[NSNib alloc] initWithNibNamed:@"ProgressIndicatorLayer" bundle:[NSBundle mainBundle]];
+ if (![nibLoader instantiateNibWithOwner:self topLevelObjects:&progressIndicatorLayerTopLevelObjects]) {
+ NSLog(@"Progress indicator layer could not be loaded; progress display will not function correctly.");
+ } else {
+ [nibObjectsToRelease addObjectsFromArray:progressIndicatorLayerTopLevelObjects];
+ }
+ [nibLoader release];
+
+ // Retain the icon accessory view to allow it to be added and removed from windows
+ [titleAccessoryView retain];
+
+ // Set up the progress indicator child window and layer - change indicator color and size
+ [taskProgressIndicator setForeColor:[NSColor whiteColor]];
+ taskProgressWindow = [[NSWindow alloc] initWithContentRect:[taskProgressLayer bounds] styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO];
+ [taskProgressWindow setReleasedWhenClosed:NO];
+ [taskProgressWindow setOpaque:NO];
+ [taskProgressWindow setBackgroundColor:[NSColor clearColor]];
+ [taskProgressWindow setAlphaValue:0.0];
+ [taskProgressWindow setContentView:taskProgressLayer];
+}
+
+/**
+ * Initialise the document with the connection file at the supplied path.
+ */
+- (void)initWithConnectionFile:(NSString *)path
+{
+ NSError *readError = nil;
+ NSString *convError = nil;
+ NSPropertyListFormat format;
+
+ NSString *encryptpw = nil;
+ NSDictionary *data = nil;
+ NSDictionary *connection = nil;
+ NSDictionary *spf = nil;
+
+ NSInteger connectionType = -1;
+
+ [self updateWindowTitle:self];
+
+ // Clean fields
+ [connectionController setName:@""];
+ [connectionController setUser:@""];
+ [connectionController setHost:@""];
+ [connectionController setPort:@""];
+ [connectionController setSocket:@""];
+ [connectionController setSshHost:@""];
+ [connectionController setSshUser:@""];
+ [connectionController setSshPort:@""];
+ [connectionController setDatabase:@""];
+ [connectionController setPassword:nil];
+ [connectionController setSshPassword:nil];
+
+ // Deselect all favorites
+ [[connectionController valueForKeyPath:@"favoritesTable"] deselectAll:connectionController];
+ // Suppress the possibility to choose an other connection from the favorites
+ // if a connection should initialized by SPF file. Otherwise it could happen
+ // that the SPF file runs out of sync.
+ [[connectionController valueForKeyPath:@"favoritesTable"] setEnabled:NO];
+
+
+ NSData *pData = [NSData dataWithContentsOfFile:path options:NSUncachedRead error:&readError];
+
+ spf = [[NSPropertyListSerialization propertyListFromData:pData
+ mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&convError] retain];
+
+ if(!spf || readError != nil || [convError length] || !(format == NSPropertyListXMLFormat_v1_0 || format == NSPropertyListBinaryFormat_v1_0)) {
+ NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while reading connection data file", @"error while reading connection data file")]
+ defaultButton:NSLocalizedString(@"OK", @"OK button")
+ alternateButton:nil
+ otherButton:nil
+ informativeTextWithFormat:NSLocalizedString(@"Connection data file couldn't be read.", @"error while reading connection data file")];
+
+ [alert setAlertStyle:NSCriticalAlertStyle];
+ [alert runModal];
+ if (spf) [spf release];
+ [self closeAndDisconnect];
+ return;
+ }
+
+ // For dispatching later
+ if(![[spf objectForKey:@"format"] isEqualToString:@"connection"]) {
+ NSLog(@"SPF file format is not 'connection'.");
+ [spf release];
+ [self closeAndDisconnect];
+ return;
+ }
+
+ if(![spf objectForKey:@"data"]) {
+ NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while reading connection data file", @"error while reading connection data file")]
+ defaultButton:NSLocalizedString(@"OK", @"OK button")
+ alternateButton:nil
+ otherButton:nil
+ informativeTextWithFormat:NSLocalizedString(@"No data found.", @"no data found")];
+
+ [alert setAlertStyle:NSCriticalAlertStyle];
+ [alert runModal];
+ [spf release];
+ [self closeAndDisconnect];
+ return;
+ }
+
+ // Ask for a password if SPF file passwords were encrypted as sheet
+ if([spf objectForKey:@"encrypted"] && [[spf valueForKey:@"encrypted"] boolValue]) {
+
+ [inputTextWindowHeader setStringValue:NSLocalizedString(@"Connection file is encrypted", @"Connection file is encrypted")];
+ [inputTextWindowMessage setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Enter password for ‘%@’:",@"Please enter the password"), [path lastPathComponent]]];
+ [inputTextWindowSecureTextField setStringValue:@""];
+ [inputTextWindowSecureTextField selectText:nil];
+
+ [NSApp beginSheet:inputTextWindow modalForWindow:parentWindow modalDelegate:self didEndSelector:nil contextInfo:nil];
+
+ // wait for encryption password
+ NSModalSession session = [NSApp beginModalSessionForWindow:inputTextWindow];
+ for (;;) {
+
+ // Execute code on DefaultRunLoop
+ [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
+ beforeDate:[NSDate distantFuture]];
+
+ // Break the run loop if editSheet was closed
+ if ([NSApp runModalSession:session] != NSRunContinuesResponse
+ || ![inputTextWindow isVisible])
+ break;
+
+ // Execute code on DefaultRunLoop
+ [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
+ beforeDate:[NSDate distantFuture]];
+
+ }
+ [NSApp endModalSession:session];
+ [inputTextWindow orderOut:nil];
+ [NSApp endSheet:inputTextWindow];
+
+ if(passwordSheetReturnCode)
+ encryptpw = [inputTextWindowSecureTextField stringValue];
+ else {
+ [self closeAndDisconnect];
+ [spf release];
+ return;
+ }
+
+ }
+
+ if([[spf objectForKey:@"data"] isKindOfClass:[NSDictionary class]])
+ data = [NSDictionary dictionaryWithDictionary:[spf objectForKey:@"data"]];
+ else if([[spf objectForKey:@"data"] isKindOfClass:[NSData class]]) {
+ NSData *decryptdata = nil;
+ decryptdata = [[[NSMutableData alloc] initWithData:[(NSData *)[spf objectForKey:@"data"] dataDecryptedWithPassword:encryptpw]] autorelease];
+ if(decryptdata != nil && [decryptdata length]) {
+ NSKeyedUnarchiver *unarchiver = [[[NSKeyedUnarchiver alloc] initForReadingWithData:decryptdata] autorelease];
+ data = (NSDictionary *)[unarchiver decodeObjectForKey:@"data"];
+ [unarchiver finishDecoding];
+ }
+ if(data == nil) {
+ NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while reading connection data file", @"error while reading connection data file")]
+ defaultButton:NSLocalizedString(@"OK", @"OK button")
+ alternateButton:nil
+ otherButton:nil
+ informativeTextWithFormat:NSLocalizedString(@"Wrong data format or password.", @"wrong data format or password")];
+
+ [alert setAlertStyle:NSCriticalAlertStyle];
+ [alert runModal];
+ [self closeAndDisconnect];
+ [spf release];
+ return;
+ }
+ }
+
+ if(data == nil) {
+ NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while reading connection data file", @"error while reading connection data file")]
+ defaultButton:NSLocalizedString(@"OK", @"OK button")
+ alternateButton:nil
+ otherButton:nil
+ informativeTextWithFormat:NSLocalizedString(@"Wrong data format.", @"wrong data format")];
+
+ [alert setAlertStyle:NSCriticalAlertStyle];
+ [alert runModal];
+ [self closeAndDisconnect];
+ [spf release];
+ return;
+ }
+
+
+ if(![data objectForKey:@"connection"]) {
+ NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while reading connection data file", @"error while reading connection data file")]
+ defaultButton:NSLocalizedString(@"OK", @"OK button")
+ alternateButton:nil
+ otherButton:nil
+ informativeTextWithFormat:NSLocalizedString(@"No connection data found.", @"no connection data found")];
+
+ [alert setAlertStyle:NSCriticalAlertStyle];
+ [alert runModal];
+ [self closeAndDisconnect];
+ [spf release];
+ return;
+ }
+
+ [spfDocData setObject:[NSNumber numberWithBool:NO] forKey:@"encrypted"];
+ if(encryptpw != nil) {
+ [spfDocData setObject:[NSNumber numberWithBool:YES] forKey:@"encrypted"];
+ [spfDocData setObject:encryptpw forKey:@"e_string"];
+ }
+ encryptpw = nil;
+
+ connection = [NSDictionary dictionaryWithDictionary:[data objectForKey:@"connection"]];
+
+ if([connection objectForKey:@"type"]) {
+ if([[connection objectForKey:@"type"] isEqualToString:@"SPTCPIPConnection"])
+ connectionType = SPTCPIPConnection;
+ else if([[connection objectForKey:@"type"] isEqualToString:@"SPSocketConnection"])
+ connectionType = SPSocketConnection;
+ else if([[connection objectForKey:@"type"] isEqualToString:@"SPSSHTunnelConnection"])
+ connectionType = SPSSHTunnelConnection;
+ else
+ connectionType = SPTCPIPConnection;
+
+ [connectionController setType:connectionType];
+ [connectionController resizeTabViewToConnectionType:connectionType animating:NO];
+ }
+
+ if([connection objectForKey:@"name"])
+ [connectionController setName:[connection objectForKey:@"name"]];
+ if([connection objectForKey:@"user"])
+ [connectionController setUser:[connection objectForKey:@"user"]];
+ if([connection objectForKey:@"host"])
+ [connectionController setHost:[connection objectForKey:@"host"]];
+ if([connection objectForKey:@"port"])
+ [connectionController setPort:[NSString stringWithFormat:@"%ld", (long)[[connection objectForKey:@"port"] integerValue]]];
+ if([connection objectForKey:@"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 length])
+ [connectionController setPassword:pw];
+ }
+
+ if(connectionType == SPSocketConnection && [connection objectForKey:@"socket"])
+ [connectionController setSocket:[connection objectForKey:@"socket"]];
+
+ if(connectionType == SPSSHTunnelConnection) {
+ if([connection objectForKey:@"ssh_host"])
+ [connectionController setSshHost:[connection objectForKey:@"ssh_host"]];
+ if([connection objectForKey:@"ssh_user"])
+ [connectionController setSshUser:[connection objectForKey:@"ssh_user"]];
+ if([connection objectForKey:@"ssh_port"])
+ [connectionController setSshPort:[NSString stringWithFormat:@"%ld", (long)[[connection objectForKey:@"ssh_port"] integerValue]]];
+
+ // Set ssh password - if not in SPF file try to get it via the KeyChain
+ if([connection objectForKey:@"ssh_password"])
+ [connectionController setSshPassword:[connection objectForKey:@"ssh_password"]];
+ else {
+ SPKeychain *keychain = [[SPKeychain alloc] init];
+ NSString *connectionSSHKeychainItemName = [[keychain nameForSSHForFavoriteName:[connectionController name] id:[self keyChainID]] retain];
+ NSString *connectionSSHKeychainItemAccount = [[keychain accountForSSHUser:[connectionController sshUser] sshHost:[connectionController sshHost]] retain];
+ NSString *pw = [keychain getPasswordForName:connectionSSHKeychainItemName account:connectionSSHKeychainItemAccount];
+ if ([pw length])
+ [connectionController setSshPassword:pw];
+ if(connectionSSHKeychainItemName) [connectionSSHKeychainItemName release];
+ if(connectionSSHKeychainItemAccount) [connectionSSHKeychainItemAccount release];
+ [keychain release];
+ }
+
+ }
+
+ 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"];
+ }
+
+ [self setFileURL:[NSURL fileURLWithPath:path]];
+ [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL:[NSURL fileURLWithPath:path]];
+
+ if([spf objectForKey:SPQueryFavorites])
+ [spfPreferences setObject:[spf objectForKey:SPQueryFavorites] forKey:SPQueryFavorites];
+ if([spf objectForKey:SPQueryHistory])
+ [spfPreferences setObject:[spf objectForKey:SPQueryHistory] forKey:SPQueryHistory];
+ if([spf objectForKey:SPContentFilters])
+ [spfPreferences setObject:[spf objectForKey:SPContentFilters] forKey:SPContentFilters];
+
+ [spfDocData setObject:[NSNumber numberWithBool:([connection objectForKey:@"password"]) ? YES : NO] forKey:@"save_password"];
+
+ [spfDocData setObject:[NSNumber numberWithBool:NO] forKey:@"auto_connect"];
+
+ if([spf objectForKey:@"auto_connect"] && [[spf valueForKey:@"auto_connect"] boolValue]) {
+ [spfDocData setObject:[NSNumber numberWithBool:YES] forKey:@"auto_connect"];
+ [connectionController initiateConnection:self];
+ }
+ [spf release];
+}
+
+/**
+ * Restore session from SPF file if given
+ */
+- (void)restoreSession
+{
+ NSAutoreleasePool *taskPool = [[NSAutoreleasePool alloc] init];
+
+ // Check and set the table
+ NSArray *tables = [tablesListInstance tables];
+
+ if([tables indexOfObject:[spfSession objectForKey:@"table"]] == NSNotFound) {
+ [self endTask];
+ [taskPool drain];
+ return;
+ }
+
+ // Restore toolbar setting
+ if([spfSession objectForKey:@"isToolbarVisible"])
+ [mainToolbar setVisible:[[spfSession objectForKey:@"isToolbarVisible"] boolValue]];
+
+ // 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];
+ }
+
+ // 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"]]]];
+
+ // Reset database view encoding if differs from default
+ if([spfSession objectForKey:@"connectionEncoding"] && ![[self connectionEncoding] isEqualToString:[spfSession objectForKey:@"connectionEncoding"]])
+ [self setConnectionEncoding:[spfSession objectForKey:@"connectionEncoding"] reloadingViews:YES];
+
+ // 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];
+
+ [[tablesListInstance valueForKeyPath:@"tablesListView"] scrollRowToVisible:[tables indexOfObject:[spfSession objectForKey:@"selectedTable"]]];
+
+ [self updateWindowTitle:self];
+
+ // dealloc spfSession data
+ [spfSession release];
+ spfSession = nil;
+
+ // End the task
+ [self endTask];
+ [taskPool drain];
+}
+
+/**
+ * Set the return code for entering the encryption passowrd sheet
+ */
+- (IBAction)closePasswordSheet:(id)sender
+{
+ passwordSheetReturnCode = 0;
+ if([sender tag]) {
+ [NSApp stopModal];
+ passwordSheetReturnCode = 1;
+ }
+ [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;
+ }
+}
+
+#pragma mark -
+#pragma mark Connection callback and methods
+
+- (void) setConnection:(MCPConnection *)theConnection
+{
+ _isConnected = YES;
+ mySQLConnection = [theConnection retain];
+
+ // 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];
+
+ // Set the connection encoding
+ NSString *encodingName = [prefs objectForKey:SPDefaultEncoding];
+ if ( [encodingName isEqualToString:@"Autodetect"] ) {
+ [self setConnectionEncoding:[self databaseEncoding] reloadingViews:NO];
+ } else {
+ [self setConnectionEncoding:[self mysqlEncodingFromDisplayEncoding:encodingName] reloadingViews:NO];
+ }
+
+ // Get the mysql version
+ mySQLVersion = [[NSString alloc] initWithString:[mySQLConnection serverVersionString]];
+
+ // 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];
+ }
+
+ // Update the database list
+ [self setDatabases:self];
+ [chooseDatabaseButton setEnabled:!_isWorkingLevel];
+
+ // For each of the main controllers, assign the current connection
+ [tablesListInstance setConnection:mySQLConnection];
+ [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];
+ [databaseDataInstance setConnection:mySQLConnection];
+ userManagerInstance.mySqlConnection = mySQLConnection;
+
+ // Set the cutom query editor's MySQL version
+ [customQueryInstance setMySQLversion:mySQLVersion];
+
+ [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"];
+
+ // 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"]];
+ }
+
+ // Insert queryEditorInitString into the Query Editor if defined
+ if(queryEditorInitString && [queryEditorInitString length]) {
+ [self viewQuery:self];
+ [customQueryInstance doPerformLoadQueryService:queryEditorInitString];
+ [queryEditorInitString release];
+ queryEditorInitString = nil;
+ }
+
+ // 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) {
+
+ // 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;
+ }
+ }
+}
+
+- (MCPConnection *) getConnection {
+ return mySQLConnection;
+}
+
+/**
+ * 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:@""];
+
+ [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"];
+}
+
+/**
+ * Displays the database server variables sheet.
+ */
+- (IBAction)showServerVariables:(id)sender
+{
+ if (!serverVariablesController) {
+ serverVariablesController = [[SPServerVariablesController alloc] init];
+
+ [serverVariablesController setConnection:mySQLConnection];
+
+ // Register to obeserve table view vertical grid line pref changes
+ [prefs addObserver:serverVariablesController forKeyPath:SPDisplayTableViewVerticalGridlines options:NSKeyValueObservingOptionNew context:NULL];
+ }
+
+ [serverVariablesController displayServerVariablesSheetAttachedToWindow:parentWindow];
+}
+
+/**
+ * Displays the database process list sheet.
+ */
+- (IBAction)showServerProcesses:(id)sender
+{
+ if (!processListController) {
+ processListController = [[SPProcessListController alloc] init];
+
+ [processListController setConnection:mySQLConnection];
+
+ // Register to obeserve table view vertical grid line pref changes
+ [prefs addObserver:processListController forKeyPath:SPDisplayTableViewVerticalGridlines options:NSKeyValueObservingOptionNew context:NULL];
+ }
+
+ [processListController displayProcessListWindow];
+}
+
+/**
+ * Returns an array of all available database names
+ */
+- (NSArray *)allDatabaseNames
+{
+ return allDatabases;
+}
+
+/**
+ * Returns an array of all available system database names
+ */
+- (NSArray *)allSystemDatabaseNames
+{
+ return allSystemDatabases;
+}
+
+/**
+ * Alert sheet method. Invoked when an alert sheet is dismissed.
+ *
+ * if contextInfo == removeDatabase -> Remove the selected database
+ * if contextInfo == addDatabase -> Add a new database
+ * if contextInfo == copyDatabase -> Duplicate the selected database
+ * if contextInfo == renameDatabase -> Rename the selected database
+ */
+- (void)sheetDidEnd:(id)sheet returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo
+{
+
+ // Order out current sheet to suppress overlapping of sheets
+ if ([sheet respondsToSelector:@selector(orderOut:)])
+ [sheet orderOut:nil];
+ else if ([sheet respondsToSelector:@selector(window)])
+ [[sheet window] orderOut:nil];
+
+ // Remove the current database
+ if ([contextInfo isEqualToString:@"removeDatabase"]) {
+ if (returnCode == NSAlertDefaultReturn) {
+ [self _removeDatabase];
+ }
+ }
+ // Add a new database
+ else if ([contextInfo isEqualToString:@"addDatabase"]) {
+ if (returnCode == NSOKButton) {
+ [self _addDatabase];
+
+ // Query the structure of all databases in the background (mainly for completion)
+ [NSThread detachNewThreadSelector:@selector(queryDbStructureWithUserInfo:) toTarget:mySQLConnection withObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], @"forceUpdate", nil]];
+
+ } else {
+ // reset chooseDatabaseButton
+ if([[self database] length])
+ [chooseDatabaseButton selectItemWithTitle:[self database]];
+ else
+ [chooseDatabaseButton selectItemAtIndex:0];
+ }
+ }
+ else if ([contextInfo isEqualToString:@"copyDatabase"]) {
+ if (returnCode == NSOKButton) {
+ [self _copyDatabase];
+ }
+ }
+ else if ([contextInfo isEqualToString:@"renameDatabase"]) {
+ if (returnCode == NSOKButton) {
+ [self _renameDatabase];
+ }
+ }
+ // Close error status sheet for OPTIMIZE, CHECK, REPAIR etc.
+ else if ([contextInfo isEqualToString:@"statusError"]) {
+ if (statusValues) [statusValues release], statusValues = nil;
+ }
+
+}
+
+/**
+ * Show Error sheet (can be called from inside of a endSheet selector)
+ * via [self performSelector:@selector(showErrorSheetWithTitle:) withObject: afterDelay:]
+ */
+-(void)showErrorSheetWith:(id)error
+{
+ // error := first object is the title , second the message, only one button OK
+ SPBeginAlertSheet([error objectAtIndex:0], NSLocalizedString(@"OK", @"OK button"),
+ nil, nil, parentWindow, self, nil, nil,
+ [error objectAtIndex:1]);
+}
+
+/**
+ * Reset the current selected database name
+ */
+- (void)refreshCurrentDatabase
+{
+ NSString *dbName = nil;
+
+ // Notify listeners that a query has started
+ [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:self];
+
+ MCPResult *theResult = [mySQLConnection queryString:@"SELECT DATABASE()"];
+ if (![mySQLConnection queryErrored]) {
+ NSInteger i;
+ NSInteger r = [theResult numOfRows];
+ if (r) [theResult dataSeek:0];
+ for ( i = 0 ; i < r ; i++ ) {
+ dbName = NSArrayObjectAtIndex([theResult fetchRowAsArray], 0);
+ }
+ if(![dbName isKindOfClass:[NSNull class]]) {
+ if(![dbName isEqualToString:selectedDatabase]) {
+ if (selectedDatabase) [selectedDatabase release], selectedDatabase = nil;
+ selectedDatabase = [[NSString alloc] initWithString:dbName];
+ [chooseDatabaseButton selectItemWithTitle:selectedDatabase];
+ [self updateWindowTitle:self];
+ }
+ } else {
+ if (selectedDatabase) [selectedDatabase release], selectedDatabase = nil;
+ [chooseDatabaseButton selectItemAtIndex:0];
+ [self updateWindowTitle:self];
+ }
+ }
+
+ //query finished
+ [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:self];
+}
+
+- (BOOL)navigatorSchemaPathExistsForDatabase:(NSString*)dbname
+{
+ return [[SPNavigatorController sharedNavigatorController] schemaPathExistsForConnection:[self connectionID] andDatabase:dbname];
+}
+
+- (NSDictionary*)getDbStructure
+{
+ return [[SPNavigatorController sharedNavigatorController] dbStructureForConnection:[self connectionID]];
+}
+
+- (NSArray *)allSchemaKeys
+{
+ return [[SPNavigatorController sharedNavigatorController] allSchemaKeysForConnection:[self connectionID]];
+}
+
+#pragma mark -
+#pragma mark Console methods
+
+/**
+ * Shows or hides the console
+ */
+- (void)toggleConsole:(id)sender
+{
+ BOOL isConsoleVisible = [[[SPQueryController sharedQueryController] window] isVisible];
+
+ // If the Console window is not visible data are not reloaded (for speed).
+ // Due to that update list if user opens the Console window.
+ if(!isConsoleVisible) {
+ [[SPQueryController sharedQueryController] updateEntries];
+ }
+
+ // Show or hide the console
+ [[[SPQueryController sharedQueryController] window] setIsVisible:(!isConsoleVisible)];
+}
+
+/**
+ * Brings the console to the fron
+ */
+- (void)showConsole:(id)sender
+{
+ BOOL isConsoleVisible = [[[SPQueryController sharedQueryController] window] isVisible];
+
+ if (!isConsoleVisible) {
+ [self toggleConsole:sender];
+ } else {
+ [[[SPQueryController sharedQueryController] window] makeKeyAndOrderFront:self];
+ }
+
+}
+
+/**
+ * Clears the console by removing all of its messages
+ */
+- (void)clearConsole:(id)sender
+{
+ [[SPQueryController sharedQueryController] clearConsole:sender];
+}
+
+/**
+ * Set a query mode, used to control logging dependant on preferences
+ */
+- (void) setQueryMode:(NSInteger)theQueryMode
+{
+ _queryMode = theQueryMode;
+}
+
+#pragma mark -
+#pragma mark Navigator methods
+
+/**
+ * Shows or hides the navigator
+ */
+- (IBAction)toggleNavigator:(id)sender
+{
+ BOOL isNavigatorVisible = [[[SPNavigatorController sharedNavigatorController] window] isVisible];
+
+ if(!isNavigatorVisible) {
+ [[SPNavigatorController sharedNavigatorController] updateEntriesForConnection:self];
+ }
+
+ // Show or hide the navigator
+ [[[SPNavigatorController sharedNavigatorController] window] setIsVisible:(!isNavigatorVisible)];
+}
+
+- (IBAction)showNavigator:(id)sender
+{
+ BOOL isNavigatorVisible = [[[SPNavigatorController sharedNavigatorController] window] isVisible];
+
+ if (!isNavigatorVisible) {
+ [self toggleNavigator:sender];
+ } else {
+ [[[SPNavigatorController sharedNavigatorController] window] makeKeyAndOrderFront:self];
+ }
+}
+
+#pragma mark -
+#pragma mark Task progress and notification methods
+
+/**
+ * Start a document-wide task, providing a short task description for
+ * display to the user. This sets the document into working mode,
+ * preventing many actions, and shows an indeterminate progress interface
+ * to the user.
+ */
+- (void) startTaskWithDescription:(NSString *)description
+{
+
+ // Set the task text. If a nil string was supplied, a generic query notification is occurring -
+ // if a task is not already active, use default text.
+ if (!description) {
+ if (!_isWorkingLevel) [taskDescriptionText setStringValue:NSLocalizedString(@"Working...", @"Generic working description")];
+
+ // Otherwise display the supplied string
+ } else {
+ [taskDescriptionText setStringValue:description];
+ }
+
+ // Increment the task level
+ _isWorkingLevel++;
+
+ // Reset the progress indicator if necessary
+ if (_isWorkingLevel == 1 || !taskDisplayIsIndeterminate) {
+ taskDisplayIsIndeterminate = YES;
+ [taskProgressIndicator setIndeterminate:YES];
+ [taskProgressIndicator startAnimation:self];
+ taskDisplayLastValue = 0;
+ }
+
+ // If the working level just moved to start a task, set up the interface
+ if (_isWorkingLevel == 1) {
+ [taskCancelButton setHidden:YES];
+
+ // Set flags and prevent further UI interaction in this window
+ databaseListIsSelectable = NO;
+ [[NSNotificationCenter defaultCenter] postNotificationName:SPDocumentTaskStartNotification object:self];
+ [mainToolbar validateVisibleItems];
+ [chooseDatabaseButton setEnabled:NO];
+
+ // Schedule appearance of the task window in the near future
+ taskDrawTimer = [[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(showTaskProgressWindow:) userInfo:nil repeats:NO] retain];
+ }
+}
+
+/**
+ * Show the task progress window, after a small delay to minimise flicker.
+ */
+- (void) showTaskProgressWindow:(NSTimer *)theTimer
+{
+ if (taskDrawTimer) [taskDrawTimer invalidate], [taskDrawTimer release], taskDrawTimer = nil;
+
+ // Center the task window and fade it in
+ [self centerTaskWindow];
+ NSDictionary *animationDetails = [NSDictionary dictionaryWithObjectsAndKeys:
+ NSViewAnimationFadeInEffect, NSViewAnimationEffectKey,
+ taskProgressWindow, NSViewAnimationTargetKey,
+ nil];
+ taskFadeAnimator = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:animationDetails]];
+ [taskFadeAnimator setDuration:0.6];
+ [taskFadeAnimator startAnimation];
+}
+
+
+/**
+ * Updates the task description shown to the user.
+ */
+- (void) setTaskDescription:(NSString *)description
+{
+ [taskDescriptionText setStringValue:description];
+}
+
+/**
+ * Sets the task percentage progress - the first call to this automatically
+ * switches the progress display to determinate.
+ */
+- (void) setTaskPercentage:(CGFloat)taskPercentage
+{
+ if (taskDisplayIsIndeterminate) {
+ taskDisplayIsIndeterminate = NO;
+ [taskProgressIndicator stopAnimation:self];
+ [taskProgressIndicator setDoubleValue:0.5];
+ }
+
+ taskProgressValue = taskPercentage;
+ if (taskProgressValue > taskDisplayLastValue + taskProgressValueDisplayInterval
+ || taskProgressValue < taskDisplayLastValue - taskProgressValueDisplayInterval)
+ {
+ [taskProgressIndicator setDoubleValue:taskProgressValue];
+ taskDisplayLastValue = taskProgressValue;
+ }
+}
+
+/**
+ * Sets the task progress indicator back to indeterminate (also performed
+ * automatically whenever a new task is started).
+ * This can optionally be called with afterDelay set, in which case the intederminate
+ * switch will be made after a short pause to minimise flicker for short actions.
+ * Should be called on the main thread.
+ */
+- (void) setTaskProgressToIndeterminateAfterDelay:(BOOL)afterDelay
+{
+ if (afterDelay) {
+ [self performSelector:@selector(setTaskProgressToIndeterminateAfterDelay:) withObject:nil afterDelay:0.5];
+ return;
+ }
+
+ if (taskDisplayIsIndeterminate) return;
+ taskDisplayIsIndeterminate = YES;
+ [taskProgressIndicator setIndeterminate:YES];
+ [taskProgressIndicator startAnimation:self];
+ taskDisplayLastValue = 0;
+}
+
+/**
+ * Hide the task progress and restore the document to allow actions again.
+ */
+- (void) endTask
+{
+
+ // Ensure a call on the main thread
+ if (![NSThread isMainThread]) {
+ [self performSelectorOnMainThread:@selector(endTask) withObject:nil waitUntilDone:YES];
+ return;
+ }
+
+ // Decrement the working level
+ _isWorkingLevel--;
+
+ // Ensure cancellation interface is reset
+ [self disableTaskCancellation];
+
+ // If all tasks have ended, re-enable the interface
+ if (!_isWorkingLevel) {
+
+ // Cancel the draw timer if it exists
+ if (taskDrawTimer) [taskDrawTimer invalidate], [taskDrawTimer release], taskDrawTimer = nil;
+
+ // Cancel the fade-in animator if it exists
+ if (taskFadeAnimator) {
+ if ([taskFadeAnimator isAnimating]) [taskFadeAnimator stopAnimation];
+ [taskFadeAnimator release], taskFadeAnimator = nil;
+ }
+
+ // Hide the task interface and reset to indeterminate
+ if (taskDisplayIsIndeterminate) [taskProgressIndicator stopAnimation:self];
+ [taskProgressWindow setAlphaValue:0.0];
+ taskDisplayIsIndeterminate = YES;
+ [taskProgressIndicator setIndeterminate:YES];
+
+ // Re-enable window interface
+ databaseListIsSelectable = YES;
+ [[NSNotificationCenter defaultCenter] postNotificationName:SPDocumentTaskEndNotification object:self];
+ [mainToolbar validateVisibleItems];
+ [chooseDatabaseButton setEnabled:_isConnected];
+ }
+}
+
+/**
+ * Allow a task to be cancelled, enabling the button with a supplied title
+ * and optionally supplying a callback object and function.
+ */
+- (void) enableTaskCancellationWithTitle:(NSString *)buttonTitle callbackObject:(id)callbackObject callbackFunction:(SEL)callbackFunction
+{
+
+ // If no task is active, return
+ if (!_isWorkingLevel) return;
+
+ if (callbackObject && callbackFunction) {
+ taskCancellationCallbackObject = callbackObject;
+ taskCancellationCallbackSelector = callbackFunction;
+ }
+ taskCanBeCancelled = YES;
+
+ [taskCancelButton setTitle:buttonTitle];
+ [taskCancelButton setEnabled:YES];
+ [taskCancelButton setHidden:NO];
+}
+
+/**
+ * Disable task cancellation. Called automatically at the end of a task.
+ */
+- (void) disableTaskCancellation
+{
+
+ // If no task is active, return
+ if (!_isWorkingLevel) return;
+
+ taskCanBeCancelled = NO;
+ taskCancellationCallbackObject = nil;
+ taskCancellationCallbackSelector = NULL;
+ [taskCancelButton setHidden:YES];
+}
+
+/**
+ * Action sent by the cancel button when it's active.
+ */
+- (IBAction) cancelTask:(id)sender
+{
+ if (!taskCanBeCancelled) return;
+
+ [taskCancelButton setEnabled:NO];
+ [mySQLConnection cancelCurrentQuery];
+
+ if (taskCancellationCallbackObject && taskCancellationCallbackSelector) {
+ [taskCancellationCallbackObject performSelector:taskCancellationCallbackSelector];
+ }
+}
+
+/**
+ * Returns whether the document is busy performing a task - allows UI or actions
+ * to be restricted as appropriate.
+ */
+- (BOOL) isWorking
+{
+ return (_isWorkingLevel > 0);
+}
+
+/**
+ * Set whether the database list is selectable or not during the task process.
+ */
+- (void) setDatabaseListIsSelectable:(BOOL)isSelectable
+{
+ databaseListIsSelectable = isSelectable;
+}
+
+/**
+ * Reposition the task window within the main window.
+ */
+- (void) centerTaskWindow
+{
+ NSPoint newBottomLeftPoint;
+ NSRect mainWindowRect = [parentWindow frame];
+ NSRect taskWindowRect = [taskProgressWindow frame];
+
+ newBottomLeftPoint.x = round(mainWindowRect.origin.x + mainWindowRect.size.width/2 - taskWindowRect.size.width/2);
+ newBottomLeftPoint.y = round(mainWindowRect.origin.y + mainWindowRect.size.height/2 - taskWindowRect.size.height/2);
+
+ [taskProgressWindow setFrameOrigin:newBottomLeftPoint];
+}
+
+#pragma mark -
+#pragma mark Encoding Methods
+
+/**
+ * Set the encoding for the database connection
+ */
+- (void)setConnectionEncoding:(NSString *)mysqlEncoding reloadingViews:(BOOL)reloadViews
+{
+ _encodingViaLatin1 = NO;
+
+ // Special-case UTF-8 over latin 1 to allow viewing/editing of mangled data.
+ if ([mysqlEncoding isEqualToString:@"utf8-"]) {
+ _encodingViaLatin1 = YES;
+ mysqlEncoding = @"utf8";
+ }
+
+ // set encoding of connection and client
+ [mySQLConnection queryString:[NSString stringWithFormat:@"SET NAMES '%@'", mysqlEncoding]];
+
+ if (![mySQLConnection queryErrored]) {
+ if (_encodingViaLatin1)
+ [mySQLConnection queryString:@"SET CHARACTER_SET_RESULTS=latin1"];
+ [mySQLConnection setEncoding:[MCPConnection encodingForMySQLEncoding:[mysqlEncoding UTF8String]]];
+ [_encoding release];
+ _encoding = [[NSString alloc] initWithString:mysqlEncoding];
+ } else {
+ [mySQLConnection queryString:[NSString stringWithFormat:@"SET NAMES '%@'", [self databaseEncoding]]];
+ _encodingViaLatin1 = NO;
+ if ([mySQLConnection queryErrored]) {
+ NSLog(@"Error: could not set encoding to %@ nor fall back to database encoding on MySQL %@", mysqlEncoding, [self mySQLVersion]);
+ return;
+ }
+ }
+
+ // update the selected menu item
+ if (_encodingViaLatin1) {
+ [self updateEncodingMenuWithSelectedEncoding:[self encodingNameFromMySQLEncoding:[NSString stringWithFormat:@"%@-", mysqlEncoding]]];
+ } else {
+ [self updateEncodingMenuWithSelectedEncoding:[self encodingNameFromMySQLEncoding:mysqlEncoding]];
+ }
+
+ // Reload stuff as appropriate
+ [tableDataInstance resetAllData];
+ if (reloadViews) {
+ if ([tablesListInstance structureLoaded]) [tableSourceInstance reloadTable:self];
+ if ([tablesListInstance contentLoaded]) [tableContentInstance reloadTable:self];
+ if ([tablesListInstance statusLoaded]) [extendedTableInfoInstance reloadTable:self];
+ }
+}
+
+/**
+ * returns the current mysql encoding for this object
+ */
+- (NSString *)connectionEncoding
+{
+ return _encoding;
+}
+
+/**
+ * Returns whether the current encoding should display results via Latin1 transport for backwards compatibility.
+ * This is a delegate method of MCPKit's MCPConnection class.
+ */
+- (BOOL)connectionEncodingViaLatin1:(id)connection
+{
+ return _encodingViaLatin1;
+}
+
+/**
+ * updates the currently selected item in the encoding menu
+ *
+ * @param NSString *encoding - the title of the menu item which will be selected
+ */
+- (void)updateEncodingMenuWithSelectedEncoding:(NSString *)encoding
+{
+ NSEnumerator *dbEncodingMenuEn = [[selectEncodingMenu itemArray] objectEnumerator];
+ id menuItem;
+ NSInteger correctStateForMenuItem;
+ while (menuItem = [dbEncodingMenuEn nextObject]) {
+ correctStateForMenuItem = [[menuItem title] isEqualToString:encoding] ? NSOnState : NSOffState;
+
+ if ([menuItem state] == correctStateForMenuItem) // don't re-apply state incase it causes performance issues
+ continue;
+
+ [menuItem setState:correctStateForMenuItem];
+ }
+}
+
+/**
+ * Returns the display name for a mysql encoding
+ */
+- (NSString *)encodingNameFromMySQLEncoding:(NSString *)mysqlEncoding
+{
+ NSDictionary *translationMap = [NSDictionary dictionaryWithObjectsAndKeys:
+ @"UCS-2 Unicode (ucs2)", @"ucs2",
+ @"UTF-8 Unicode (utf8)", @"utf8",
+ @"UTF-8 Unicode via Latin 1", @"utf8-",
+ @"US ASCII (ascii)", @"ascii",
+ @"ISO Latin 1 (latin1)", @"latin1",
+ @"Mac Roman (macroman)", @"macroman",
+ @"Windows Latin 2 (cp1250)", @"cp1250",
+ @"ISO Latin 2 (latin2)", @"latin2",
+ @"Windows Arabic (cp1256)", @"cp1256",
+ @"ISO Greek (greek)", @"greek",
+ @"ISO Hebrew (hebrew)", @"hebrew",
+ @"ISO Turkish (latin5)", @"latin5",
+ @"Windows Baltic (cp1257)", @"cp1257",
+ @"Windows Cyrillic (cp1251)", @"cp1251",
+ @"Big5 Traditional Chinese (big5)", @"big5",
+ @"Shift-JIS Japanese (sjis)", @"sjis",
+ @"EUC-JP Japanese (ujis)", @"ujis",
+ @"EUC-KR Korean (euckr)", @"euckr",
+ nil];
+ NSString *encodingName = [translationMap valueForKey:mysqlEncoding];
+
+ if (!encodingName)
+ return [NSString stringWithFormat:@"Unknown Encoding (%@)", mysqlEncoding, nil];
+
+ return encodingName;
+}
+
+/**
+ * Returns the mysql encoding for an encoding string that is displayed to the user
+ */
+- (NSString *)mysqlEncodingFromDisplayEncoding:(NSString *)encodingName
+{
+ NSDictionary *translationMap = [NSDictionary dictionaryWithObjectsAndKeys:
+ @"ucs2", @"UCS-2 Unicode (ucs2)",
+ @"utf8", @"UTF-8 Unicode (utf8)",
+ @"utf8-", @"UTF-8 Unicode via Latin 1",
+ @"ascii", @"US ASCII (ascii)",
+ @"latin1", @"ISO Latin 1 (latin1)",
+ @"macroman", @"Mac Roman (macroman)",
+ @"cp1250", @"Windows Latin 2 (cp1250)",
+ @"latin2", @"ISO Latin 2 (latin2)",
+ @"cp1256", @"Windows Arabic (cp1256)",
+ @"greek", @"ISO Greek (greek)",
+ @"hebrew", @"ISO Hebrew (hebrew)",
+ @"latin5", @"ISO Turkish (latin5)",
+ @"cp1257", @"Windows Baltic (cp1257)",
+ @"cp1251", @"Windows Cyrillic (cp1251)",
+ @"big5", @"Big5 Traditional Chinese (big5)",
+ @"sjis", @"Shift-JIS Japanese (sjis)",
+ @"ujis", @"EUC-JP Japanese (ujis)",
+ @"euckr", @"EUC-KR Korean (euckr)",
+ nil];
+ NSString *mysqlEncoding = [translationMap valueForKey:encodingName];
+
+ if (!mysqlEncoding)
+ return @"utf8";
+
+ return mysqlEncoding;
+}
+
+/**
+ * Detect and return the database connection encoding.
+ * TODO: See http://code.google.com/p/sequel-pro/issues/detail?id=134 - some question over why this [historically] uses _connection not _database...
+ */
+- (NSString *)databaseEncoding
+{
+ MCPResult *charSetResult;
+ NSString *mysqlEncoding;
+
+ // MySQL > 4.0
+ charSetResult = [mySQLConnection queryString:@"SHOW VARIABLES LIKE 'character_set_connection'"];
+ [charSetResult setReturnDataAsStrings:YES];
+ mysqlEncoding = [[charSetResult fetchRowAsDictionary] objectForKey:@"Value"];
+ _supportsEncoding = (mysqlEncoding != nil);
+
+ // mysql 4.0 or older -> only default character set possible, cannot choose others using "set names xy"
+ if ( !mysqlEncoding ) {
+ mysqlEncoding = [[[mySQLConnection queryString:@"SHOW VARIABLES LIKE 'character_set'"] fetchRowAsDictionary] objectForKey:@"Value"];
+ }
+
+ // older version? -> set encoding to mysql default encoding latin1
+ if ( !mysqlEncoding ) {
+ NSLog(@"Error: no character encoding found, mysql version is %@", [self mySQLVersion]);
+ mysqlEncoding = @"latin1";
+ }
+
+ return mysqlEncoding;
+}
+
+/**
+ * When sent by an NSMenuItem, will set the encoding based on the title of the menu item
+ */
+- (IBAction)chooseEncoding:(id)sender
+{
+ [self setConnectionEncoding:[self mysqlEncodingFromDisplayEncoding:[(NSMenuItem *)sender title]] reloadingViews:YES];
+}
+
+/**
+ * return YES if MySQL server supports choosing connection and table encodings (MySQL 4.1 and newer)
+ */
+- (BOOL)supportsEncoding
+{
+ return _supportsEncoding;
+}
+
+#pragma mark -
+#pragma mark Table Methods
+
+/**
+ * Displays the CREATE TABLE syntax of the selected table to the user via a HUD panel.
+ */
+- (IBAction)showCreateTableSyntax:(id)sender
+{
+ //Create the query and get results
+ NSInteger colOffs = 1;
+ NSString *query = nil;
+ NSString *typeString = @"";
+
+ if( [tablesListInstance tableType] == SPTableTypeTable ) {
+ query = [NSString stringWithFormat:@"SHOW CREATE TABLE %@", [[self table] backtickQuotedString]];
+ typeString = @"table";
+ }
+ else if( [tablesListInstance tableType] == SPTableTypeView ) {
+ query = [NSString stringWithFormat:@"SHOW CREATE VIEW %@", [[self table] backtickQuotedString]];
+ typeString = @"view";
+ }
+ else if( [tablesListInstance tableType] == SPTableTypeProc ) {
+ query = [NSString stringWithFormat:@"SHOW CREATE PROCEDURE %@", [[self table] backtickQuotedString]];
+ typeString = @"procedure";
+ colOffs = 2;
+ }
+ else if( [tablesListInstance tableType] == SPTableTypeFunc ) {
+ query = [NSString stringWithFormat:@"SHOW CREATE FUNCTION %@", [[self table] backtickQuotedString]];
+ typeString = @"function";
+ colOffs = 2;
+ }
+
+ if (query == nil) return;
+
+ MCPResult *theResult = [mySQLConnection queryString:query];
+ [theResult setReturnDataAsStrings:YES];
+
+ // Check for errors, only displaying if the connection hasn't been terminated
+ if ([mySQLConnection queryErrored]) {
+ if ([mySQLConnection isConnected]) {
+ NSRunAlertPanel(@"Error", [NSString stringWithFormat:@"An error occured while creating table syntax.\n\n: %@",[mySQLConnection getLastErrorMessage]], @"OK", nil, nil);
+ }
+
+ return;
+ }
+
+ NSString *tableSyntax = [[theResult fetchRowAsArray] objectAtIndex:colOffs];
+
+ // A NULL value indicates that the user does not have permission to view the syntax
+ if ([tableSyntax isNSNull]) {
+ [[NSAlert alertWithMessageText:NSLocalizedString(@"Permission Denied", @"Permission Denied")
+ defaultButton:NSLocalizedString(@"OK", @"OK button")
+ alternateButton:nil otherButton:nil
+ informativeTextWithFormat:NSLocalizedString(@"The creation syntax could not be retrieved due to a permissions error.\n\nPlease check your user permissions with an administrator.", @"Create syntax permission denied detail")]
+ beginSheetModalForWindow:parentWindow
+ modalDelegate:self didEndSelector:NULL contextInfo:NULL];
+ return;
+ }
+
+ [createTableSyntaxTextField setStringValue:[NSString stringWithFormat:@"Create syntax for %@ '%@'", typeString, [self table]]];
+
+ [createTableSyntaxTextView setEditable:YES];
+ [createTableSyntaxTextView setString:@""];
+ [createTableSyntaxTextView insertText:([tablesListInstance tableType] == SPTableTypeView) ? [[tableSyntax createViewSyntaxPrettifier] stringByAppendingString:@";"] : [tableSyntax stringByAppendingString:@";"]];
+ [createTableSyntaxTextView setEditable:NO];
+
+ [createTableSyntaxWindow makeFirstResponder:createTableSyntaxTextField];
+
+ // Show variables sheet
+ [NSApp beginSheet:createTableSyntaxWindow
+ modalForWindow:parentWindow
+ modalDelegate:self
+ didEndSelector:nil
+ contextInfo:nil];
+
+}
+
+/**
+ * Copies the CREATE TABLE syntax of the selected table to the pasteboard.
+ */
+- (IBAction)copyCreateTableSyntax:(id)sender
+{
+ // Create the query and get results
+ NSString *query = nil;
+ NSInteger colOffs = 1;
+
+ if( [tablesListInstance tableType] == SPTableTypeTable ) {
+ query = [NSString stringWithFormat:@"SHOW CREATE TABLE %@", [[self table] backtickQuotedString]];
+ }
+ else if( [tablesListInstance tableType] == SPTableTypeView ) {
+ query = [NSString stringWithFormat:@"SHOW CREATE VIEW %@", [[self table] backtickQuotedString]];
+ }
+ else if( [tablesListInstance tableType] == SPTableTypeProc ) {
+ query = [NSString stringWithFormat:@"SHOW CREATE PROCEDURE %@", [[self table] backtickQuotedString]];
+ colOffs = 2;
+ }
+ else if( [tablesListInstance tableType] == SPTableTypeFunc ) {
+ query = [NSString stringWithFormat:@"SHOW CREATE FUNCTION %@", [[self table] backtickQuotedString]];
+ colOffs = 2;
+ }
+
+ if( query == nil )
+ return;
+
+ MCPResult *theResult = [mySQLConnection queryString:query];
+ [theResult setReturnDataAsStrings:YES];
+
+ // Check for errors, only displaying if the connection hasn't been terminated
+ if ([mySQLConnection queryErrored]) {
+ if ([mySQLConnection isConnected]) {
+ NSRunAlertPanel(@"Error", [NSString stringWithFormat:@"An error occured while creating table syntax.\n\n: %@",[mySQLConnection getLastErrorMessage]], @"OK", nil, nil);
+ }
+ return;
+ }
+
+ NSString *tableSyntax = [[theResult fetchRowAsArray] objectAtIndex:colOffs];
+
+ // A NULL value indicates that the user does not have permission to view the syntax
+ if ([tableSyntax isNSNull]) {
+ [[NSAlert alertWithMessageText:NSLocalizedString(@"Permission Denied", @"Permission Denied")
+ defaultButton:NSLocalizedString(@"OK", @"OK button")
+ alternateButton:nil otherButton:nil
+ informativeTextWithFormat:NSLocalizedString(@"The creation syntax could not be retrieved due to a permissions error.\n\nPlease check your user permissions with an administrator.", @"Create syntax permission denied detail")]
+ beginSheetModalForWindow:parentWindow
+ modalDelegate:self didEndSelector:NULL contextInfo:NULL];
+ return;
+ }
+
+ // copy to the clipboard
+ NSPasteboard *pb = [NSPasteboard generalPasteboard];
+ [pb declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:self];
+ if([tablesListInstance tableType] == SPTableTypeView)
+ [pb setString:[tableSyntax createViewSyntaxPrettifier] forType:NSStringPboardType];
+ else
+ [pb setString:tableSyntax forType:NSStringPboardType];
+
+ // Table syntax copied Growl notification
+ [[SPGrowlController sharedGrowlController] notifyWithTitle:@"Syntax Copied"
+ description:[NSString stringWithFormat:NSLocalizedString(@"Syntax for %@ table copied",@"description for table syntax copied growl notification"), [self table]]
+ document:self
+ notificationName:@"Syntax Copied"];
+}
+
+/**
+ * Performs a MySQL check table on the selected table and presents the result to the user via an alert sheet.
+ */
+- (IBAction)checkTable:(id)sender
+{
+
+ NSArray *selectedItems = [tablesListInstance selectedTableItems];
+ id message = nil;
+
+ if([selectedItems count] == 0) return;
+
+ MCPResult *theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"CHECK TABLE %@", [selectedItems componentsJoinedAndBacktickQuoted]]];
+
+ NSString *what = ([selectedItems count]>1) ? NSLocalizedString(@"selected items", @"selected items") : [NSString stringWithFormat:@"%@ '%@'", NSLocalizedString(@"table", @"table"), [self table]];
+
+ // Check for errors, only displaying if the connection hasn't been terminated
+ if ([mySQLConnection queryErrored]) {
+ NSString *mText = ([selectedItems count]>1) ? NSLocalizedString(@"Unable to check selected items", @"unable to check selected items message") : NSLocalizedString(@"Unable to check table", @"unable to check table message");
+ if ([mySQLConnection isConnected]) {
+
+ [[NSAlert alertWithMessageText:mText
+ defaultButton:@"OK"
+ alternateButton:nil
+ otherButton:nil
+ informativeTextWithFormat:[NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to check the %@.\n\nMySQL said:%@",@"an error occurred while trying to check the %@.\n\nMySQL said:%@"), what, [mySQLConnection getLastErrorMessage]]]
+ beginSheetModalForWindow:parentWindow
+ modalDelegate:self
+ didEndSelector:NULL
+ contextInfo:NULL];
+ }
+
+ return;
+ }
+
+ NSDictionary *result = [theResult fetch2DResultAsType:MCPTypeDictionary];
+ BOOL statusOK = YES;
+ for(id res in result) {
+ if(![[res objectForKey:@"Msg_type"] isEqualToString:@"status"]) {
+ statusOK = NO;
+ break;
+ }
+ }
+
+ // Process result
+ if([selectedItems count] == 1) {
+ NSDictionary *lastresult = [[theResult fetch2DResultAsType:MCPTypeDictionary] lastObject];
+
+ message = ([[lastresult objectForKey:@"Msg_type"] isEqualToString:@"status"]) ? NSLocalizedString(@"Check table successfully passed.",@"check table successfully passed message") : NSLocalizedString(@"Check table failed.", @"check table failed message");
+
+ message = [NSString stringWithFormat:@"%@\n\nMySQL said: %@", message, [lastresult objectForKey:@"Msg_text"]];
+ } else if(statusOK) {
+ message = NSLocalizedString(@"Check of all selected items successfully passed.",@"check of all selected items successfully passed message");
+ }
+
+ if(message) {
+ [[NSAlert alertWithMessageText:[NSString stringWithFormat:@"Check %@", what]
+ defaultButton:@"OK"
+ alternateButton:nil
+ otherButton:nil
+ informativeTextWithFormat:message]
+ beginSheetModalForWindow:parentWindow
+ modalDelegate:self
+ didEndSelector:NULL
+ contextInfo:NULL];
+ } else {
+ message = NSLocalizedString(@"MySQL said:",@"mysql said message");
+ if (statusValues) [statusValues release], statusValues = nil;
+ statusValues = [result retain];
+ NSAlert *alert = [[NSAlert new] autorelease];
+ [alert setInformativeText:message];
+ [alert setMessageText:NSLocalizedString(@"Error while checking selected items", @"error while checking selected items message")];
+ [alert setAccessoryView:statusTableAccessoryView];
+ [alert beginSheetModalForWindow:parentWindow modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:@"statusError"];
+ }
+}
+
+/**
+ * Analyzes the selected table and presents the result to the user via an alert sheet.
+ */
+- (IBAction)analyzeTable:(id)sender
+{
+
+ NSArray *selectedItems = [tablesListInstance selectedTableItems];
+ id message = nil;
+
+ if([selectedItems count] == 0) return;
+
+ MCPResult *theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"ANALYZE TABLE %@", [selectedItems componentsJoinedAndBacktickQuoted]]];
+
+ NSString *what = ([selectedItems count]>1) ? NSLocalizedString(@"selected items", @"selected items") : [NSString stringWithFormat:@"%@ '%@'", NSLocalizedString(@"table", @"table"), [self table]];
+
+ // Check for errors, only displaying if the connection hasn't been terminated
+ if ([mySQLConnection queryErrored]) {
+ NSString *mText = ([selectedItems count]>1) ? NSLocalizedString(@"Unable to analyze selected items", @"unable to analyze selected items message") : NSLocalizedString(@"Unable to analyze table", @"unable to analyze table message");
+ if ([mySQLConnection isConnected]) {
+
+ [[NSAlert alertWithMessageText:mText
+ defaultButton:@"OK"
+ alternateButton:nil
+ otherButton:nil
+ informativeTextWithFormat:[NSString stringWithFormat:NSLocalizedString(@"An error occurred while analyzing the %@.\n\nMySQL said:%@",@"an error occurred while analyzing the %@.\n\nMySQL said:%@"), what, [mySQLConnection getLastErrorMessage]]]
+ beginSheetModalForWindow:parentWindow
+ modalDelegate:self
+ didEndSelector:NULL
+ contextInfo:NULL];
+ }
+
+ return;
+ }
+
+ NSDictionary *result = [theResult fetch2DResultAsType:MCPTypeDictionary];
+ BOOL statusOK = YES;
+ for(id res in result) {
+ if(![[res objectForKey:@"Msg_type"] isEqualToString:@"status"]) {
+ statusOK = NO;
+ break;
+ }
+ }
+
+ // Process result
+ if([selectedItems count] == 1) {
+ NSDictionary *lastresult = [[theResult fetch2DResultAsType:MCPTypeDictionary] lastObject];
+
+ message = ([[lastresult objectForKey:@"Msg_type"] isEqualToString:@"status"]) ? NSLocalizedString(@"Successfully analyzed table.",@"analyze table successfully passed message") : NSLocalizedString(@"Analyze table failed.", @"analyze table failed message");
+
+ message = [NSString stringWithFormat:@"%@\n\nMySQL said: %@", message, [lastresult objectForKey:@"Msg_text"]];
+ } else if(statusOK) {
+ message = NSLocalizedString(@"Successfully analyzed all selected items.",@"successfully analyzed all selected items message");
+ }
+
+ if(message) {
+ [[NSAlert alertWithMessageText:[NSString stringWithFormat:@"Analyze %@", what]
+ defaultButton:@"OK"
+ alternateButton:nil
+ otherButton:nil
+ informativeTextWithFormat:message]
+ beginSheetModalForWindow:parentWindow
+ modalDelegate:self
+ didEndSelector:NULL
+ contextInfo:NULL];
+ } else {
+ message = NSLocalizedString(@"MySQL said:",@"mysql said message");
+ if (statusValues) [statusValues release], statusValues = nil;
+ statusValues = [result retain];
+ NSAlert *alert = [[NSAlert new] autorelease];
+ [alert setInformativeText:message];
+ [alert setMessageText:NSLocalizedString(@"Error while analyzing selected items", @"error while analyzing selected items message")];
+ [alert setAccessoryView:statusTableAccessoryView];
+ [alert beginSheetModalForWindow:parentWindow modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:@"statusError"];
+ }
+}
+
+/**
+ * Optimizes the selected table and presents the result to the user via an alert sheet.
+ */
+- (IBAction)optimizeTable:(id)sender
+{
+
+ NSArray *selectedItems = [tablesListInstance selectedTableItems];
+ id message = nil;
+
+ if([selectedItems count] == 0) return;
+
+ MCPResult *theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"OPTIMIZE TABLE %@", [selectedItems componentsJoinedAndBacktickQuoted]]];
+
+ NSString *what = ([selectedItems count]>1) ? NSLocalizedString(@"selected items", @"selected items") : [NSString stringWithFormat:@"%@ '%@'", NSLocalizedString(@"table", @"table"), [self table]];
+
+ // Check for errors, only displaying if the connection hasn't been terminated
+ if ([mySQLConnection queryErrored]) {
+ NSString *mText = ([selectedItems count]>1) ? NSLocalizedString(@"Unable to optimze selected items", @"unable to optimze selected items message") : NSLocalizedString(@"Unable to optimze table", @"unable to optimze table message");
+ if ([mySQLConnection isConnected]) {
+
+ [[NSAlert alertWithMessageText:mText
+ defaultButton:@"OK"
+ alternateButton:nil
+ otherButton:nil
+ informativeTextWithFormat:[NSString stringWithFormat:NSLocalizedString(@"An error occurred while optimzing the %@.\n\nMySQL said:%@",@"an error occurred while trying to optimze the %@.\n\nMySQL said:%@"), what, [mySQLConnection getLastErrorMessage]]]
+ beginSheetModalForWindow:parentWindow
+ modalDelegate:self
+ didEndSelector:NULL
+ contextInfo:NULL];
+ }
+
+ return;
+ }
+
+ NSDictionary *result = [theResult fetch2DResultAsType:MCPTypeDictionary];
+ BOOL statusOK = YES;
+ for(id res in result) {
+ if(![[res objectForKey:@"Msg_type"] isEqualToString:@"status"]) {
+ statusOK = NO;
+ break;
+ }
+ }
+
+ // Process result
+ if([selectedItems count] == 1) {
+ NSDictionary *lastresult = [[theResult fetch2DResultAsType:MCPTypeDictionary] lastObject];
+
+ message = ([[lastresult objectForKey:@"Msg_type"] isEqualToString:@"status"]) ? NSLocalizedString(@"Successfully optimized table.",@"optimize table successfully passed message") : NSLocalizedString(@"Optimize table failed.", @"optimize table failed message");
+
+ message = [NSString stringWithFormat:@"%@\n\nMySQL said: %@", message, [lastresult objectForKey:@"Msg_text"]];
+ } else if(statusOK) {
+ message = NSLocalizedString(@"Successfully optimized all selected items.",@"successfully optimized all selected items message");
+ }
+
+ if(message) {
+ [[NSAlert alertWithMessageText:[NSString stringWithFormat:@"Optimize %@", what]
+ defaultButton:@"OK"
+ alternateButton:nil
+ otherButton:nil
+ informativeTextWithFormat:message]
+ beginSheetModalForWindow:parentWindow
+ modalDelegate:self
+ didEndSelector:NULL
+ contextInfo:NULL];
+ } else {
+ message = NSLocalizedString(@"MySQL said:",@"mysql said message");
+ if (statusValues) [statusValues release], statusValues = nil;
+ statusValues = [result retain];
+ NSAlert *alert = [[NSAlert new] autorelease];
+ [alert setInformativeText:message];
+ [alert setMessageText:NSLocalizedString(@"Error while optimizing selected items", @"error while optimizing selected items message")];
+ [alert setAccessoryView:statusTableAccessoryView];
+ [alert beginSheetModalForWindow:parentWindow modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:@"statusError"];
+ }
+}
+
+/**
+ * Repairs the selected table and presents the result to the user via an alert sheet.
+ */
+- (IBAction)repairTable:(id)sender
+{
+ NSArray *selectedItems = [tablesListInstance selectedTableItems];
+ id message = nil;
+
+ if([selectedItems count] == 0) return;
+
+ MCPResult *theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"REPAIR TABLE %@", [selectedItems componentsJoinedAndBacktickQuoted]]];
+
+ NSString *what = ([selectedItems count]>1) ? NSLocalizedString(@"selected items", @"selected items") : [NSString stringWithFormat:@"%@ '%@'", NSLocalizedString(@"table", @"table"), [self table]];
+
+ // Check for errors, only displaying if the connection hasn't been terminated
+ if ([mySQLConnection queryErrored]) {
+ NSString *mText = ([selectedItems count]>1) ? NSLocalizedString(@"Unable to repair selected items", @"unable to repair selected items message") : NSLocalizedString(@"Unable to repair table", @"unable to repair table message");
+ if ([mySQLConnection isConnected]) {
+
+ [[NSAlert alertWithMessageText:mText
+ defaultButton:@"OK"
+ alternateButton:nil
+ otherButton:nil
+ informativeTextWithFormat:[NSString stringWithFormat:NSLocalizedString(@"An error occurred while repairing the %@.\n\nMySQL said:%@",@"an error occurred while trying to repair the %@.\n\nMySQL said:%@"), what, [mySQLConnection getLastErrorMessage]]]
+ beginSheetModalForWindow:parentWindow
+ modalDelegate:self
+ didEndSelector:NULL
+ contextInfo:NULL];
+ }
+
+ return;
+ }
+
+ NSDictionary *result = [theResult fetch2DResultAsType:MCPTypeDictionary];
+ BOOL statusOK = YES;
+ for(id res in result) {
+ if(![[res objectForKey:@"Msg_type"] isEqualToString:@"status"]) {
+ statusOK = NO;
+ break;
+ }
+ }
+
+ // Process result
+ if([selectedItems count] == 1) {
+ NSDictionary *lastresult = [[theResult fetch2DResultAsType:MCPTypeDictionary] lastObject];
+
+ message = ([[lastresult objectForKey:@"Msg_type"] isEqualToString:@"status"]) ? NSLocalizedString(@"Successfully repaired table.",@"repair table successfully passed message") : NSLocalizedString(@"Repair table failed.", @"repair table failed message");
+
+ message = [NSString stringWithFormat:@"%@\n\nMySQL said: %@", message, [lastresult objectForKey:@"Msg_text"]];
+ } else if(statusOK) {
+ message = NSLocalizedString(@"Successfully repaired all selected items.",@"successfully repaired all selected items message");
+ }
+
+ if(message) {
+ [[NSAlert alertWithMessageText:[NSString stringWithFormat:@"Repair %@", what]
+ defaultButton:@"OK"
+ alternateButton:nil
+ otherButton:nil
+ informativeTextWithFormat:message]
+ beginSheetModalForWindow:parentWindow
+ modalDelegate:self
+ didEndSelector:NULL
+ contextInfo:NULL];
+ } else {
+ message = NSLocalizedString(@"MySQL said:",@"mysql said message");
+ if (statusValues) [statusValues release], statusValues = nil;
+ statusValues = [result retain];
+ NSAlert *alert = [[NSAlert new] autorelease];
+ [alert setInformativeText:message];
+ [alert setMessageText:NSLocalizedString(@"Error while repairing selected items", @"error while repairing selected items message")];
+ [alert setAccessoryView:statusTableAccessoryView];
+ [alert beginSheetModalForWindow:parentWindow modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:@"statusError"];
+ }
+}
+
+/**
+ * Flush the selected table and inform the user via a dialog sheet.
+ */
+- (IBAction)flushTable:(id)sender
+{
+ NSArray *selectedItems = [tablesListInstance selectedTableItems];
+ id message = nil;
+
+ if([selectedItems count] == 0) return;
+
+ MCPResult *theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"FLUSH TABLE %@", [selectedItems componentsJoinedAndBacktickQuoted]]];
+
+ NSString *what = ([selectedItems count]>1) ? NSLocalizedString(@"selected items", @"selected items") : [NSString stringWithFormat:@"%@ '%@'", NSLocalizedString(@"table", @"table"), [self table]];
+
+ // Check for errors, only displaying if the connection hasn't been terminated
+ if ([mySQLConnection queryErrored]) {
+ NSString *mText = ([selectedItems count]>1) ? NSLocalizedString(@"Unable to flush selected items", @"unable to flush selected items message") : NSLocalizedString(@"Unable to flush table", @"unable to flush table message");
+ if ([mySQLConnection isConnected]) {
+
+ [[NSAlert alertWithMessageText:mText
+ defaultButton:@"OK"
+ alternateButton:nil
+ otherButton:nil
+ informativeTextWithFormat:[NSString stringWithFormat:NSLocalizedString(@"An error occurred while flushing the %@.\n\nMySQL said:%@",@"an error occurred while trying to flush the %@.\n\nMySQL said:%@"), what, [mySQLConnection getLastErrorMessage]]]
+ beginSheetModalForWindow:parentWindow
+ modalDelegate:self
+ didEndSelector:NULL
+ contextInfo:NULL];
+ }
+
+ return;
+ }
+
+ NSDictionary *result = [theResult fetch2DResultAsType:MCPTypeDictionary];
+ BOOL statusOK = YES;
+ for(id res in result) {
+ if(![[res objectForKey:@"Msg_type"] isEqualToString:@"status"]) {
+ statusOK = NO;
+ break;
+ }
+ }
+
+ // Process result
+ if([selectedItems count] == 1) {
+ NSDictionary *lastresult = [[theResult fetch2DResultAsType:MCPTypeDictionary] lastObject];
+
+ message = ([[lastresult objectForKey:@"Msg_type"] isEqualToString:@"status"]) ? NSLocalizedString(@"Successfully flushed table.",@"flush table successfully passed message") : NSLocalizedString(@"Flush table failed.", @"flush table failed message");
+
+ message = [NSString stringWithFormat:@"%@\n\nMySQL said: %@", message, [lastresult objectForKey:@"Msg_text"]];
+ } else if(statusOK) {
+ message = NSLocalizedString(@"Successfully flushed all selected items.",@"successfully flushed all selected items message");
+ }
+
+ if(message) {
+ [[NSAlert alertWithMessageText:[NSString stringWithFormat:@"Flush %@", what]
+ defaultButton:@"OK"
+ alternateButton:nil
+ otherButton:nil
+ informativeTextWithFormat:message]
+ beginSheetModalForWindow:parentWindow
+ modalDelegate:self
+ didEndSelector:NULL
+ contextInfo:NULL];
+ } else {
+ message = NSLocalizedString(@"MySQL said:",@"mysql said message");
+ if (statusValues) [statusValues release], statusValues = nil;
+ statusValues = [result retain];
+ NSAlert *alert = [[NSAlert new] autorelease];
+ [alert setInformativeText:message];
+ [alert setMessageText:NSLocalizedString(@"Error while flushing selected items", @"error while flushing selected items message")];
+ [alert setAccessoryView:statusTableAccessoryView];
+ [alert beginSheetModalForWindow:parentWindow modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:@"statusError"];
+ }
+}
+
+/**
+ * Runs a MySQL checksum on the selected table and present the result to the user via an alert sheet.
+ */
+- (IBAction)checksumTable:(id)sender
+{
+ NSArray *selectedItems = [tablesListInstance selectedTableItems];
+ id message = nil;
+
+ if([selectedItems count] == 0) return;
+
+ MCPResult *theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"CHECKSUM TABLE %@", [selectedItems componentsJoinedAndBacktickQuoted]]];
+
+ NSString *what = ([selectedItems count]>1) ? NSLocalizedString(@"selected items", @"selected items") : [NSString stringWithFormat:@"%@ '%@'", NSLocalizedString(@"table", @"table"), [self table]];
+
+ // Check for errors, only displaying if the connection hasn't been terminated
+ if ([mySQLConnection queryErrored]) {
+ if ([mySQLConnection isConnected]) {
+
+ [[NSAlert alertWithMessageText:NSLocalizedString(@"Unable to perform the checksum", @"unable to perform the checksum")
+ defaultButton:@"OK"
+ alternateButton:nil
+ otherButton:nil
+ informativeTextWithFormat:[NSString stringWithFormat:NSLocalizedString(@"An error occurred while performing the checksum on %@.\n\nMySQL said:%@",@"an error occurred while performing the checksum on the %@.\n\nMySQL said:%@"), what, [mySQLConnection getLastErrorMessage]]]
+ beginSheetModalForWindow:parentWindow
+ modalDelegate:self
+ didEndSelector:NULL
+ contextInfo:NULL];
+ }
+
+ return;
+ }
+
+ // Process result
+ if([selectedItems count] == 1) {
+ message = [[[theResult fetch2DResultAsType:MCPTypeDictionary] lastObject] objectForKey:@"Checksum"];
+ [[NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Checksum %@",@"checksum %@ message"), what]
+ defaultButton:@"OK"
+ alternateButton:nil
+ otherButton:nil
+ informativeTextWithFormat:[NSString stringWithFormat:NSLocalizedString(@"Table checksum: %@",@"table checksum: %@"), message]]
+ beginSheetModalForWindow:parentWindow
+ modalDelegate:self
+ didEndSelector:NULL
+ contextInfo:NULL];
+ } else {
+ NSDictionary *result = [theResult fetch2DResultAsType:MCPTypeDictionary];
+ if (statusValues) [statusValues release], statusValues = nil;
+ statusValues = [result retain];
+ NSAlert *alert = [[NSAlert new] autorelease];
+ [alert setInformativeText:[NSString stringWithFormat:NSLocalizedString(@"Checksums of %@",@"Checksums of %@ message"), what]];
+ [alert setMessageText:NSLocalizedString(@"Table checksum",@"table checksum message")];
+ [alert setAccessoryView:statusTableAccessoryView];
+ [alert beginSheetModalForWindow:parentWindow modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:@"statusError"];
+ }
+}
+
+/**
+ * Saves the current tables create syntax to the selected file.
+ */
+- (IBAction)saveCreateSyntax:(id)sender
+{
+ NSSavePanel *panel = [NSSavePanel savePanel];
+
+ [panel setRequiredFileType:@"sql"];
+
+ [panel setExtensionHidden:NO];
+ [panel setAllowsOtherFileTypes:YES];
+ [panel setCanSelectHiddenExtension:YES];
+
+ [panel beginSheetForDirectory:nil file:@"CreateSyntax" modalForWindow:createTableSyntaxWindow modalDelegate:self didEndSelector:@selector(savePanelDidEnd:returnCode:contextInfo:) contextInfo:@"CreateSyntax"];
+}
+
+/**
+ * Copy the create syntax in the create syntax text view to the pasteboard.
+ */
+- (IBAction)copyCreateTableSyntaxFromSheet:(id)sender
+{
+ NSString *createSyntax = [createTableSyntaxTextView string];
+
+ if ([createSyntax length] > 0) {
+ // Copy to the clipboard
+ NSPasteboard *pb = [NSPasteboard generalPasteboard];
+
+ [pb declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:self];
+ [pb setString:createSyntax forType:NSStringPboardType];
+
+ // Table syntax copied Growl notification
+ [[SPGrowlController sharedGrowlController] notifyWithTitle:@"Syntax Copied"
+ description:[NSString stringWithFormat:NSLocalizedString(@"Syntax for %@ table copied", @"description for table syntax copied growl notification"), [self table]]
+ document:self
+ notificationName:@"Syntax Copied"];
+ }
+}
+
+/**
+ * Switches to the content view and makes the filter field the first responder (has focus).
+ */
+- (IBAction)focusOnTableContentFilter:(id)sender
+{
+ [self viewContent:self];
+
+ [tableContentInstance performSelector:@selector(makeContentFilterHaveFocus) withObject:nil afterDelay:0.1];
+}
+
+/**
+ * Makes the tables list filter field the first responder.
+ */
+- (IBAction)focusOnTableListFilter:(id)sender
+{
+ [tablesListInstance performSelector:@selector(makeTableListFilterHaveFocus) withObject:nil afterDelay:0.1];
+}
+
+/**
+ * Exports the selected tables in the chosen file format.
+ */
+- (IBAction)exportSelectedTablesAs:(id)sender
+{
+ [exportControllerInstance exportTables:[tablesListInstance selectedTableItems] asFormat:[sender tag]];
+}
+
+#pragma mark -
+#pragma mark Other Methods
+
+/**
+ * Set that query which will be inserted into the Query Editor
+ * after establishing the connection
+ */
+
+- (void)initQueryEditorWithString:(NSString *)query
+{
+ queryEditorInitString = [query retain];
+}
+
+/**
+ * Invoked when user hits the cancel button or close button in
+ * dialogs such as the variableSheet or the createTableSyntaxSheet
+ */
+- (IBAction)closeSheet:(id)sender
+{
+ [NSApp stopModalWithCode:0];
+}
+
+/**
+ * Closes either the server variables or create syntax sheets.
+ */
+- (IBAction)closePanelSheet:(id)sender
+{
+ [NSApp endSheet:[sender window] returnCode:[sender tag]];
+ [[sender window] orderOut:self];
+}
+
+/**
+ * Displays the user account manager.
+ */
+- (IBAction)showUserManager:(id)sender
+{
+ // Before displaying the user manager make sure the current user has access to the mysql.user table.
+ MCPResult *result = [mySQLConnection queryString:@"SELECT * FROM `mysql`.`user` ORDER BY `user`"];
+
+ if ([mySQLConnection queryErrored] && ([result numOfRows] == 0)) {
+
+ NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Unable to get list of users", @"unable to get list of users message")
+ defaultButton:NSLocalizedString(@"OK", @"OK button")
+ alternateButton:nil
+ otherButton:nil
+ informativeTextWithFormat:NSLocalizedString(@"An error occurred while trying to get the list of users. Please make sure you have the necessary privileges to perform user management, including access to the mysql.user table.", @"unable to get list of users informative message")];
+
+ [alert setAlertStyle:NSCriticalAlertStyle];
+
+ [alert beginSheetModalForWindow:parentWindow modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:@"cannotremovefield"];
+
+ return;
+ }
+
+ [NSApp beginSheet:[userManagerInstance window]
+ modalForWindow:parentWindow
+ modalDelegate:userManagerInstance
+ didEndSelector:@selector(userManagerSheetDidEnd:returnCode:contextInfo:)
+ contextInfo:nil];
+}
+
+/**
+ * Passes query to tablesListInstance
+ */
+- (void)doPerformQueryService:(NSString *)query
+{
+ [parentWindow makeKeyAndOrderFront:self];
+ [tablesListInstance doPerformQueryService:query];
+}
+
+/**
+ * Inserts query into the Custom Query editor
+ */
+- (void)doPerformLoadQueryService:(NSString *)query
+{
+ [self viewQuery:nil];
+ [customQueryInstance doPerformLoadQueryService:query];
+}
+
+/**
+ * Flushes the mysql privileges
+ */
+- (void)flushPrivileges:(id)sender
+{
+ [mySQLConnection queryString:@"FLUSH PRIVILEGES"];
+
+ if (![mySQLConnection queryErrored]) {
+ //flushed privileges without errors
+ SPBeginAlertSheet(NSLocalizedString(@"Flushed Privileges", @"title of panel when successfully flushed privs"), NSLocalizedString(@"OK", @"OK button"), nil, nil, parentWindow, self, nil, nil, NSLocalizedString(@"Successfully flushed privileges.", @"message of panel when successfully flushed privs"));
+ } else {
+ //error while flushing privileges
+ SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, parentWindow, self, nil, nil, [NSString stringWithFormat:NSLocalizedString(@"Couldn't flush privileges.\nMySQL said: %@", @"message of panel when flushing privs failed"),
+ [mySQLConnection getLastErrorMessage]]);
+ }
+}
+
+- (IBAction)openCurrentConnectionInNewWindow:(id)sender
+{
+ [[NSApp delegate] newWindow:self];
+ SPDatabaseDocument *newTableDocument = [[NSApp delegate] frontDocument];
+ [newTableDocument initWithConnectionFile:[[self fileURL] path]];
+}
+
+/**
+ * Ask the connection controller to initiate connection, if it hasn't
+ * already. Used to support automatic connections on window open,
+ */
+- (void)connect
+{
+ if (mySQLVersion) return;
+ [connectionController initiateConnection:self];
+}
+
+- (void)closeConnection
+{
+ [mySQLConnection disconnect];
+ _isConnected = NO;
+
+ // Disconnected Growl notification
+ [[SPGrowlController sharedGrowlController] notifyWithTitle:@"Disconnected"
+ description:[NSString stringWithFormat:NSLocalizedString(@"Disconnected from %@",@"description for disconnected growl notification"), [parentTabViewItem label]]
+ document:self
+ notificationName:@"Disconnected"];
+}
+
+/**
+ * This method is called as part of Key Value Observing which is used to watch for prefernce changes which effect the interface.
+ */
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
+{
+ if ([keyPath isEqualToString:SPConsoleEnableLogging]) {
+ [mySQLConnection setDelegateQueryLogging:[[change objectForKey:NSKeyValueChangeNewKey] boolValue]];
+ }
+}
+
+/*
+ * Is current document Untitled?
+ */
+- (BOOL)isUntitled
+{
+ return ([self fileURL] && [[self fileURL] isFileURL]) ? NO : YES;
+}
+
+/**
+ * Asks any currently editing views to commit their changes;
+ * returns YES if changes were successfully committed, and NO
+ * if an error occurred or user interaction is required.
+ */
+- (BOOL)couldCommitCurrentViewActions
+{
+ [parentWindow endEditingFor:nil];
+ switch ([tableTabView indexOfTabViewItem:[tableTabView selectedTabViewItem]]) {
+
+ // Table structure view
+ case 0:
+ return [tableSourceInstance saveRowOnDeselect];
+
+ // Table content view
+ case 1:
+ return [tableContentInstance saveRowOnDeselect];
+
+ default:
+ break;
+ }
+
+ return YES;
+}
+
+#pragma mark -
+#pragma mark Accessor methods
+
+
+/**
+ * Returns the parent view, which in its turn contains the database view for this
+ * connection.
+ */
+- (NSView *)parentView
+{
+ return parentView;
+}
+
+/**
+ * Returns the host
+ */
+- (NSString *)host
+{
+ if ([connectionController type] == SPSocketConnection) return @"localhost";
+ NSString *theHost = [connectionController host];
+ if (!theHost) theHost = @"";
+ return theHost;
+}
+
+/**
+ * Returns the name
+ */
+- (NSString *)name
+{
+ if ([connectionController name] && [[connectionController name] length]) {
+ return [connectionController name];
+ }
+ if ([connectionController type] == SPSocketConnection) {
+ return [NSString stringWithFormat:@"%@@localhost", ([connectionController user] && [[connectionController user] length])?[connectionController user]:@"anonymous"];
+ }
+ return [NSString stringWithFormat:@"%@@%@", ([connectionController user] && [[connectionController user] length])?[connectionController user]:@"anonymous", [connectionController host]?[connectionController host]:@""];
+}
+
+/**
+ * Returns a string to identify the connection uniquely (mainly used to set up db structure with unique keys)
+ */
+- (NSString *)connectionID
+{
+
+ if(!_isConnected) return @"_";
+
+ NSString *port;
+ if([[self port] length])
+ port = [NSString stringWithFormat:@":%@", [self port]];
+ else
+ port = @"";
+
+ switch([connectionController type]) {
+ case SPSocketConnection:
+ return [NSString stringWithFormat:@"%@@localhost%@", ([connectionController user] && [[connectionController user] length])?[connectionController user]:@"anonymous", port];
+ break;
+ case SPTCPIPConnection:
+ return [NSString stringWithFormat:@"%@@%@%@",
+ ([connectionController user] && [[connectionController user] length])?[connectionController user]:@"anonymous",
+ [connectionController host]?[connectionController host]:@"",
+ port];
+ break;
+ case SPSSHTunnelConnection:
+ return [NSString stringWithFormat:@"%@@%@%@&SSH&%@@%@:%@",
+ ([connectionController user] && [[connectionController user] length])?[connectionController user]:@"anonymous",
+ [connectionController host]?[connectionController host]:@"",
+ port,
+ ([connectionController sshUser] && [[connectionController sshUser] length])?[connectionController sshUser]:@"anonymous",
+ [connectionController sshHost]?[connectionController sshHost]:@"",
+ ([[connectionController sshPort] length])?[connectionController sshPort]:@"22"];
+ }
+
+ return @"_";
+
+}
+
+/**
+ * Returns the currently selected database
+ */
+- (NSString *)database
+{
+ return selectedDatabase;
+}
+
+/**
+ * Returns the currently selected table (passing the request to SPTablesList)
+ */
+- (NSString *)table
+{
+ return [tablesListInstance tableName];
+}
+
+/**
+ * Returns the MySQL version
+ */
+- (NSString *)mySQLVersion
+{
+ return mySQLVersion;
+}
+
+/**
+ * Returns the current user
+ */
+- (NSString *)user
+{
+ NSString *theUser = [connectionController user];
+ if (!theUser) theUser = @"";
+ return theUser;
+}
+
+/**
+ * Returns the current host's port
+ */
+- (NSString *)port
+{
+ NSString *thePort = [connectionController port];
+ if (!thePort) return @"";
+ return thePort;
+}
+
+- (NSString *)keyChainID
+{
+ return keyChainID;
+}
+
+#pragma mark -
+#pragma mark Notification center methods
+
+/**
+ * Invoked before a query is performed
+ */
+- (void)willPerformQuery:(NSNotification *)notification
+{
+ [self setIsProcessing:YES];
+ [queryProgressBar startAnimation:self];
+}
+
+/**
+ * Invoked after a query has been performed
+ */
+- (void)hasPerformedQuery:(NSNotification *)notification
+{
+ [self setIsProcessing:NO];
+ [queryProgressBar stopAnimation:self];
+}
+
+/**
+ * Invoked when the application will terminate
+ */
+- (void)applicationWillTerminate:(NSNotification *)notification
+{
+ // Auto-save preferences to spf file based connection
+ if([self fileURL] && [[[self fileURL] path] length] && ![self isUntitled])
+ if(_isConnected && ![self saveDocumentWithFilePath:nil inBackground:YES onlyPreferences:YES]) {
+ NSLog(@"Preference data for file ‘%@’ could not be saved.", [[self fileURL] path]);
+ NSBeep();
+ }
+
+ [tablesListInstance selectionShouldChangeInTableView:nil];
+
+ // Note that this call does not need to be removed in release builds as leaks analysis output is only
+ // dumped if [[SPLogger logger] setDumpLeaksOnTermination]; has been called first.
+ [[SPLogger logger] dumpLeaks];
+}
+
+#pragma mark -
+#pragma mark Menu methods
+
+
+/**
+ * Saves SP session or if Custom Query tab is active the editor's content as SQL file
+ * If sender == nil then the call came from [self writeSafelyToURL:ofType:forSaveOperation:error]
+ */
+- (IBAction)saveConnectionSheet:(id)sender
+{
+
+ NSSavePanel *panel = [NSSavePanel savePanel];
+ NSString *filename;
+ NSString *contextInfo;
+
+ [panel setAllowsOtherFileTypes:NO];
+ [panel setCanSelectHiddenExtension:YES];
+
+ // Save Query…
+ if( sender != nil && [sender tag] == 1006 ) {
+
+ // Save the editor's content as SQL file
+ [panel setAccessoryView:[SPEncodingPopupAccessory encodingAccessory:[prefs integerForKey:SPLastSQLFileEncoding]
+ includeDefaultEntry:NO encodingPopUp:&encodingPopUp]];
+ // [panel setMessage:NSLocalizedString(@"Save SQL file", @"Save SQL file")];
+ [panel setAllowedFileTypes:[NSArray arrayWithObjects:@"sql", nil]];
+ if(![prefs stringForKey:@"lastSqlFileName"]) {
+ [prefs setObject:@"" forKey:@"lastSqlFileName"];
+ [prefs synchronize];
+ }
+
+ filename = [prefs stringForKey:@"lastSqlFileName"];
+ contextInfo = @"saveSQLfile";
+
+ // If no lastSqlFileEncoding in prefs set it to UTF-8
+ if(![prefs integerForKey:SPLastSQLFileEncoding]) {
+ [prefs setInteger:4 forKey:SPLastSQLFileEncoding];
+ [prefs synchronize];
+ }
+
+ [encodingPopUp setEnabled:YES];
+
+ // Save As… or Save
+ } else if(sender == nil || [sender tag] == 1005 || [sender tag] == 1004) {
+
+ // If Save was invoked check for fileURL and Untitled docs and save the spf file without save panel
+ // otherwise ask for file name
+ if(sender != nil && [sender tag] == 1004 && [[[self fileURL] path] length] && ![self isUntitled]) {
+ [self saveDocumentWithFilePath:nil inBackground:YES onlyPreferences:NO];
+ return;
+ }
+
+ // Load accessory nib each time.
+ // Note that the top-level objects aren't released automatically, but are released when the panel ends.
+ if(![NSBundle loadNibNamed:@"SaveSPFAccessory" owner:self]) {
+ NSLog(@"SaveSPFAccessory accessory dialog could not be loaded.");
+ return;
+ }
+
+ // Save current session (open connection windows as SPF file)
+ [panel setAllowedFileTypes:[NSArray arrayWithObjects:@"spf", nil]];
+
+ //Restore accessory view settings if possible
+ if([spfDocData objectForKey:@"save_password"])
+ [saveConnectionSavePassword setState:[[spfDocData objectForKey:@"save_password"] boolValue]];
+ if([spfDocData objectForKey:@"auto_connect"])
+ [saveConnectionAutoConnect setState:[[spfDocData objectForKey:@"auto_connect"] boolValue]];
+ if([spfDocData objectForKey:@"encrypted"])
+ [saveConnectionEncrypt setState:[[spfDocData objectForKey:@"encrypted"] boolValue]];
+ if([spfDocData objectForKey:@"include_session"])
+ [saveConnectionIncludeData setState:[[spfDocData objectForKey:@"include_session"] boolValue]];
+ if([spfDocData objectForKey:@"include_session"])
+ [saveConnectionIncludeQuery setState:[[spfDocData objectForKey:@"save_editor_content"] boolValue]];
+
+ [saveConnectionIncludeQuery setEnabled:([[[[customQueryInstance valueForKeyPath:@"textView"] textStorage] string] length])];
+
+ // Update accessory button states
+ [self validateSaveConnectionAccessory:nil];
+
+ // TODO note: it seems that one has problems with a NSSecureTextField
+ // inside an accessory view - ask HansJB
+ [[saveConnectionEncryptString cell] setControlView:saveConnectionAccessory];
+ [panel setAccessoryView:saveConnectionAccessory];
+
+ // Set file name
+ if([[[self fileURL] path] length])
+ filename = [self displayName];
+ else
+ filename = [NSString stringWithFormat:@"%@", [self name]];
+
+ if(sender == nil)
+ contextInfo = @"saveSPFfileAndClose";
+ else
+ contextInfo = @"saveSPFfile";
+
+ } else {
+ return;
+ }
+
+ [panel beginSheetForDirectory:nil
+ file:filename
+ modalForWindow:parentWindow
+ modalDelegate:self
+ didEndSelector:@selector(saveConnectionPanelDidEnd:returnCode:contextInfo:)
+ contextInfo:contextInfo];
+}
+/**
+ * Control the save connection panel's accessory view
+ */
+- (IBAction)validateSaveConnectionAccessory:(id)sender
+{
+
+ // [saveConnectionAutoConnect setEnabled:([saveConnectionSavePassword state] == NSOnState)];
+ [saveConnectionSavePasswordAlert setHidden:([saveConnectionSavePassword state] == NSOffState)];
+
+ // If user checks the Encrypt check box set focus to password field
+ if(sender == saveConnectionEncrypt && [saveConnectionEncrypt state] == NSOnState)
+ [saveConnectionEncryptString selectText:sender];
+
+ // Unfocus saveConnectionEncryptString
+ if(sender == saveConnectionEncrypt && [saveConnectionEncrypt state] == NSOffState) {
+ // [saveConnectionEncryptString setStringValue:[saveConnectionEncryptString stringValue]];
+ // TODO how can one make it better ?
+ [[saveConnectionEncryptString window] makeFirstResponder:[[saveConnectionEncryptString window] initialFirstResponder]];
+ }
+
+}
+
+- (void)saveConnectionPanelDidEnd:(NSSavePanel *)panel returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo
+{
+ if ( returnCode ) {
+
+ NSString *fileName = [panel filename];
+ NSError *error = nil;
+
+ // Save file as SQL file by using the chosen encoding
+ if(contextInfo == @"saveSQLfile") {
+
+ [prefs setInteger:[[encodingPopUp selectedItem] tag] forKey:SPLastSQLFileEncoding];
+ [prefs setObject:[fileName lastPathComponent] forKey:@"lastSqlFileName"];
+ [prefs synchronize];
+
+ NSString *content = [NSString stringWithString:[[[customQueryInstance valueForKeyPath:@"textView"] textStorage] string]];
+ [content writeToFile:fileName
+ atomically:YES
+ encoding:[[encodingPopUp selectedItem] tag]
+ error:&error];
+
+ if(error != nil) {
+ NSAlert *errorAlert = [NSAlert alertWithError:error];
+ [errorAlert runModal];
+ }
+
+ [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL:[NSURL fileURLWithPath:fileName]];
+
+ return;
+ }
+
+ // Save connection and session as SPF file
+ else if(contextInfo == @"saveSPFfile" || contextInfo == @"saveSPFfileAndClose") {
+ // Save changes of saveConnectionEncryptString
+ [[saveConnectionEncryptString window] makeFirstResponder:[[saveConnectionEncryptString window] initialFirstResponder]];
+
+ [self saveDocumentWithFilePath:fileName inBackground:NO onlyPreferences:NO];
+
+ // Manually loaded nibs don't have their top-level objects released automatically - do that here.
+ [saveConnectionAccessory release];
+
+ if(contextInfo == @"saveSPFfileAndClose")
+ [self closeAndDisconnect];
+ }
+ }
+}
+
+- (BOOL)saveDocumentWithFilePath:(NSString *)fileName inBackground:(BOOL)saveInBackground onlyPreferences:(BOOL)saveOnlyPreferences
+{
+ // Do not save if no connection is/was available
+ if(saveInBackground && ([self mySQLVersion] == nil || ![[self mySQLVersion] length]))
+ return NO;
+
+ NSMutableDictionary *spfDocData_temp = [NSMutableDictionary dictionary];
+
+ if(fileName == nil)
+ fileName = [[self fileURL] path]; //[[[self fileURL] absoluteString] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+
+ // Store save panel settings or take them from spfDocData
+ if(!saveInBackground) {
+ [spfDocData_temp setObject:[NSNumber numberWithBool:([saveConnectionEncrypt state]==NSOnState) ? YES : NO ] forKey:@"encrypted"];
+ if([[spfDocData_temp objectForKey:@"encrypted"] boolValue])
+ [spfDocData_temp setObject:[saveConnectionEncryptString stringValue] forKey:@"e_string"];
+ [spfDocData_temp setObject:[NSNumber numberWithBool:([saveConnectionAutoConnect state]==NSOnState) ? YES : NO ] forKey:@"auto_connect"];
+ [spfDocData_temp setObject:[NSNumber numberWithBool:([saveConnectionSavePassword state]==NSOnState) ? YES : NO ] forKey:@"save_password"];
+ [spfDocData_temp setObject:[NSNumber numberWithBool:([saveConnectionIncludeData state]==NSOnState) ? YES : NO ] forKey:@"include_session"];
+ [spfDocData_temp setObject:[NSNumber numberWithBool:NO] forKey:@"save_editor_content"];
+ if([[[[customQueryInstance valueForKeyPath:@"textView"] textStorage] string] length])
+ [spfDocData_temp setObject:[NSNumber numberWithBool:([saveConnectionIncludeQuery state]==NSOnState) ? YES : NO ] forKey:@"save_editor_content"];
+
+ } else {
+ [spfDocData_temp addEntriesFromDictionary:spfDocData];
+ }
+
+ // Update only query favourites, history, etc. by reading the file again
+ if(saveOnlyPreferences) {
+
+ // Check URL for safety reasons
+ if(![[[self fileURL] path] length] || [self isUntitled]) {
+ NSLog(@"Couldn't save data. No file URL found!");
+ NSBeep();
+ return NO;
+ }
+
+ NSError *readError = nil;
+ NSString *convError = nil;
+ NSPropertyListFormat format;
+ NSMutableDictionary *spf = [[NSMutableDictionary alloc] init];
+
+ NSData *pData = [NSData dataWithContentsOfFile:fileName options:NSUncachedRead error:&readError];
+
+ [spf addEntriesFromDictionary:[NSPropertyListSerialization propertyListFromData:pData
+ mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&convError]];
+
+ if(!spf || ![spf count] || 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. Please try to save the document under a different name.", @"message error while reading connection data file and suggesting to save it under a differnet name")];
+
+ [alert setAlertStyle:NSCriticalAlertStyle];
+ [alert runModal];
+ if (spf) [spf release];
+ // [self close];
+ return NO;
+ }
+
+ // For dispatching later
+ if(![[spf objectForKey:@"format"] isEqualToString:@"connection"]) {
+ NSLog(@"SPF file format is not 'connection'.");
+ [spf release];
+ return NO;
+ }
+
+ // Update the keys
+ [spf setObject:[[SPQueryController sharedQueryController] favoritesForFileURL:[self fileURL]] forKey:SPQueryFavorites];
+ [spf setObject:[[SPQueryController sharedQueryController] historyForFileURL:[self fileURL]] forKey:SPQueryHistory];
+ [spf setObject:[[SPQueryController sharedQueryController] contentFilterForFileURL:[self fileURL]] forKey:SPContentFilters];
+
+ // Save it again
+ NSString *err = nil;
+ NSData *plist = [NSPropertyListSerialization dataFromPropertyList:spf
+ format:NSPropertyListXMLFormat_v1_0
+ errorDescription:&err];
+
+ [spf release];
+ 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;
+ }
+
+ [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL:[NSURL fileURLWithPath:fileName]];
+
+ return YES;
+
+ }
+
+ NSString *aString;
+
+ NSMutableDictionary *spfdata = [NSMutableDictionary dictionary];
+ NSMutableDictionary *connection = [NSMutableDictionary dictionary];
+ NSMutableDictionary *session = nil;
+ NSMutableDictionary *data = [NSMutableDictionary dictionary];
+
+ NSIndexSet *contentSelectedIndexSet = [tableContentInstance selectedRowIndexes];
+
+ [spfdata setObject:[NSNumber numberWithInteger:1] forKey:@"version"];
+ [spfdata setObject:@"connection" forKey:@"format"];
+ [spfdata setObject:@"mysql" forKey:@"rdbms_type"];
+ [spfdata setObject:[self mySQLVersion] forKey:@"rdbms_version"];
+
+ // Store the preferences - take them from the current document URL to catch renaming
+ [spfdata setObject:[[SPQueryController sharedQueryController] favoritesForFileURL:[self fileURL]] forKey:SPQueryFavorites];
+ [spfdata setObject:[[SPQueryController sharedQueryController] historyForFileURL:[self fileURL]] forKey:SPQueryHistory];
+ [spfdata setObject:[[SPQueryController sharedQueryController] contentFilterForFileURL:[self fileURL]] forKey:SPContentFilters];
+
+ [spfdata setObject:[spfDocData_temp objectForKey:@"encrypted"] forKey:@"encrypted"];
+
+ // if([[spfDocData_temp objectForKey:@"save_password"] boolValue])
+ [spfdata setObject:[spfDocData_temp objectForKey:@"auto_connect"] forKey:@"auto_connect"];
+
+ if([[self keyChainID] length])
+ [connection setObject:[self keyChainID] forKey:@"kcid"];
+ [connection setObject:[self name] forKey:@"name"];
+ [connection setObject:[self host] forKey:@"host"];
+ [connection setObject:[self user] forKey:@"user"];
+
+ 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"];
+ if([connectionController sshPort] && [[connectionController sshPort] length])
+ [connection setObject:[NSNumber numberWithInteger:[[connectionController sshPort] integerValue]] forKey:@"ssh_port"];
+ break;
+ default:
+ aString = @"SPTCPIPConnection";
+ }
+ [connection setObject:aString forKey:@"type"];
+
+
+ if([[spfDocData_temp objectForKey:@"save_password"] boolValue]) {
+ NSString *pw = [self keychainPasswordForConnection:nil];
+ if(![pw length]) pw = [connectionController password];
+ if (pw) [connection setObject:pw forKey:@"password"];
+ if([connectionController type] == SPSSHTunnelConnection && [connectionController sshPassword])
+ [connection setObject:[connectionController sshPassword] forKey:@"ssh_password"];
+ }
+
+ if([connectionController port] && [[connectionController port] length])
+ [connection setObject:[NSNumber numberWithInteger:[[connectionController port] integerValue]] forKey:@"port"];
+
+ if([[self database] length])
+ [connection setObject:[self database] forKey:@"database"];
+
+ // Include session data like selected table, view etc. ?
+ if([[spfDocData_temp objectForKey:@"include_session"] boolValue]) {
+
+ session = [NSMutableDictionary dictionary];
+
+ if([[self table] length])
+ [session setObject:[self table] forKey:@"table"];
+ if([tableContentInstance sortColumnName])
+ [session setObject:[tableContentInstance sortColumnName] forKey:@"contentSortCol"];
+
+ switch([spHistoryControllerInstance currentlySelectedView]){
+ case SPTableViewStructure:
+ aString = @"SP_VIEW_STRUCTURE";
+ break;
+ case SPTableViewContent:
+ aString = @"SP_VIEW_CONTENT";
+ break;
+ case SPTableViewCustomQuery:
+ aString = @"SP_VIEW_CUSTOMQUERY";
+ break;
+ case SPTableViewStatus:
+ aString = @"SP_VIEW_STATUS";
+ break;
+ case SPTableViewRelations:
+ aString = @"SP_VIEW_RELATIONS";
+ break;
+ case SPTableViewTriggers:
+ aString = @"SP_VIEW_TRIGGERS";
+ break;
+ default:
+ aString = @"SP_VIEW_STRUCTURE";
+ }
+ [session setObject:aString forKey:@"view"];
+
+ [session setObject:[NSNumber numberWithBool:[[parentWindow toolbar] isVisible]] forKey:@"isToolbarVisible"];
+ [session setObject:[self connectionEncoding] forKey:@"connectionEncoding"];
+
+ [session setObject:[NSNumber numberWithBool:[tableContentInstance sortColumnIsAscending]] forKey:@"contentSortColIsAsc"];
+ [session setObject:[NSNumber numberWithInteger:[tableContentInstance pageNumber]] forKey:@"contentPageNumber"];
+ [session setObject:NSStringFromRect([tableContentInstance viewport]) forKey:@"contentViewport"];
+ if([tableContentInstance filterSettings])
+ [session setObject:[tableContentInstance filterSettings] forKey:@"contentFilter"];
+
+ if (contentSelectedIndexSet && [contentSelectedIndexSet count]) {
+ NSMutableArray *indices = [NSMutableArray array];
+ NSUInteger indexBuffer[[contentSelectedIndexSet count]];
+ NSUInteger limit = [contentSelectedIndexSet getIndexes:indexBuffer maxCount:[contentSelectedIndexSet count] inIndexRange:NULL];
+ NSUInteger idx;
+ for (idx = 0; idx < limit; idx++) {
+ [indices addObject:[NSNumber numberWithInteger:indexBuffer[idx]]];
+ }
+ [session setObject:indices forKey:@"contentSelectedIndexSet"];
+ }
+ }
+
+ if([[spfDocData_temp objectForKey:@"save_editor_content"] boolValue]) {
+ if(session == nil)
+ session = [NSMutableDictionary dictionary];
+
+ if([[[[customQueryInstance valueForKeyPath:@"textView"] textStorage] string] length] > 50000)
+ [session setObject:[[[[[customQueryInstance valueForKeyPath:@"textView"] textStorage] string] dataUsingEncoding:NSUTF8StringEncoding] compress] forKey:@"queries"];
+ else
+ [session setObject:[[[customQueryInstance valueForKeyPath:@"textView"] textStorage] string] forKey:@"queries"];
+ }
+
+ [data setObject:connection forKey:@"connection"];
+ if(session != nil)
+ [data setObject:session forKey:@"session"];
+
+ if(![[spfDocData_temp objectForKey:@"encrypted"] boolValue]) {
+ [spfdata setObject:data forKey:@"data"];
+ } 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"];
+ }
+
+ NSString *err = nil;
+ NSData *plist = [NSPropertyListSerialization dataFromPropertyList:spfdata
+ 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;
+ }
+
+ // 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];
+
+ [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;
+
+}
+
+/**
+ * Passes the request to the tableDump object
+ */
+- (IBAction)import:(id)sender
+{
+ [tableDumpInstance importFile];
+}
+
+/**
+ * Passes the request to the tableDump object
+ */
+- (IBAction)importFromClipboard:(id)sender
+{
+ [tableDumpInstance importFromClipboard];
+}
+
+/**
+ * Passes the request to the tableDump object
+ */
+- (IBAction)export:(id)sender
+{
+ if ([sender tag] == -1) {
+ [exportControllerInstance export];
+ }
+ else {
+ [tableDumpInstance exportFile:[sender tag]];
+ }
+}
+
+- (IBAction)exportTable:(id)sender
+{
+ return [self export:sender];
+}
+
+- (IBAction)exportMultipleTables:(id)sender
+{
+ return [self export:sender];
+}
+
+/*
+ * 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:) || [menuItem action] == @selector(newTab:));
+ }
+
+ 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:)) {
+ return (([self database] != nil) && ([[[tablesListInstance valueForKeyPath:@"tablesListView"] selectedRowIndexes] count]));
+ }
+
+ if ([menuItem action] == @selector(import:) ||
+ [menuItem action] == @selector(exportMultipleTables:) ||
+ [menuItem action] == @selector(removeDatabase:) ||
+ [menuItem action] == @selector(copyDatabase:) ||
+ [menuItem action] == @selector(renameDatabase:))
+ {
+ 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(exportTable:)) {
+ return ([self database] != nil && [self table] != nil);
+ }
+
+ 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];
+ }
+
+ // table menu items
+ if ([menuItem action] == @selector(showCreateTableSyntax:) ||
+ [menuItem action] == @selector(copyCreateTableSyntax:))
+ {
+ return ([self table] != nil && [[self table] isNotEqualTo:@""]);
+ }
+
+ 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:))
+ {
+ 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;
+}
+
+- (IBAction)viewStructure:(id)sender
+{
+ // Cancel the selection if currently editing a view and unable to save
+ if (![self couldCommitCurrentViewActions]) {
+ [mainToolbar setSelectedItemIdentifier:*SPViewModeToMainToolbarMap[[prefs integerForKey:SPLastViewMode]]];
+ return;
+ }
+
+ [tableTabView selectTabViewItemAtIndex:0];
+ [mainToolbar setSelectedItemIdentifier:SPMainToolbarTableStructure];
+ [spHistoryControllerInstance updateHistoryEntries];
+
+ [prefs setInteger:SPStructureViewMode forKey:SPLastViewMode];
+}
+
+- (IBAction)viewContent:(id)sender
+{
+
+ // Cancel the selection if currently editing a view and unable to save
+ if (![self couldCommitCurrentViewActions]) {
+ [mainToolbar setSelectedItemIdentifier:*SPViewModeToMainToolbarMap[[prefs integerForKey:SPLastViewMode]]];
+ return;
+ }
+
+ [tableTabView selectTabViewItemAtIndex:1];
+ [mainToolbar setSelectedItemIdentifier:SPMainToolbarTableContent];
+ [spHistoryControllerInstance updateHistoryEntries];
+
+ [prefs setInteger:SPContentViewMode forKey:SPLastViewMode];
+}
+
+- (IBAction)viewQuery:(id)sender
+{
+
+ // Cancel the selection if currently editing a view and unable to save
+ if (![self couldCommitCurrentViewActions]) {
+ [mainToolbar setSelectedItemIdentifier:*SPViewModeToMainToolbarMap[[prefs integerForKey:SPLastViewMode]]];
+ return;
+ }
+
+ [tableTabView selectTabViewItemAtIndex:2];
+ [mainToolbar setSelectedItemIdentifier:SPMainToolbarCustomQuery];
+ [spHistoryControllerInstance updateHistoryEntries];
+
+ // Set the focus on the text field
+ [parentWindow makeFirstResponder:customQueryTextView];
+
+ [prefs setInteger:SPQueryEditorViewMode forKey:SPLastViewMode];
+}
+
+- (IBAction)viewStatus:(id)sender
+{
+
+ // Cancel the selection if currently editing a view and unable to save
+ if (![self couldCommitCurrentViewActions]) {
+ [mainToolbar setSelectedItemIdentifier:*SPViewModeToMainToolbarMap[[prefs integerForKey:SPLastViewMode]]];
+ return;
+ }
+
+ [tableTabView selectTabViewItemAtIndex:3];
+ [mainToolbar setSelectedItemIdentifier:SPMainToolbarTableInfo];
+ [spHistoryControllerInstance updateHistoryEntries];
+
+ // Refresh data
+ if([self table] && [[self table] length]) {
+ [tableDataInstance resetAllData];
+ [extendedTableInfoInstance loadTable:[self table]];
+ }
+
+ [parentWindow makeFirstResponder:[extendedTableInfoInstance valueForKeyPath:@"tableCreateSyntaxTextView"]];
+
+ [prefs setInteger:SPTableInfoViewMode forKey:SPLastViewMode];
+}
+
+- (IBAction)viewRelations:(id)sender
+{
+
+ // Cancel the selection if currently editing a view and unable to save
+ if (![self couldCommitCurrentViewActions]) {
+ [mainToolbar setSelectedItemIdentifier:*SPViewModeToMainToolbarMap[[prefs integerForKey:SPLastViewMode]]];
+ return;
+ }
+
+ [tableTabView selectTabViewItemAtIndex:4];
+ [mainToolbar setSelectedItemIdentifier:SPMainToolbarTableRelations];
+ [spHistoryControllerInstance updateHistoryEntries];
+
+ [prefs setInteger:SPRelationsViewMode forKey:SPLastViewMode];
+}
+
+- (IBAction)viewTriggers:(id)sender
+{
+
+ // Cancel the selection if currently editing a view and unable to save
+ if (![self couldCommitCurrentViewActions]) {
+ [mainToolbar setSelectedItemIdentifier:*SPViewModeToMainToolbarMap[[prefs integerForKey:SPLastViewMode]]];
+ return;
+ }
+
+ [tableTabView selectTabViewItemAtIndex:5];
+ [mainToolbar setSelectedItemIdentifier:SPMainToolbarTableTriggers];
+ [spHistoryControllerInstance updateHistoryEntries];
+
+ [prefs setInteger:SPTriggersViewMode forKey:SPLastViewMode];
+}
+
+
+/**
+ * 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];
+}
+
+/**
+ * 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];
+
+ 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];
+ }
+ }
+ }
+}
+
+/*
+ * Return the createTableSyntaxWindow
+ */
+- (NSWindow *)getCreateTableSyntaxWindow
+{
+ return createTableSyntaxWindow;
+}
+
+#pragma mark -
+#pragma mark Titlebar Methods
+
+/**
+ * Update the window title.
+ */
+- (void) updateWindowTitle:(id)sender
+{
+ 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 (!_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]];
+
+ // Also add to the frontmost tab, and other tabs if the host is different, not connected, or no db is selected
+ if (frontTableDocument == self || [[frontTableDocument name] isNotEqualTo:[self name]] || ![frontTableDocument getConnection] || ![self database]) {
+ [tabTitle appendString:[self name]];
+ }
+
+ // If a database is selected, add to the window - and other tabs if host is the same but table is set
+ if ([self database]) {
+ [windowTitle appendFormat:@"/%@", [self database]];
+ if (frontTableDocument == self
+ || [[frontTableDocument name] isNotEqualTo:[self name]]
+ || ![[self table] length])
+ {
+ if ([tabTitle length]) [tabTitle appendString:@"/"];
+ [tabTitle appendString:[self database]];
+ }
+ }
+
+ // 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];
+ }
+
+ // 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;
+
+ NSImage *image = [[[NSImage alloc] initByReferencingFile:imagePath] autorelease];
+ [titleImageView setImage:image];
+}
+
+- (void)setTitlebarStatus:(NSString *)status
+{
+ [self clearStatusIcon];
+ [titleStringView setStringValue:status];
+}
+
+/**
+ * Clear the connection status icon in the titlebar
+ */
+- (void)clearStatusIcon
+{
+ [titleImageView setImage:nil];
+}
+
+#pragma mark -
+#pragma mark Toolbar Methods
+
+/**
+ * set up the standard toolbar
+ */
+- (void)setupToolbar
+{
+ // create a new toolbar instance, and attach it to our document window
+ mainToolbar = [[NSToolbar alloc] initWithIdentifier:@"TableWindowToolbar"];
+
+ // set up toolbar properties
+ [mainToolbar setAllowsUserCustomization:YES];
+ [mainToolbar setAutosavesConfiguration:YES];
+ [mainToolbar setDisplayMode:NSToolbarDisplayModeIconAndLabel];
+
+ // set ourself as the delegate
+ [mainToolbar setDelegate:self];
+
+ // update the toolbar item size
+ [self updateChooseDatabaseToolbarItemWidth];
+
+ // The history controller needs to track toolbar item state - trigger setup.
+ [spHistoryControllerInstance setupInterface];
+}
+
+/**
+ * Return the identifier for the currently selected toolbar item, or nil if none is selected.
+ */
+- (NSString *)selectedToolbarItemIdentifier;
+{
+ return [mainToolbar selectedItemIdentifier];
+}
+
+/**
+ * toolbar delegate method
+ */
+- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)willBeInsertedIntoToolbar
+{
+ NSToolbarItem *toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier] autorelease];
+
+ 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 (willBeInsertedIntoToolbar) {
+ chooseDatabaseToolbarItem = toolbarItem;
+ [self updateChooseDatabaseToolbarItemWidth];
+ }
+
+ } else if ([itemIdentifier isEqualToString:SPMainToolbarHistoryNavigation]) {
+ [toolbarItem setLabel:NSLocalizedString(@"Table History", @"toolbar item for navigation history")];
+ [toolbarItem setPaletteLabel:[toolbarItem label]];
+ [toolbarItem setView:historyControl];
+
+ } 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")];
+
+ [toolbarItem setLabel:NSLocalizedString(@"Console", @"Console")];
+ [toolbarItem setImage:[NSImage imageNamed:@"hideconsole"]];
+
+ //set up the target action
+ [toolbarItem setTarget:self];
+ [toolbarItem setAction:@selector(showConsole:)];
+
+ } 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:)];
+
+ } 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:)];
+
+ } 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:)];
+
+ } 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:)];
+
+ } 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:)];
+
+ } 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:)];
+
+ } 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 toolbarItem;
+}
+
+/**
+ * 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];
+}
+
+/**
+ * toolbar delegate method
+ */
+- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar*)toolbar
+{
+ return [NSArray arrayWithObjects:
+ SPMainToolbarDatabaseSelection,
+ SPMainToolbarTableStructure,
+ SPMainToolbarTableContent,
+ SPMainToolbarTableRelations,
+ SPMainToolbarTableInfo,
+ SPMainToolbarCustomQuery,
+ NSToolbarFlexibleSpaceItemIdentifier,
+ SPMainToolbarHistoryNavigation,
+ SPMainToolbarUserManager,
+ SPMainToolbarShowConsole,
+ nil];
+}
+
+/**
+ * toolbar delegate method
+ */
+- (NSArray *)toolbarSelectableItemIdentifiers:(NSToolbar *)toolbar
+{
+ return [NSArray arrayWithObjects:
+ SPMainToolbarTableStructure,
+ SPMainToolbarTableContent,
+ SPMainToolbarCustomQuery,
+ SPMainToolbarTableInfo,
+ SPMainToolbarTableRelations,
+ SPMainToolbarTableTriggers,
+ nil];
+
+}
+
+/**
+ * Validates the toolbar items
+ */
+- (BOOL)validateToolbarItem:(NSToolbarItem *)toolbarItem
+{
+ if (!_isConnected || _isWorkingLevel) return NO;
+
+ NSString *identifier = [toolbarItem itemIdentifier];
+
+ // 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;
+ }
+ }
+
+ // Clear console item
+ if ([identifier isEqualToString:SPMainToolbarClearConsole]) {
+ return ([[SPQueryController sharedQueryController] consoleMessageCount] > 0);
+ }
+
+ if (![identifier isEqualToString:SPMainToolbarCustomQuery] && ![identifier isEqualToString:SPMainToolbarUserManager]) {
+ return (([tablesListInstance tableType] == SPTableTypeTable) ||
+ ([tablesListInstance tableType] == SPTableTypeView));
+ }
+
+ 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];
+ if(isSaved)
+ [[SPQueryController sharedQueryController] removeRegisteredDocumentWithFileURL:[self fileURL]];
+ return isSaved;
+ }
+
+ // Return YES by default
+ return YES;
+}
+
+/**
+ * Invoked when the parent tab is about to close
+ */
+- (void)parentTabDidClose
+{
+
+ // 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];
+
+
+ [[SPNavigatorController sharedNavigatorController] removeConnection:[self connectionID]];
+
+ [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];
+}
+
+/**
+ * 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
+{
+
+ // Remove the icon accessory view from the title bar
+ [titleAccessoryView removeFromSuperview];
+
+ // Remove the task progress window
+ [parentWindow removeChildWindow:taskProgressWindow];
+ [taskProgressWindow orderOut:self];
+}
+
+/**
+ * Invoked when the parent tab became the active tab in the window,
+ * to allow the window to reflect the contents of this view.
+ */
+- (void)didBecomeActiveTabInWindow
+{
+
+ // Update the toolbar
+ [parentWindow setToolbar:mainToolbar];
+
+ // 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];
+
+ // 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];
+ }
+}
+
+/**
+ * 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];
+}
+
+/**
+ * Set the parent window
+ */
+- (void)setParentWindow:(NSWindow *)aWindow
+{
+ parentWindow = aWindow;
+ SPSSHTunnel *currentTunnel = [connectionController valueForKeyPath:@"sshTunnel"];
+ if (currentTunnel) [currentTunnel setParentWindow:parentWindow];
+}
+
+/**
+ * Return the parent window
+ */
+- (NSWindow *)parentWindow
+{
+ return parentWindow;
+}
+
+#pragma mark -
+#pragma mark NSDocument compatibility
+
+/**
+ * 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];
+ }
+}
+
+/**
+ * Retrieve the NSURL for the .spf file for this connection instance (if any)
+ */
+- (NSURL *)fileURL
+{
+ return [[spfFileURL copy] autorelease];
+}
+
+/*
+ * 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])
+ );
+}
+
+/**
+ * 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"];
+
+ }
+ return [[[self fileURL] path] lastPathComponent];
+}
+
+#pragma mark -
+#pragma mark Connection controller delegate methods
+
+/**
+ * Invoked by the connection controller when it starts the process of initiating a connection.
+ */
+- (void)connectionControllerInitiatingConnection:(id)controller
+{
+ // Update the window title to indicate that we are try to establish a connection
+ [parentTabViewItem setLabel:NSLocalizedString(@"Connecting…", @"window title string indicating that sp is connecting")];
+ if ([parentWindowController selectedTableDocument] == self) {
+ [parentWindow setTitle:NSLocalizedString(@"Connecting…", @"window title string indicating that sp is connecting")];
+ }
+}
+
+/**
+ * Invoked by the connection controller when the attempt to initiate a connection failed.
+ */
+- (void)connectionControllerConnectAttemptFailed:(id)controller
+{
+ // Reset the window title
+ [self updateWindowTitle:self];
+}
+
+#pragma mark -
+#pragma mark Text field delegate methods
+
+/**
+ * When adding a database, enable the button only if the new name has a length.
+ */
+- (void)controlTextDidChange:(NSNotification *)notification
+{
+ id object = [notification object];
+
+ if (object == databaseNameField) {
+ [addDatabaseButton setEnabled:([[databaseNameField stringValue] length] > 0 && ![allDatabases containsObject: [databaseNameField stringValue]])];
+ }
+ else if (object == databaseCopyNameField) {
+ [copyDatabaseButton setEnabled:([[databaseCopyNameField stringValue] length] > 0 && ![allDatabases containsObject: [databaseCopyNameField stringValue]])];
+ }
+ else if (object == databaseRenameNameField) {
+ [renameDatabaseButton setEnabled:([[databaseRenameNameField stringValue] length] > 0 && ![allDatabases containsObject: [databaseRenameNameField stringValue]])];
+ }
+ else if (object == saveConnectionEncryptString) {
+ [saveConnectionEncryptString setStringValue:[saveConnectionEncryptString stringValue]];
+ }
+
+}
+
+#pragma mark -
+#pragma mark General sheet delegate methods
+
+- (NSRect)window:(NSWindow *)window willPositionSheet:(NSWindow *)sheet usingRect:(NSRect)rect {
+
+ // Locate the sheet "Reset Auto Increment" just centered beneath the chosen index row
+ // if Structure Pane is active
+ if([tableTabView indexOfTabViewItem:[tableTabView selectedTabViewItem]] == 0
+ && [[sheet title] isEqualToString:@"Reset Auto Increment"]) {
+
+ id it = [tableSourceInstance valueForKeyPath:@"indexView"];
+ NSRect mwrect = [[NSApp mainWindow] frame];
+ NSRect ltrect = [[tablesListInstance valueForKeyPath:@"tablesListView"] frame];
+ NSRect rowrect = [it rectOfRow:[it selectedRow]];
+ rowrect.size.width = mwrect.size.width - ltrect.size.width;
+ rowrect.origin.y -= [it rowHeight]/2.0f+2;
+ rowrect.origin.x -= 8;
+ return [it convertRect:rowrect toView:nil];
+
+ } else
+ return rect;
+
+}
+
+#pragma mark -
+#pragma mark SplitView delegate methods
+
+/**
+ * tells the splitView that it can collapse views
+ */
+- (BOOL)splitView:(NSSplitView *)sender canCollapseSubview:(NSView *)subview
+{
+ return subview == [[tableInfoTable superview] superview];
+}
+
+- (void)splitViewDidResizeSubviews:(NSNotification *)notification
+{
+ [self updateChooseDatabaseToolbarItemWidth];
+}
+
+- (NSRect)splitView:(NSSplitView *)splitView additionalEffectiveRectOfDividerAtIndex:(NSInteger)dividerIndex
+{
+ if (sidebarGrabber != nil) {
+ return [sidebarGrabber convertRect:[sidebarGrabber bounds] toView:splitView];
+ } else {
+ return NSZeroRect;
+ }
+}
+
+- (void)updateChooseDatabaseToolbarItemWidth
+{
+ // make sure the toolbar item is actually in the toolbar
+ if (!chooseDatabaseToolbarItem)
+ return;
+
+ // grab the width of the left pane
+ CGFloat leftPaneWidth = [[[contentViewSplitter subviews] objectAtIndex:0] frame].size.width;
+
+ // subtract some pixels to allow for misc stuff
+ leftPaneWidth -= 12;
+
+ // make sure it's not too small or to big
+ if (leftPaneWidth < 130)
+ leftPaneWidth = 130;
+ if (leftPaneWidth > 360)
+ leftPaneWidth = 360;
+
+ // apply the size
+ [chooseDatabaseToolbarItem setMinSize:NSMakeSize(leftPaneWidth, 26)];
+ [chooseDatabaseToolbarItem setMaxSize:NSMakeSize(leftPaneWidth, 32)];
+}
+
+#pragma mark -
+#pragma mark Datasource methods
+
+- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
+{
+ if(statusTableView && aTableView == statusTableView)
+ return [statusValues count];
+ return 0;
+}
+
+- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
+{
+ if(statusTableView && aTableView == statusTableView && rowIndex < [statusValues count]) {
+ if ([[aTableColumn identifier] isEqualToString:@"table_name"]) {
+ if([[statusValues objectAtIndex:rowIndex] objectForKey:@"table_name"])
+ return [[statusValues objectAtIndex:rowIndex] objectForKey:@"table_name"];
+ else if([[statusValues objectAtIndex:rowIndex] objectForKey:@"Table"])
+ return [[statusValues objectAtIndex:rowIndex] objectForKey:@"Table"];
+ return @"";
+ }
+ else if ([[aTableColumn identifier] isEqualToString:@"msg_status"]) {
+ if([[statusValues objectAtIndex:rowIndex] objectForKey:@"Msg_type"])
+ return [[[statusValues objectAtIndex:rowIndex] objectForKey:@"Msg_type"] capitalizedString];
+ return @"";
+ }
+ else if ([[aTableColumn identifier] isEqualToString:@"msg_text"]) {
+ if([[statusValues objectAtIndex:rowIndex] objectForKey:@"Msg_text"]) {
+ [[aTableColumn headerCell] setStringValue:NSLocalizedString(@"Message",@"message column title")];
+ return [[statusValues objectAtIndex:rowIndex] objectForKey:@"Msg_text"];
+ }
+ else if([[statusValues objectAtIndex:rowIndex] objectForKey:@"Checksum"]) {
+ [[aTableColumn headerCell] setStringValue:@"Checksum"];
+ return [[statusValues objectAtIndex:rowIndex] objectForKey:@"Checksum"];
+ }
+ return @"";
+ }
+ }
+ return nil;
+}
+
+- (BOOL)tableView:(NSTableView *)aTableView shouldEditTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
+{
+ return NO;
+}
+
+
+#pragma mark -
+#pragma mark status accessory view
+
+- (IBAction)copyChecksumFromSheet:(id)sender
+{
+ NSMutableString *tmp = [NSMutableString string];
+ for(id row in statusValues)
+ if([row objectForKey:@"Msg_type"])
+ [tmp appendFormat:@"%@\t%@\t%@\n", [[row objectForKey:@"Table"] description],
+ [[row objectForKey:@"Msg_type"] description],
+ [[row objectForKey:@"Msg_text"] description]];
+ else
+ [tmp appendFormat:@"%@\t%@\n", [[row objectForKey:@"Table"] description],
+ [[row objectForKey:@"Checksum"] description]];
+ if ( [tmp length] )
+ {
+ NSPasteboard *pb = [NSPasteboard generalPasteboard];
+
+ [pb declareTypes:[NSArray arrayWithObjects: NSTabularTextPboardType,
+ NSStringPboardType, nil]
+ owner:nil];
+
+ [pb setString:tmp forType:NSStringPboardType];
+ [pb setString:tmp forType:NSTabularTextPboardType];
+ }
+}
+
+#pragma mark -
+
+/**
+ * Dealloc
+ */
+- (void)dealloc
+{
+
+ // Unregister observers
+ [prefs removeObserver:self forKeyPath:SPDisplayTableViewVerticalGridlines];
+ [prefs removeObserver:tableSourceInstance forKeyPath:SPDisplayTableViewVerticalGridlines];
+ [prefs removeObserver:tableContentInstance forKeyPath:SPDisplayTableViewVerticalGridlines];
+ [prefs removeObserver:customQueryInstance forKeyPath:SPDisplayTableViewVerticalGridlines];
+ [prefs removeObserver:tableRelationsInstance forKeyPath:SPDisplayTableViewVerticalGridlines];
+ [prefs removeObserver:[SPQueryController sharedQueryController] forKeyPath:SPDisplayTableViewVerticalGridlines];
+ [prefs removeObserver:tableSourceInstance forKeyPath:SPUseMonospacedFonts];
+ [prefs removeObserver:[SPQueryController sharedQueryController] forKeyPath:SPUseMonospacedFonts];
+ [prefs removeObserver:tableContentInstance forKeyPath:SPGlobalResultTableFont];
+ [prefs removeObserver:[SPQueryController sharedQueryController] forKeyPath:SPConsoleEnableLogging];
+ [prefs removeObserver:self forKeyPath:SPConsoleEnableLogging];
+ if (processListController) [prefs removeObserver:processListController forKeyPath:SPDisplayTableViewVerticalGridlines];
+ if (serverVariablesController) [prefs removeObserver:serverVariablesController forKeyPath:SPDisplayTableViewVerticalGridlines];
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [NSObject cancelPreviousPerformRequestsWithTarget:self];
+
+ for (id retainedObject in nibObjectsToRelease) [retainedObject release];
+ [nibObjectsToRelease release];
+
+ [_encoding release];
+ [allDatabases release];
+ [allSystemDatabases release];
+ [printWebView release];
+ [taskProgressWindow close];
+
+ if (connectionController) [connectionController release];
+ if (processListController) [processListController release];
+ if (serverVariablesController) [serverVariablesController release];
+ if (mySQLConnection) [mySQLConnection release];
+ if (selectedDatabase) [selectedDatabase release];
+ if (mySQLVersion) [mySQLVersion release];
+ if (taskDrawTimer) [taskDrawTimer release];
+ if (taskFadeAnimator) [taskFadeAnimator release];
+ if (queryEditorInitString) [queryEditorInitString release];
+ if (spfFileURL) [spfFileURL release];
+ if (spfPreferences) [spfPreferences release];
+ if (spfSession) [spfSession release];
+ if (spfDocData) [spfDocData release];
+ if (keyChainID) [keyChainID release];
+ if (mainToolbar) [mainToolbar release];
+ if (titleAccessoryView) [titleAccessoryView release];
+ if (taskProgressWindow) [taskProgressWindow release];
+
+ [super dealloc];
+}
+
+@end
+
+@implementation SPDatabaseDocument (PrivateAPI)
+
+- (void)_copyDatabase {
+ if ([[databaseCopyNameField stringValue] isEqualToString:@""]) {
+ SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, parentWindow, self, nil, nil, NSLocalizedString(@"Database must have a name.", @"message of panel when no db name is given"));
+ return;
+ }
+ SPDatabaseCopy *dbActionCopy = [[SPDatabaseCopy alloc] init];
+ [dbActionCopy setConnection: [self getConnection]];
+ [dbActionCopy setMessageWindow: parentWindow];
+
+ BOOL copyWithContent = [copyDatabaseDataButton state] == NSOnState;
+
+ if ([dbActionCopy copyDatabaseFrom: [self database]
+ to: [databaseCopyNameField stringValue]
+ withContent: copyWithContent]) {
+ [self selectDatabase:[databaseCopyNameField stringValue] item:nil];
+ }
+ [dbActionCopy release];
+ [self setDatabases: self];
+}
+
+- (void)_renameDatabase {
+ if ([[databaseRenameNameField stringValue] isEqualToString:@""]) {
+ SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, parentWindow, self, nil, nil, NSLocalizedString(@"Database must have a name.", @"message of panel when no db name is given"));
+ return;
+ }
+ SPDatabaseRename *dbActionRename = [[SPDatabaseRename alloc] init];
+ [dbActionRename setConnection: [self getConnection]];
+ [dbActionRename setMessageWindow: parentWindow];
+
+ if ([dbActionRename renameDatabaseFrom: [self database]
+ to: [databaseRenameNameField stringValue]]) {
+ [self selectDatabase:[databaseRenameNameField stringValue] item:nil];
+ }
+ [dbActionRename release];
+ [self setDatabases: self];
+}
+
+/**
+ * Adds a new database.
+ */
+- (void)_addDatabase
+{
+ // This check is not necessary anymore as the add database button is now only enabled if the name field
+ // has a length greater than zero. We'll leave it in just in case.
+ if ([[databaseNameField stringValue] isEqualToString:@""]) {
+ SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, parentWindow, self, nil, nil, NSLocalizedString(@"Database must have a name.", @"message of panel when no db name is given"));
+ return;
+ }
+
+ NSString *createStatement = [NSString stringWithFormat:@"CREATE DATABASE %@", [[databaseNameField stringValue] backtickQuotedString]];
+
+ // If there is an encoding selected other than the default we must specify it in CREATE DATABASE statement
+ if ([databaseEncodingButton indexOfSelectedItem] > 0) {
+ createStatement = [NSString stringWithFormat:@"%@ DEFAULT CHARACTER SET %@", createStatement, [[self mysqlEncodingFromDisplayEncoding:[databaseEncodingButton title]] backtickQuotedString]];
+ }
+
+ // Create the database
+ [mySQLConnection queryString:createStatement];
+
+ if ([mySQLConnection queryErrored]) {
+ // An error occurred
+ SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, parentWindow, self, nil, nil, [NSString stringWithFormat:NSLocalizedString(@"Couldn't create database.\nMySQL said: %@", @"message of panel when creation of db failed"), [mySQLConnection getLastErrorMessage]]);
+
+ return;
+ }
+
+ // Error while selecting the new database (is this even possible?)
+ if (![mySQLConnection selectDB:[databaseNameField stringValue]] ) {
+ SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, parentWindow, self, nil, nil, [NSString stringWithFormat:NSLocalizedString(@"Unable to connect to database %@.\nBe sure that you have the necessary privileges.", @"message of panel when connection to db failed after selecting from popupbutton"), [databaseNameField stringValue]]);
+
+ [self setDatabases:self];
+
+ return;
+ }
+
+ // Select the new database
+ if (selectedDatabase) [selectedDatabase release], selectedDatabase = nil;
+
+
+ selectedDatabase = [[NSString alloc] initWithString:[databaseNameField stringValue]];
+ [self setDatabases:self];
+
+ [tablesListInstance setConnection:mySQLConnection];
+ [tableDumpInstance setConnection:mySQLConnection];
+
+ [self updateWindowTitle:self];
+}
+
+/**
+ * Removes the current database.
+ */
+- (void)_removeDatabase
+{
+ // Drop the database from the server
+ [mySQLConnection queryString:[NSString stringWithFormat:@"DROP DATABASE %@", [[self database] backtickQuotedString]]];
+
+ if ([mySQLConnection queryErrored]) {
+ // An error occurred
+ [self performSelector:@selector(showErrorSheetWith:)
+ withObject:[NSArray arrayWithObjects:NSLocalizedString(@"Error", @"error"),
+ [NSString stringWithFormat:NSLocalizedString(@"Couldn't delete the database.\nMySQL said: %@", @"message of panel when deleting db failed"),
+ [mySQLConnection getLastErrorMessage]],
+ nil]
+ afterDelay:0.3];
+
+ return;
+ }
+
+ // Remove db from navigator and completion list array,
+ // do to threading we have to delete it from 'allDatabases' directly
+ // before calling navigator
+ [allDatabases removeObject:[self database]];
+ // This only deletes the db and refreshes the navigator since nothing is changed
+ // that's why we can run this on main thread
+ [mySQLConnection queryDbStructureWithUserInfo:nil];
+
+ // Delete was successful
+ if (selectedDatabase) [selectedDatabase release], selectedDatabase = nil;
+
+ [self setDatabases:self];
+
+ [tablesListInstance setConnection:mySQLConnection];
+ [tableDumpInstance setConnection:mySQLConnection];
+
+ [self updateWindowTitle:self];
+}
+
+/**
+ * Select the specified database and, optionally, table.
+ */
+- (void)_selectDatabaseAndItem:(NSDictionary *)selectionDetails
+{
+ NSAutoreleasePool *taskPool = [[NSAutoreleasePool alloc] init];
+ NSString *targetDatabaseName = [selectionDetails objectForKey:@"database"];
+ NSString *targetItemName = [selectionDetails objectForKey:@"item"];
+
+ // Save existing scroll position and details, and ensure no duplicate entries are created as table list changes
+ BOOL historyStateChanging = [spHistoryControllerInstance modifyingState];
+ if (!historyStateChanging) {
+ [spHistoryControllerInstance updateHistoryEntries];
+ [spHistoryControllerInstance setModifyingState:YES];
+ }
+
+ if (![targetDatabaseName isEqualToString:selectedDatabase]) {
+
+ // Attempt to select the specified database, and abort on failure
+ if ([chooseDatabaseButton indexOfItemWithTitle:targetDatabaseName] == NSNotFound
+ || ![mySQLConnection selectDB:targetDatabaseName])
+ {
+ if ( [mySQLConnection isConnected] ) {
+ SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, parentWindow, self, nil, nil, [NSString stringWithFormat:NSLocalizedString(@"Unable to connect to database %@.\nBe sure that you have the necessary privileges.", @"message of panel when connection to db failed after selecting from popupbutton"), targetDatabaseName]);
+
+ // Update the database list
+ [self setDatabases:self];
+ }
+
+ [self endTask];
+ [taskPool drain];
+ return;
+ }
+
+ [[chooseDatabaseButton onMainThread] selectItemWithTitle:targetDatabaseName];
+ if (selectedDatabase) [selectedDatabase release], selectedDatabase = nil;
+ selectedDatabase = [[NSString alloc] initWithString:[chooseDatabaseButton titleOfSelectedItem]];
+
+ // If the item has changed, clear the item selection for cleaner loading
+ if (![targetItemName isEqualToString:[self table]]) {
+ [[tablesListInstance onMainThread] setTableListSelectability:YES];
+ [[[tablesListInstance valueForKey:@"tablesListView"] onMainThread] deselectAll:self];
+ [[tablesListInstance onMainThread] setTableListSelectability:NO];
+ }
+
+ // Set the connection of SPTablesList and TablesDump to reload tables in db
+ [tablesListInstance setConnection:mySQLConnection];
+ [tableDumpInstance setConnection:mySQLConnection];
+
+ // Update the window title
+ [[self onMainThread] updateWindowTitle:self];
+
+ // Add a history entry
+ if (!historyStateChanging) {
+ [spHistoryControllerInstance setModifyingState:NO];
+ [spHistoryControllerInstance updateHistoryEntries];
+ }
+
+ // Set focus to table list filter field if visible
+ // otherwise set focus to Table List view
+ if ( [[tablesListInstance tables] count] > 20 )
+ [[parentWindow onMainThread] makeFirstResponder:listFilterField];
+ else
+ [[parentWindow onMainThread] makeFirstResponder:[tablesListInstance valueForKeyPath:@"tablesListView"]];
+ }
+
+ // If a the table has changed, update the selection
+ if (![targetItemName isEqualToString:[self table]]) {
+ if (targetItemName) {
+ [tablesListInstance selectItemWithName:targetItemName];
+ } else {
+ [[tablesListInstance onMainThread] setTableListSelectability:YES];
+ [[[tablesListInstance valueForKey:@"tablesListView"] onMainThread] deselectAll:self];
+ [[tablesListInstance onMainThread] setTableListSelectability:NO];
+ }
+ }
+
+ [self endTask];
+ [taskPool drain];
+}
+@end