diff options
author | stuconnolly <stuart02@gmail.com> | 2009-06-03 20:46:57 +0000 |
---|---|---|
committer | stuconnolly <stuart02@gmail.com> | 2009-06-03 20:46:57 +0000 |
commit | fc748400d92a0b7874a19eba3fa573cdf1415ee5 (patch) | |
tree | cb304884f89e8b4c2eafb3fb1637346b5a052a87 /Source | |
parent | a9b766e758d8494c7ba56831b238e5c91258a6c5 (diff) | |
download | sequelpro-fc748400d92a0b7874a19eba3fa573cdf1415ee5.tar.gz sequelpro-fc748400d92a0b7874a19eba3fa573cdf1415ee5.tar.bz2 sequelpro-fc748400d92a0b7874a19eba3fa573cdf1415ee5.zip |
Various enhancements to relation functionality, including:
- Only presenting valid tables for possible relations, that is InnoDB tables.
- Only presenting valid columns for possible relations, that is columns of the same data type.
- Loads of interface validation.
Diffstat (limited to 'Source')
-rw-r--r-- | Source/SPGrowlController.m | 10 | ||||
-rw-r--r-- | Source/SPTableRelations.h | 50 | ||||
-rw-r--r-- | Source/SPTableRelations.m | 396 |
3 files changed, 235 insertions, 221 deletions
diff --git a/Source/SPGrowlController.m b/Source/SPGrowlController.m index d65b5201..13e1698a 100644 --- a/Source/SPGrowlController.m +++ b/Source/SPGrowlController.m @@ -29,7 +29,7 @@ static SPGrowlController *sharedGrowlController = nil; @implementation SPGrowlController -/* +/** * Returns the shared Growl controller. */ + (SPGrowlController *)sharedGrowlController @@ -58,14 +58,14 @@ static SPGrowlController *sharedGrowlController = nil; - (id)init { - if (self = [super init]) { + if ((self = [super init])) { [GrowlApplicationBridge setGrowlDelegate:self]; } return self; } -/* +/** * The following base protocol methods are implemented to ensure the singleton status of this class. */ @@ -79,7 +79,7 @@ static SPGrowlController *sharedGrowlController = nil; - (void)release { } -/* +/** * Posts a Growl notification using the supplied details and default values. */ - (void)notifyWithTitle:(NSString *)title description:(NSString *)description notificationName:(NSString *)name @@ -93,7 +93,7 @@ static SPGrowlController *sharedGrowlController = nil; clickContext:nil]; } -/* +/** * Posts a Growl notification using the supplied details and effectively ignoring the default values. */ - (void)notifyWithTitle:(NSString *)title description:(NSString *)description notificationName:(NSString *)name iconData:(NSData *)data priority:(int)priority isSticky:(BOOL)sticky clickContext:(id)clickContext diff --git a/Source/SPTableRelations.h b/Source/SPTableRelations.h index bc1cf1fa..72806b77 100644 --- a/Source/SPTableRelations.h +++ b/Source/SPTableRelations.h @@ -31,39 +31,41 @@ @interface SPTableRelations : NSObject { IBOutlet id tableDocumentInstance; - IBOutlet id tablesListInstance; + IBOutlet id tablesListInstance; + IBOutlet id tableDataInstance; + IBOutlet id tableList; IBOutlet id tableWindow; - IBOutlet id tableDataInstance; - IBOutlet id addButton; - IBOutlet id removeButton; - IBOutlet id refreshButton; - IBOutlet id labelText; - IBOutlet id relationsView; - IBOutlet id relationSheet; + + IBOutlet NSButton *addRelationButton; + IBOutlet NSButton *removeRelationButton; + IBOutlet NSButton *refreshRelationsButton; + IBOutlet NSTextField *labelTextField; + IBOutlet NSTableView *relationsTableView; + IBOutlet NSPanel *addRelationPanel; - IBOutlet id tableBox; - IBOutlet id columnSelect; - IBOutlet id refTableSelect; - IBOutlet id refColumnSelect; - IBOutlet id onUpdateSelect; - IBOutlet id onDeleteSelect; + IBOutlet NSBox *addRelationTableBox; + IBOutlet NSPopUpButton *columnPopUpButton; + IBOutlet NSPopUpButton *refTablePopUpButton; + IBOutlet NSPopUpButton *refColumnPopUpButton; + IBOutlet NSPopUpButton *onUpdatePopUpButton; + IBOutlet NSPopUpButton *onDeletePopUpButton; + IBOutlet NSButton *confirmAddRelationButton; - CMMCPConnection *mySQLConnection; + CMMCPConnection *connection; - NSMutableArray *relData; + NSMutableArray *relationData; } -- (void)setConnection:(CMMCPConnection *)theConnection; +@property (readwrite, assign) CMMCPConnection *connection; // IB action methods -- (IBAction)addRow:(id)sender; -- (IBAction)removeRow:(id)sender; -- (IBAction)closeRelationSheet:(id)sender; - (IBAction)addRelation:(id)sender; -- (IBAction)chooseRefTable:(id)sender; -- (IBAction)refresh:(id)sender; - -- (void)tableChanged:(NSNotification *)notification; +- (IBAction)removeRelation:(id)sender; +- (IBAction)closeRelationSheet:(id)sender; +- (IBAction)confirmAddRelation:(id)sender; +- (IBAction)selectTableColumn:(id)sender; +- (IBAction)selectReferenceTable:(id)sender; +- (IBAction)refreshRelations:(id)sender; @end diff --git a/Source/SPTableRelations.m b/Source/SPTableRelations.m index 35ce0ec7..a08639f9 100644 --- a/Source/SPTableRelations.m +++ b/Source/SPTableRelations.m @@ -29,59 +29,46 @@ #import "CMMCPConnection.h" #import "CMMCPResult.h" #import "SPTableData.h" +#import "SPStringAdditions.h" + +@interface SPTableRelations (PrivateAPI) + +- (void)_updateAvailableTableColumns; + +@end @implementation SPTableRelations -/* +@synthesize connection; + +/** * init */ - (id)init { - if (![super init]) - return nil; - - relData = [[NSMutableArray alloc] init]; + if ((self = [super init])) { + relationData = [[NSMutableArray alloc] init]; + } return self; } -/* - * dealloc - */ -- (void)dealloc -{ - [relData release], relData = nil; - - [super dealloc]; -} - -/* - * awakeFromNib +/** + * Register to listen for table selection changes upon nib awakening. */ - (void)awakeFromNib { [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(tableChanged:) + selector:@selector(tableSelectionChanged:) name:NSTableViewSelectionDidChangeNotification object:tableList]; } -/* - * setConnection - * set the database connection - */ -- (void)setConnection:(CMMCPConnection *)theConnection -{ - // Weak reference - mySQLConnection = theConnection; -} - #pragma mark - #pragma mark IB action methods -/* - * closeRelationSheet - * happens if the user hits cancel +/** + * Closes the relation sheet. */ - (IBAction)closeRelationSheet:(id)sender { @@ -89,162 +76,155 @@ [NSApp stopModalWithCode:0]; } -/* - * addRelation - * attempt to add the relations from the relationSheet data +/** + * Add a new relation using the selected values. */ -- (IBAction)addRelation:(id)sender -{ - // 0 = success - int retCode = 0; - NSString *thisTable = [tablesListInstance tableName]; - NSString *thisColumn = [columnSelect titleOfSelectedItem]; - NSString *thatTable = [refTableSelect titleOfSelectedItem]; - NSString *thatColumn = [refColumnSelect titleOfSelectedItem]; - NSString *onUpdate = [onUpdateSelect titleOfSelectedItem]; - NSString *onDelete = [onDeleteSelect titleOfSelectedItem]; - NSString *query = [NSString stringWithFormat: - @"ALTER TABLE `%@` ADD FOREIGN KEY (`%@`) REFERENCES `%@` (`%@`)", - thisTable, - thisColumn, - thatTable, - thatColumn]; +- (IBAction)confirmAddRelation:(id)sender +{ + NSString *thisTable = [tablesListInstance tableName]; + NSString *thisColumn = [columnPopUpButton titleOfSelectedItem]; + NSString *thatTable = [refTablePopUpButton titleOfSelectedItem]; + NSString *thatColumn = [refColumnPopUpButton titleOfSelectedItem]; + + NSString *query = [NSString stringWithFormat:@"ALTER TABLE %@ ADD FOREIGN KEY (%@) REFERENCES %@ (%@)", + [thisTable backtickQuotedString], + thisColumn, + [thatTable backtickQuotedString], + thatColumn]; - if( [onDelete length] ) { - query = [query stringByAppendingString:[NSString stringWithFormat:@" ON DELETE %@", onDelete]]; + // If required add ON DELETE + if ([onDeletePopUpButton indexOfSelectedItem] > 0) { + query = [query stringByAppendingString:[NSString stringWithFormat:@" ON DELETE %@", [onDeletePopUpButton titleOfSelectedItem]]]; } - if( [onUpdate length] ) { - query = [query stringByAppendingString:[NSString stringWithFormat:@" ON UPDATE %@", onUpdate]]; + + // If required add ON UPDATE + if ([onUpdatePopUpButton indexOfSelectedItem] > 0) { + query = [query stringByAppendingString:[NSString stringWithFormat:@" ON UPDATE %@", [onUpdatePopUpButton titleOfSelectedItem]]]; } - [mySQLConnection queryString:query]; + // Execute query + [connection queryString:query]; - if ( ! [[mySQLConnection getLastErrorMessage] isEqualToString:@""] ) { - NSLog(@"error: %@", [mySQLConnection getLastErrorMessage]); - retCode = 1; - } + int retCode = (![[connection getLastErrorMessage] isEqualToString:@""]); [NSApp stopModalWithCode:retCode]; } -/* - * chooseRefTable - * update the columns select when the user chooses a reference table +/** + * Updates the available columns when the user selects a table. */ -- (IBAction)chooseRefTable:(id)sender +- (IBAction)selectTableColumn:(id)sender { - NSString *table = [refTableSelect titleOfSelectedItem]; - - [refColumnSelect removeAllItems]; - - NSDictionary *info = [tableDataInstance informationForTable:table]; - NSArray *cols = [info objectForKey:@"columns"]; - NSMutableArray *colNames = [[NSMutableArray alloc] init]; - // TODO depending on the selected column type, it would be smart to only - // show columns that are valid to linkage. this.int -> ints only - for( int i = 0; i < [cols count]; i++ ) { - [colNames addObject:[[cols objectAtIndex:i] objectForKey:@"name"]]; - } - [refColumnSelect addItemsWithTitles:colNames]; - [colNames release]; + [self _updateAvailableTableColumns]; } -/* - * addRow - * called when the user indicated they want to add a relation +/** + * Updates the available columns when the user selects a table. */ -- (IBAction)addRow:(id)sender +- (IBAction)selectReferenceTable:(id)sender { - // TODO check that this is an INNO table + [self _updateAvailableTableColumns]; +} + +/** + * Called whenever the user selected to add a new relation. + */ +- (IBAction)addRelation:(id)sender +{ + // Set up the controls + [addRelationTableBox setTitle:[NSString stringWithFormat:@"Table: %@", [tablesListInstance tableName]]]; - // set up the controls - [tableBox setTitle:[NSString stringWithFormat:@"Table: %@",[tablesListInstance tableName] ]]; - [columnSelect removeAllItems]; - [columnSelect addItemsWithTitles:[tableDataInstance columnNames]]; - [refTableSelect removeAllItems]; - // grab only real tables - // TODO filter this so it only shows INNO tables - NSArray *tables = [tablesListInstance tables]; - NSArray *types = [tablesListInstance tableTypes]; - NSMutableArray *validTables = [[NSMutableArray alloc] init]; - for( int i = 0; i < [tables count]; i++ ) { - if( [[types objectAtIndex:i] intValue] == SP_TABLETYPE_TABLE ) { - [validTables addObject:[tables objectAtIndex:i]]; - } + [columnPopUpButton removeAllItems]; + [columnPopUpButton addItemsWithTitles:[tableDataInstance columnNames]]; + + [refTablePopUpButton removeAllItems]; + + // Get all InnoDB tables in the current database + CMMCPResult *result = [connection queryString:[NSString stringWithFormat:@"SELECT table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE' AND engine = 'InnoDB' AND table_schema = '%@' AND table_name != '%@'", [tableDocumentInstance database], [tablesListInstance tableName]]]; + + [result dataSeek:0]; + + for (int i = 0; i < [result numOfRows]; i++) + { + [refTablePopUpButton addItemWithTitle:[[result fetchRowAsArray] objectAtIndex:0]]; } - [refTableSelect addItemsWithTitles:validTables]; - [validTables release]; - [self chooseRefTable:nil]; - [NSApp beginSheet:relationSheet - modalForWindow:tableWindow - modalDelegate:self - didEndSelector:nil - contextInfo:nil]; + [self selectReferenceTable:nil]; + [NSApp beginSheet:addRelationPanel + modalForWindow:tableWindow + modalDelegate:self + didEndSelector:nil + contextInfo:nil]; - int code = [NSApp runModalForWindow:relationSheet]; + int code = [NSApp runModalForWindow:addRelationPanel]; - [NSApp endSheet:relationSheet]; - [relationSheet orderOut:nil]; + [NSApp endSheet:addRelationPanel]; + [addRelationPanel orderOut:nil]; // 0 indicates success - if( code ) { + if (code) { NSBeginAlertSheet(NSLocalizedString(@"Error creating relation", @"error creating relation message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [NSApp mainWindow], nil, nil, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"The specified relation was unable to be created.\n\nMySQL said: %@", @"error creating relation informative message"), [mySQLConnection getLastErrorMessage]]); - } else { - [self refresh:nil]; + [NSString stringWithFormat:NSLocalizedString(@"The specified relation was unable to be created.\n\nMySQL said: %@", @"error creating relation informative message"), [connection getLastErrorMessage]]); + } + else { + [self refreshRelations:nil]; } } -/* - * removeRow - * called when rows are selected and the user wants to remove those relations +/** + * Removes the selected relations. */ -- (IBAction)removeRow:(id)sender +- (IBAction)removeRelation:(id)sender { - if ( [relationsView numberOfSelectedRows] ) { - int resp = NSRunAlertPanel(NSLocalizedString(@"Delete relation", @"delete relation message"), - NSLocalizedString(@"Are you sure you want to delete the selected relations? This action cannot be undone", @"delete selected relation informative message"), - NSLocalizedString(@"Delete", @"delete button"), - NSLocalizedString(@"Cancel", @"cancel button"), nil ); + if ([relationsTableView numberOfSelectedRows] > 0) { + + int response = NSRunAlertPanel(NSLocalizedString(@"Delete relation", @"delete relation message"), + NSLocalizedString(@"Are you sure you want to delete the selected relations? This action cannot be undone", @"delete selected relation informative message"), + NSLocalizedString(@"Delete", @"delete button"), + NSLocalizedString(@"Cancel", @"cancel button"), nil ); - if( resp == NSAlertDefaultReturn ) { + if (response == NSAlertDefaultReturn) { + NSString *thisTable = [tablesListInstance tableName]; - NSIndexSet *selectedSet = [relationsView selectedRowIndexes]; + NSIndexSet *selectedSet = [relationsTableView selectedRowIndexes]; + unsigned int row = [selectedSet lastIndex]; - while( row != NSNotFound ) + + while (row != NSNotFound) { - NSArray *relName = [[relData objectAtIndex:row] objectForKey:@"name"]; - NSString *query = [NSString stringWithFormat:@"ALTER TABLE `%@` DROP FOREIGN KEY `%@`", thisTable, relName]; + NSString *relationName = [[relationData objectAtIndex:row] objectForKey:@"name"]; + NSString *query = [NSString stringWithFormat:@"ALTER TABLE %@ DROP FOREIGN KEY %@", [thisTable backtickQuotedString], [relationName backtickQuotedString]]; - [mySQLConnection queryString:query]; + [connection queryString:query]; - if (![[mySQLConnection getLastErrorMessage] isEqualToString:@""] ) { + if (![[connection getLastErrorMessage] isEqualToString:@""] ) { NSBeginAlertSheet(NSLocalizedString(@"Unable to remove relation", @"error removing relation message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [NSApp mainWindow], nil, nil, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"The selected relation couldn't be removed.\n\nMySQL said: %@", @"error removing relation informative message"), [mySQLConnection getLastErrorMessage]]); - // abort loop + [NSString stringWithFormat:NSLocalizedString(@"The selected relation couldn't be removed.\n\nMySQL said: %@", @"error removing relation informative message"), [connection getLastErrorMessage]]); + + // Abort loop break; } + row = [selectedSet indexLessThanIndex:row]; } - [self refresh:nil]; + + [self refreshRelations:nil]; } } } -/* - * refresh - * called to refesh the relations list +/** + * Refreshes the displayed relations. */ -- (IBAction)refresh:(id)sender +- (IBAction)refreshRelations:(id)sender { - [relData removeAllObjects]; + [relationData removeAllObjects]; if ([tablesListInstance tableType] == SP_TABLETYPE_TABLE) { @@ -252,32 +232,33 @@ NSArray *constraints = [tableDataInstance getConstraints]; - for( int i = 0; i < [constraints count]; i++ ) { - [relData addObject:[NSDictionary dictionaryWithObjectsAndKeys: - [tablesListInstance tableName], @"table", - [[constraints objectAtIndex:i] objectForKey:@"name"], @"name", - [[constraints objectAtIndex:i] objectForKey:@"columns"], @"columns", - [[constraints objectAtIndex:i] objectForKey:@"ref_table"], @"fk_table", - [[constraints objectAtIndex:i] objectForKey:@"ref_columns"], @"fk_columns", - [[constraints objectAtIndex:i] objectForKey:@"update"], @"on_update", - [[constraints objectAtIndex:i] objectForKey:@"delete"], @"on_delete", - nil]]; + for (NSDictionary *constraint in constraints) + { + [relationData addObject:[NSDictionary dictionaryWithObjectsAndKeys: + [tablesListInstance tableName], @"table", + [constraint objectForKey:@"name"], @"name", + [constraint objectForKey:@"columns"], @"columns", + [constraint objectForKey:@"ref_table"], @"fk_table", + [constraint objectForKey:@"ref_columns"], @"fk_columns", + [constraint objectForKey:@"update"], @"on_update", + [constraint objectForKey:@"delete"], @"on_delete", + nil]]; } } - [relationsView reloadData]; + [relationsTableView reloadData]; } -/* - * tableChanged - * notification from the tableList when the users click a table +/** + * Called whenever the user selects a different table. */ -- (void)tableChanged:(NSNotification *)notification +- (void)tableSelectionChanged:(NSNotification *)notification { // To begin enable all interface elements - [addButton setEnabled:YES]; - [refreshButton setEnabled:YES]; + [addRelationButton setEnabled:YES]; + [refreshRelationsButton setEnabled:YES]; + [relationsTableView setEnabled:YES]; // Get the current table's storage engine NSString *engine = [tableDataInstance statusValueForKey:@"Engine"]; @@ -285,76 +266,107 @@ if (([tablesListInstance tableType] == SP_TABLETYPE_TABLE) && ([[engine lowercaseString] isEqualToString:@"innodb"])) { // Update the text label - [labelText setStringValue:[NSString stringWithFormat:@"Relations for table: %@", [tablesListInstance tableName]]]; + [labelTextField setStringValue:[NSString stringWithFormat:@"Relations for table: %@", [tablesListInstance tableName]]]; - [addButton setEnabled:YES]; - [refreshButton setEnabled:YES]; + [addRelationButton setEnabled:YES]; + [refreshRelationsButton setEnabled:YES]; + [relationsTableView setEnabled:YES]; } else { - [addButton setEnabled:NO]; - [refreshButton setEnabled:NO]; + [addRelationButton setEnabled:NO]; + [refreshRelationsButton setEnabled:NO]; + [relationsTableView setEnabled:NO]; - [labelText setStringValue:([tablesListInstance tableType] == SP_TABLETYPE_TABLE) ? @"This table does not support relations" : @""]; + [labelTextField setStringValue:([tablesListInstance tableType] == SP_TABLETYPE_TABLE) ? @"This table does not support relations" : @""]; } - [self refresh:self]; + [self refreshRelations:self]; } #pragma mark - #pragma mark Tableview datasource methods -- (int)numberOfRowsInTableView:(NSTableView *)aTableView +- (int)numberOfRowsInTableView:(NSTableView *)tableView { - return [relData count]; + return [relationData count]; } -- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn - row:(int)rowIndex +- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)rowIndex { - return [[relData objectAtIndex:rowIndex] objectForKey:[aTableColumn identifier]]; -} - -- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex -{ - + return [[relationData objectAtIndex:rowIndex] objectForKey:[tableColumn identifier]]; } #pragma mark - #pragma mark Tableview delegate methods -- (void)tableView:(NSTableView*)tableView didClickTableColumn:(NSTableColumn *)tableColumn -{ - -} - -- (void)tableViewSelectionDidChange:(NSNotification *)aNotification +/** + * Called whenever the relations table view selection changes. + */ +- (void)tableViewSelectionDidChange:(NSNotification *)notification { - [removeButton setEnabled:([relationsView numberOfSelectedRows] > 0)]; + [removeRelationButton setEnabled:([relationsTableView numberOfSelectedRows] > 0)]; } -- (void)tableViewSelectionIsChanging:(NSNotification *)aNotification -{ - -} +#pragma mark - +#pragma mark Other -- (void)tableViewColumnDidResize:(NSNotification *)aNotification -{ +/* + * Dealloc. + */ +- (void)dealloc +{ + [relationData release], relationData = nil; + [super dealloc]; } -- (BOOL)tableView:(NSTableView *)aTableView shouldEditTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex -{ - return NO; -} +@end -- (BOOL)tableView:(NSTableView *)tableView writeRows:(NSArray*)rows toPasteboard:(NSPasteboard*)pboard -{ - return NO; -} +@implementation SPTableRelations (PrivateAPI) -- (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)command +/** + * Updates the available table columns that the reference is pointing to. Available columns are those that are + * within the selected table and are of the same data type as the column the reference is from. + */ +- (void)_updateAvailableTableColumns { - return NO; + NSString *column = [columnPopUpButton titleOfSelectedItem]; + NSString *table = [refTablePopUpButton titleOfSelectedItem]; + + [tableDataInstance resetAllData]; + [tableDataInstance updateInformationForCurrentTable]; + + NSDictionary *columnInfo = [[tableDataInstance columnWithName:column] copy]; + + [refColumnPopUpButton setEnabled:NO]; + [confirmAddRelationButton setEnabled:NO]; + + [refColumnPopUpButton removeAllItems]; + + [tableDataInstance resetAllData]; + NSDictionary *tableInfo = [tableDataInstance informationForTable:table]; + + NSArray *columns = [tableInfo objectForKey:@"columns"]; + + NSMutableArray *validColumns = [NSMutableArray array]; + + // Only add columns of the same data type + for (int i = 0; i < [columns count]; i++) + { + if ([[columnInfo objectForKey:@"type"] isEqualToString:[[columns objectAtIndex:i] objectForKey:@"type"]]) { + [validColumns addObject:[[columns objectAtIndex:i] objectForKey:@"name"]]; + } + } + + // Add the valid columns + if ([validColumns count] > 0) { + [refColumnPopUpButton addItemsWithTitles:validColumns]; + + [refColumnPopUpButton setEnabled:YES]; + [confirmAddRelationButton setEnabled:YES]; + } + + [columnInfo release]; } @end |