//
// $Id$
//
// SPTablesList.m
// sequel-pro
//
// Created by Lorenz Textor (lorenz@textor.ch) on Wed May 1, 2002.
// Copyright (c) 2002-2003 Lorenz Textor. All rights reserved.
// Copyright (c) 2012 Sequel Pro Team. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// More info at
#import "SPTablesList.h"
#import "SPDatabaseDocument.h"
#import "SPTableStructure.h"
#import "SPDatabaseViewController.h"
#ifndef SP_CODA /* headers */
#import "SPTableContent.h"
#endif
#import "SPTableData.h"
#ifndef SP_CODA /* headers */
#import "SPTableInfo.h"
#import "SPDataImport.h"
#import "SPTableView.h"
#import "ImageAndTextCell.h"
#import "RegexKitLite.h"
#endif
#import "SPDatabaseData.h"
#import "SPAlertSheets.h"
#ifndef SP_CODA /* headers */
#import "SPNavigatorController.h"
#import "SPHistoryController.h"
#endif
#import "SPServerSupport.h"
#ifndef SP_CODA /* headers */
#import "SPWindowController.h"
#import "SPAppController.h"
#import "SPSplitView.h"
#endif
#import "SPThreadAdditions.h"
#ifdef SP_CODA
#import "SQLSidebarViewController.h"
#endif
#import "SPCharsetCollationHelper.h"
#import
// Constants
static NSString *SPAddRow = @"SPAddRow";
static NSString *SPAddNewTable = @"SPAddNewTable";
static NSString *SPRemoveTable = @"SPRemoveTable";
#ifndef SP_CODA
static NSString *SPTruncateTable = @"SPTruncateTable";
static NSString *SPDuplicateTable = @"SPDuplicateTable";
#endif
@interface SPTablesList ()
#ifndef SP_CODA
- (void)_removeTable;
- (void)_truncateTable;
#endif
- (void)_addTable;
#ifndef SP_CODA
- (void)_copyTable;
#endif
- (void)_renameTableOfType:(SPTableType)tableType from:(NSString *)oldTableName to:(NSString *)newTableName;
@end
@implementation SPTablesList
#ifdef SP_CODA
@synthesize sidebarViewController;
@synthesize databaseDataInstance;
@synthesize toolbarAddButton;
@synthesize toolbarDeleteButton;
@synthesize toolbarReloadButton;
@synthesize tableSourceInstance;
@synthesize tableContentInstance;
@synthesize tableSheet;
@synthesize tableNameField;
@synthesize tableEncodingButton;
@synthesize tableTypeButton;
@synthesize addTableButton;
@synthesize tablesListView;
#endif
#pragma mark -
#pragma mark Initialisation
- (id)init
{
if ((self = [super init])) {
tables = [[NSMutableArray alloc] init];
filteredTables = tables;
tableTypes = [[NSMutableArray alloc] init];
filteredTableTypes = tableTypes;
isTableListFiltered = NO;
tableListIsSelectable = YES;
tableListContainsViews = NO;
selectedTableType = SPTableTypeNone;
selectedTableName = nil;
#ifndef SP_CODA
[tables addObject:NSLocalizedString(@"TABLES", @"header for table list")];
smallSystemFont = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];
#endif
addTableCharsetHelper = nil; //initialized in awakeFromNib
}
return self;
}
- (void)awakeFromNib
{
#ifndef SP_CODA
// Configure the table information pane
[tableListSplitView setCollapsibleSubviewIndex:1];
// Collapse the pane if the last state was collapsed
if ([[[NSUserDefaults standardUserDefaults] objectForKey:SPTableInformationPanelCollapsed] boolValue]) {
[tableListSplitView setCollapsibleSubviewCollapsed:YES animate:NO];
}
// Configure the table list filter, starting it collapsed
[tableListFilterSplitView setCollapsibleSubviewIndex:0];
[tableListFilterSplitView setCollapsibleSubviewCollapsed:YES animate:NO];
// Disable tab edit behaviour in the tables list
[tablesListView setTabEditingDisabled:YES];
#endif
// Add observers for document task activity
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(startDocumentTaskForTab:)
name:SPDocumentTaskStartNotification
object:tableDocumentInstance];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(endDocumentTaskForTab:)
name:SPDocumentTaskEndNotification
object:tableDocumentInstance];
#ifndef SP_CODA
[tablesListView registerForDraggedTypes:[NSArray arrayWithObjects:SPNavigatorTableDataPasteboardDragType, nil]];
#endif
//create the charset helper
addTableCharsetHelper = [[SPCharsetCollationHelper alloc] initWithCharsetButton:tableEncodingButton CollationButton:tableCollationButton];
}
#pragma mark -
#pragma mark IBAction methods
/**
* Loads all table names in array tables and reload the tableView
*/
- (IBAction)updateTables:(id)sender
{
SPMySQLResult *theResult;
NSString *previousSelectedTable = nil;
NSString *previousFilterString = nil;
BOOL previousTableListIsSelectable = tableListIsSelectable;
BOOL changeEncoding = ![[mySQLConnection encoding] isEqualToString:@"utf8"];
if (selectedTableName) previousSelectedTable = [[NSString alloc] initWithString:selectedTableName];
#ifndef SP_CODA /* table list filtering */
if (isTableListFiltered) {
previousFilterString = [[NSString alloc] initWithString:[listFilterField stringValue]];
if (filteredTables) [filteredTables release];
filteredTables = tables;
if (filteredTableTypes) [filteredTableTypes release];
filteredTableTypes = tableTypes;
isTableListFiltered = NO;
[[self onMainThread] clearFilter];
}
tableListContainsViews = NO;
#endif
tableListIsSelectable = YES;
#ifndef SP_CODA
[[tablesListView onMainThread] deselectAll:self];
tableListIsSelectable = previousTableListIsSelectable;
#endif
[tables removeAllObjects];
[tableTypes removeAllObjects];
if ([tableDocumentInstance database]) {
// Notify listeners that a query has started
[[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryWillBePerformed" object:tableDocumentInstance];
// Use UTF8 for identifier-based queries
if (changeEncoding) {
[mySQLConnection storeEncodingForRestoration];
[mySQLConnection setEncoding:@"utf8"];
}
// Select the table list for the current database. On MySQL versions after 5 this will include
// views; on MySQL versions >= 5.0.02 select the "full" list to also select the table type column.
theResult = [mySQLConnection queryString:@"SHOW /*!50002 FULL*/ TABLES"];
[theResult setDefaultRowReturnType:SPMySQLResultRowAsArray];
if ([theResult numberOfFields] == 1) {
for (NSArray *eachRow in theResult) {
[tables addObject:[eachRow objectAtIndex:0]];
[tableTypes addObject:[NSNumber numberWithInteger:SPTableTypeTable]];
}
} else {
for (NSArray *eachRow in theResult) {
// Due to encoding problems it can be the case that [resultRow objectAtIndex:0]
// return NSNull, thus catch that case for safety reasons
id tableName = [eachRow objectAtIndex:0];
if ([tableName isNSNull]) {
tableName = @"...";
}
[tables addObject:tableName];
if ([[eachRow objectAtIndex:1] isEqualToString:@"VIEW"]) {
[tableTypes addObject:[NSNumber numberWithInteger:SPTableTypeView]];
tableListContainsViews = YES;
} else {
[tableTypes addObject:[NSNumber numberWithInteger:SPTableTypeTable]];
}
}
}
// Reorder the tables in alphabetical order
[tables sortArrayUsingSelector:@selector(localizedCompare:) withPairedMutableArrays:tableTypes, nil];
#ifndef SP_CODA /* table procedures and functions */
/* Grab the procedures and functions
*
* Using information_schema gives us more info (for information window perhaps?) but breaks
* backward compatibility with pre 4 I believe. I left the other methods below, in case.
*/
if ([[tableDocumentInstance serverSupport] supportsInformationSchema]) {
NSString *pQuery = [NSString stringWithFormat:@"SELECT * FROM information_schema.routines WHERE routine_schema = %@ ORDER BY routine_name", [[tableDocumentInstance database] tickQuotedString]];
theResult = [mySQLConnection queryString:pQuery];
[theResult setDefaultRowReturnType:SPMySQLResultRowAsArray];
// Check for mysql errors - if information_schema is not accessible for some reasons
// omit adding procedures and functions
if(![mySQLConnection queryErrored] && theResult != nil && [theResult numberOfRows] && [theResult numberOfFields] > 3) {
// Add the header row
[tables addObject:NSLocalizedString(@"PROCS & FUNCS",@"header for procs & funcs list")];
[tableTypes addObject:[NSNumber numberWithInteger:SPTableTypeNone]];
for (NSArray *eachRow in theResult) {
[tables addObject:NSArrayObjectAtIndex(eachRow, 3)];
if ([NSArrayObjectAtIndex(eachRow, 4) isEqualToString:@"PROCEDURE"]) {
[tableTypes addObject:[NSNumber numberWithInteger:SPTableTypeProc]];
} else {
[tableTypes addObject:[NSNumber numberWithInteger:SPTableTypeFunc]];
}
}
}
}
#endif
// Restore encoding if appropriate
if (changeEncoding) [mySQLConnection restoreStoredEncoding];
// Notify listeners that the query has finished
[[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance];
}
#ifndef SP_CODA
// Add the table headers even if no tables were found
if (tableListContainsViews) {
[tables insertObject:NSLocalizedString(@"TABLES & VIEWS",@"header for table & views list") atIndex:0];
}
else {
[tables insertObject:NSLocalizedString(@"TABLES",@"header for table list") atIndex:0];
}
[tableTypes insertObject:[NSNumber numberWithInteger:SPTableTypeNone] atIndex:0];
#endif
#ifndef SP_CODA /* ui manipulation */
[[tablesListView onMainThread] reloadData];
#else
[sidebarViewController setTableNames:[self allTableNames] selectedTableName:selectedTableName];
[sidebarViewController tableViewSelectionDidChange:nil];
#endif
// if the previous selected table still exists, select it
// but not if the update was called from SPTableData since it calls that method
// if a selected table doesn't exist - this happens if a table was deleted/renamed by an other user
// or if the table name contains characters which are not supported by the current set encoding
if ( ![sender isKindOfClass:[SPTableData class]] && previousSelectedTable != nil && [tables indexOfObject:previousSelectedTable] < [tables count]) {
NSInteger itemToReselect = [tables indexOfObject:previousSelectedTable];
tableListIsSelectable = YES;
#ifndef SP_CODA /* ui manipulation */
[[tablesListView onMainThread] selectRowIndexes:[NSIndexSet indexSetWithIndex:itemToReselect] byExtendingSelection:NO];
#endif
tableListIsSelectable = previousTableListIsSelectable;
if (selectedTableName) [selectedTableName release];
selectedTableName = [[NSString alloc] initWithString:[tables objectAtIndex:itemToReselect]];
selectedTableType = (SPTableType)[[tableTypes objectAtIndex:itemToReselect] integerValue];
}
else {
if (selectedTableName) [selectedTableName release];
selectedTableName = nil;
selectedTableType = SPTableTypeNone;
}
#ifndef SP_CODA /* table list filtering */
// Determine whether or not to preserve the existing filter, and whether to
// show or hide the list filter based on the number of tables
if ([tables count] > 20) {
[self showFilter];
if (previousFilterString) {
[[listFilterField onMainThread] setStringValue:previousFilterString];
[[self onMainThread] updateFilter:self];
}
} else {
[self hideFilter];
}
// Set the filter placeholder text
if ([tableDocumentInstance database]) {
[[[listFilterField cell] onMainThread] setPlaceholderString:NSLocalizedString(@"Filter", @"filter label")];
}
#endif
if (previousSelectedTable) [previousSelectedTable release];
if (previousFilterString) [previousFilterString release];
// Query the structure of all databases in the background
if (sender == self)
// Invoked by SP
[NSThread detachNewThreadWithName:@"SPNavigatorController database structure querier" target:[tableDocumentInstance databaseStructureRetrieval] selector:@selector(queryDbStructureWithUserInfo:) object:nil];
else
// User press refresh button ergo force update
[NSThread detachNewThreadWithName:@"SPNavigatorController database structure querier" target:[tableDocumentInstance databaseStructureRetrieval] selector:@selector(queryDbStructureWithUserInfo:) object:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], @"forceUpdate", [NSNumber numberWithBool:YES], @"cancelQuerying", nil]];
}
/**
* Adds a new table to the tables-array (no changes in mysql-db)
*/
- (IBAction)addTable:(id)sender
{
if ((![tableSourceInstance saveRowOnDeselect]) || (![tableContentInstance saveRowOnDeselect]) || (![tableDocumentInstance database])) return;
[[tableDocumentInstance parentWindow] endEditingFor:nil];
// Populate the table type (engine) popup button
[tableTypeButton removeAllItems];
NSArray *engines = [databaseDataInstance getDatabaseStorageEngines];
// Add default menu item
[tableTypeButton addItemWithTitle:[NSString stringWithFormat:NSLocalizedString(@"Default (%@)", @"New Table Sheet : Table Engine Dropdown : Default"), [databaseDataInstance getDatabaseDefaultStorageEngine]]];
[[tableTypeButton menu] addItem:[NSMenuItem separatorItem]];
for (NSDictionary *engine in engines)
{
[tableTypeButton addItemWithTitle:[engine objectForKey:@"Engine"]];
}
// Setup the charset and collation dropdowns
[addTableCharsetHelper setDatabaseData:databaseDataInstance];
[addTableCharsetHelper setServerSupport:[tableDocumentInstance serverSupport]];
[addTableCharsetHelper setPromoteUTF8:YES];
[addTableCharsetHelper setDefaultCharsetFormatString:NSLocalizedString(@"Inherit from database (%@)", @"New Table Sheet : Table Encoding Dropdown : Default inherited from database")];
[addTableCharsetHelper setDefaultCharset:[databaseDataInstance getDatabaseDefaultCharacterSet]];
[addTableCharsetHelper setDefaultCollation:[databaseDataInstance getDatabaseDefaultCollation]];
[addTableCharsetHelper setSelectedCharset:nil]; //reset to not carry over state from last time sheet was shown
[addTableCharsetHelper setSelectedCollation:nil];
[addTableCharsetHelper setEnabled:YES];
// Set the focus to the name field
[tableSheet makeFirstResponder:tableNameField];
[NSApp beginSheet:tableSheet
modalForWindow:[tableDocumentInstance parentWindow]
modalDelegate:self
didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
contextInfo:SPAddNewTable];
}
- (IBAction)tableEncodingButtonChanged:(id)sender
{
NSString *fmtStrDefaultId = NSLocalizedString(@"Default (%@)",@"Add Table : Collation : Default ($1 = collation name)");
NSString *fmtStrDefaultUnknown = NSLocalizedString(@"Default",@"Add Table Sheet : Collation : Default (unknown)"); // MySQL < 4.1.0
//throw out all items
[tableCollationButton removeAllItems];
//we'll enable that later if the user can actually change the selection.
[tableCollationButton setEnabled:NO];
/* logic below is as follows:
* if the database default charset is selected also use the database default collation
* regardless of default charset or not get the list of all collations that apply
* if a non-default charset is selected look out for it's default collation and promote that to the top as default
*
* Selecting a default charset (or collation) means that we don't want to specify one in the CREATE TABLE statement.
*/
//is the default charset currently selected?
BOOL isDefaultCharset = ([tableEncodingButton indexOfSelectedItem] == 0);
if(isDefaultCharset) {
NSString *defaultCollation = [databaseDataInstance getDatabaseDefaultCollation];
NSString *defaultItemTitle = (defaultCollation)? [NSString stringWithFormat:fmtStrDefaultId,defaultCollation] : fmtStrDefaultUnknown;
[tableCollationButton addItemWithTitle:defaultItemTitle];
//add the separator for the real items
[[tableCollationButton menu] addItem:[NSMenuItem separatorItem]];
}
//if the server actually has support for charsets & collations we will now get a list of all collations
//for the current charset. Even if the default charset is kept by the user he can change the default collation
//so we search in that case, too.
if(![[tableDocumentInstance serverSupport] supportsPost41CharacterSetHandling])
return;
//get the charset id the lazy way
NSString *charsetName = [[tableEncodingButton title] stringByMatching:@"\\((.*)\\)\\Z" capture:1L];
//this should not fail as even default is "Default (charset)" - if it does there's nothing we can do
if(!charsetName) {
NSLog(@"%s: Can't find charset id in encoding name <%@>. Format should be .",__func__,[tableEncodingButton title]);
return;
}
//now let's get the list of collations for the selected charset id
NSArray *applicableCollations = [databaseDataInstance getDatabaseCollationsForEncoding:charsetName];
//got something?
if (![applicableCollations count])
return;
//add the real items
for (NSDictionary *collation in applicableCollations)
{
NSString *collationName = [collation objectForKey:@"COLLATION_NAME"];
[tableCollationButton addItemWithTitle:collationName];
//if this is not the server default charset let's find it's default collation too
if(!isDefaultCharset && [[collation objectForKey:@"IS_DEFAULT"] isEqualToString:@"Yes"]) {
NSString *defaultCollateTitle = [NSString stringWithFormat:fmtStrDefaultId,collationName];
//add it to the top of the list
[tableCollationButton insertItemWithTitle:defaultCollateTitle atIndex:0];
//add a separator underneath
[[tableCollationButton menu] insertItem:[NSMenuItem separatorItem] atIndex:1];
}
}
//reset selection to first item (it may moved when adding the default item)
[tableCollationButton selectItemAtIndex:0];
//yay, now there is actually something not the Default item, so we can enable the button
[tableCollationButton setEnabled:YES];
}
/**
* Closes the current sheet and stops the modal session
*/
- (IBAction)closeSheet:(id)sender
{
[NSApp endSheet:[sender window] returnCode:[sender tag]];
[[sender window] orderOut:self];
}
/**
* Invoked when user hits the remove button alert sheet to ask user if he really wants to delete the table.
*/
- (IBAction)removeTable:(id)sender
{
if (![tablesListView numberOfSelectedRows])
return;
[[tableDocumentInstance parentWindow] endEditingFor:nil];
NSAlert *alert = [NSAlert alertWithMessageText:@"" defaultButton:NSLocalizedString(@"Delete", @"delete button") alternateButton:NSLocalizedString(@"Cancel", @"cancel button") otherButton:nil informativeTextWithFormat:@""];
[alert setAlertStyle:NSCriticalAlertStyle];
NSArray *buttons = [alert buttons];
#ifndef SP_CODA
// 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"];
#else
[[buttons objectAtIndex:0] setKeyEquivalent:@"\r"]; // Return = OK
[[buttons objectAtIndex:1] setKeyEquivalent:@"\e"]; // Esc = Cancel
#endif
NSIndexSet *indexes = [tablesListView selectedRowIndexes];
NSString *tblTypes = @"";
NSUInteger currentIndex = [indexes lastIndex];
if ([tablesListView numberOfSelectedRows] == 1) {
if([[filteredTableTypes objectAtIndex:currentIndex] integerValue] == SPTableTypeView)
tblTypes = NSLocalizedString(@"view", @"view");
else if([[filteredTableTypes objectAtIndex:currentIndex] integerValue] == SPTableTypeTable)
tblTypes = NSLocalizedString(@"table", @"table");
else if([[filteredTableTypes objectAtIndex:currentIndex] integerValue] == SPTableTypeProc)
tblTypes = NSLocalizedString(@"procedure", @"procedure");
else if([[filteredTableTypes objectAtIndex:currentIndex] integerValue] == SPTableTypeFunc)
tblTypes = NSLocalizedString(@"function", @"function");
[alert setMessageText:[NSString stringWithFormat:NSLocalizedString(@"Delete %@ '%@'?", @"delete table/view message"), tblTypes, [filteredTables objectAtIndex:[tablesListView selectedRow]]]];
[alert setInformativeText:[NSString stringWithFormat:NSLocalizedString(@"Are you sure you want to delete the %@ '%@'? This operation cannot be undone.", @"delete table/view informative message"), tblTypes, [filteredTables objectAtIndex:[tablesListView selectedRow]]]];
}
else {
BOOL areTableTypeEqual = YES;
NSInteger lastType = [[filteredTableTypes objectAtIndex:currentIndex] integerValue];
while (currentIndex != NSNotFound)
{
if([[filteredTableTypes objectAtIndex:currentIndex] integerValue]!=lastType)
{
areTableTypeEqual = NO;
break;
}
currentIndex = [indexes indexLessThanIndex:currentIndex];
}
if(areTableTypeEqual)
{
switch(lastType) {
case SPTableTypeTable:
tblTypes = NSLocalizedString(@"tables", @"tables");
break;
case SPTableTypeView:
tblTypes = NSLocalizedString(@"views", @"views");
break;
case SPTableTypeProc:
tblTypes = NSLocalizedString(@"procedures", @"procedures");
break;
case SPTableTypeFunc:
tblTypes = NSLocalizedString(@"functions", @"functions");
break;
}
} else
tblTypes = NSLocalizedString(@"items", @"items");
[alert setMessageText:[NSString stringWithFormat:NSLocalizedString(@"Delete selected %@?", @"delete tables/views message"), tblTypes]];
[alert setInformativeText:[NSString stringWithFormat:NSLocalizedString(@"Are you sure you want to delete the selected %@? This operation cannot be undone.", @"delete tables/views informative message"), tblTypes]];
}
[alert beginSheetModalForWindow:[tableDocumentInstance parentWindow] modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:SPRemoveTable];
}
#ifndef SP_CODA /* whole table operations */
/**
* Copies a table/view/proc/func, if desired with content
*/
- (IBAction)copyTable:(id)sender
{
NSString *tableType = @"";
if ([tablesListView numberOfSelectedRows] != 1) return;
if (![tableSourceInstance saveRowOnDeselect] || ![tableContentInstance saveRowOnDeselect]) return;
[[tableDocumentInstance parentWindow] endEditingFor:nil];
// Detect table type: table or view
NSInteger tblType = [[filteredTableTypes objectAtIndex:[tablesListView selectedRow]] integerValue];
switch (tblType){
case SPTableTypeTable:
tableType = NSLocalizedString(@"table",@"table");
[copyTableContentSwitch setEnabled:YES];
break;
case SPTableTypeView:
tableType = NSLocalizedString(@"view",@"view");
[copyTableContentSwitch setEnabled:NO];
break;
case SPTableTypeProc:
tableType = NSLocalizedString(@"procedure",@"procedure");
[copyTableContentSwitch setEnabled:NO];
break;
case SPTableTypeFunc:
tableType = NSLocalizedString(@"function",@"function");
[copyTableContentSwitch setEnabled:NO];
break;
}
[copyTableMessageField setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Duplicate %@ '%@' to:", @"duplicate object message"), tableType, [self tableName]]];
//open copyTableSheet
[copyTableNameField setStringValue:[NSString stringWithFormat:@"%@_copy", [filteredTables objectAtIndex:[tablesListView selectedRow]]]];
[copyTableContentSwitch setState:NSOffState];
[NSApp beginSheet:copyTableSheet
modalForWindow:[tableDocumentInstance parentWindow]
modalDelegate:self
didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
contextInfo:SPDuplicateTable];
}
/**
* This action starts editing the table name in the table list
*/
- (IBAction)renameTable:(id)sender
{
if ((![tableSourceInstance saveRowOnDeselect]) || (![tableContentInstance saveRowOnDeselect]) || (![tableDocumentInstance database])) {
return;
}
[[tableDocumentInstance parentWindow] endEditingFor:nil];
if ([tablesListView numberOfSelectedRows] != 1) return;
if (![[self tableName] length]) return;
[tablesListView editColumn:0 row:[tablesListView selectedRow] withEvent:nil select:YES];
}
/**
* Truncates the currently selected table(s).
*/
- (IBAction)truncateTable:(id)sender
{
if (![tablesListView numberOfSelectedRows])
return;
[[tableDocumentInstance parentWindow] endEditingFor:nil];
NSAlert *alert = [NSAlert alertWithMessageText:@""
defaultButton:NSLocalizedString(@"Truncate", @"truncate button")
alternateButton:NSLocalizedString(@"Cancel", @"cancel button")
otherButton:nil
informativeTextWithFormat:@""];
[alert setAlertStyle:NSCriticalAlertStyle];
NSArray *buttons = [alert buttons];
// Change the alert's cancel button to have the key equivalent of return
[[buttons objectAtIndex:0] setKeyEquivalent:@"t"];
[[buttons objectAtIndex:0] setKeyEquivalentModifierMask:NSCommandKeyMask];
[[buttons objectAtIndex:1] setKeyEquivalent:@"\r"];
if ([tablesListView numberOfSelectedRows] == 1) {
[alert setMessageText:[NSString stringWithFormat:NSLocalizedString(@"Truncate table '%@'?", @"truncate table message"), [filteredTables objectAtIndex:[tablesListView selectedRow]]]];
[alert setInformativeText:[NSString stringWithFormat:NSLocalizedString(@"Are you sure you want to delete ALL records in the table '%@'? This operation cannot be undone.", @"truncate table informative message"), [filteredTables objectAtIndex:[tablesListView selectedRow]]]];
}
else {
[alert setMessageText:NSLocalizedString(@"Truncate selected tables?", @"truncate tables message")];
[alert setInformativeText:NSLocalizedString(@"Are you sure you want to delete ALL records in the selected tables? This operation cannot be undone.", @"truncate tables informative message")];
}
[alert beginSheetModalForWindow:[tableDocumentInstance parentWindow] modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:SPTruncateTable];
}
/**
* Open the table in a new tab.
*/
- (IBAction)openTableInNewTab:(id)sender
{
// Add a new tab to the window
[[[tableDocumentInstance parentWindow] windowController] addNewConnection:self];
// Get the state of the document
NSDictionary *allStateDetails = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], @"connection",
[NSNumber numberWithBool:YES], @"history",
[NSNumber numberWithBool:YES], @"session",
[NSNumber numberWithBool:YES], @"query",
[NSNumber numberWithBool:YES], @"password",
nil];
NSMutableDictionary *documentState = [NSMutableDictionary dictionaryWithDictionary:[tableDocumentInstance stateIncludingDetails:allStateDetails]];
// Ensure it's set to autoconnect
[documentState setObject:[NSNumber numberWithBool:YES] forKey:@"auto_connect"];
// Set the connection on the new tab
[[[NSApp delegate] frontDocument] setState:documentState];
}
/**
* Toggle whether the splitview is collapsed.
*/
- (IBAction)togglePaneCollapse:(id)sender
{
[tableListSplitView toggleCollapse:sender];
[[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:[tableListSplitView isCollapsibleSubviewCollapsed]] forKey:SPTableInformationPanelCollapsed];
}
#endif
#pragma mark -
#pragma mark Alert sheet methods
/**
* Method for alert sheets.
*/
- (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];
}
if ([contextInfo isEqualToString:SPAddRow]) {
alertSheetOpened = NO;
}
else if ([contextInfo isEqualToString:SPRemoveTable]) {
if (returnCode == NSAlertDefaultReturn) {
[self performSelector:@selector(_removeTable) withObject:nil afterDelay:0.0];
}
}
#ifndef SP_CODA
else if ([contextInfo isEqualToString:SPTruncateTable]) {
if (returnCode == NSAlertDefaultReturn) {
[self _truncateTable];
}
}
else
#endif
if ([contextInfo isEqualToString:SPAddNewTable]) {
[addTableCharsetHelper setEnabled:NO];
if (returnCode == NSOKButton) {
[self _addTable];
}
}
#ifndef SP_CODA
else if ([contextInfo isEqualToString:SPDuplicateTable]) {
if (returnCode == NSOKButton) {
[self _copyTable];
}
}
#endif
}
#pragma mark -
#pragma mark Additional methods
/**
* Sets the connection (received from SPDatabaseDocument) and makes things that have to be done only once
*/
- (void)setConnection:(SPMySQLConnection *)theConnection
{
mySQLConnection = theConnection;
[self updateTables:self];
}
/**
* Performs interface validation for various controls.
*/
- (void)controlTextDidChange:(NSNotification *)notification
{
id object = [notification object];
if (object == tableNameField) {
[addTableButton setEnabled:[self isTableNameValid:[tableNameField stringValue] forType: SPTableTypeTable]];
}
#ifndef SP_CODA
else if (object == copyTableNameField) {
[copyTableButton setEnabled:[self isTableNameValid:[copyTableNameField stringValue] forType:[self tableType]]];
}
#endif
}
/**
* Controls the NSTextField's press RETURN event of Add/Rename/Duplicate sheets
*/
- (void)controlTextDidEndEditing:(NSNotification *)notification
{
id object = [notification object];
// Only RETURN/ENTER will be recognized for Add/Rename/Duplicate sheets to
// activate the Add/Rename/Duplicate buttons
if([[[notification userInfo] objectForKey:@"NSTextMovement"] integerValue] != 0)
return;
if (object == tableNameField) {
[addTableButton performClick:object];
}
#ifndef SP_CODA
else if (object == copyTableNameField) {
[copyTableButton performClick:object];
}
#endif
}
/**
* Updates application state to match the current selection, including
* updating the interface selection if appropriate.
* Takes a dictionary of selection details, containing the selection name
* and type, and updates stored variables and the table list interface to
* match.
* Should be called on the main thread.
*/
- (void)setSelectionState:(NSDictionary *)selectionDetails
{
// First handle empty or multiple selections
if (!selectionDetails || ![selectionDetails objectForKey:@"name"]) {
#ifndef SP_CODA
NSIndexSet *indexes = [tablesListView selectedRowIndexes];
#endif
// Update the selected table name and type
if (selectedTableName) [selectedTableName release];
selectedTableName = nil;
#ifndef SP_CODA /* ui manipulation */
// Set gear menu items Remove/Duplicate table/view according to the table types
// if at least one item is selected
if ([indexes count]) {
NSUInteger currentIndex = [indexes lastIndex];
BOOL areTableTypeEqual = YES;
NSInteger lastType = [[filteredTableTypes objectAtIndex:currentIndex] integerValue];
while (currentIndex != NSNotFound)
{
if ([[filteredTableTypes objectAtIndex:currentIndex] integerValue] != lastType) {
areTableTypeEqual = NO;
break;
}
currentIndex = [indexes indexLessThanIndex:currentIndex];
}
if (areTableTypeEqual) {
switch (lastType)
{
case SPTableTypeTable:
[removeTableMenuItem setTitle:NSLocalizedString(@"Delete Tables", @"delete tables menu title")];
[truncateTableButton setTitle:NSLocalizedString(@"Truncate Tables", @"truncate tables menu item")];
[removeTableContextMenuItem setTitle:NSLocalizedString(@"Delete Tables", @"delete tables menu title")];
[truncateTableContextMenuItem setTitle:NSLocalizedString(@"Truncate Tables", @"truncate tables menu item")];
[truncateTableButton setHidden:NO];
[truncateTableContextMenuItem setHidden:NO];
break;
case SPTableTypeView:
[removeTableMenuItem setTitle:NSLocalizedString(@"Delete Views", @"delete views menu title")];
[removeTableContextMenuItem setTitle:NSLocalizedString(@"Delete Views", @"delete views menu title")];
[truncateTableButton setHidden:YES];
[truncateTableContextMenuItem setHidden:YES];
break;
case SPTableTypeProc:
[removeTableMenuItem setTitle:NSLocalizedString(@"Delete Procedures", @"delete procedures menu title")];
[removeTableContextMenuItem setTitle:NSLocalizedString(@"Delete Procedures", @"delete procedures menu title")];
[truncateTableButton setHidden:YES];
[truncateTableContextMenuItem setHidden:YES];
break;
case SPTableTypeFunc:
[removeTableMenuItem setTitle:NSLocalizedString(@"Delete Functions", @"delete functions menu title")];
[removeTableContextMenuItem setTitle:NSLocalizedString(@"Delete Functions", @"delete functions menu title")];
[truncateTableButton setHidden:YES];
[truncateTableContextMenuItem setHidden:YES];
break;
}
} else {
[removeTableMenuItem setTitle:NSLocalizedString(@"Delete Items", @"delete items menu title")];
[removeTableContextMenuItem setTitle:NSLocalizedString(@"Delete Items", @"delete items menu title")];
[truncateTableButton setHidden:YES];
[truncateTableContextMenuItem setHidden:YES];
}
}
// Context menu
[renameTableContextMenuItem setHidden:YES];
[openTableInNewTabContextMenuItem setHidden:YES];
[separatorTableContextMenuItem3 setHidden:YES];
[duplicateTableContextMenuItem setHidden:YES];
[separatorTableContextMenuItem setHidden:YES];
[separatorTableContextMenuItem2 setHidden:NO];
[showCreateSyntaxContextMenuItem setTitle:NSLocalizedString(@"Show Create Syntaxes...", @"show create syntaxes menu item")];
[showCreateSyntaxContextMenuItem setHidden:NO];
[copyCreateSyntaxContextMenuItem setTitle:NSLocalizedString(@"Copy Create Syntaxes",@"Table List : Context Menu : Copy CREATE syntax (multiple selection)")];
[copyCreateSyntaxContextMenuItem setHidden:NO];
// 'Gear' menu
[renameTableMenuItem setHidden:YES];
[openTableInNewTabMenuItem setHidden:YES];
[separatorTableMenuItem3 setHidden:YES];
[duplicateTableMenuItem setHidden:YES];
[separatorTableMenuItem setHidden:YES];
[separatorTableMenuItem2 setHidden:NO];
[showCreateSyntaxMenuItem setTitle:NSLocalizedString(@"Show Create Syntaxes...", @"show create syntaxes menu item")];
[showCreateSyntaxMenuItem setHidden:NO];
[copyCreateSyntaxMenuItem setTitle:NSLocalizedString(@"Copy Create Syntaxes", @"Table List : Gear Menu : Copy CREATE syntax (multiple selection)")];
[copyCreateSyntaxMenuItem setHidden:NO];
// Get main menu "Table"'s submenu
NSMenu *tableSubMenu = [[[NSApp mainMenu] itemWithTag:SPMainMenuTable] submenu];
[[tableSubMenu itemAtIndex:4] setTitle:NSLocalizedString(@"Copy Create Syntaxes", @"copy create syntaxes menu item")];
[[tableSubMenu itemAtIndex:5] setTitle:NSLocalizedString(@"Show Create Syntaxes...", @"show create syntaxes menu item")];
[[tableSubMenu itemAtIndex:7] setTitle:NSLocalizedString(@"Check Selected Items", @"check selected items menu item")];
[[tableSubMenu itemAtIndex:8] setTitle:NSLocalizedString(@"Repair Selected Items", @"repair selected items menu item")];
[[tableSubMenu itemAtIndex:10] setTitle:NSLocalizedString(@"Analyze Selected Items", @"analyze selected items menu item")];
[[tableSubMenu itemAtIndex:11] setTitle:NSLocalizedString(@"Optimize Selected Items", @"optimize selected items menu item")];
[[tableSubMenu itemAtIndex:12] setTitle:NSLocalizedString(@"Flush Selected Items", @"flush selected items menu item")];
[[tableSubMenu itemAtIndex:13] setTitle:NSLocalizedString(@"Checksum Selected Items", @"checksum selected items menu item")];
[[tableSubMenu itemAtIndex:4] setHidden:NO];
[[tableSubMenu itemAtIndex:5] setHidden:NO];
[[tableSubMenu itemAtIndex:6] setHidden:NO];
[[tableSubMenu itemAtIndex:7] setHidden:NO];
[[tableSubMenu itemAtIndex:8] setHidden:NO];
[[tableSubMenu itemAtIndex:9] setHidden:NO];
[[tableSubMenu itemAtIndex:10] setHidden:NO];
[[tableSubMenu itemAtIndex:11] setHidden:NO];
#endif
return;
}
// If a new selection has been provided, store variables and update the interface to match
NSString *selectedItemName = [selectionDetails objectForKey:@"name"];
SPTableType selectedItemType = (SPTableType)[[selectionDetails objectForKey:@"type"] integerValue];
// Update the selected table name and type
if (selectedTableName) [selectedTableName release];
selectedTableName = [[NSString alloc] initWithString:selectedItemName];
selectedTableType = selectedItemType;
#ifndef SP_CODA /* ui manipulation */
// Remove the "current selection" item for filtered lists if appropriate
if (isTableListFiltered && [tablesListView selectedRow] < (NSInteger)[filteredTables count] - 2 && [filteredTables count] > 2
&& [[filteredTableTypes objectAtIndex:[filteredTableTypes count]-2] integerValue] == SPTableTypeNone
&& [[filteredTables objectAtIndex:[filteredTables count]-2] isEqualToString:NSLocalizedString(@"CURRENT SELECTION",@"header for current selection in filtered list")])
{
[filteredTables removeObjectsInRange:NSMakeRange([filteredTables count]-2, 2)];
[filteredTableTypes removeObjectsInRange:NSMakeRange([filteredTableTypes count]-2, 2)];
[tablesListView reloadData];
}
// Show menu separators
[separatorTableMenuItem setHidden:NO];
[separatorTableContextMenuItem setHidden:NO];
[separatorTableMenuItem2 setHidden:NO];
[separatorTableContextMenuItem2 setHidden:NO];
// Set gear menu items Remove/Duplicate table/view and mainMenu > Table items
// according to the table types
NSMenu *tableSubMenu = [[[NSApp mainMenu] itemWithTag:SPMainMenuTable] submenu];
// Enable/disable the various menu items depending on the selected item. Also update their titles.
// Note, that this should ideally be moved to menu item validation as opposed to using fixed item positions.
if (selectedTableType == SPTableTypeView)
{
// Change mainMenu > Table > ... according to table type
[[tableSubMenu itemAtIndex:4] setTitle:NSLocalizedString(@"Copy Create View Syntax", @"copy create view syntax menu item")];
[[tableSubMenu itemAtIndex:5] setTitle:NSLocalizedString(@"Show Create View Syntax...", @"show create view syntax menu item")];
[[tableSubMenu itemAtIndex:6] setHidden:NO]; // Divider
[[tableSubMenu itemAtIndex:7] setHidden:NO];
[[tableSubMenu itemAtIndex:7] setTitle:NSLocalizedString(@"Check View", @"check view menu item")];
[[tableSubMenu itemAtIndex:8] setHidden:YES]; // Repair
[[tableSubMenu itemAtIndex:9] setHidden:YES]; // Divider
[[tableSubMenu itemAtIndex:10] setHidden:YES]; // Analyse
[[tableSubMenu itemAtIndex:11] setHidden:YES]; // Optimize
[[tableSubMenu itemAtIndex:12] setHidden:NO];
[[tableSubMenu itemAtIndex:12] setTitle:NSLocalizedString(@"Flush View", @"flush view menu item")];
[[tableSubMenu itemAtIndex:13] setHidden:YES]; // Checksum
[renameTableMenuItem setHidden:NO]; // we don't have to check the mysql version
[renameTableMenuItem setTitle:NSLocalizedString(@"Rename View...", @"rename view menu title")];
[duplicateTableMenuItem setHidden:NO];
[duplicateTableMenuItem setTitle:NSLocalizedString(@"Duplicate View...", @"duplicate view menu title")];
[truncateTableButton setHidden:YES];
[removeTableMenuItem setTitle:NSLocalizedString(@"Delete View", @"delete view menu title")];
[openTableInNewTabMenuItem setHidden:NO];
[separatorTableMenuItem3 setHidden:NO];
[openTableInNewTabMenuItem setTitle:NSLocalizedString(@"Open View in New Tab", @"open view in new table title")];
[showCreateSyntaxMenuItem setHidden:NO];
[showCreateSyntaxMenuItem setTitle:NSLocalizedString(@"Show Create View Syntax...", @"show create view syntax menu item")];
[copyCreateSyntaxMenuItem setHidden:NO];
[copyCreateSyntaxMenuItem setTitle:NSLocalizedString(@"Copy Create View Syntax",@"Table List : Gear Menu : Copy CREATE view statement")];
[renameTableContextMenuItem setHidden:NO]; // we don't have to check the mysql version
[renameTableContextMenuItem setTitle:NSLocalizedString(@"Rename View...", @"rename view menu title")];
[duplicateTableContextMenuItem setHidden:NO];
[duplicateTableContextMenuItem setTitle:NSLocalizedString(@"Duplicate View...", @"duplicate view menu title")];
[truncateTableContextMenuItem setHidden:YES];
[removeTableContextMenuItem setTitle:NSLocalizedString(@"Delete View", @"delete view menu title")];
[openTableInNewTabContextMenuItem setHidden:NO];
[separatorTableContextMenuItem3 setHidden:NO];
[openTableInNewTabContextMenuItem setTitle:NSLocalizedString(@"Open View in New Tab", @"open view in new table title")];
[showCreateSyntaxContextMenuItem setHidden:NO];
[showCreateSyntaxContextMenuItem setTitle:NSLocalizedString(@"Show Create View Syntax...", @"show create view syntax menu item")];
[copyCreateSyntaxContextMenuItem setHidden:NO];
[copyCreateSyntaxContextMenuItem setTitle:NSLocalizedString(@"Copy Create View Syntax",@"Table List : Context Menu : Copy CREATE view statement")];
}
else if (selectedTableType == SPTableTypeTable) {
[[tableSubMenu itemAtIndex:4] setTitle:NSLocalizedString(@"Copy Create Table Syntax", @"copy create table syntax menu item")];
[[tableSubMenu itemAtIndex:5] setTitle:NSLocalizedString(@"Show Create Table Syntax...", @"show create table syntax menu item")];
[[tableSubMenu itemAtIndex:6] setHidden:NO]; // divider
[[tableSubMenu itemAtIndex:7] setHidden:NO];
[[tableSubMenu itemAtIndex:7] setTitle:NSLocalizedString(@"Check Table", @"check table menu item")];
[[tableSubMenu itemAtIndex:8] setHidden:NO];
[[tableSubMenu itemAtIndex:8] setTitle:NSLocalizedString(@"Repair Table", @"repair table menu item")];
[[tableSubMenu itemAtIndex:9] setHidden:NO]; // divider
[[tableSubMenu itemAtIndex:10] setHidden:NO];
[[tableSubMenu itemAtIndex:10] setTitle:NSLocalizedString(@"Analyze Table", @"analyze table menu item")];
[[tableSubMenu itemAtIndex:11] setHidden:NO];
[[tableSubMenu itemAtIndex:11] setTitle:NSLocalizedString(@"Optimize Table", @"optimize table menu item")];
[[tableSubMenu itemAtIndex:12] setHidden:NO];
[[tableSubMenu itemAtIndex:12] setTitle:NSLocalizedString(@"Flush Table", @"flush table menu item")];
[[tableSubMenu itemAtIndex:13] setHidden:NO];
[[tableSubMenu itemAtIndex:13] setTitle:NSLocalizedString(@"Checksum Table", @"checksum table menu item")];
[renameTableMenuItem setHidden:NO];
[renameTableMenuItem setTitle:NSLocalizedString(@"Rename Table...", @"rename table menu title")];
[duplicateTableMenuItem setHidden:NO];
[duplicateTableMenuItem setTitle:NSLocalizedString(@"Duplicate Table...", @"duplicate table menu title")];
[truncateTableButton setHidden:NO];
[truncateTableButton setTitle:NSLocalizedString(@"Truncate Table", @"truncate table menu title")];
[removeTableMenuItem setTitle:NSLocalizedString(@"Delete Table", @"delete table menu title")];
[openTableInNewTabMenuItem setHidden:NO];
[openTableInNewTabMenuItem setTitle:NSLocalizedString(@"Open Table in New Tab", @"open table in new table title")];
[separatorTableMenuItem3 setHidden:NO];
[showCreateSyntaxMenuItem setHidden:NO];
[showCreateSyntaxMenuItem setTitle:NSLocalizedString(@"Show Create Table Syntax...", @"show create table syntax menu item")];
[copyCreateSyntaxMenuItem setHidden:NO];
[copyCreateSyntaxMenuItem setTitle:NSLocalizedString(@"Copy Create Table Syntax",@"Table List : Context Menu : Copy CREATE syntax (single table)")];
[renameTableContextMenuItem setHidden:NO];
[renameTableContextMenuItem setTitle:NSLocalizedString(@"Rename Table...", @"rename table menu title")];
[duplicateTableContextMenuItem setHidden:NO];
[duplicateTableContextMenuItem setTitle:NSLocalizedString(@"Duplicate Table...", @"duplicate table menu title")];
[truncateTableContextMenuItem setHidden:NO];
[truncateTableContextMenuItem setTitle:NSLocalizedString(@"Truncate Table", @"truncate table menu title")];
[removeTableContextMenuItem setTitle:NSLocalizedString(@"Delete Table", @"delete table menu title")];
[openTableInNewTabContextMenuItem setHidden:NO];
[separatorTableContextMenuItem3 setHidden:NO];
[openTableInNewTabContextMenuItem setTitle:NSLocalizedString(@"Open Table in New Tab", @"open table in new table title")];
[showCreateSyntaxContextMenuItem setHidden:NO];
[showCreateSyntaxContextMenuItem setTitle:NSLocalizedString(@"Show Create Table Syntax...", @"show create table syntax menu item")];
[copyCreateSyntaxContextMenuItem setHidden:NO];
[copyCreateSyntaxContextMenuItem setTitle:NSLocalizedString(@"Copy Create Table Syntax",@"Table List : Gear Menu : Copy CREATE syntax (single table)")];
}
else if (selectedTableType == SPTableTypeProc) {
[[tableSubMenu itemAtIndex:4] setTitle:NSLocalizedString(@"Copy Create Procedure Syntax", @"copy create proc syntax menu item")];
[[tableSubMenu itemAtIndex:5] setTitle:NSLocalizedString(@"Show Create Procedure Syntax...", @"show create proc syntax menu item")];
[[tableSubMenu itemAtIndex:6] setHidden:YES]; // divider
[[tableSubMenu itemAtIndex:7] setHidden:YES]; // copy columns
[[tableSubMenu itemAtIndex:8] setHidden:YES]; // divider
[[tableSubMenu itemAtIndex:9] setHidden:YES];
[[tableSubMenu itemAtIndex:10] setHidden:YES];
[[tableSubMenu itemAtIndex:11] setHidden:YES]; // divider
[[tableSubMenu itemAtIndex:12] setHidden:YES];
[[tableSubMenu itemAtIndex:13] setHidden:YES];
[renameTableMenuItem setHidden:NO];
[renameTableMenuItem setTitle:NSLocalizedString(@"Rename Procedure...", @"rename proc menu title")];
[duplicateTableMenuItem setHidden:NO];
[duplicateTableMenuItem setTitle:NSLocalizedString(@"Duplicate Procedure...", @"duplicate proc menu title")];
[truncateTableButton setHidden:YES];
[removeTableMenuItem setTitle:NSLocalizedString(@"Delete Procedure", @"delete proc menu title")];
[openTableInNewTabMenuItem setHidden:NO];
[openTableInNewTabMenuItem setTitle:NSLocalizedString(@"Open Procedure in New Tab", @"open procedure in new table title")];
[separatorTableMenuItem3 setHidden:NO];
[showCreateSyntaxMenuItem setHidden:NO];
[showCreateSyntaxMenuItem setTitle:NSLocalizedString(@"Show Create Procedure Syntax...", @"show create proc syntax menu item")];
[copyCreateSyntaxMenuItem setHidden:NO];
[copyCreateSyntaxMenuItem setTitle:NSLocalizedString(@"Copy Create Procedure Syntax",@"Table List : Gear Menu : Copy CREATE PROCEDURE syntax")];
[renameTableContextMenuItem setHidden:NO];
[renameTableContextMenuItem setTitle:NSLocalizedString(@"Rename Procedure...", @"rename proc menu title")];
[duplicateTableContextMenuItem setHidden:NO];
[duplicateTableContextMenuItem setTitle:NSLocalizedString(@"Duplicate Procedure...", @"duplicate proc menu title")];
[truncateTableContextMenuItem setHidden:YES];
[removeTableContextMenuItem setTitle:NSLocalizedString(@"Delete Procedure", @"delete proc menu title")];
[openTableInNewTabContextMenuItem setHidden:NO];
[separatorTableContextMenuItem3 setHidden:NO];
[openTableInNewTabContextMenuItem setTitle:NSLocalizedString(@"Open Procedure in New Tab", @"open procedure in new table title")];
[showCreateSyntaxContextMenuItem setHidden:NO];
[showCreateSyntaxContextMenuItem setTitle:NSLocalizedString(@"Show Create Procedure Syntax...", @"show create proc syntax menu item")];
[copyCreateSyntaxContextMenuItem setHidden:NO];
[copyCreateSyntaxContextMenuItem setTitle:NSLocalizedString(@"Copy Create Procedure Syntax",@"Table List : Context Menu : Copy CREATE PROCEDURE syntax")];
}
else if (selectedTableType == SPTableTypeFunc) {
[[tableSubMenu itemAtIndex:4] setTitle:NSLocalizedString(@"Copy Create Function Syntax", @"copy create func syntax menu item")];
[[tableSubMenu itemAtIndex:5] setTitle:NSLocalizedString(@"Show Create Function Syntax...", @"show create func syntax menu item")];
[[tableSubMenu itemAtIndex:6] setHidden:YES]; // divider
[[tableSubMenu itemAtIndex:7] setHidden:YES]; // copy columns
[[tableSubMenu itemAtIndex:8] setHidden:YES]; // divider
[[tableSubMenu itemAtIndex:9] setHidden:YES];
[[tableSubMenu itemAtIndex:10] setHidden:YES];
[[tableSubMenu itemAtIndex:11] setHidden:YES]; // divider
[[tableSubMenu itemAtIndex:12] setHidden:YES];
[[tableSubMenu itemAtIndex:13] setHidden:YES];
[renameTableMenuItem setHidden:NO];
[renameTableMenuItem setTitle:NSLocalizedString(@"Rename Function...", @"rename func menu title")];
[duplicateTableMenuItem setHidden:NO];
[duplicateTableMenuItem setTitle:NSLocalizedString(@"Duplicate Function...", @"duplicate func menu title")];
[truncateTableButton setHidden:YES];
[removeTableMenuItem setTitle:NSLocalizedString(@"Delete Function", @"delete func menu title")];
[openTableInNewTabMenuItem setHidden:NO];
[separatorTableMenuItem3 setHidden:NO];
[openTableInNewTabMenuItem setTitle:NSLocalizedString(@"Open Function in New Tab", @"open function in new table title")];
[showCreateSyntaxMenuItem setHidden:NO];
[showCreateSyntaxMenuItem setTitle:NSLocalizedString(@"Show Create Function Syntax...", @"show create func syntax menu item")];
[copyCreateSyntaxMenuItem setHidden:NO];
[copyCreateSyntaxMenuItem setTitle:NSLocalizedString(@"Copy Create Function Syntax",@"Table List : Context Menu : copy CREATE FUNCTION syntax")];
[renameTableContextMenuItem setHidden:NO];
[renameTableContextMenuItem setTitle:NSLocalizedString(@"Rename Function...", @"rename func menu title")];
[duplicateTableContextMenuItem setHidden:NO];
[duplicateTableContextMenuItem setTitle:NSLocalizedString(@"Duplicate Function...", @"duplicate func menu title")];
[truncateTableContextMenuItem setHidden:YES];
[removeTableContextMenuItem setTitle:NSLocalizedString(@"Delete Function", @"delete func menu title")];
[openTableInNewTabContextMenuItem setHidden:NO];
[separatorTableContextMenuItem3 setHidden:NO];
[openTableInNewTabContextMenuItem setTitle:NSLocalizedString(@"Open Function in New Tab", @"open function in new table title")];
[showCreateSyntaxContextMenuItem setHidden:NO];
[showCreateSyntaxContextMenuItem setTitle:NSLocalizedString(@"Show Create Function Syntax...", @"show create func syntax menu item")];
[copyCreateSyntaxContextMenuItem setHidden:NO];
[copyCreateSyntaxContextMenuItem setTitle:NSLocalizedString(@"Copy Create Function Syntax",@"Table List : Context Menu : copy CREATE FUNCTION syntax")];
}
#endif
}
#pragma mark -
#pragma mark Getter methods
- (NSArray *)selectedTableNames
{
NSIndexSet *indexes = [tablesListView selectedRowIndexes];
NSUInteger currentIndex = [indexes firstIndex];
NSMutableArray *selTables = [NSMutableArray array];
while (currentIndex != NSNotFound) {
if([[filteredTableTypes objectAtIndex:currentIndex] integerValue] == SPTableTypeTable)
[selTables addObject:[filteredTables objectAtIndex:currentIndex]];
currentIndex = [indexes indexGreaterThanIndex:currentIndex];
}
return selTables;
}
- (NSArray *)selectedTableItems
{
NSIndexSet *indexes = [tablesListView selectedRowIndexes];
NSUInteger currentIndex = [indexes firstIndex];
NSMutableArray *selTables = [NSMutableArray array];
while (currentIndex != NSNotFound) {
[selTables addObject:[filteredTables objectAtIndex:currentIndex]];
currentIndex = [indexes indexGreaterThanIndex:currentIndex];
}
return selTables;
}
- (NSArray *)selectedTableTypes
{
NSIndexSet *indexes = [tablesListView selectedRowIndexes];
NSUInteger currentIndex = [indexes firstIndex];
NSMutableArray *selTables = [NSMutableArray array];
while (currentIndex != NSNotFound) {
[selTables addObject:[filteredTableTypes objectAtIndex:currentIndex]];
currentIndex = [indexes indexGreaterThanIndex:currentIndex];
}
return selTables;
}
/**
* Returns the currently selected table or nil if no table or mulitple tables are selected
*/
- (NSString *)tableName
{
return selectedTableName;
}
/**
* Returns the currently selected table type, or -1 if no table or multiple tables are selected
*/
- (SPTableType) tableType
{
return selectedTableType;
}
/**
* Database tables accessor
*/
- (NSArray *)tables
{
return tables;
}
/**
* Database tables accessors for a given table type
*/
- (NSArray *)allTableAndViewNames
{
NSMutableArray *returnArray = [NSMutableArray array];
NSInteger i;
NSInteger cnt = [[self tables] count];
for(i=0; i= (NSInteger)[filteredTables count]) return @"";
return [filteredTables objectAtIndex:rowIndex];
}
/**
* Prevent table renames while tasks are active
*/
- (BOOL)tableView:(NSTableView *)aTableView shouldEditTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
return ![tableDocumentInstance isWorking];
}
#endif
/**
* Renames a table (in tables-array and mysql-db).
*/
- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
//first trim whitespace whitespace
NSString *newTableName = [anObject stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if ([selectedTableName isEqualToString:newTableName]) {
// No changes in table name
return;
}
if ([newTableName isEqualToString:@""]) {
// empty table names are not allowed
// don't annoy the user about it, just ignore this
// this is also how the MacOS Finder handles renaming files
return;
}
if (![self isTableNameValid:newTableName forType:selectedTableType ignoringSelectedTable:YES]) {
// Table has invalid name
// Since we trimmed whitespace and checked for empty string, this means there is already a table with that name
SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"),
NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self,
@selector(sheetDidEnd:returnCode:contextInfo:), nil,
[NSString stringWithFormat: NSLocalizedString(@"The name '%@' is already used.", @"message when trying to rename a table/view/proc/etc to an already used name"), newTableName]);
return;
}
@try {
// first: update the database
[self _renameTableOfType:selectedTableType from:selectedTableName to:newTableName];
// second: update the table list
if (isTableListFiltered) {
NSInteger unfilteredIndex = [tables indexOfObject:[filteredTables objectAtIndex:rowIndex]];
[tables replaceObjectAtIndex:unfilteredIndex withObject:newTableName];
}
[filteredTables replaceObjectAtIndex:rowIndex withObject:newTableName];
if (selectedTableName) [selectedTableName release];
selectedTableName = [[NSString alloc] initWithString:newTableName];
// if the 'table' is a view or a table, ensure data is reloaded
if (selectedTableType == SPTableTypeTable || selectedTableType == SPTableTypeView)
{
[tableDocumentInstance loadTable:selectedTableName ofType:selectedTableType];
}
}
@catch (NSException * myException) {
SPBeginAlertSheet( NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, [myException reason]);
}
#ifndef SP_CODA
// Set window title to reflect the new table name
[tableDocumentInstance updateWindowTitle:self];
#endif
// Query the structure of all databases in the background (mainly for completion)
[NSThread detachNewThreadWithName:@"SPNavigatorController database structure querier" target:[tableDocumentInstance databaseStructureRetrieval] selector:@selector(queryDbStructureWithUserInfo:) object:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], @"forceUpdate", nil]];
}
#ifndef SP_CODA
#pragma mark -
#pragma mark TableView delegate methods
/**
* Traps enter and esc and edit/cancel without entering next row
*/
- (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)command
{
// When enter/return is used, save the row.
if ( [textView methodForSelector:command] == [textView methodForSelector:@selector(insertNewline:)] ) {
[[control window] makeFirstResponder:control];
return TRUE;
// When the escape key is used, abort the rename.
} else if ( [[control window] methodForSelector:command] == [[control window] methodForSelector:@selector(cancelOperation:)] ||
[textView methodForSelector:command] == [textView methodForSelector:@selector(complete:)] ) {
[control abortEditing];
[[NSApp mainWindow] makeFirstResponder:tablesListView];
return TRUE;
} else{
return FALSE;
}
}
#endif
/**
* Table view delegate method
*/
- (BOOL)selectionShouldChangeInTableView:(NSTableView *)aTableView
{
// Don't allow selection changes while performing a task.
if (!tableListIsSelectable) return NO;
// End editing (otherwise problems when user hits reload button)
[[tableDocumentInstance parentWindow] endEditingFor:nil];
if ( alertSheetOpened ) {
return NO;
}
// We have to be sure that document views have finished editing
return [tableDocumentInstance couldCommitCurrentViewActions];
}
#ifndef SP_CODA
/**
* Loads a table in content or source view (if tab selected)
*/
- (void)tableViewSelectionDidChange:(NSNotification *)aNotification
{
if ([tablesListView numberOfSelectedRows] != 1) {
// Ensure the state is cleared
if ([tableDocumentInstance table]) {
[tableDocumentInstance loadTable:nil ofType:SPTableTypeNone];
}
else {
[self setSelectionState:nil];
[tableInfoInstance tableChanged:nil];
}
if (selectedTableName) [selectedTableName release], selectedTableName = nil;
selectedTableType = SPTableTypeNone;
return;
}
NSInteger selectedRowIndex = [tablesListView selectedRow];
if (![[filteredTables objectAtIndex:selectedRowIndex] isKindOfClass:[NSString class]]) return;
// Reset selectability after change if necessary
if ([tableDocumentInstance isWorking]) tableListIsSelectable = NO;
// Perform no action if the selected table hasn't actually changed - reselection etc
NSString *newName = [filteredTables objectAtIndex:selectedRowIndex];
SPTableType newType = (SPTableType)[[filteredTableTypes objectAtIndex:selectedRowIndex] integerValue];
if ([selectedTableName isEqualToString:newName] && selectedTableType == newType) return;
// Save existing scroll position and details
[spHistoryControllerInstance updateHistoryEntries];
if (selectedTableName) [selectedTableName release], selectedTableName = nil;
selectedTableName = [[NSString alloc] initWithString:newName];
selectedTableType = newType;
[tableDocumentInstance loadTable:selectedTableName ofType:selectedTableType];
if([[SPNavigatorController sharedNavigatorController] syncMode]) {
NSMutableString *schemaPath = [NSMutableString string];
[schemaPath setString:[tableDocumentInstance connectionID]];
if([tableDocumentInstance database] && [[tableDocumentInstance database] length]) {
[schemaPath appendString:SPUniqueSchemaDelimiter];
[schemaPath appendString:[tableDocumentInstance database]];
[schemaPath appendString:SPUniqueSchemaDelimiter];
[schemaPath appendString:selectedTableName];
}
[[SPNavigatorController sharedNavigatorController] selectPath:schemaPath];
}
}
/**
* Table view delegate method
*/
- (BOOL)tableView:(NSTableView *)aTableView shouldSelectRow:(NSInteger)rowIndex
{
// Disallow selection while the document is working on a task
if ([tableDocumentInstance isWorking]) return NO;
// Allow deselections
if (rowIndex == -1) return YES;
if(![[filteredTables objectAtIndex:rowIndex] isKindOfClass:[NSString class]])
return NO;
//return (rowIndex != 0);
if( [filteredTableTypes count] == 0 )
return (rowIndex != 0 );
return ([[filteredTableTypes objectAtIndex:rowIndex] integerValue] != SPTableTypeNone );
}
/**
* Table view delegate method
*/
- (BOOL)tableView:(NSTableView *)aTableView isGroupRow:(NSInteger)rowIndex
{
// For empty tables - title still present - or while lists are being altered
if (rowIndex >= (NSInteger)[filteredTableTypes count]) return (rowIndex == 0 );
return ([[filteredTableTypes objectAtIndex:rowIndex] integerValue] == SPTableTypeNone );
}
/**
* Table view delegate method
*/
- (void)tableView:(NSTableView *)aTableView willDisplayCell:(ImageAndTextCell*)aCell forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
if (rowIndex > 0 && rowIndex < (NSInteger)[filteredTableTypes count] && [[aTableColumn identifier] isEqualToString:@"tables"]) {
id item = NSArrayObjectAtIndex(filteredTables, rowIndex);
if(![item isKindOfClass:[NSString class]]) {
[aCell setImage:nil];
[aCell setIndentationLevel:0];
return;
}
switch([NSArrayObjectAtIndex(filteredTableTypes, rowIndex) integerValue]) {
case SPTableTypeView:
[aCell setImage:[NSImage imageNamed:@"table-view-small"]];
[aCell setIndentationLevel:1];
[aCell setFont:smallSystemFont];
break;
case SPTableTypeTable:
[aCell setImage:[NSImage imageNamed:@"table-small"]];
[aCell setIndentationLevel:1];
[aCell setFont:smallSystemFont];
break;
case SPTableTypeProc:
[aCell setImage:[NSImage imageNamed:@"proc-small"]];
[aCell setIndentationLevel:1];
[aCell setFont:smallSystemFont];
break;
case SPTableTypeFunc:
[aCell setImage:[NSImage imageNamed:@"func-small"]];
[aCell setIndentationLevel:1];
[aCell setFont:smallSystemFont];
break;
case SPTableTypeNone:
[aCell setImage:nil];
[aCell setIndentationLevel:0];
break;
default:
[aCell setIndentationLevel:1];
[aCell setFont:smallSystemFont];
}
}
else {
[aCell setImage:nil];
[aCell setIndentationLevel:0];
}
}
/**
* Table view delegate method
*/
- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row
{
return (row == 0) ? 25 : 17;
}
- (BOOL)tableView:(NSTableView *)aTableView acceptDrop:(id )info row:(NSInteger)row dropOperation:(NSTableViewDropOperation)operation
{
NSPasteboard *pboard = [info draggingPasteboard];
// tables were dropped coming from the Navigator
if ( [[pboard types] containsObject:SPNavigatorTableDataPasteboardDragType] ) {
NSString *query = [pboard stringForType:SPNavigatorTableDataPasteboardDragType];
if(!query) return NO;
[mySQLConnection queryString:query];
if ([mySQLConnection queryErrored]) {
NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Error while importing table", @"error while importing table message")
defaultButton:NSLocalizedString(@"OK", @"OK button")
alternateButton:nil
otherButton:nil
informativeTextWithFormat:[NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to import a table via: \n%@\n\n\nMySQL said: %@", @"error importing table informative message"),
query, [mySQLConnection lastErrorMessage]]];
[alert setAlertStyle:NSCriticalAlertStyle];
[alert beginSheetModalForWindow:[tableDocumentInstance parentWindow] modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:@"truncateTableError"];
return NO;
}
[self updateTables:nil];
return YES;
}
return NO;
}
- (NSDragOperation)tableView:(NSTableView *)aTableView validateDrop:(id < NSDraggingInfo >)info proposedRow:(NSInteger)row proposedDropOperation:(NSTableViewDropOperation)operation
{
[tablesListView setDropRow:row dropOperation:NSTableViewDropAbove];
return NSDragOperationCopy;
}
#pragma mark -
#pragma mark Interface validation
/**
* Menu item interface validation
*/
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
{
SEL action = [menuItem action];
NSInteger selectedRows = [tablesListView numberOfSelectedRows];
if (action == @selector(copyTable:) ||
action == @selector(renameTable:) ||
action == @selector(openTableInNewTab:))
{
return selectedRows == 1 && [[self tableName] length];
}
if (action == @selector(removeTable:) ||
action == @selector(truncateTable:))
{
return selectedRows > 0;
}
//Default to YES (like Apple)
return YES;
}
#pragma mark -
#pragma mark Table list filter interaction
/**
* Show the filter box if it's currently hidden. Use a delay to ensure
* action is executed on first load.
*/
- (void) showFilter
{
if ([tableListFilterSplitView isCollapsibleSubviewCollapsed]) {
[tableListFilterSplitView performSelectorOnMainThread:@selector(toggleCollapse:) withObject:nil waitUntilDone:NO];
}
}
/**
* Hide the filter box if it's currently shown. Use a delay to ensure
* action is executed on first load.
*/
- (void) hideFilter
{
if (![tableListFilterSplitView isCollapsibleSubviewCollapsed]) {
[tableListFilterSplitView performSelectorOnMainThread:@selector(toggleCollapse:) withObject:nil waitUntilDone:NO];
}
}
/**
* Clear the current content of the filter box
*/
- (void) clearFilter
{
[listFilterField setStringValue:@""];
}
/**
* Set focus to table list filter search field, or the table list if the filter
* field is not visible.
*/
- (void) makeTableListFilterHaveFocus
{
if([tables count] > 20) {
[[tableDocumentInstance parentWindow] makeFirstResponder:listFilterField];
}
else {
[[tableDocumentInstance parentWindow] makeFirstResponder:tablesListView];
}
}
/**
* Set focus to the table list.
*/
- (void) makeTableListHaveFocus
{
[[tableDocumentInstance parentWindow] makeFirstResponder:tablesListView];
}
#endif
/**
* Update the filter search.
*/
- (IBAction) updateFilter:(id)sender
{
// Don't try and maintain selections of multiple rows through filtering
if ([tablesListView numberOfSelectedRows] > 1) {
[tablesListView deselectAll:self];
if (selectedTableName) [selectedTableName release], selectedTableName = nil;
}
#ifndef SP_CODA
if ([[listFilterField stringValue] length]) {
if (isTableListFiltered) {
[filteredTables release];
[filteredTableTypes release];
}
filteredTables = [[NSMutableArray alloc] init];
filteredTableTypes = [[NSMutableArray alloc] init];
NSUInteger i;
NSInteger lastTableType = NSNotFound, tableType;
NSRange substringRange;
NSString *filterString = [listFilterField stringValue];
for (i = 0; i < [tables count]; i++) {
tableType = [[tableTypes objectAtIndex:i] integerValue];
if (tableType == SPTableTypeNone) continue;
// First check the table name against the string as a regex, falling back to direct string match
if (![[tables objectAtIndex:i] isMatchedByRegex:filterString]) {
substringRange = [[tables objectAtIndex:i] rangeOfString:filterString options:NSCaseInsensitiveSearch];
if (substringRange.location == NSNotFound) continue;
}
#ifndef SP_CODA
// Add a title if necessary
if ((tableType == SPTableTypeTable || tableType == SPTableTypeView) && lastTableType == NSNotFound)
{
if (tableListContainsViews) {
[filteredTables addObject:NSLocalizedString(@"TABLES & VIEWS",@"header for table & views list")];
} else {
[filteredTables addObject:NSLocalizedString(@"TABLES",@"header for table list")];
}
[filteredTableTypes addObject:[NSNumber numberWithInteger:SPTableTypeNone]];
} else if ((tableType == SPTableTypeProc || tableType == SPTableTypeFunc)
&& (lastTableType == NSNotFound || lastTableType == SPTableTypeTable || lastTableType == SPTableTypeView))
{
[filteredTables addObject:NSLocalizedString(@"PROCS & FUNCS",@"header for procs & funcs list")];
[filteredTableTypes addObject:[NSNumber numberWithInteger:SPTableTypeNone]];
}
#endif
lastTableType = tableType;
// Add the item
[filteredTables addObject:[tables objectAtIndex:i]];
[filteredTableTypes addObject:[tableTypes objectAtIndex:i]];
}
// Add a "no matches" title if nothing matches the current filter settings
if (![filteredTables count]) {
[filteredTables addObject:NSLocalizedString(@"NO MATCHES",@"header for no matches in filtered list")];
[filteredTableTypes addObject:[NSNumber numberWithInteger:SPTableTypeNone]];
}
// If the currently selected table isn't present in the filter list, add it as a special entry
if (selectedTableName && [filteredTables indexOfObject:selectedTableName] == NSNotFound) {
[filteredTables addObject:NSLocalizedString(@"CURRENT SELECTION",@"header for current selection in filtered list")];
[filteredTableTypes addObject:[NSNumber numberWithInteger:SPTableTypeNone]];
[filteredTables addObject:selectedTableName];
[filteredTableTypes addObject:[NSNumber numberWithInteger:selectedTableType]];
}
isTableListFiltered = YES;
}
else if (isTableListFiltered) {
isTableListFiltered = NO;
[filteredTables release];
#endif
filteredTables = tables;
#ifndef SP_CODA
[filteredTableTypes release];
filteredTableTypes = tableTypes;
}
#endif
#ifndef SP_CODA
// Reselect correct row and reload the table view display
if ([tablesListView numberOfRows] < (NSInteger)[filteredTables count]) [tablesListView noteNumberOfRowsChanged];
if (selectedTableName) [tablesListView selectRowIndexes:[NSIndexSet indexSetWithIndex:[filteredTables indexOfObject:selectedTableName]] byExtendingSelection:NO];
[tablesListView reloadData];
#endif
}
/**
* Select the supplied row index; added for convenience to allow
* use with performSelector:withObject:afterDelay: for re-selection.
*/
- (void) selectTableAtIndex:(NSNumber *)row
{
NSUInteger rowIndex = [row unsignedIntegerValue];
#ifndef SP_CODA
if (rowIndex == NSNotFound || rowIndex > [filteredTables count] || [[filteredTableTypes objectAtIndex:rowIndex] integerValue] == SPTableTypeNone)
return;
#else
if (rowIndex == NSNotFound)
return;
#endif
[tablesListView selectRowIndexes:[NSIndexSet indexSetWithIndex:rowIndex] byExtendingSelection:NO];
}
#pragma mark -
#pragma mark Task interaction
/**
* Disable all table list interactive elements during an ongoing task.
*/
- (void) startDocumentTaskForTab:(NSNotification *)aNotification
{
tableListIsSelectable = NO;
[toolbarAddButton setEnabled:NO];
#ifndef SP_CODA
[toolbarActionsButton setEnabled:NO];
#endif
[toolbarReloadButton setEnabled:NO];
}
/**
* Enable all table list interactive elements after an ongoing task.
*/
- (void) endDocumentTaskForTab:(NSNotification *)aNotification
{
tableListIsSelectable = YES;
[toolbarAddButton setEnabled:YES];
#ifndef SP_CODA
[toolbarActionsButton setEnabled:YES];
#endif
[toolbarReloadButton setEnabled:YES];
}
/**
* Set the table list to selectable or not during the task process.
*/
- (void) setTableListSelectability:(BOOL)isSelectable
{
tableListIsSelectable = isSelectable;
}
#ifndef SP_CODA
#pragma mark -
#pragma mark SplitView Delegate Methods
/**
* Prevent the table info pane from being resized manually, by making the splitter
* not-selectable.
*/
- (NSRect)splitView:(NSSplitView *)splitView effectiveRect:(NSRect)proposedEffectiveRect forDrawnRect:(NSRect)drawnRect ofDividerAtIndex:(NSInteger)dividerIndex
{
if (splitView == (NSSplitView *)tableListSplitView || splitView == (NSSplitView *)tableListFilterSplitView) {
return NSZeroRect;
}
return proposedEffectiveRect;
}
/**
* Never show the divider bar for the table list filter split view.
*/
- (BOOL)splitView:(NSSplitView *)splitView shouldHideDividerAtIndex:(NSInteger)dividerIndex
{
if (splitView == (NSSplitView *)tableListFilterSplitView) {
return YES;
}
// Because both the info pane split view and filter view split view use this class
// as a delegate, we now have to duplicate some logic in SPSplitView to match the
// default behaviour - thanks to the override above.
if (splitView == (NSSplitView *)tableListSplitView) {
return [tableListSplitView isSubviewCollapsed:[[tableListSplitView subviews] objectAtIndex:1]];
}
return NO;
}
#endif
#pragma mark -
#pragma mark Other
#ifdef SP_CODA /* glue */
- (void)setDatabaseDocument:(SPDatabaseDocument*)val
{
tableDocumentInstance = val;
}
#endif
#pragma mark -
#pragma mark Private API
/**
* Removes the selected object (table, view, procedure, function, etc.) from the database and tableView.
*/
- (void)_removeTable
{
NSIndexSet *indexes = [tablesListView selectedRowIndexes];
[tablesListView selectRowIndexes:[NSIndexSet indexSet] byExtendingSelection:NO];
// get last index
NSUInteger currentIndex = [indexes lastIndex];
while (currentIndex != NSNotFound)
{
if([[filteredTableTypes objectAtIndex:currentIndex] integerValue] == SPTableTypeView) {
[mySQLConnection queryString: [NSString stringWithFormat: @"DROP VIEW %@",
[[filteredTables objectAtIndex:currentIndex] backtickQuotedString]
]];
} else if([[filteredTableTypes objectAtIndex:currentIndex] integerValue] == SPTableTypeTable) {
[mySQLConnection queryString: [NSString stringWithFormat: @"DROP TABLE %@",
[[filteredTables objectAtIndex:currentIndex] backtickQuotedString]
]];
} else if([[filteredTableTypes objectAtIndex:currentIndex] integerValue] == SPTableTypeProc) {
[mySQLConnection queryString: [NSString stringWithFormat: @"DROP PROCEDURE %@",
[[filteredTables objectAtIndex:currentIndex] backtickQuotedString]
]];
} else if([[filteredTableTypes objectAtIndex:currentIndex] integerValue] == SPTableTypeFunc) {
[mySQLConnection queryString: [NSString stringWithFormat: @"DROP FUNCTION %@",
[[filteredTables objectAtIndex:currentIndex] backtickQuotedString]
]];
}
// If no error is recorded, the table was successfully dropped - remove it from the list
if (![mySQLConnection queryErrored]) {
//dropped table with success
if (isTableListFiltered) {
NSInteger unfilteredIndex = [tables indexOfObject:[filteredTables objectAtIndex:currentIndex]];
[tables removeObjectAtIndex:unfilteredIndex];
[tableTypes removeObjectAtIndex:unfilteredIndex];
}
[filteredTables removeObjectAtIndex:currentIndex];
[filteredTableTypes removeObjectAtIndex:currentIndex];
// Get next index (beginning from the end)
currentIndex = [indexes indexLessThanIndex:currentIndex];
// Otherwise, display an alert - and if there's tables left, ask whether to proceed
} else {
NSAlert *alert = [[[NSAlert alloc] init] autorelease];
if ([indexes indexLessThanIndex:currentIndex] == NSNotFound) {
[alert addButtonWithTitle:NSLocalizedString(@"OK", @"OK button")];
} else {
[alert addButtonWithTitle:NSLocalizedString(@"Continue", @"continue button")];
[alert addButtonWithTitle:NSLocalizedString(@"Stop", @"stop button")];
}
[alert setMessageText:NSLocalizedString(@"Error", @"error")];
[alert setInformativeText:[NSString stringWithFormat:NSLocalizedString(@"Couldn't delete '%@'.\n\nMySQL said: %@", @"message of panel when an item cannot be deleted"), [filteredTables objectAtIndex:currentIndex], [mySQLConnection lastErrorMessage]]];
[alert setAlertStyle:NSWarningAlertStyle];
if ([indexes indexLessThanIndex:currentIndex] == NSNotFound) {
[alert beginSheetModalForWindow:[tableDocumentInstance parentWindow] modalDelegate:self didEndSelector:nil contextInfo:nil];
currentIndex = NSNotFound;
} else {
NSInteger choice = [alert runModal];
if (choice == NSAlertFirstButtonReturn) {
currentIndex = [indexes indexLessThanIndex:currentIndex];
} else {
currentIndex = NSNotFound;
}
}
}
}
// Remove the isolated "current selection" item for filtered lists if appropriate
if (isTableListFiltered && [filteredTables count] > 1
&& [[filteredTableTypes objectAtIndex:[filteredTableTypes count]-1] integerValue] == SPTableTypeNone
&& [[filteredTables objectAtIndex:[filteredTables count]-1] isEqualToString:NSLocalizedString(@"CURRENT SELECTION",@"header for current selection in filtered list")])
{
[filteredTables removeLastObject];
[filteredTableTypes removeLastObject];
}
[tablesListView reloadData];
[tablesListView deselectAll:self];
#ifndef SP_CODA
// set window title
[tableDocumentInstance updateWindowTitle:self];
#endif
#ifdef SP_CODA
[sidebarViewController setTableNames:filteredTables selectedTableName:nil];
#endif
// Query the structure of all databases in the background (mainly for completion)
[NSThread detachNewThreadWithName:@"SPNavigatorController database structure querier" target:[tableDocumentInstance databaseStructureRetrieval] selector:@selector(queryDbStructureWithUserInfo:) object:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], @"forceUpdate", nil]];
}
#ifndef SP_CODA /* operations performed on whole tables */
/**
* Trucates the selected table(s).
*/
- (void)_truncateTable
{
NSIndexSet *indexes = [tablesListView selectedRowIndexes];
// Get last index
NSUInteger currentIndex = [indexes lastIndex];
while (currentIndex != NSNotFound)
{
[mySQLConnection queryString:[NSString stringWithFormat: @"TRUNCATE TABLE %@", [[filteredTables objectAtIndex:currentIndex] backtickQuotedString]]];
// Couldn't truncate table
if ([mySQLConnection queryErrored]) {
NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Error truncating table", @"error truncating table message")
defaultButton:NSLocalizedString(@"OK", @"OK button")
alternateButton:nil
otherButton:nil
informativeTextWithFormat:[NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to truncate the table '%@'.\n\nMySQL said: %@", @"error truncating table informative message"),
[filteredTables objectAtIndex:currentIndex], [mySQLConnection lastErrorMessage]]];
[alert setAlertStyle:NSCriticalAlertStyle];
[alert beginSheetModalForWindow:[tableDocumentInstance parentWindow]
modalDelegate:self
didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
contextInfo:@"truncateTableError"];
}
// Get next index (beginning from the end)
currentIndex = [indexes indexLessThanIndex:currentIndex];
}
// Ensure the the table's content view is updated to show that it has been truncated
[tableDocumentInstance setContentRequiresReload:YES];
[tableDataInstance resetStatusData];
}
#endif
/**
* Adds a new table table to the database using the selected character set encoding and storage engine.
*/
- (void)_addTable
{
// Ensure the task is performed on a background thread to group addition and loads
if ([NSThread isMainThread]) {
[NSThread detachNewThreadWithName:@"SPTablesList table addition task" target:self selector:@selector(_addTable) object:nil];
return;
}
NSAutoreleasePool *tableAdditionPool = [[NSAutoreleasePool alloc] init];
[tableDocumentInstance startTaskWithDescription:[NSString stringWithFormat:NSLocalizedString(@"Creating %@...", @"Creating table task string"), [tableNameField stringValue]]];
NSString *charSetStatement = @"";
NSString *collationStatement = @"";
NSString *engineStatement = @"";
NSString *tableType = [tableTypeButton title];
NSString *tableName = [tableNameField stringValue];
// Ensure the use of UTF8 when creating new tables
BOOL changeEncoding = ![[mySQLConnection encoding] isEqualToString:@"utf8"];
if (changeEncoding) {
[mySQLConnection storeEncodingForRestoration];
[mySQLConnection setEncoding:@"utf8"];
}
// If there is an encoding selected other than the default we must specify it in CREATE TABLE statement
NSString *encodingName = [addTableCharsetHelper selectedCharset];
if (encodingName)
charSetStatement = [NSString stringWithFormat:@"DEFAULT CHARACTER SET %@", [encodingName backtickQuotedString]];
// If there is a collation selected other than the default we must specify it in the CREATE TABLE statement
NSString *collationName = [addTableCharsetHelper selectedCollation];
if (collationName)
collationStatement = [NSString stringWithFormat:@"DEFAULT COLLATE %@",[collationName backtickQuotedString]];
// If there is a type selected other than the default we must specify it in CREATE TABLE statement
if ([tableTypeButton indexOfSelectedItem] > 0) {
engineStatement = [NSString stringWithFormat:@"%@ = %@", [[tableDocumentInstance serverSupport] engineTypeQueryName], [[tableDocumentInstance serverSupport] supportsQuotingEngineTypeInCreateSyntax] ? [tableType backtickQuotedString] : tableType];
}
NSString *createStatement = [NSString stringWithFormat:@"CREATE TABLE %@ (id INT(11) UNSIGNED NOT NULL%@) %@ %@ %@", [tableName backtickQuotedString], [tableType isEqualToString:@"CSV"] ? @"" : @" PRIMARY KEY AUTO_INCREMENT", charSetStatement, collationStatement, engineStatement];
// Create the table
[mySQLConnection queryString:createStatement];
if (![mySQLConnection queryErrored]) {
// Table creation was successful - insert the new item into the tables list and select it.
NSInteger addItemAtIndex = NSNotFound;
for (NSUInteger i = 0; i < [tables count]; i++)
{
NSInteger eachTableType = [[tableTypes objectAtIndex:i] integerValue];
if (eachTableType == SPTableTypeNone) continue;
if (eachTableType == SPTableTypeProc || eachTableType == SPTableTypeFunc) {
addItemAtIndex = (i - 1);
break;
}
if ([tableName localizedCompare:[tables objectAtIndex:i]] == NSOrderedAscending) {
addItemAtIndex = i;
break;
}
}
if (addItemAtIndex == NSNotFound) {
[tables addObject:tableName];
[tableTypes addObject:[NSNumber numberWithInteger:SPTableTypeTable]];
}
else {
[tables insertObject:tableName atIndex:addItemAtIndex];
[tableTypes insertObject:[NSNumber numberWithInteger:SPTableTypeTable] atIndex:addItemAtIndex];
}
// Set the selected table name and type, and then update the filter list and the
// selection.
if (selectedTableName) [selectedTableName release];
selectedTableName = [[NSString alloc] initWithString:tableName];
selectedTableType = SPTableTypeTable;
[[self onMainThread] updateFilter:self];
[[tablesListView onMainThread] scrollRowToVisible:[tablesListView selectedRow]];
// Select the newly created table and switch to the table structure view for easier setup
[tableDocumentInstance loadTable:selectedTableName ofType:selectedTableType];
#ifndef SP_CODA
[tableDocumentInstance viewStructure:self];
#endif
#ifdef SP_CODA
[sidebarViewController setTableNames:[self allTableNames] selectedTableName:selectedTableName];
#endif
// Query the structure of all databases in the background (mainly for completion)
[NSThread detachNewThreadWithName:@"SPNavigatorController database structure querier"
target:[tableDocumentInstance databaseStructureRetrieval]
selector:@selector(queryDbStructureWithUserInfo:)
object:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], @"forceUpdate", nil]];
}
else {
// Error while creating new table
alertSheetOpened = YES;
SPBeginAlertSheet(NSLocalizedString(@"Error adding new table", @"error adding new table message"),
NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self,
@selector(sheetDidEnd:returnCode:contextInfo:), SPAddRow,
[NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to add the new table '%@'.\n\nMySQL said: %@", @"error adding new table informative message"), tableName, [mySQLConnection lastErrorMessage]]);
if (changeEncoding) [mySQLConnection restoreStoredEncoding];
[[tablesListView onMainThread] reloadData];
}
// Clear table name
[[tableNameField onMainThread] setStringValue:@""];
[tableDocumentInstance endTask];
[tableAdditionPool release];
}
#ifndef SP_CODA
/**
* Copies the currently selected object (table, view, procedure, function, etc.).
*/
- (void)_copyTable
{
NSString *tableType = @"";
if ([[copyTableNameField stringValue] isEqualToString:@""]) {
SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, NSLocalizedString(@"Table must have a name.", @"message of panel when no name is given for table"));
return;
}
BOOL copyTableContent = ([copyTableContentSwitch state] == NSOnState);
SPTableType tblType = (SPTableType)[[filteredTableTypes objectAtIndex:[tablesListView selectedRow]] integerValue];
// Set up the table type and whether content can be duplicated. The table type is used
// in queries and should not be localized.
switch (tblType){
case SPTableTypeTable:
tableType = @"table";
[copyTableContentSwitch setEnabled:YES];
break;
case SPTableTypeView:
tableType = @"view";
[copyTableContentSwitch setEnabled:NO];
break;
case SPTableTypeProc:
tableType = @"procedure";
[copyTableContentSwitch setEnabled:NO];
break;
case SPTableTypeFunc:
tableType = @"function";
[copyTableContentSwitch setEnabled:NO];
break;
default:
break;
}
// Get table/view structure
SPMySQLResult *queryResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW CREATE %@ %@",
[tableType uppercaseString],
[[filteredTables objectAtIndex:[tablesListView selectedRow]] backtickQuotedString]
]];
[queryResult setReturnDataAsStrings:YES];
if ( ![queryResult numberOfRows] ) {
//error while getting table structure
SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil,
[NSString stringWithFormat:NSLocalizedString(@"Couldn't get create syntax.\nMySQL said: %@", @"message of panel when table information cannot be retrieved"), [mySQLConnection lastErrorMessage]]);
} else {
//insert new table name in create syntax and create new table
NSScanner *scanner;
NSString *scanString;
if(tblType == SPTableTypeView){
scanner = [[NSScanner alloc] initWithString:[[queryResult getRowAsDictionary] objectForKey:@"Create View"]];
[scanner scanUpToString:@"AS" intoString:nil];
[scanner scanUpToString:@"" intoString:&scanString];
[scanner release];
[mySQLConnection queryString:[NSString stringWithFormat:@"CREATE VIEW %@ %@", [[copyTableNameField stringValue] backtickQuotedString], scanString]];
}
else if(tblType == SPTableTypeTable){
scanner = [[NSScanner alloc] initWithString:[[queryResult getRowAsDictionary] objectForKey:@"Create Table"]];
[scanner scanUpToString:@"(" intoString:nil];
[scanner scanUpToString:@"" intoString:&scanString];
[scanner release];
// If there are any InnoDB referencial constraints we need to strip out the names as they must be unique.
// MySQL will generate the new names based on the new table name.
scanString = [scanString stringByReplacingOccurrencesOfRegex:[NSString stringWithFormat:@"CONSTRAINT `[^`]+` "] withString:@""];
// If we're not copying the tables content as well then we need to strip out any AUTO_INCREMENT presets.
if (!copyTableContent) {
scanString = [scanString stringByReplacingOccurrencesOfRegex:[NSString stringWithFormat:@"AUTO_INCREMENT=[0-9]+ "] withString:@""];
}
[mySQLConnection queryString:[NSString stringWithFormat:@"CREATE TABLE %@ %@", [[copyTableNameField stringValue] backtickQuotedString], scanString]];
}
else if(tblType == SPTableTypeFunc || tblType == SPTableTypeProc)
{
// get the create syntax
SPMySQLResult *theResult;
if(selectedTableType == SPTableTypeProc)
theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW CREATE PROCEDURE %@", [selectedTableName backtickQuotedString]]];
else if([self tableType] == SPTableTypeFunc)
theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW CREATE FUNCTION %@", [selectedTableName backtickQuotedString]]];
else
return;
// Check for errors, only displaying if the connection hasn't been terminated
if ([mySQLConnection queryErrored]) {
if ([mySQLConnection isConnected]) {
SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil,
[NSString stringWithFormat:NSLocalizedString(@"An error occured while retrieving the create syntax for '%@'.\nMySQL said: %@", @"message of panel when create syntax cannot be retrieved"), selectedTableName, [mySQLConnection lastErrorMessage]]);
}
return;
}
[theResult setReturnDataAsStrings:YES];
NSString *tableSyntax = [[theResult getRowAsArray] objectAtIndex:2];
// replace the old name by the new one and drop the old one
[mySQLConnection queryString:[tableSyntax stringByReplacingOccurrencesOfRegex:[NSString stringWithFormat:@"(?<=%@ )(`[^`]+?`)", [tableType uppercaseString]] withString:[[copyTableNameField stringValue] backtickQuotedString]]];
if ([mySQLConnection queryErrored]) {
SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil,
[NSString stringWithFormat:NSLocalizedString(@"Couldn't duplicate '%@'.\nMySQL said: %@", @"message of panel when an item cannot be renamed"), [copyTableNameField stringValue], [mySQLConnection lastErrorMessage]]);
}
}
if ([mySQLConnection queryErrored]) {
//error while creating new table
SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil,
[NSString stringWithFormat:NSLocalizedString(@"Couldn't create '%@'.\nMySQL said: %@", @"message of panel when table cannot be created"), [copyTableNameField stringValue], [mySQLConnection lastErrorMessage]]);
} else {
if (copyTableContent) {
//copy table content
[mySQLConnection queryString:[NSString stringWithFormat:
@"INSERT INTO %@ SELECT * FROM %@",
[[copyTableNameField stringValue] backtickQuotedString],
[selectedTableName backtickQuotedString]
]];
if ([mySQLConnection queryErrored]) {
SPBeginAlertSheet(
NSLocalizedString(@"Warning", @"warning"),
NSLocalizedString(@"OK", @"OK button"),
nil,
nil,
[tableDocumentInstance parentWindow],
self,
nil,
nil,
NSLocalizedString(@"There have been errors while copying table content. Please control the new table.", @"message of panel when table content cannot be copied")
);
}
}
// Insert the new item into the tables list and select it.
NSInteger addItemAtIndex = NSNotFound;
for (NSUInteger i = 0; i < [tables count]; i++) {
NSInteger theTableType = [[tableTypes objectAtIndex:i] integerValue];
if (theTableType == SPTableTypeNone) continue;
if ((theTableType == SPTableTypeView || theTableType == SPTableTypeTable)
&& (tblType == SPTableTypeProc || tblType == SPTableTypeFunc)) {
continue;
}
if ((theTableType == SPTableTypeProc || theTableType == SPTableTypeFunc)
&& (tblType == SPTableTypeView || tblType == SPTableTypeTable)) {
addItemAtIndex = i - 1;
break;
}
if ([[copyTableNameField stringValue] localizedCompare:[tables objectAtIndex:i]] == NSOrderedAscending) {
addItemAtIndex = i;
break;
}
}
if (addItemAtIndex == NSNotFound) {
[tables addObject:[copyTableNameField stringValue]];
[tableTypes addObject:[NSNumber numberWithInteger:tblType]];
} else {
[tables insertObject:[copyTableNameField stringValue] atIndex:addItemAtIndex];
[tableTypes insertObject:[NSNumber numberWithInteger:tblType] atIndex:addItemAtIndex];
}
// Set the selected table name and type, and use updateFilter to update the filter list and selection
if (selectedTableName) [selectedTableName release];
selectedTableName = [[NSString alloc] initWithString:[copyTableNameField stringValue]];
selectedTableType = tblType;
[self updateFilter:self];
[tablesListView scrollRowToVisible:[tablesListView selectedRow]];
[tableDocumentInstance loadTable:selectedTableName ofType:selectedTableType];
// Query the structure of all databases in the background (mainly for completion)
[NSThread detachNewThreadWithName:@"SPNavigatorController database structure querier" target:[tableDocumentInstance databaseStructureRetrieval] selector:@selector(queryDbStructureWithUserInfo:) object:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], @"forceUpdate", nil]];
}
}
}
#endif
/**
* Renames a table, view, procedure or function. Also handles only changes in case!
* This function ONLY changes the database. It does NOT refresh the views etc.
* CAREFUL: This function raises an exception if renaming fails, and does not show an error message.
*/
- (void)_renameTableOfType:(SPTableType)tableType from:(NSString *)oldTableName to:(NSString *)newTableName
{
// check if the name really changed
if ([oldTableName isEqualToString:newTableName]) return;
// check if only the case changed - then we have to do two renames, see http://code.google.com/p/sequel-pro/issues/detail?id=484
if ([[oldTableName lowercaseString] isEqualToString:[newTableName lowercaseString]])
{
// first try finding an unused temporary name
// this code should be improved in case we find out that something uses table names like mytable-1, mytable-2, etc.
NSString* tempTableName;
int tempNumber;
for (tempNumber=2; tempNumber<100; tempNumber++)
{
tempTableName = [NSString stringWithFormat:@"%@-%d",selectedTableName,tempNumber];
if ([self isTableNameValid:tempTableName forType:tableType]) break;
}
if (tempNumber==100) {
// we couldn't find a temporary name
[NSException raise:@"No Tempname found" format:NSLocalizedString(@"An error occured while renaming '%@'. No temporary name could be found. Please try renaming to something else first.", @"rename table error - no temporary name found"), oldTableName];
}
[self _renameTableOfType:tableType from:oldTableName to:tempTableName];
[self _renameTableOfType:tableType from:tempTableName to:newTableName];
return;
}
//check if we are trying to rename a TABLE or a VIEW
if (tableType == SPTableTypeView || tableType == SPTableTypeTable) {
// we can use the rename table statement
[mySQLConnection queryString:[NSString stringWithFormat:@"RENAME TABLE %@ TO %@", [oldTableName backtickQuotedString], [newTableName backtickQuotedString]]];
// check for errors
if ([mySQLConnection queryErrored]) {
[NSException raise:@"MySQL Error" format:NSLocalizedString(@"An error occured while renaming '%@'.\n\nMySQL said: %@", @"rename table error informative message"), oldTableName, [mySQLConnection lastErrorMessage]];
}
return;
}
//check if we are trying to rename a PROCEDURE or a FUNCTION
if (tableType == SPTableTypeProc || tableType == SPTableTypeFunc) {
// procedures and functions can only be renamed if one creates a new one and deletes the old one
// first get the create syntax
NSString *stringTableType = @"";
switch (tableType){
case SPTableTypeProc: stringTableType = @"PROCEDURE"; break;
case SPTableTypeFunc: stringTableType = @"FUNCTION"; break;
default: break;
}
SPMySQLResult *theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW CREATE %@ %@", stringTableType, [oldTableName backtickQuotedString] ] ];
if ([mySQLConnection queryErrored]) {
[NSException raise:@"MySQL Error" format:NSLocalizedString(@"An error occured while renaming. I couldn't retrieve the syntax for '%@'.\n\nMySQL said: %@", @"rename precedure/function error - can't retrieve syntax"), oldTableName, [mySQLConnection lastErrorMessage]];
}
[theResult setReturnDataAsStrings:YES];
NSString *oldCreateSyntax = [[theResult getRowAsArray] objectAtIndex:2];
// replace the old name with the new name
NSRange rangeOfProcedureName = [oldCreateSyntax rangeOfString: [NSString stringWithFormat:@"%@ %@", stringTableType, [oldTableName backtickQuotedString] ] ];
if (rangeOfProcedureName.length == 0) {
[NSException raise:@"Unknown Syntax" format:NSLocalizedString(@"An error occured while renaming. The CREATE syntax of '%@' could not be parsed.", @"rename error - invalid create syntax"), oldTableName];
}
NSString *newCreateSyntax = [oldCreateSyntax stringByReplacingCharactersInRange: rangeOfProcedureName
withString: [NSString stringWithFormat:@"%@ %@", stringTableType, [newTableName backtickQuotedString] ] ];
[mySQLConnection queryString: newCreateSyntax];
if ([mySQLConnection queryErrored]) {
[NSException raise:@"MySQL Error" format:NSLocalizedString(@"An error occured while renaming. I couldn't recreate '%@'.\n\nMySQL said: %@", @"rename precedure/function error - can't recreate procedure"), oldTableName, [mySQLConnection lastErrorMessage]];
}
[mySQLConnection queryString: [NSString stringWithFormat: @"DROP %@ %@", stringTableType, [oldTableName backtickQuotedString]]];
if ([mySQLConnection queryErrored]) {
[NSException raise:@"MySQL Error" format:NSLocalizedString(@"An error occured while renaming. I couldn't delete '%@'.\n\nMySQL said: %@", @"rename precedure/function error - can't delete old procedure"), oldTableName, [mySQLConnection lastErrorMessage]];
}
return;
}
[NSException raise:@"Object of unknown type" format:NSLocalizedString(@"An error occured while renaming. '%@' is of an unknown type.", @"rename error - don't know what type the renamed thing is"), oldTableName];
}
#pragma mark -
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[tables release];
[tableTypes release];
#ifndef SP_CODA
if (isTableListFiltered && filteredTables) [filteredTables release];
if (isTableListFiltered && filteredTableTypes) [filteredTableTypes release];
#endif
if (selectedTableName) [selectedTableName release];
if (addTableCharsetHelper) [addTableCharsetHelper release];
[super dealloc];
}
@end