diff options
Diffstat (limited to 'Source/SPTableTriggers.m')
-rw-r--r-- | Source/SPTableTriggers.m | 515 |
1 files changed, 515 insertions, 0 deletions
diff --git a/Source/SPTableTriggers.m b/Source/SPTableTriggers.m new file mode 100644 index 00000000..6825f504 --- /dev/null +++ b/Source/SPTableTriggers.m @@ -0,0 +1,515 @@ +// +// SPTableTriggers.m +// sequel-pro +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// More info at <http://code.google.com/p/sequel-pro/> + +#import "SPTableTriggers.h" +#import "TableDocument.h" +#import "TablesList.h" +#import "SPTableData.h" +#import "SPStringAdditions.h" +#import "SPConstants.h" +#import "SPAlertSheets.h" + +@interface SPTableTriggers (PrivateAPI) + +- (void)_refreshRelationDataForcingCacheRefresh:(BOOL)clearAllCaches; +- (void)_updateAvailableTableColumns; + +@end + +@implementation SPTableTriggers + +@synthesize connection; + +/** + * init + */ +- (id)init +{ + if ((self = [super init])) { + triggerData = [[NSMutableArray alloc] init]; + } + + return self; +} + +/** + * Register to listen for table selection changes upon nib awakening. + */ +- (void)awakeFromNib +{ + // Set the table relation view's vertical gridlines if required + [triggersTableView setGridStyleMask:([[NSUserDefaults standardUserDefaults] boolForKey:SPDisplayTableViewVerticalGridlines]) ? NSTableViewSolidVerticalGridLineMask : NSTableViewGridNone]; + + // Set the strutcture and index view's font + BOOL useMonospacedFont = [[NSUserDefaults standardUserDefaults] boolForKey:SPUseMonospacedFonts]; + + for (NSTableColumn *column in [triggersTableView tableColumns]) + { + [[column dataCell] setFont:(useMonospacedFont) ? [NSFont fontWithName:SPDefaultMonospacedFontName size:[NSFont smallSystemFontSize]] : [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; + } + + // Register as an observer for the when the UseMonospacedFonts preference changes + [[NSUserDefaults standardUserDefaults] addObserver:self forKeyPath:SPUseMonospacedFonts options:NSKeyValueObservingOptionNew context:NULL]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(tableSelectionChanged:) + name:SPTableChangedNotification + object:tableDocumentInstance]; + + // 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]; +} + +#pragma mark - +#pragma mark IB action methods + +/** + * Closes the relation sheet. + */ +- (IBAction)closeRelationSheet:(id)sender +{ + [NSApp endSheet:addTriggerPanel returnCode:0]; + [addTriggerPanel orderOut:self]; +} + +/** + * Add a new relation using the selected values. + */ +- (IBAction)confirmAddRelation:(id)sender +{ + [self closeRelationSheet:self]; + + 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 backtickQuotedString], + [thatTable backtickQuotedString], + [thatColumn backtickQuotedString]]; + + // If required add ON DELETE + if ([onDeletePopUpButton indexOfSelectedItem] > 0) { + query = [query stringByAppendingString:[NSString stringWithFormat:@" ON DELETE %@", [[onDeletePopUpButton titleOfSelectedItem] uppercaseString]]]; + } + + // If required add ON UPDATE + if ([onUpdatePopUpButton indexOfSelectedItem] > 0) { + query = [query stringByAppendingString:[NSString stringWithFormat:@" ON UPDATE %@", [[onUpdatePopUpButton titleOfSelectedItem] uppercaseString]]]; + } + + // Execute query + [connection queryString:query]; + + NSInteger retCode = (![[connection getLastErrorMessage] isEqualToString:@""]); + + // 0 indicates success + if (retCode) { + SPBeginAlertSheet(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"), [connection getLastErrorMessage]]); + } + else { + [self _refreshRelationDataForcingCacheRefresh:YES]; + } +} + +/** + * Updates the available columns when the user selects a table. + */ +- (IBAction)selectTableColumn:(id)sender +{ + [self _updateAvailableTableColumns]; +} + +/** + * Updates the available columns when the user selects a table. + */ +- (IBAction)selectReferenceTable:(id)sender +{ + [self _updateAvailableTableColumns]; +} + +/** + * Called whenever the user selected to add a new trigger. + */ +- (IBAction)addTrigger:(id)sender +{ + // Set up the controls + [addTriggerTableBox setTitle:[NSString stringWithFormat:@"Table: %@", [tablesListInstance tableName]]]; + + [columnPopUpButton removeAllItems]; + [columnPopUpButton addItemsWithTitles:[tableDataInstance columnNames]]; + + [refTablePopUpButton removeAllItems]; + + // Get all InnoDB tables in the current database + MCPResult *result = [connection queryString:[NSString stringWithFormat:@"SELECT table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE' AND engine = 'InnoDB' AND table_schema = %@", [[tableDocumentInstance database] tickQuotedString]]]; + + [result dataSeek:0]; + + for (NSInteger i = 0; i < [result numOfRows]; i++) + { + [refTablePopUpButton addItemWithTitle:[[result fetchRowAsArray] objectAtIndex:0]]; + } + + [self selectReferenceTable:nil]; + + [NSApp beginSheet:addTriggerPanel + modalForWindow:tableWindow + modalDelegate:self + didEndSelector:nil + contextInfo:nil]; +} + +/** + * Removes the selected trigger. + */ +- (IBAction)removeTrigger:(id)sender +{ + if ([triggersTableView numberOfSelectedRows] > 0) { + + NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Delete relation", @"delete relation message") + defaultButton:NSLocalizedString(@"Delete", @"delete button") + alternateButton:NSLocalizedString(@"Cancel", @"cancel button") + otherButton:nil + informativeTextWithFormat:NSLocalizedString(@"Are you sure you want to delete the selected relations? This action cannot be undone.", @"delete selected relation informative message")]; + + [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"]; + + [alert beginSheetModalForWindow:tableWindow modalDelegate:self didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:@"removeRelation"]; + } +} + +/** + * Trigger a refresh of the displayed relations via the interface. + */ +- (IBAction)refreshTriggers:(id)sender +{ + [self _refreshRelationDataForcingCacheRefresh:YES]; +} + +/** + * Called whenever the user selects a different table. + */ +- (void)tableSelectionChanged:(NSNotification *)notification +{ + BOOL enableInteraction = ![[tableDocumentInstance selectedToolbarItemIdentifier] isEqualToString:SPMainToolbarTableRelations] || ![tableDocumentInstance isWorking]; + + // To begin enable all interface elements + [addTriggerButton setEnabled:enableInteraction]; + [refreshTriggersButton setEnabled:enableInteraction]; + [triggersTableView setEnabled:YES]; + + // Get the current table's storage engine + NSString *engine = [tableDataInstance statusValueForKey:@"Engine"]; + + if (([tablesListInstance tableType] == SP_TABLETYPE_TABLE) && ([[engine lowercaseString] isEqualToString:@"innodb"])) { + + // Update the text label + [labelTextField setStringValue:[NSString stringWithFormat:@"Relations for table: %@", [tablesListInstance tableName]]]; + + [addTriggerButton setEnabled:enableInteraction]; + [refreshTriggersButton setEnabled:enableInteraction]; + [triggersTableView setEnabled:YES]; + } + else { + [addTriggerButton setEnabled:NO]; + [refreshTriggersButton setEnabled:NO]; + [triggersTableView setEnabled:NO]; + + [labelTextField setStringValue:([tablesListInstance tableType] == SP_TABLETYPE_TABLE) ? @"This table currently does not support relations. Only tables that use the InnoDB storage engine support them." : @""]; + } + + [self _refreshRelationDataForcingCacheRefresh:NO]; +} + +#pragma mark - +#pragma mark Tableview datasource methods + +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView +{ + return [triggerData count]; +} + +- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex +{ + return [[triggerData objectAtIndex:rowIndex] objectForKey:[tableColumn identifier]]; +} + +#pragma mark - +#pragma mark Tableview delegate methods + +/** + * Called whenever the relations table view selection changes. + */ +- (void)tableViewSelectionDidChange:(NSNotification *)notification +{ + [removeTriggerButton setEnabled:([triggersTableView numberOfSelectedRows] > 0)]; +} + +/* + * Double-click action on table cells - for the time being, return + * NO to disable editing. + */ +- (BOOL)tableView:(NSTableView *)aTableView shouldEditTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex +{ + if ([tableDocumentInstance isWorking]) return NO; + + return NO; +} + +/** + * Disable row selection while the document is working. + */ +- (BOOL)tableView:(NSTableView *)aTableView shouldSelectRow:(NSInteger)rowIndex +{ + return ![tableDocumentInstance isWorking]; +} + +#pragma mark - +#pragma mark Task interaction + +/** + * Disable all content interactive elements during an ongoing task. + */ +- (void)startDocumentTaskForTab:(NSNotification *)aNotification +{ + + // Only proceed if this view is selected. + if (![[tableDocumentInstance selectedToolbarItemIdentifier] isEqualToString:SPMainToolbarTableRelations]) + return; + + [addTriggerButton setEnabled:NO]; + [refreshTriggersButton setEnabled:NO]; + [removeTriggerButton setEnabled:NO]; +} + +/** + * Enable all content interactive elements after an ongoing task. + */ +- (void)endDocumentTaskForTab:(NSNotification *)aNotification +{ + + // Only proceed if this view is selected. + if (![[tableDocumentInstance selectedToolbarItemIdentifier] isEqualToString:SPMainToolbarTableRelations]) + return; + + if ([triggersTableView isEnabled]) { + [addTriggerButton setEnabled:YES]; + [refreshTriggersButton setEnabled:YES]; + } + [removeTriggerButton setEnabled:([triggersTableView numberOfSelectedRows] > 0)]; +} + +#pragma mark - +#pragma mark Other + +/** + * NSAlert didEnd method. + */ +- (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo +{ + if ([contextInfo isEqualToString:@"removeRelation"]) { + + if (returnCode == NSAlertDefaultReturn) { + + NSString *thisTable = [tablesListInstance tableName]; + NSIndexSet *selectedSet = [triggersTableView selectedRowIndexes]; + + NSUInteger row = [selectedSet lastIndex]; + + while (row != NSNotFound) + { + NSString *relationName = [[triggerData objectAtIndex:row] objectForKey:@"name"]; + NSString *query = [NSString stringWithFormat:@"ALTER TABLE %@ DROP FOREIGN KEY %@", [thisTable backtickQuotedString], [relationName backtickQuotedString]]; + + [connection queryString:query]; + + if (![[connection getLastErrorMessage] isEqualToString:@""] ) { + + SPBeginAlertSheet(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"), [connection getLastErrorMessage]]); + + // Abort loop + break; + } + + row = [selectedSet indexLessThanIndex:row]; + } + + [self _refreshRelationDataForcingCacheRefresh:YES]; + } + } +} + +/** + * This method is called as part of Key Value Observing which is used to watch for prefernce changes which effect the interface. + */ +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context +{ + // Display table veiew vertical gridlines preference changed + if ([keyPath isEqualToString:SPDisplayTableViewVerticalGridlines]) { + [triggersTableView setGridStyleMask:([[change objectForKey:NSKeyValueChangeNewKey] boolValue]) ? NSTableViewSolidVerticalGridLineMask : NSTableViewGridNone]; + } + // Use monospaced fonts preference changed + else if ([keyPath isEqualToString:SPUseMonospacedFonts]) { + + BOOL useMonospacedFont = [[change objectForKey:NSKeyValueChangeNewKey] boolValue]; + + for (NSTableColumn *column in [triggersTableView tableColumns]) + { + [[column dataCell] setFont:(useMonospacedFont) ? [NSFont fontWithName:SPDefaultMonospacedFontName size:[NSFont smallSystemFontSize]] : [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; + } + + [triggersTableView reloadData]; + } +} + +/** + * Menu validation + */ +- (BOOL)validateMenuItem:(NSMenuItem *)menuItem +{ + // Remove row + if ([menuItem action] == @selector(removeRelation:)) { + [menuItem setTitle:([triggersTableView numberOfSelectedRows] > 1) ? @"Delete Relations" : @"Delete Relation"]; + + return ([triggersTableView numberOfSelectedRows] > 0); + } + + return YES; +} + +#pragma mark - + +/* + * Dealloc. + */ +- (void)dealloc +{ + [triggerData release], triggerData = nil; + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [super dealloc]; +} + +@end + +@implementation SPTableTriggers (PrivateAPI) + +/** + * Refresh the displayed relations, optionally forcing a refresh of the underlying cache. + */ +- (void)_refreshRelationDataForcingCacheRefresh:(BOOL)clearAllCaches +{ + [triggerData removeAllObjects]; + + if ([tablesListInstance tableType] == SP_TABLETYPE_TABLE) { + + if (clearAllCaches) [tableDataInstance updateInformationForCurrentTable]; + + NSArray *triggers = [tableDataInstance triggers]; + + for (NSDictionary *trigger in triggers) + { + [triggerData addObject:[NSDictionary dictionaryWithObjectsAndKeys: + [trigger objectForKey:@"Table"], @"table", + [trigger objectForKey:@"Trigger"], @"trigger", + [trigger objectForKey:@"Event"], @"event", + [trigger objectForKey:@"Timing"], @"timing", + [trigger objectForKey:@"Statement"], @"statement", + [trigger objectForKey:@"Definer"], @"definer", + [trigger objectForKey:@"Created"], @"created", + [trigger objectForKey:@"sql_mode"], @"sql_mode", + nil]]; + + } + // NSLog(@"Triggers: %@", triggers); + } + + [triggersTableView reloadData]; +} + +/** + * 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 +{ + NSString *column = [columnPopUpButton titleOfSelectedItem]; + NSString *table = [refTablePopUpButton titleOfSelectedItem]; + + [tableDataInstance resetAllData]; + [tableDataInstance updateInformationForCurrentTable]; + + NSDictionary *columnInfo = [[tableDataInstance columnWithName:column] copy]; + + [refColumnPopUpButton setEnabled:NO]; + [confirmAddTriggerButton 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 (NSDictionary *column in columns) + { + if ([[columnInfo objectForKey:@"type"] isEqualToString:[column objectForKey:@"type"]]) { + [validColumns addObject:[column objectForKey:@"name"]]; + } + } + + // Add the valid columns + if ([validColumns count] > 0) { + [refColumnPopUpButton addItemsWithTitles:validColumns]; + + [refColumnPopUpButton setEnabled:YES]; + [confirmAddTriggerButton setEnabled:YES]; + } + + [columnInfo release]; +} + +@end |