// // $Id$ // // SPTablesList.m // sequel-pro // // Created by lorenz textor (lorenz@textor.ch) on Wed May 01 2002. // Copyright (c) 2002-2003 Lorenz Textor. All rights reserved. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // More info at #import "SPTablesList.h" #ifndef SP_REFACTOR /* headers */ #import "SPConnectionController.h" #endif #import "SPDatabaseDocument.h" #import "SPTableStructure.h" #import "SPDatabaseViewController.h" #ifndef SP_REFACTOR /* headers */ #import "SPTableContent.h" #endif #import "SPTableData.h" #ifndef SP_REFACTOR /* headers */ #import "SPTableInfo.h" #import "SPDataImport.h" #import "SPTableView.h" #import "ImageAndTextCell.h" #import "RegexKitLite.h" #import "SPDatabaseData.h" #import "SPAlertSheets.h" #import "SPNavigatorController.h" #import "SPHistoryController.h" #import "SPServerSupport.h" #import "SPWindowController.h" #import "SPAppController.h" @interface SPTablesList (PrivateAPI) - (void)removeTable; - (void)truncateTable; - (void)addTable; - (void)copyTable; - (void)renameTableOfType:(SPTableType)tableType from:(NSString *)oldTableName to:(NSString *)newTableName; @end #endif @implementation SPTablesList #pragma mark - #pragma mark IBAction methods /** * Loads all table names in array tables and reload the tableView */ - (IBAction)updateTables:(id)sender { MCPResult *theResult; NSArray *resultRow; NSUInteger i; NSString *previousSelectedTable = nil; NSString *previousFilterString = nil; #ifndef SP_REFACTOR /* table list filtering */ BOOL previousTableListIsSelectable = tableListIsSelectable; #endif BOOL changeEncoding = ![[mySQLConnection encoding] isEqualToString:@"utf8"]; if (selectedTableName) previousSelectedTable = [[NSString alloc] initWithString:selectedTableName]; #ifndef SP_REFACTOR /* 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; tableListIsSelectable = YES; [[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"]; if ([theResult numOfRows]) [theResult dataSeek:0]; if ([theResult numOfFields] == 1) { for ( i = 0 ; i < [theResult numOfRows] ; i++ ) { [tables addObject:[[theResult fetchRowAsArray] objectAtIndex:0]]; [tableTypes addObject:[NSNumber numberWithInteger:SPTableTypeTable]]; } } else { for ( i = 0 ; i < [theResult numOfRows] ; i++ ) { resultRow = [theResult fetchRowAsArray]; // Due to encoding problems it can be the case that [resultRow objectAtIndex:0] // return NSNull, thus catch that case for safety reasons NSString *row = [resultRow objectAtIndex:0]; NSString *tableName; if([row isKindOfClass:[NSString class]]) tableName = [NSString stringWithUTF8String:[row cStringUsingEncoding:[mySQLConnection stringEncoding]]]; else tableName = @"..."; [tables addObject:tableName]; if ([[resultRow 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_REFACTOR /* 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]]; theResult = [mySQLConnection queryString:pQuery]; // Check for mysql errors - if information_schema is not accessible for some reasons // omit adding procedures and functions if(![mySQLConnection queryErrored] && theResult != nil && [theResult numOfRows] ) { // add the header row [tables addObject:NSLocalizedString(@"PROCS & FUNCS",@"header for procs & funcs list")]; [tableTypes addObject:[NSNumber numberWithInteger:SPTableTypeNone]]; [theResult dataSeek:0]; if( [theResult numOfFields] == 1 ) { for( i = 0; i < [theResult numOfRows]; i++ ) { [tables addObject:NSArrayObjectAtIndex([theResult fetchRowAsArray],3)]; if( [NSArrayObjectAtIndex([theResult fetchRowAsArray], 4) isEqualToString:@"PROCEDURE"]) { [tableTypes addObject:[NSNumber numberWithInteger:SPTableTypeProc]]; } else { [tableTypes addObject:[NSNumber numberWithInteger:SPTableTypeFunc]]; } } } else { for( i = 0; i < [theResult numOfRows]; i++ ) { resultRow = [theResult fetchRowAsArray]; [tables addObject:NSArrayObjectAtIndex(resultRow, 3)]; if( [NSArrayObjectAtIndex(resultRow, 4) isEqualToString:@"PROCEDURE"] ) { [tableTypes addObject:[NSNumber numberWithInteger:SPTableTypeProc]]; } else { [tableTypes addObject:[NSNumber numberWithInteger:SPTableTypeFunc]]; } } } } } #endif /* BOOL addedPFHeader = FALSE; NSString *pQuery = [NSString stringWithFormat:@"SHOW PROCEDURE STATUS WHERE db = '%@'",[tableDocumentInstance database]]; theResult = [mySQLConnection queryString:pQuery]; if( [theResult numOfRows] ) { // add the header row [tables addObject:NSLocalizedString(@"PROCS & FUNCS",@"header for procs & funcs list")]; [tableTypes addObject:[NSNumber numberWithInt:SPTableTypeNone]]; addedPFHeader = TRUE; [theResult dataSeek:0]; if( [theResult numOfFields] == 1 ) { for( i = 0; i < [theResult numOfRows]; i++ ) { [tables addObject:[[theResult fetchRowAsArray] objectAtIndex:1]]; [tableTypes addObject:[NSNumber numberWithInt:SPTableTypeProc]]; } } else { for( i = 0; i < [theResult numOfRows]; i++ ) { resultRow = [theResult fetchRowAsArray]; [tables addObject:[resultRow objectAtIndex:1]]; [tableTypes addObject:[NSNumber numberWithInt:SPTableTypeProc]]; } } } pQuery = [NSString stringWithFormat:@"SHOW FUNCTION STATUS WHERE db = '%@'",[tableDocumentInstance database]]; theResult = [mySQLConnection queryString:pQuery]; if( [theResult numOfRows] ) { if( !addedPFHeader ) { // add the header row [tables addObject:NSLocalizedString(@"PROCS & FUNCS",@"header for procs & funcs list")]; [tableTypes addObject:[NSNumber numberWithInt:SPTableTypeNone]]; } [theResult dataSeek:0]; if( [theResult numOfFields] == 1 ) { for( i = 0; i < [theResult numOfRows]; i++ ) { [tables addObject:[[theResult fetchRowAsArray] objectAtIndex:1]]; [tableTypes addObject:[NSNumber numberWithInt:SPTableTypeFunc]]; } } else { for( i = 0; i < [theResult numOfRows]; i++ ) { resultRow = [theResult fetchRowAsArray]; [tables addObject:[resultRow objectAtIndex:1]]; [tableTypes addObject:[NSNumber numberWithInt:SPTableTypeFunc]]; } } } */ // Restore encoding if appropriate if (changeEncoding) [mySQLConnection restoreStoredEncoding]; // Notify listeners that the query has finished [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; } // 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]; #ifndef SP_REFACTOR /* ui manipulation */ [[tablesListView onMainThread] reloadData]; #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]; #ifndef SP_REFACTOR /* ui manipulation */ tableListIsSelectable = YES; [[tablesListView onMainThread] selectRowIndexes:[NSIndexSet indexSetWithIndex:itemToReselect] byExtendingSelection:NO]; tableListIsSelectable = previousTableListIsSelectable; #endif 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_REFACTOR /* 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 detachNewThreadSelector:@selector(queryDbStructureWithUserInfo:) toTarget:mySQLConnection withObject:nil]; else // User press refresh button ergo force update [NSThread detachNewThreadSelector:@selector(queryDbStructureWithUserInfo:) toTarget:mySQLConnection withObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], @"forceUpdate", [NSNumber numberWithBool:YES], @"cancelQuerying", nil]]; } #ifndef SP_REFACTOR /* whole table operations */ /** * 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:@"Default"]; [[tableTypeButton menu] addItem:[NSMenuItem separatorItem]]; for (NSDictionary *engine in engines) { [tableTypeButton addItemWithTitle:[engine objectForKey:@"Engine"]]; } // Populate the table encoding popup button with a default menu item [tableEncodingButton removeAllItems]; [tableEncodingButton addItemWithTitle:@"Default"]; // Retrieve the server-supported encodings and add them to the menu NSArray *encodings = [databaseDataInstance getDatabaseCharacterSetEncodings]; NSString *utf8MenuItemTitle = nil; [tableEncodingButton setEnabled:YES]; if (([encodings count] > 0) && [[tableDocumentInstance serverSupport] supportsPost41CharacterSetHandling]) { [[tableEncodingButton menu] addItem:[NSMenuItem separatorItem]]; for (NSDictionary *encoding in encodings) { NSString *menuItemTitle = (![encoding objectForKey:@"DESCRIPTION"]) ? [encoding objectForKey:@"CHARACTER_SET_NAME"] : [NSString stringWithFormat:@"%@ (%@)", [encoding objectForKey:@"DESCRIPTION"], [encoding objectForKey:@"CHARACTER_SET_NAME"]]; [tableEncodingButton addItemWithTitle:menuItemTitle]; // If the UTF8 entry has been encountered, store the menu title if ([[encoding objectForKey:@"CHARACTER_SET_NAME"] isEqualToString:@"utf8"]) { utf8MenuItemTitle = [NSString stringWithString:menuItemTitle]; } } // If a UTF8 entry was found, promote it to the top of the list if (utf8MenuItemTitle) { [[tableEncodingButton menu] insertItem:[NSMenuItem separatorItem] atIndex:2]; [tableEncodingButton insertItemWithTitle:utf8MenuItemTitle atIndex:2]; } } else { [tableEncodingButton setEnabled:NO]; } // Set the focus to the name field [tableSheet makeFirstResponder:tableNameField]; [NSApp beginSheet:tableSheet modalForWindow:[tableDocumentInstance parentWindow] modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:@"addTable"]; } /** * 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]; // 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"]; 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:@"removeRow"]; } /** * 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:@"copyTable"]; } /** * 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]; /* [tableRenameField setStringValue:[self tableName]]; [renameTableButton setEnabled:NO]; NSString *tableType; switch([self tableType]){ case SPTableTypeTable: tableType = NSLocalizedString(@"table",@"table"); break; case SPTableTypeView: tableType = NSLocalizedString(@"view",@"view"); break; case SPTableTypeProc: tableType = NSLocalizedString(@"procedure",@"procedure"); break; case SPTableTypeFunc: tableType = NSLocalizedString(@"function",@"function"); break; } [tableRenameText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Rename %@ '%@' to:",@"rename item name to:"), tableType, [self tableName]]]; [NSApp beginSheet:tableRenameSheet modalForWindow:[tableDocumentInstance parentWindow] modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:@"renameTable"]; */ } /** * 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:@"truncateTable"]; } /** * 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:([tableInfoCollapseButton state] == NSOffState)] forKey:SPTableInformationPanelCollapsed]; [tableInfoCollapseButton setToolTip:([tableInfoCollapseButton state] == NSOffState) ? NSLocalizedString(@"Show Table Information", @"Show Table Information") : NSLocalizedString(@"Hide Table Information", @"Hide Table Information")]; } #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:@"addRow"]) { alertSheetOpened = NO; } else if ([contextInfo isEqualToString:@"removeRow"]) { if (returnCode == NSAlertDefaultReturn) { [self performSelector:@selector(removeTable) withObject:nil afterDelay:0.0]; } } else if ([contextInfo isEqualToString:@"truncateTable"]) { if (returnCode == NSAlertDefaultReturn) { [self truncateTable]; } } else if ([contextInfo isEqualToString:@"addTable"]) { if (returnCode == NSOKButton) { [self addTable]; } } else if ([contextInfo isEqualToString:@"copyTable"]) { if (returnCode == NSOKButton) { [self copyTable]; } } } #pragma mark - #pragma mark Additional methods #endif /** * Sets the connection (received from SPDatabaseDocument) and makes things that have to be done only once */ - (void)setConnection:(MCPConnection *)theConnection { mySQLConnection = theConnection; [self updateTables:self]; } #ifndef SP_REFACTOR /* ui validation */ /** * 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]]; } else if (object == copyTableNameField) { [copyTableButton setEnabled:[self isTableNameValid:[copyTableNameField stringValue] forType:[self tableType]]]; } } /** * 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]; } 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_REFACTOR /* ui manipulation */ NSIndexSet *indexes = [tablesListView selectedRowIndexes]; #endif // Update the selected table name and type if (selectedTableName) [selectedTableName release]; #ifndef SP_REFACTOR /* ui manipulation */ if ([indexes count]) { selectedTableName = [[NSString alloc] initWithString:@""]; } else { #endif selectedTableName = nil; #ifndef SP_REFACTOR /* ui manipulation */ } #endif #ifndef SP_REFACTOR /* 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]; [duplicateTableContextMenuItem setHidden:YES]; [separatorTableContextMenuItem setHidden:YES]; [separatorTableContextMenuItem2 setHidden:NO]; [showCreateSyntaxContextMenuItem setTitle:NSLocalizedString(@"Show Create Syntaxes...", @"show create syntaxes menu item")]; [showCreateSyntaxContextMenuItem setHidden:NO]; // 'Gear' menu [renameTableMenuItem setHidden:YES]; [openTableInNewTabMenuItem 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]; // Get main menu "Table"'s submenu NSMenu *tableSubMenu = [[[NSApp mainMenu] itemWithTag:SPMainMenuTable] submenu]; [[tableSubMenu itemAtIndex:3] setTitle:NSLocalizedString(@"Copy Create Syntaxes", @"copy create syntaxes menu item")]; [[tableSubMenu itemAtIndex:4] setTitle:NSLocalizedString(@"Show Create Syntaxes...", @"show create syntaxes menu item")]; [[tableSubMenu itemAtIndex:6] setTitle:NSLocalizedString(@"Check Selected Items", @"check selected items menu item")]; [[tableSubMenu itemAtIndex:7] setTitle:NSLocalizedString(@"Repair Selected Items", @"repair selected items menu item")]; [[tableSubMenu itemAtIndex:9] setTitle:NSLocalizedString(@"Analyze Selected Items", @"analyze selected items menu item")]; [[tableSubMenu itemAtIndex:10] setTitle:NSLocalizedString(@"Optimize Selected Items", @"optimize selected items menu item")]; [[tableSubMenu itemAtIndex:11] setTitle:NSLocalizedString(@"Flush Selected Items", @"flush selected items menu item")]; [[tableSubMenu itemAtIndex:12] setTitle:NSLocalizedString(@"Checksum Selected Items", @"checksum selected items menu item")]; [[tableSubMenu itemAtIndex:3] setHidden:NO]; [[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]; return; #endif } // 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_REFACTOR /* 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:3] setTitle:NSLocalizedString(@"Copy Create View Syntax", @"copy create view syntax menu item")]; [[tableSubMenu itemAtIndex:4] setTitle:NSLocalizedString(@"Show Create View Syntax...", @"show create view syntax menu item")]; [[tableSubMenu itemAtIndex:5] setHidden:NO]; // Divider [[tableSubMenu itemAtIndex:6] setHidden:NO]; [[tableSubMenu itemAtIndex:6] setTitle:NSLocalizedString(@"Check View", @"check view menu item")]; [[tableSubMenu itemAtIndex:7] setHidden:YES]; // Repair [[tableSubMenu itemAtIndex:8] setHidden:YES]; // Divider [[tableSubMenu itemAtIndex:9] setHidden:YES]; // Analyse [[tableSubMenu itemAtIndex:10] setHidden:YES]; // Optimize [[tableSubMenu itemAtIndex:11] setHidden:NO]; [[tableSubMenu itemAtIndex:11] setTitle:NSLocalizedString(@"Flush View", @"flush view menu item")]; [[tableSubMenu itemAtIndex:12] 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]; [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")]; [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]; [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")]; } else if (selectedTableType == SPTableTypeTable) { [[tableSubMenu itemAtIndex:3] setTitle:NSLocalizedString(@"Copy Create Table Syntax", @"copy create table syntax menu item")]; [[tableSubMenu itemAtIndex:4] setTitle:NSLocalizedString(@"Show Create Table Syntax...", @"show create table syntax menu item")]; [[tableSubMenu itemAtIndex:5] setHidden:NO]; // divider [[tableSubMenu itemAtIndex:6] setHidden:NO]; [[tableSubMenu itemAtIndex:6] setTitle:NSLocalizedString(@"Check Table", @"check table menu item")]; [[tableSubMenu itemAtIndex:7] setHidden:NO]; [[tableSubMenu itemAtIndex:7] setTitle:NSLocalizedString(@"Repair Table", @"repair table menu item")]; [[tableSubMenu itemAtIndex:8] setHidden:NO]; // divider [[tableSubMenu itemAtIndex:9] setHidden:NO]; [[tableSubMenu itemAtIndex:9] setTitle:NSLocalizedString(@"Analyze Table", @"analyze table menu item")]; [[tableSubMenu itemAtIndex:10] setHidden:NO]; [[tableSubMenu itemAtIndex:10] setTitle:NSLocalizedString(@"Optimize Table", @"optimize table menu item")]; [[tableSubMenu itemAtIndex:11] setHidden:NO]; [[tableSubMenu itemAtIndex:11] setTitle:NSLocalizedString(@"Flush Table", @"flush table menu item")]; [[tableSubMenu itemAtIndex:12] setHidden:NO]; [[tableSubMenu itemAtIndex:12] 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")]; [showCreateSyntaxMenuItem setHidden:NO]; [showCreateSyntaxMenuItem setTitle:NSLocalizedString(@"Show Create Table Syntax...", @"show create table syntax menu item")]; [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]; [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")]; } else if (selectedTableType == SPTableTypeProc) { [[tableSubMenu itemAtIndex:3] setTitle:NSLocalizedString(@"Copy Create Procedure Syntax", @"copy create proc syntax menu item")]; [[tableSubMenu itemAtIndex:4] setTitle:NSLocalizedString(@"Show Create Procedure Syntax...", @"show create proc syntax menu item")]; [[tableSubMenu itemAtIndex:5] setHidden:YES]; // divider [[tableSubMenu itemAtIndex:6] setHidden:YES]; // copy columns [[tableSubMenu itemAtIndex:7] setHidden:YES]; // divider [[tableSubMenu itemAtIndex:8] setHidden:YES]; [[tableSubMenu itemAtIndex:9] setHidden:YES]; [[tableSubMenu itemAtIndex:10] setHidden:YES]; // divider [[tableSubMenu itemAtIndex:11] setHidden:YES]; [[tableSubMenu itemAtIndex:12] 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")]; [showCreateSyntaxMenuItem setHidden:NO]; [showCreateSyntaxMenuItem setTitle:NSLocalizedString(@"Show Create Procedure Syntax...", @"show create proc syntax menu item")]; [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]; [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")]; } else if (selectedTableType == SPTableTypeFunc) { [[tableSubMenu itemAtIndex:3] setTitle:NSLocalizedString(@"Copy Create Function Syntax", @"copy create func syntax menu item")]; [[tableSubMenu itemAtIndex:4] setTitle:NSLocalizedString(@"Show Create Function Syntax...", @"show create func syntax menu item")]; [[tableSubMenu itemAtIndex:5] setHidden:YES]; // divider [[tableSubMenu itemAtIndex:6] setHidden:YES]; // copy columns [[tableSubMenu itemAtIndex:7] setHidden:YES]; // divider [[tableSubMenu itemAtIndex:8] setHidden:YES]; [[tableSubMenu itemAtIndex:9] setHidden:YES]; [[tableSubMenu itemAtIndex:10] setHidden:YES]; // divider [[tableSubMenu itemAtIndex:11] setHidden:YES]; [[tableSubMenu itemAtIndex:12] 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]; [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")]; [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]; [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")]; } #endif } #ifndef SP_REFACTOR /* getters */ #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; } #endif /** * 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]; } #ifndef SP_REFACTOR /** * 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]); } // Set window title to reflect the new table name [tableDocumentInstance updateWindowTitle:self]; // Query the structure of all databases in the background (mainly for completion) [NSThread detachNewThreadSelector:@selector(queryDbStructureWithUserInfo:) toTarget:mySQLConnection withObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], @"forceUpdate", nil]]; } #endif #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; } } /** * 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]; } /** * 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]; 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:@"SPDragTableDataFromNavigatorPboardType"] ) { NSString *query = [pboard stringForType:@"SPDragTableDataFromNavigatorPboardType"]; 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 getLastErrorMessage]]]; [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 { // popup button below table list if ([menuItem action] == @selector(copyTable:)) { return (([tablesListView numberOfSelectedRows] == 1) && [[self tableName] length] && [tablesListView numberOfSelectedRows] > 0); } if ([menuItem action] == @selector(removeTable:) || [menuItem action] == @selector(truncateTable:)) { return ([tablesListView numberOfSelectedRows] > 0); } if ([menuItem action] == @selector(renameTable:) || [menuItem action] == @selector(openTableInNewTab:)) { return (([tablesListView numberOfSelectedRows] == 1) && [[self tableName] length]); } return [super validateMenuItem:menuItem]; } #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 collapsibleSubviewIsCollapsed]) [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 collapsibleSubviewIsCollapsed]) [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 */ - (void) makeTableListFilterHaveFocus { if([tables count] > 20) { [[tableDocumentInstance parentWindow] makeFirstResponder:listFilterField]; } else if([tables count] > 2) { [[tableDocumentInstance parentWindow] makeFirstResponder:tablesListView]; if([tablesListView numberOfSelectedRows] < 1) [tablesListView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO]; } } /** * 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; } 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; for (i = 0; i < [tables count]; i++) { tableType = [[tableTypes objectAtIndex:i] integerValue]; if (tableType == SPTableTypeNone) continue; substringRange = [[tables objectAtIndex:i] rangeOfString:[listFilterField stringValue] options:NSCaseInsensitiveSearch]; if (substringRange.location == NSNotFound) continue; // 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]]; } 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]; filteredTables = tables; [filteredTableTypes release]; filteredTableTypes = tableTypes; } // 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]; } /** * 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]; if (rowIndex == NSNotFound || rowIndex > [filteredTables count] || [[filteredTableTypes objectAtIndex:rowIndex] integerValue] == SPTableTypeNone) return; [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]; [toolbarActionsButton setEnabled:NO]; [toolbarReloadButton setEnabled:NO]; } /** * Enable all table list interactive elements after an ongoing task. */ - (void) endDocumentTaskForTab:(NSNotification *)aNotification { tableListIsSelectable = YES; [toolbarAddButton setEnabled:YES]; [toolbarActionsButton setEnabled:YES]; [toolbarReloadButton setEnabled:YES]; } /** * Set the table list to selectable or not during the task process. */ - (void) setTableListSelectability:(BOOL)isSelectable { tableListIsSelectable = isSelectable; } #pragma mark - #pragma mark SplitView Delegate Methods - (NSRect)splitView:(NSSplitView *)splitView effectiveRect:(NSRect)proposedEffectiveRect forDrawnRect:(NSRect)drawnRect ofDividerAtIndex:(NSInteger)dividerIndex { return (splitView == tableListSplitView ? NSZeroRect : proposedEffectiveRect); } #endif #pragma mark - #pragma mark Other /** * Standard init method. Performs various ivar initialisations. */ - (id)init { if ((self = [super init])) { tables = [[NSMutableArray alloc] init]; #ifndef SP_REFACTOR filteredTables = tables; #endif tableTypes = [[NSMutableArray alloc] init]; #ifndef SP_REFACTOR filteredTableTypes = tableTypes; isTableListFiltered = NO; tableListIsSelectable = YES; #endif tableListContainsViews = NO; selectedTableType = SPTableTypeNone; selectedTableName = nil; [tables addObject:NSLocalizedString(@"TABLES",@"header for table list")]; #ifndef SP_REFACTOR /* font */ smallSystemFont = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]; #endif } return self; } #ifndef SP_REFACTOR /* awakeFromNib */ /** * Standard awakeFromNib method for interface loading. */ - (void)awakeFromNib { // Collapse the table information pane if preference to do so is set if ([[[NSUserDefaults standardUserDefaults] objectForKey:SPTableInformationPanelCollapsed] boolValue] && [tableListSplitView collapsibleSubview]) { [tableInfoCollapseButton setNextState]; [tableInfoCollapseButton setToolTip:NSLocalizedString(@"Show Table Information",@"Show Table Information")]; [tableListSplitView setValue:[NSNumber numberWithFloat:[tableListSplitView collapsibleSubview].frame.size.height] forKey:@"uncollapsedSize"]; [[tableListSplitView collapsibleSubview] setAutoresizesSubviews:NO]; [[tableListSplitView collapsibleSubview] setFrameSize:NSMakeSize([tableListSplitView collapsibleSubview].frame.size.width, 0)]; [tableListSplitView setCollapsibleSubviewCollapsed:YES]; [[tableListSplitView collapsibleSubview] setAutoresizesSubviews:YES]; } else { [tableInfoCollapseButton setToolTip:NSLocalizedString(@"Hide Table Information",@"Hide Table Information")]; } // Start the table filter list collapsed if ([tableListFilterSplitView collapsibleSubview]) { [tableListFilterSplitView setValue:[NSNumber numberWithFloat:[tableListFilterSplitView collapsibleSubview].frame.size.height] forKey:@"uncollapsedSize"]; // Set search bar view to the height of 1 instead of 0 to ensure that the view will be visible // after opening a next connection window which has more than 20 tables [[tableListFilterSplitView collapsibleSubview] setFrameSize:NSMakeSize([tableListFilterSplitView collapsibleSubview].frame.size.width, 1)]; [tableListFilterSplitView setCollapsibleSubviewCollapsed:YES]; } // Disable tab edit behaviour in the tables list [tablesListView setTabEditingDisabled:YES]; // 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]; [tablesListView registerForDraggedTypes:[NSArray arrayWithObjects:@"SPDragTableDataFromNavigatorPboardType", nil]]; } #endif /** * Standard dealloc method. */ - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; [tables release]; [tableTypes release]; #ifndef SP_REFACTOR if (isTableListFiltered && filteredTables) [filteredTables release]; if (isTableListFiltered && filteredTableTypes) [filteredTableTypes release]; #endif if (selectedTableName) [selectedTableName release]; [super dealloc]; } #ifdef SP_REFACTOR /* glue */ - (void)setDatabaseDocument:(SPDatabaseDocument*)val { tableDocumentInstance = val; } #endif #ifndef SP_REFACTOR /* operations performed on whole tables */ @end @implementation SPTablesList (PrivateAPI) /** * 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 '%@'.\nMySQL said: %@", @"message of panel when an item cannot be deleted"), [tables objectAtIndex:currentIndex], [mySQLConnection getLastErrorMessage]]]; [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]; // set window title [tableDocumentInstance updateWindowTitle:self]; // Query the structure of all databases in the background (mainly for completion) [NSThread detachNewThreadSelector:@selector(queryDbStructureWithUserInfo:) toTarget:mySQLConnection withObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], @"forceUpdate", nil]]; } /** * 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 getLastErrorMessage]]]; [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"]; [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]; } /** * Adds a new table table to the database using the selected character set encoding and storage engine. */ - (void)addTable { NSString *charSetStatement = @""; 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 if ([tableEncodingButton indexOfSelectedItem] > 0) { NSString *encodingName = [[tableEncodingButton title] stringByMatching:@"\\((.*)\\)" capture:1L]; if (!encodingName) encodingName = @"utf8"; charSetStatement = [NSString stringWithFormat:@"DEFAULT CHARACTER SET %@", [encodingName 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], [tableType backtickQuotedString]]; } NSString *createStatement = [NSString stringWithFormat:@"CREATE TABLE %@ (%@) %@ %@", [tableName backtickQuotedString], ([tableType isEqualToString:@"CSV"]) ? @"id INT NOT NULL" : @"id INT", charSetStatement, 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 updateFilter:self]; [tablesListView scrollRowToVisible:[tablesListView selectedRow]]; // Select the newly created table and switch to the table structure view for easier setup [tableDocumentInstance loadTable:selectedTableName ofType:selectedTableType]; [tableDocumentInstance viewStructure:self]; // Query the structure of all databases in the background (mainly for completion) [NSThread detachNewThreadSelector:@selector(queryDbStructureWithUserInfo:) toTarget:mySQLConnection withObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], @"forceUpdate", nil]]; } else { // 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:), @"addRow", [NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to add the new table '%@'.\n\nMySQL said: %@", @"error adding new table informative message"), tableName, [mySQLConnection getLastErrorMessage]]); if (changeEncoding) [mySQLConnection restoreStoredEncoding]; [tablesListView reloadData]; } // Clear table name [tableNameField setStringValue:@""]; } /** * 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 MCPResult *queryResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW CREATE %@ %@", [tableType uppercaseString], [[filteredTables objectAtIndex:[tablesListView selectedRow]] backtickQuotedString] ]]; [queryResult setReturnDataAsStrings:YES]; if ( ![queryResult numOfRows] ) { //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 getLastErrorMessage]]); } else { //insert new table name in create syntax and create new table NSScanner *scanner; NSString *scanString; if(tblType == SPTableTypeView){ scanner = [[NSScanner alloc] initWithString:[[queryResult fetchRowAsDictionary] 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 fetchRowAsDictionary] 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 MCPResult *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 getLastErrorMessage]]); } return; } [theResult setReturnDataAsStrings:YES]; NSString *tableSyntax = [[theResult fetchRowAsArray] 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 getLastErrorMessage]]); } } 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 getLastErrorMessage]]); } 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 detachNewThreadSelector:@selector(queryDbStructureWithUserInfo:) toTarget:mySQLConnection withObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], @"forceUpdate", nil]]; } } } /** * 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 getLastErrorMessage]]; } 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; } MCPResult *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 getLastErrorMessage]]; } [theResult setReturnDataAsStrings:YES]; NSString *oldCreateSyntax = [[theResult fetchRowAsArray] 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 getLastErrorMessage]]; } [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 getLastErrorMessage]]; } 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]; } #endif /** * Check tableName for length and if the tableName doesn't match * against current database table/view names (case-insensitive). */ - (BOOL)isTableNameValid:(NSString *)tableName forType:(SPTableType)tableType { return [self isTableNameValid:tableName forType:tableType ignoringSelectedTable:NO]; } /** * Check tableName for length and if the tableName doesn't match * against current database table/view names (case-insensitive). */ - (BOOL)isTableNameValid:(NSString *)tableName forType:(SPTableType)tableType ignoringSelectedTable:(BOOL)ignoreSelectedTable { BOOL isValid = YES; // delete trailing whitespaces since 'foo ' or ' ' are not valid table names NSString *fieldStr = [tableName stringByMatching:@"(.*?)\\s*$" capture:1]; NSString *lowercaseFieldStr = [fieldStr lowercaseString]; // If table name has trailing whitespaces return 'no valid' if([fieldStr length] != [tableName length]) return NO; // empty table names are invalid if([fieldStr length] == 0) return NO; NSArray *similarTables; switch (tableType) { case SPTableTypeView: case SPTableTypeTable: similarTables = [self allTableAndViewNames]; break; case SPTableTypeProc: similarTables = [self allProcedureNames]; break; case SPTableTypeFunc: similarTables = [self allFunctionNames]; break; default: // if some other table type is given, just return yes // better a mysql error than not being able to change something at all return YES; } for(id table in similarTables) { //compare case insensitive here if([lowercaseFieldStr isEqualToString:[table lowercaseString]]) { if (ignoreSelectedTable) { // if table is the selectedTable, ignore it // we must compare CASE SENSITIVE here! if ([table isEqualToString:selectedTableName]) continue; } isValid = NO; break; } } return isValid; } @end