aboutsummaryrefslogtreecommitdiffstats
path: root/Source
diff options
context:
space:
mode:
Diffstat (limited to 'Source')
-rw-r--r--Source/SPTableStructure.m532
-rw-r--r--Source/SPTableStructureDelegate.h39
-rw-r--r--Source/SPTableStructureDelegate.m567
3 files changed, 608 insertions, 530 deletions
diff --git a/Source/SPTableStructure.m b/Source/SPTableStructure.m
index 5c5a400a..3290df89 100644
--- a/Source/SPTableStructure.m
+++ b/Source/SPTableStructure.m
@@ -47,6 +47,7 @@
@implementation SPTableStructure
#pragma mark -
+#pragma mark Initialization
/**
* Init.
@@ -168,6 +169,7 @@
}
#pragma mark -
+#pragma mark Table loading
/**
* Loads aTable, put it in an array, update the tableViewColumns and reload the tableView
@@ -1451,542 +1453,12 @@ returns a dictionary containing enum/set field names as key and possible values
[refreshIndexesButton setEnabled:YES];
}
-#pragma mark -
-#pragma mark TableView datasource methods
-
-- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
-{
- return [tableFields count];
-}
-
-- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex
-{
- // Return a placeholder if the table is reloading
- if (rowIndex >= [tableFields count]) return @"...";
-
- if([[tableColumn identifier] isEqualToString:@"collation"]) {
- NSInteger idx = 0;
- if((idx = [[NSArrayObjectAtIndex(tableFields,rowIndex) objectForKey:@"encoding"] integerValue]) > 0) {
- NSString *enc = [[encodingPopupCell itemAtIndex:idx] title];
- NSInteger start = [enc rangeOfString:@"("].location+1;
- NSInteger end = [enc length] - start - 1;
- collations = [databaseDataInstance getDatabaseCollationsForEncoding:[enc substringWithRange:NSMakeRange(start, end)]];
- } else {
- if([tableDataInstance tableEncoding] != nil) {
- collations = [databaseDataInstance getDatabaseCollationsForEncoding:[tableDataInstance tableEncoding]];
- } else {
- collations = [NSArray array];
- }
- }
-
- [[tableColumn dataCell] removeAllItems];
-
- if ([collations count] > 0) {
- [[tableColumn dataCell] addItemWithTitle:@""];
- // Populate collation popup button
- for (NSDictionary *collation in collations)
- [[tableColumn dataCell] addItemWithTitle:[collation objectForKey:@"COLLATION_NAME"]];
- }
- }
-
- return [NSArrayObjectAtIndex(tableFields, rowIndex) objectForKey:[tableColumn identifier]];
-}
-
-- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
-{
- // Make sure that the drag operation is for the right table view
- if (aTableView != tableSourceView) return;
-
- if (!isEditingRow) {
- [oldRow setDictionary:[tableFields objectAtIndex:rowIndex]];
- isEditingRow = YES;
- currentlyEditingRow = rowIndex;
- }
-
- NSDictionary *currentRow = [tableFields objectAtIndex:rowIndex];
-
- // Reset collation if encoding was changed
- if([[aTableColumn identifier] isEqualToString:@"encoding"]) {
- if([[currentRow objectForKey:@"encoding"] integerValue] != [anObject integerValue]) {
- [currentRow setObject:[NSNumber numberWithInteger:0] forKey:@"collation"];
- [tableSourceView reloadData];
- }
- }
- // Reset collation if BINARY was set to 1 since BINARY sets collation to *_bin
- else if([[aTableColumn identifier] isEqualToString:@"binary"]) {
- if([[currentRow objectForKey:@"binary"] integerValue] != [anObject integerValue]) {
- if([anObject integerValue] == 1) {
- [currentRow setObject:[NSNumber numberWithInteger:0] forKey:@"collation"];
- }
- [tableSourceView reloadData];
- }
- }
- // Reset collation if BINARY was set to 1 since BINARY sets collation to *_bin
- else if([[aTableColumn identifier] isEqualToString:@"Extra"]) {
- if(![[currentRow objectForKey:@"Extra"] isEqualToString:anObject]) {
- if([[[currentRow objectForKey:@"Extra"] uppercaseString] isEqualToString:@"AUTO_INCREMENT"]) {
- [currentRow setObject:[NSNumber numberWithInteger:0] forKey:@"null"];
- }
- [tableSourceView reloadData];
- }
- }
- // Reset default to "" if field doesn't allow NULL and current default is set to NULL
- else if([[aTableColumn identifier] isEqualToString:@"null"]) {
- if([[currentRow objectForKey:@"null"] integerValue] != [anObject integerValue]) {
- if([anObject integerValue] == 0) {
- if([[currentRow objectForKey:@"default"] isEqualToString:[prefs objectForKey:SPNullValue]])
- [currentRow setObject:@"" forKey:@"default"];
- }
- [tableSourceView reloadData];
- }
- }
-
- // Store new value but not if user choose "---" for type and reset values if required
- if([[aTableColumn identifier] isEqualToString:@"type"]) {
- if(anObject && [(NSString*)anObject length] && ![(NSString*)anObject hasPrefix:@"--"]) {
- [currentRow setObject:[(NSString*)anObject uppercaseString] forKey:@"type"];
- // If type is BLOB or TEXT reset DEFAULT since these field types don't allow a default
- if([[currentRow objectForKey:@"type"] hasSuffix:@"TEXT"] || [[currentRow objectForKey:@"type"] hasSuffix:@"BLOB"] || [self _isFieldTypeGeometry:[currentRow objectForKey:@"type"]]) {
- [currentRow setObject:@"" forKey:@"default"];
- [currentRow setObject:@"" forKey:@"length"];
- }
- [tableSourceView reloadData];
- }
- } else {
- [currentRow setObject:(anObject) ? anObject : @"" forKey:[aTableColumn identifier]];
- }
-
-
-}
-
-/**
- * Confirm whether to allow editing of a row. Returns YES by default, but NO for views.
- */
-- (BOOL)tableView:(NSTableView *)aTableView shouldEditTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
-{
- if ([tableDocumentInstance isWorking]) return NO;
-
- // Return NO for views
- if ([tablesListInstance tableType] == SPTableTypeView) return NO;
-
- return YES;
-}
-
-/**
-Begin a drag and drop operation from the table - copy a single dragged row to the drag pasteboard.
-*/
-- (BOOL)tableView:(NSTableView *)aTableView writeRowsWithIndexes:(NSIndexSet *)rows toPasteboard:(NSPasteboard*)pboard
-{
- // Make sure that the drag operation is started from the right table view
- if (aTableView != tableSourceView) return NO;
-
- // Check whether a save of the current field row is required.
- if ( ![self saveRowOnDeselect] ) return NO;
-
- if ([rows count] == 1) {
- [pboard declareTypes:[NSArray arrayWithObject:@"SequelProPasteboard"] owner:nil];
- [pboard setString:[[NSNumber numberWithInteger:[rows firstIndex]] stringValue] forType:@"SequelProPasteboard"];
- return YES;
- } else {
- return NO;
- }
-}
-
-/**
-Determine whether to allow a drag and drop operation on this table - for the purposes of drag reordering,
-validate that the original source is of the correct type and within the same table, and that the drag
-would result in a position change.
-*/
-- (NSDragOperation)tableView:(NSTableView*)tableView validateDrop:(id <NSDraggingInfo>)info proposedRow:(NSInteger)row
- proposedDropOperation:(NSTableViewDropOperation)operation
-{
- //make sure that the drag operation is for the right table view
- if (tableView!=tableSourceView) return NO;
-
- NSArray *pboardTypes = [[info draggingPasteboard] types];
- NSInteger originalRow;
-
- // Ensure the drop is of the correct type
- if (operation == NSTableViewDropAbove && row != -1 && [pboardTypes containsObject:@"SequelProPasteboard"]) {
-
- // Ensure the drag originated within this table
- if ([info draggingSource] == tableView) {
- originalRow = [[[info draggingPasteboard] stringForType:@"SequelProPasteboard"] integerValue];
-
- if (row != originalRow && row != (originalRow+1)) {
- return NSDragOperationMove;
- }
- }
- }
-
- return NSDragOperationNone;
-}
-
-/**
- * Having validated a drop, perform the field/column reordering to match.
- */
-- (BOOL)tableView:(NSTableView*)tableView acceptDrop:(id <NSDraggingInfo>)info row:(NSInteger)destinationRowIndex dropOperation:(NSTableViewDropOperation)operation
-{
- //make sure that the drag operation is for the right table view
- if (tableView!=tableSourceView) return NO;
-
- NSInteger originalRowIndex;
- NSMutableString *queryString;
- NSDictionary *originalRow;
-
- // Extract the original row position from the pasteboard and retrieve the details
- originalRowIndex = [[[info draggingPasteboard] stringForType:@"SequelProPasteboard"] integerValue];
- originalRow = [[NSDictionary alloc] initWithDictionary:[tableFields objectAtIndex:originalRowIndex]];
-
- [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:tableDocumentInstance];
-
- // Begin construction of the reordering query
- queryString = [NSMutableString stringWithFormat:@"ALTER TABLE %@ MODIFY COLUMN %@ %@", [selectedTable backtickQuotedString],
- [[originalRow objectForKey:@"name"] backtickQuotedString],
- [[originalRow objectForKey:@"type"] uppercaseString]];
-
- // Add the length parameter if necessary
- if ( [originalRow objectForKey:@"length"] && ![[originalRow objectForKey:@"length"] isEqualToString:@""]) {
- [queryString appendFormat:@"(%@)", [originalRow objectForKey:@"length"]];
- }
-
- NSString *fieldEncoding = @"";
- if([[originalRow objectForKey:@"encoding"] integerValue] > 0) {
- NSString *enc = [[encodingPopupCell itemAtIndex:[[originalRow objectForKey:@"encoding"] integerValue]] title];
- NSInteger start = [enc rangeOfString:@"("].location+1;
- NSInteger end = [enc length] - start - 1;
- fieldEncoding = [enc substringWithRange:NSMakeRange(start, end)];
- [queryString appendFormat:@" CHARACTER SET %@", fieldEncoding];
- }
- if(![fieldEncoding length] && [tableDataInstance tableEncoding]) {
- fieldEncoding = [tableDataInstance tableEncoding];
- }
- if([fieldEncoding length] && [[originalRow objectForKey:@"collation"] integerValue] > 0) {
- NSArray *theCollations = [databaseDataInstance getDatabaseCollationsForEncoding:fieldEncoding];
- NSString *col = [[theCollations objectAtIndex:[[originalRow objectForKey:@"collation"] integerValue]-1] objectForKey:@"COLLATION_NAME"];
- [queryString appendFormat:@" COLLATE %@", col];
- }
-
-
- // Add unsigned, zerofill, binary, not null if necessary
- if ([[originalRow objectForKey:@"unsigned"] integerValue]) {
- [queryString appendString:@" UNSIGNED"];
- }
- if ([[originalRow objectForKey:@"zerofill"] integerValue]) {
- [queryString appendString:@" ZEROFILL"];
- }
- if ([[originalRow objectForKey:@"binary"] integerValue]) {
- [queryString appendString:@" BINARY"];
- }
- if (![[originalRow objectForKey:@"null"] integerValue]) {
- [queryString appendString:@" NOT NULL"];
- }
- if (![[originalRow objectForKey:@"Extra"] isEqualToString:@"None"] ) {
- [queryString appendString:@" "];
- [queryString appendString:[[originalRow objectForKey:@"Extra"] uppercaseString]];
- }
-
- BOOL isTimestampType = [[[originalRow objectForKey:@"type"] lowercaseString] isEqualToString:@"timestamp"];
-
- // Add the default value, skip it for auto_increment
- if([originalRow objectForKey:@"Extra"] && ![[originalRow objectForKey:@"Extra"] isEqualToString:@"auto_increment"]) {
- if ([[originalRow objectForKey:@"default"] isEqualToString:[prefs objectForKey:SPNullValue]]) {
- if ([[originalRow objectForKey:@"null"] integerValue] == 1) {
- [queryString appendString:(isTimestampType) ? @" NULL DEFAULT NULL" : @" DEFAULT NULL"];
- }
- }
- else if (isTimestampType && ([[[originalRow objectForKey:@"default"] uppercaseString] isEqualToString:@"CURRENT_TIMESTAMP"]) ) {
- [queryString appendString:@" DEFAULT CURRENT_TIMESTAMP"];
- }
- else {
- [queryString appendFormat:@" DEFAULT '%@'", [mySQLConnection prepareString:[originalRow objectForKey:@"default"]]];
- }
- }
-
- // Any column comments
- if ([[originalRow objectForKey:@"comment"] length]) {
- [queryString appendFormat:@" COMMENT '%@'", [mySQLConnection prepareString:[originalRow objectForKey:@"comment"]]];
- }
-
- // Unparsed details - column formats, storage, reference definitions
- if ([originalRow objectForKey:@"unparsed"]) {
- [queryString appendString:[originalRow objectForKey:@"unparsed"]];
- }
-
- // Add the new location
- if ( destinationRowIndex == 0 ){
- [queryString appendString:@" FIRST"];
- } else {
- [queryString appendFormat:@" AFTER %@",
- [[[tableFields objectAtIndex:destinationRowIndex-1] objectForKey:@"name"] backtickQuotedString]];
- }
-
- // Run the query; report any errors, or reload the table on success
- [mySQLConnection queryString:queryString];
-
- if ([mySQLConnection queryErrored]) {
- SPBeginAlertSheet(NSLocalizedString(@"Error moving field", @"error moving field message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil,
- [NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to move the field.\n\nMySQL said: %@", @"error moving field informative message"), [mySQLConnection getLastErrorMessage]]);
- } else {
- [tableDataInstance resetAllData];
- [tablesListInstance setStatusRequiresReload:YES];
- [self loadTable:selectedTable];
-
- // Mark the content table cache for refresh
- [tablesListInstance setContentRequiresReload:YES];
-
- if ( originalRowIndex < destinationRowIndex ) {
- [tableSourceView selectRowIndexes:[NSIndexSet indexSetWithIndex:destinationRowIndex-1] byExtendingSelection:NO];
- } else {
- [tableSourceView selectRowIndexes:[NSIndexSet indexSetWithIndex:destinationRowIndex] byExtendingSelection:NO];
- }
- }
-
- [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance];
-
- [originalRow release];
- return YES;
-}
-
-#pragma mark -
-#pragma mark TableView delegate methods
-
-- (BOOL)tableView:(NSTableView *)aTableView shouldSelectRow:(NSInteger)rowIndex
-{
- // If we are editing a row, attempt to save that row - if saving failed, do not select the new row.
- if (isEditingRow && ![self addRowToDB]) return NO;
- return YES;
-}
-
-/**
- * Performs various interface validation
- */
-- (void)tableViewSelectionDidChange:(NSNotification *)aNotification
-{
- id object = [aNotification object];
-
- // Check for which table view the selection changed
- if (object == tableSourceView) {
-
- // If we are editing a row, attempt to save that row - if saving failed, reselect the edit row.
- if (isEditingRow && [tableSourceView selectedRow] != currentlyEditingRow && ![self saveRowOnDeselect]) return;
-
- [copyFieldButton setEnabled:YES];
-
- // Check if there is currently a field selected and change button state accordingly
- if ([tableSourceView numberOfSelectedRows] > 0 && [tablesListInstance tableType] == SPTableTypeTable) {
- [removeFieldButton setEnabled:YES];
- } else {
- [removeFieldButton setEnabled:NO];
- [copyFieldButton setEnabled:NO];
- }
-
- // If the table only has one field, disable the remove button. This removes the need to check that the user
- // is attempting to remove the last field in a table in removeField: above, but leave it in just in case.
- if ([tableSourceView numberOfRows] == 1) {
- [removeFieldButton setEnabled:NO];
- }
- }
-}
-
-/**
- * Traps enter and esc and make/cancel editing without entering next row
- */
-- (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)command
-{
- NSInteger row, column;
-
- row = [tableSourceView editedRow];
- column = [tableSourceView editedColumn];
-
- // Trap the tab key, selecting the next item in the line
- if ( [textView methodForSelector:command] == [textView methodForSelector:@selector(insertTab:)] && [tableSourceView numberOfColumns] - 1 == column)
- {
- //save current line
- [[control window] makeFirstResponder:control];
-
- if ( [self addRowToDB] && [textView methodForSelector:command] == [textView methodForSelector:@selector(insertTab:)] ) {
- if ( row < ([tableSourceView numberOfRows] - 1) ) {
- [tableSourceView selectRowIndexes:[NSIndexSet indexSetWithIndex:row+1] byExtendingSelection:NO];
- [tableSourceView editColumn:0 row:row+1 withEvent:nil select:YES];
- } else {
- [tableSourceView selectRowIndexes:[NSIndexSet indexSetWithIndex:0] byExtendingSelection:NO];
- [tableSourceView editColumn:0 row:0 withEvent:nil select:YES];
- }
- }
- return YES;
-
- }
-
- // Trap shift-tab key
- else if ( [textView methodForSelector:command] == [textView methodForSelector:@selector(insertBacktab:)] && column < 1)
- {
- if ( [self addRowToDB] && [textView methodForSelector:command] == [textView methodForSelector:@selector(insertBacktab:)] ) {
- [[control window] makeFirstResponder:control];
- if ( row > 0) {
- [tableSourceView selectRowIndexes:[NSIndexSet indexSetWithIndex:row-1] byExtendingSelection:NO];
- [tableSourceView editColumn:([tableFields count]-1) row:row-1 withEvent:nil select:YES];
- } else {
- [tableSourceView selectRowIndexes:[NSIndexSet indexSetWithIndex:([tableFields count]-1)] byExtendingSelection:NO];
- [tableSourceView editColumn:([tableFields count]-1) row:([tableSourceView numberOfRows]-1) withEvent:nil select:YES];
- }
- }
- return YES;
- }
-
- // Trap the enter key, triggering a save
- else if ( [textView methodForSelector:command] == [textView methodForSelector:@selector(insertNewline:)] )
- {
- // Suppress enter for non-text fields to allow selecting of chosen items from comboboxes or popups
- if(![[[[[[tableSourceView tableColumns] objectAtIndex:column] dataCell] class] description] isEqualToString:@"NSTextFieldCell"])
- return YES;
-
- [[control window] makeFirstResponder:control];
- [self addRowToDB];
- [tableSourceView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
- [[tableDocumentInstance parentWindow] makeFirstResponder:tableSourceView];
- return YES;
-
- }
-
- // Trap escape, aborting the edit and reverting the row
- else if ( [[control window] methodForSelector:command] == [[control window] methodForSelector:@selector(cancelOperation:)] )
- {
- [control abortEditing];
- [self cancelRowEditing];
- return YES;
- } else {
- return NO;
- }
-}
-
-
-/**
- * Modify cell display by disabling table cells when a view is selected, meaning structure/index
- * is uneditable and do cell validation due to row's field type.
- */
-- (void)tableView:(NSTableView *)tableView willDisplayCell:(id)aCell forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
-{
-
- //make sure that the message is from the right table view
- if (tableView != tableSourceView) return;
-
- if([tablesListInstance tableType] == SPTableTypeView) {
- [aCell setEnabled:NO];
- } else {
-
- // validate cell against current field type
- NSDictionary *theRow = NSArrayObjectAtIndex(tableFields, rowIndex);
- NSString *theRowType = @"";
- if(theRowType = [theRow objectForKey:@"type"])
- theRowType = [[theRowType stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] uppercaseString];
-
- // Only string fields allow encoding settings
- if(([[aTableColumn identifier] isEqualToString:@"encoding"])) {
- [aCell setEnabled:([self _isFieldTypeString:theRowType] && ![theRowType hasSuffix:@"BINARY"] && ![theRowType hasSuffix:@"BLOB"])];
- }
- // Only string fields allow collation settings and string field is not set to BINARY since BINARY sets the collation to *_bin
- else if([[aTableColumn identifier] isEqualToString:@"collation"]){
- [aCell setEnabled:([self _isFieldTypeString:theRowType] && [[theRow objectForKey:@"binary"] integerValue] == 0 && ![theRowType hasSuffix:@"BINARY"] && ![theRowType hasSuffix:@"BLOB"])];
- }
- // Check if UNSIGNED and ZEROFILL is allowed
- else if([[aTableColumn identifier] isEqualToString:@"zerofill"] || [[aTableColumn identifier] isEqualToString:@"unsigned"]) {
- [aCell setEnabled:([self _isFieldTypeNumeric:theRowType])];
- }
- // Check if BINARY is allowed
- else if([[aTableColumn identifier] isEqualToString:@"binary"]) {
- [aCell setEnabled:([self _isFieldTypeAllowBinary:theRowType])];
- }
- // TEXT, BLOB, and GEOMETRY fields don't allow a DEFAULT
- else if([[aTableColumn identifier] isEqualToString:@"default"]) {
- [aCell setEnabled:([theRowType hasSuffix:@"TEXT"] || [theRowType hasSuffix:@"BLOB"] || [self _isFieldTypeGeometry:theRowType]) ? NO : YES];
- }
- // Check allow NULL
- else if([[aTableColumn identifier] isEqualToString:@"null"]) {
- [aCell setEnabled:([[theRow objectForKey:@"Key"] isEqualToString:@"PRI"] || [[[theRow objectForKey:@"Extra"] uppercaseString] isEqualToString:@"AUTO_INCREMENT"]) ? NO : YES];
- }
- // TEXT, BLOB, date, and GEOMETRY fields don't allow a length
- else if([[aTableColumn identifier] isEqualToString:@"length"]) {
- [aCell setEnabled:([theRowType hasSuffix:@"TEXT"] || [theRowType hasSuffix:@"BLOB"] || [self _isFieldTypeDate:theRowType] || [self _isFieldTypeGeometry:theRowType]) ? NO : YES];
- }
- else {
- [aCell setEnabled:YES];
- }
- }
-}
-
-#pragma mark -
-#pragma mark SplitView delegate methods
-
-- (BOOL)splitView:(NSSplitView *)sender canCollapseSubview:(NSView *)subview
-{
- return YES;
-}
-
-- (CGFloat)splitView:(NSSplitView *)sender constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)offset
-{
- return proposedMax - 130;
-}
-
-- (CGFloat)splitView:(NSSplitView *)sender constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)offset
-{
- return proposedMin + 130;
-}
-
-- (NSRect)splitView:(NSSplitView *)splitView additionalEffectiveRectOfDividerAtIndex:(NSInteger)dividerIndex
-{
- return [structureGrabber convertRect:[structureGrabber bounds] toView:splitView];
-}
-
-- (void)splitViewDidResizeSubviews:(NSNotification *)aNotification
-{
- if ([aNotification object] == tablesIndexesSplitView) {
- NSView *indexesView = [[tablesIndexesSplitView subviews] objectAtIndex:1];
- if ([tablesIndexesSplitView isSubviewCollapsed:indexesView]) {
- [indexesShowButton setHidden:NO];
- } else {
- [indexesShowButton setHidden:YES];
- }
- }
-}
-
- (IBAction)unhideIndexesView:(id)sender
{
[tablesIndexesSplitView setPosition:[tablesIndexesSplitView frame].size.height-130 ofDividerAtIndex:0];
}
#pragma mark -
-#pragma mark NSComboBox delegates
-
-- (id)comboBoxCell:(NSComboBoxCell *)aComboBoxCell objectValueForItemAtIndex:(NSInteger)index
-{
- return NSArrayObjectAtIndex(typeSuggestions, index);
-}
-
-- (NSInteger)numberOfItemsInComboBoxCell:(NSComboBoxCell *)aComboBoxCell
-{
- return [typeSuggestions count];
-}
-
-/**
- * Allow completion for lowercased input
- */
-- (NSString *)comboBoxCell:(NSComboBoxCell *)aComboBoxCell completedString:(NSString *)uncompletedString
-{
-
- if([uncompletedString hasPrefix:@"-"]) return @"";
-
- NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF BEGINSWITH[c] %@", [uncompletedString uppercaseString]];
- NSArray *result = [typeSuggestions filteredArrayUsingPredicate:predicate];
-
- if(result && [result count])
- return [result objectAtIndex:0];
-
- return @"";
-
-}
-
-#pragma mark -
#pragma mark Private API methods
/**
diff --git a/Source/SPTableStructureDelegate.h b/Source/SPTableStructureDelegate.h
new file mode 100644
index 00000000..8986957e
--- /dev/null
+++ b/Source/SPTableStructureDelegate.h
@@ -0,0 +1,39 @@
+//
+// $Id$
+//
+// SPConnectionDelegate.h
+// sequel-pro
+//
+// Created by Stuart Connolly (stuconnolly.com) on October 26, 2010
+// Copyright (c) 2010 Stuart Connolly. 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 <http://code.google.com/p/sequel-pro/>
+
+#import "SPTableStructure.h"
+
+/**
+ * @category SPTableStructureDelegate SPTableStructureDelegate.h
+ *
+ * @author Stuart Connolly http://stuconnolly.com/
+ *
+ * This category is intended to contain all of SPTableStructure's delegate and datasource methods. It is
+ * defined as a category simply as a convenient way to separate these methods from SPTableStructure's main
+ * logic.
+ */
+@interface SPTableStructure (SPTableStructureDelegate)
+
+@end
diff --git a/Source/SPTableStructureDelegate.m b/Source/SPTableStructureDelegate.m
new file mode 100644
index 00000000..377fd7b3
--- /dev/null
+++ b/Source/SPTableStructureDelegate.m
@@ -0,0 +1,567 @@
+//
+// $Id$
+//
+// SPTableStructureDelegate.m
+// sequel-pro
+//
+// Created by Stuart Connolly (stuconnolly.com) on October 26, 2010
+// Copyright (c) 2010 Stuart Connolly. 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 <http://code.google.com/p/sequel-pro/>
+
+#import "SPTableStructureDelegate.h"
+#import "SPAlertSheets.h"
+
+@implementation SPTableStructure (SPTableStructureDelegate)
+
+#pragma mark -
+#pragma mark Table view datasource methods
+
+- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
+{
+ return [tableFields count];
+}
+
+- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex
+{
+ // Return a placeholder if the table is reloading
+ if (rowIndex >= [tableFields count]) return @"...";
+
+ if([[tableColumn identifier] isEqualToString:@"collation"]) {
+ NSInteger idx = 0;
+ if((idx = [[NSArrayObjectAtIndex(tableFields,rowIndex) objectForKey:@"encoding"] integerValue]) > 0) {
+ NSString *enc = [[encodingPopupCell itemAtIndex:idx] title];
+ NSInteger start = [enc rangeOfString:@"("].location+1;
+ NSInteger end = [enc length] - start - 1;
+ collations = [databaseDataInstance getDatabaseCollationsForEncoding:[enc substringWithRange:NSMakeRange(start, end)]];
+ } else {
+ if([tableDataInstance tableEncoding] != nil) {
+ collations = [databaseDataInstance getDatabaseCollationsForEncoding:[tableDataInstance tableEncoding]];
+ } else {
+ collations = [NSArray array];
+ }
+ }
+
+ [[tableColumn dataCell] removeAllItems];
+
+ if ([collations count] > 0) {
+ [[tableColumn dataCell] addItemWithTitle:@""];
+ // Populate collation popup button
+ for (NSDictionary *collation in collations)
+ [[tableColumn dataCell] addItemWithTitle:[collation objectForKey:@"COLLATION_NAME"]];
+ }
+ }
+
+ return [NSArrayObjectAtIndex(tableFields, rowIndex) objectForKey:[tableColumn identifier]];
+}
+
+- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
+{
+ // Make sure that the drag operation is for the right table view
+ if (aTableView != tableSourceView) return;
+
+ if (!isEditingRow) {
+ [oldRow setDictionary:[tableFields objectAtIndex:rowIndex]];
+ isEditingRow = YES;
+ currentlyEditingRow = rowIndex;
+ }
+
+ NSDictionary *currentRow = [tableFields objectAtIndex:rowIndex];
+
+ // Reset collation if encoding was changed
+ if([[aTableColumn identifier] isEqualToString:@"encoding"]) {
+ if([[currentRow objectForKey:@"encoding"] integerValue] != [anObject integerValue]) {
+ [currentRow setObject:[NSNumber numberWithInteger:0] forKey:@"collation"];
+ [tableSourceView reloadData];
+ }
+ }
+ // Reset collation if BINARY was set to 1 since BINARY sets collation to *_bin
+ else if([[aTableColumn identifier] isEqualToString:@"binary"]) {
+ if([[currentRow objectForKey:@"binary"] integerValue] != [anObject integerValue]) {
+ if([anObject integerValue] == 1) {
+ [currentRow setObject:[NSNumber numberWithInteger:0] forKey:@"collation"];
+ }
+ [tableSourceView reloadData];
+ }
+ }
+ // Reset collation if BINARY was set to 1 since BINARY sets collation to *_bin
+ else if([[aTableColumn identifier] isEqualToString:@"Extra"]) {
+ if(![[currentRow objectForKey:@"Extra"] isEqualToString:anObject]) {
+ if([[[currentRow objectForKey:@"Extra"] uppercaseString] isEqualToString:@"AUTO_INCREMENT"]) {
+ [currentRow setObject:[NSNumber numberWithInteger:0] forKey:@"null"];
+ }
+ [tableSourceView reloadData];
+ }
+ }
+ // Reset default to "" if field doesn't allow NULL and current default is set to NULL
+ else if([[aTableColumn identifier] isEqualToString:@"null"]) {
+ if([[currentRow objectForKey:@"null"] integerValue] != [anObject integerValue]) {
+ if([anObject integerValue] == 0) {
+ if([[currentRow objectForKey:@"default"] isEqualToString:[prefs objectForKey:SPNullValue]])
+ [currentRow setObject:@"" forKey:@"default"];
+ }
+ [tableSourceView reloadData];
+ }
+ }
+
+ // Store new value but not if user choose "---" for type and reset values if required
+ if([[aTableColumn identifier] isEqualToString:@"type"]) {
+ if(anObject && [(NSString*)anObject length] && ![(NSString*)anObject hasPrefix:@"--"]) {
+ [currentRow setObject:[(NSString*)anObject uppercaseString] forKey:@"type"];
+ // If type is BLOB or TEXT reset DEFAULT since these field types don't allow a default
+ if([[currentRow objectForKey:@"type"] hasSuffix:@"TEXT"] || [[currentRow objectForKey:@"type"] hasSuffix:@"BLOB"] || [self _isFieldTypeGeometry:[currentRow objectForKey:@"type"]]) {
+ [currentRow setObject:@"" forKey:@"default"];
+ [currentRow setObject:@"" forKey:@"length"];
+ }
+ [tableSourceView reloadData];
+ }
+ }
+ else {
+ [currentRow setObject:(anObject) ? anObject : @"" forKey:[aTableColumn identifier]];
+ }
+}
+
+/**
+ * Confirm whether to allow editing of a row. Returns YES by default, but NO for views.
+ */
+- (BOOL)tableView:(NSTableView *)aTableView shouldEditTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
+{
+ if ([tableDocumentInstance isWorking]) return NO;
+
+ // Return NO for views
+ if ([tablesListInstance tableType] == SPTableTypeView) return NO;
+
+ return YES;
+}
+
+/**
+ Begin a drag and drop operation from the table - copy a single dragged row to the drag pasteboard.
+ */
+- (BOOL)tableView:(NSTableView *)aTableView writeRowsWithIndexes:(NSIndexSet *)rows toPasteboard:(NSPasteboard*)pboard
+{
+ // Make sure that the drag operation is started from the right table view
+ if (aTableView != tableSourceView) return NO;
+
+ // Check whether a save of the current field row is required.
+ if ( ![self saveRowOnDeselect] ) return NO;
+
+ if ([rows count] == 1) {
+ [pboard declareTypes:[NSArray arrayWithObject:@"SequelProPasteboard"] owner:nil];
+ [pboard setString:[[NSNumber numberWithInteger:[rows firstIndex]] stringValue] forType:@"SequelProPasteboard"];
+ return YES;
+ }
+ else {
+ return NO;
+ }
+}
+
+/**
+ Determine whether to allow a drag and drop operation on this table - for the purposes of drag reordering,
+ validate that the original source is of the correct type and within the same table, and that the drag
+ would result in a position change.
+ */
+- (NSDragOperation)tableView:(NSTableView*)tableView validateDrop:(id <NSDraggingInfo>)info proposedRow:(NSInteger)row
+ proposedDropOperation:(NSTableViewDropOperation)operation
+{
+ //make sure that the drag operation is for the right table view
+ if (tableView!=tableSourceView) return NO;
+
+ NSArray *pboardTypes = [[info draggingPasteboard] types];
+ NSInteger originalRow;
+
+ // Ensure the drop is of the correct type
+ if (operation == NSTableViewDropAbove && row != -1 && [pboardTypes containsObject:@"SequelProPasteboard"]) {
+
+ // Ensure the drag originated within this table
+ if ([info draggingSource] == tableView) {
+ originalRow = [[[info draggingPasteboard] stringForType:@"SequelProPasteboard"] integerValue];
+
+ if (row != originalRow && row != (originalRow+1)) {
+ return NSDragOperationMove;
+ }
+ }
+ }
+
+ return NSDragOperationNone;
+}
+
+/**
+ * Having validated a drop, perform the field/column reordering to match.
+ */
+- (BOOL)tableView:(NSTableView*)tableView acceptDrop:(id <NSDraggingInfo>)info row:(NSInteger)destinationRowIndex dropOperation:(NSTableViewDropOperation)operation
+{
+ //make sure that the drag operation is for the right table view
+ if (tableView!=tableSourceView) return NO;
+
+ NSInteger originalRowIndex;
+ NSMutableString *queryString;
+ NSDictionary *originalRow;
+
+ // Extract the original row position from the pasteboard and retrieve the details
+ originalRowIndex = [[[info draggingPasteboard] stringForType:@"SequelProPasteboard"] integerValue];
+ originalRow = [[NSDictionary alloc] initWithDictionary:[tableFields objectAtIndex:originalRowIndex]];
+
+ [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:tableDocumentInstance];
+
+ // Begin construction of the reordering query
+ queryString = [NSMutableString stringWithFormat:@"ALTER TABLE %@ MODIFY COLUMN %@ %@", [selectedTable backtickQuotedString],
+ [[originalRow objectForKey:@"name"] backtickQuotedString],
+ [[originalRow objectForKey:@"type"] uppercaseString]];
+
+ // Add the length parameter if necessary
+ if ( [originalRow objectForKey:@"length"] && ![[originalRow objectForKey:@"length"] isEqualToString:@""]) {
+ [queryString appendFormat:@"(%@)", [originalRow objectForKey:@"length"]];
+ }
+
+ NSString *fieldEncoding = @"";
+ if([[originalRow objectForKey:@"encoding"] integerValue] > 0) {
+ NSString *enc = [[encodingPopupCell itemAtIndex:[[originalRow objectForKey:@"encoding"] integerValue]] title];
+ NSInteger start = [enc rangeOfString:@"("].location+1;
+ NSInteger end = [enc length] - start - 1;
+ fieldEncoding = [enc substringWithRange:NSMakeRange(start, end)];
+ [queryString appendFormat:@" CHARACTER SET %@", fieldEncoding];
+ }
+ if(![fieldEncoding length] && [tableDataInstance tableEncoding]) {
+ fieldEncoding = [tableDataInstance tableEncoding];
+ }
+ if([fieldEncoding length] && [[originalRow objectForKey:@"collation"] integerValue] > 0) {
+ NSArray *theCollations = [databaseDataInstance getDatabaseCollationsForEncoding:fieldEncoding];
+ NSString *col = [[theCollations objectAtIndex:[[originalRow objectForKey:@"collation"] integerValue]-1] objectForKey:@"COLLATION_NAME"];
+ [queryString appendFormat:@" COLLATE %@", col];
+ }
+
+
+ // Add unsigned, zerofill, binary, not null if necessary
+ if ([[originalRow objectForKey:@"unsigned"] integerValue]) {
+ [queryString appendString:@" UNSIGNED"];
+ }
+ if ([[originalRow objectForKey:@"zerofill"] integerValue]) {
+ [queryString appendString:@" ZEROFILL"];
+ }
+ if ([[originalRow objectForKey:@"binary"] integerValue]) {
+ [queryString appendString:@" BINARY"];
+ }
+ if (![[originalRow objectForKey:@"null"] integerValue]) {
+ [queryString appendString:@" NOT NULL"];
+ }
+ if (![[originalRow objectForKey:@"Extra"] isEqualToString:@"None"] ) {
+ [queryString appendString:@" "];
+ [queryString appendString:[[originalRow objectForKey:@"Extra"] uppercaseString]];
+ }
+
+ BOOL isTimestampType = [[[originalRow objectForKey:@"type"] lowercaseString] isEqualToString:@"timestamp"];
+
+ // Add the default value, skip it for auto_increment
+ if([originalRow objectForKey:@"Extra"] && ![[originalRow objectForKey:@"Extra"] isEqualToString:@"auto_increment"]) {
+ if ([[originalRow objectForKey:@"default"] isEqualToString:[prefs objectForKey:SPNullValue]]) {
+ if ([[originalRow objectForKey:@"null"] integerValue] == 1) {
+ [queryString appendString:(isTimestampType) ? @" NULL DEFAULT NULL" : @" DEFAULT NULL"];
+ }
+ }
+ else if (isTimestampType && ([[[originalRow objectForKey:@"default"] uppercaseString] isEqualToString:@"CURRENT_TIMESTAMP"]) ) {
+ [queryString appendString:@" DEFAULT CURRENT_TIMESTAMP"];
+ }
+ else {
+ [queryString appendFormat:@" DEFAULT '%@'", [mySQLConnection prepareString:[originalRow objectForKey:@"default"]]];
+ }
+ }
+
+ // Any column comments
+ if ([[originalRow objectForKey:@"comment"] length]) {
+ [queryString appendFormat:@" COMMENT '%@'", [mySQLConnection prepareString:[originalRow objectForKey:@"comment"]]];
+ }
+
+ // Unparsed details - column formats, storage, reference definitions
+ if ([originalRow objectForKey:@"unparsed"]) {
+ [queryString appendString:[originalRow objectForKey:@"unparsed"]];
+ }
+
+ // Add the new location
+ if ( destinationRowIndex == 0 ){
+ [queryString appendString:@" FIRST"];
+ } else {
+ [queryString appendFormat:@" AFTER %@",
+ [[[tableFields objectAtIndex:destinationRowIndex-1] objectForKey:@"name"] backtickQuotedString]];
+ }
+
+ // Run the query; report any errors, or reload the table on success
+ [mySQLConnection queryString:queryString];
+
+ if ([mySQLConnection queryErrored]) {
+ SPBeginAlertSheet(NSLocalizedString(@"Error moving field", @"error moving field message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil,
+ [NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to move the field.\n\nMySQL said: %@", @"error moving field informative message"), [mySQLConnection getLastErrorMessage]]);
+ }
+ else {
+ [tableDataInstance resetAllData];
+ [tablesListInstance setStatusRequiresReload:YES];
+ [self loadTable:selectedTable];
+
+ // Mark the content table cache for refresh
+ [tablesListInstance setContentRequiresReload:YES];
+
+ if ( originalRowIndex < destinationRowIndex ) {
+ [tableSourceView selectRowIndexes:[NSIndexSet indexSetWithIndex:destinationRowIndex-1] byExtendingSelection:NO];
+ } else {
+ [tableSourceView selectRowIndexes:[NSIndexSet indexSetWithIndex:destinationRowIndex] byExtendingSelection:NO];
+ }
+ }
+
+ [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance];
+
+ [originalRow release];
+
+ return YES;
+}
+
+#pragma mark -
+#pragma mark Table view delegate methods
+
+- (BOOL)tableView:(NSTableView *)aTableView shouldSelectRow:(NSInteger)rowIndex
+{
+ // If we are editing a row, attempt to save that row - if saving failed, do not select the new row.
+ if (isEditingRow && ![self addRowToDB]) return NO;
+
+ return YES;
+}
+
+/**
+ * Performs various interface validation
+ */
+- (void)tableViewSelectionDidChange:(NSNotification *)aNotification
+{
+ id object = [aNotification object];
+
+ // Check for which table view the selection changed
+ if (object == tableSourceView) {
+
+ // If we are editing a row, attempt to save that row - if saving failed, reselect the edit row.
+ if (isEditingRow && [tableSourceView selectedRow] != currentlyEditingRow && ![self saveRowOnDeselect]) return;
+
+ [copyFieldButton setEnabled:YES];
+
+ // Check if there is currently a field selected and change button state accordingly
+ if ([tableSourceView numberOfSelectedRows] > 0 && [tablesListInstance tableType] == SPTableTypeTable) {
+ [removeFieldButton setEnabled:YES];
+ } else {
+ [removeFieldButton setEnabled:NO];
+ [copyFieldButton setEnabled:NO];
+ }
+
+ // If the table only has one field, disable the remove button. This removes the need to check that the user
+ // is attempting to remove the last field in a table in removeField: above, but leave it in just in case.
+ if ([tableSourceView numberOfRows] == 1) {
+ [removeFieldButton setEnabled:NO];
+ }
+ }
+}
+
+/**
+ * Traps enter and esc and make/cancel editing without entering next row
+ */
+- (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)command
+{
+ NSInteger row, column;
+
+ row = [tableSourceView editedRow];
+ column = [tableSourceView editedColumn];
+
+ // Trap the tab key, selecting the next item in the line
+ if ( [textView methodForSelector:command] == [textView methodForSelector:@selector(insertTab:)] && [tableSourceView numberOfColumns] - 1 == column)
+ {
+ //save current line
+ [[control window] makeFirstResponder:control];
+
+ if ( [self addRowToDB] && [textView methodForSelector:command] == [textView methodForSelector:@selector(insertTab:)] ) {
+ if ( row < ([tableSourceView numberOfRows] - 1) ) {
+ [tableSourceView selectRowIndexes:[NSIndexSet indexSetWithIndex:row+1] byExtendingSelection:NO];
+ [tableSourceView editColumn:0 row:row+1 withEvent:nil select:YES];
+ } else {
+ [tableSourceView selectRowIndexes:[NSIndexSet indexSetWithIndex:0] byExtendingSelection:NO];
+ [tableSourceView editColumn:0 row:0 withEvent:nil select:YES];
+ }
+ }
+
+ return YES;
+ }
+
+ // Trap shift-tab key
+ else if ( [textView methodForSelector:command] == [textView methodForSelector:@selector(insertBacktab:)] && column < 1)
+ {
+ if ( [self addRowToDB] && [textView methodForSelector:command] == [textView methodForSelector:@selector(insertBacktab:)] ) {
+ [[control window] makeFirstResponder:control];
+ if ( row > 0) {
+ [tableSourceView selectRowIndexes:[NSIndexSet indexSetWithIndex:row-1] byExtendingSelection:NO];
+ [tableSourceView editColumn:([tableFields count]-1) row:row-1 withEvent:nil select:YES];
+ } else {
+ [tableSourceView selectRowIndexes:[NSIndexSet indexSetWithIndex:([tableFields count]-1)] byExtendingSelection:NO];
+ [tableSourceView editColumn:([tableFields count]-1) row:([tableSourceView numberOfRows]-1) withEvent:nil select:YES];
+ }
+ }
+
+ return YES;
+ }
+
+ // Trap the enter key, triggering a save
+ else if ( [textView methodForSelector:command] == [textView methodForSelector:@selector(insertNewline:)] )
+ {
+ // Suppress enter for non-text fields to allow selecting of chosen items from comboboxes or popups
+ if(![[[[[[tableSourceView tableColumns] objectAtIndex:column] dataCell] class] description] isEqualToString:@"NSTextFieldCell"])
+ return YES;
+
+ [[control window] makeFirstResponder:control];
+ [self addRowToDB];
+ [tableSourceView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
+ [[tableDocumentInstance parentWindow] makeFirstResponder:tableSourceView];
+
+ return YES;
+ }
+
+ // Trap escape, aborting the edit and reverting the row
+ else if ( [[control window] methodForSelector:command] == [[control window] methodForSelector:@selector(cancelOperation:)] )
+ {
+ [control abortEditing];
+ [self cancelRowEditing];
+
+ return YES;
+ }
+ else {
+ return NO;
+ }
+}
+
+
+/**
+ * Modify cell display by disabling table cells when a view is selected, meaning structure/index
+ * is uneditable and do cell validation due to row's field type.
+ */
+- (void)tableView:(NSTableView *)tableView willDisplayCell:(id)aCell forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
+{
+ //make sure that the message is from the right table view
+ if (tableView != tableSourceView) return;
+
+ if ([tablesListInstance tableType] == SPTableTypeView) {
+ [aCell setEnabled:NO];
+ }
+ else {
+ // validate cell against current field type
+ NSDictionary *theRow = NSArrayObjectAtIndex(tableFields, rowIndex);
+ NSString *theRowType = @"";
+ if(theRowType = [theRow objectForKey:@"type"])
+ theRowType = [[theRowType stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] uppercaseString];
+
+ // Only string fields allow encoding settings
+ if(([[aTableColumn identifier] isEqualToString:@"encoding"])) {
+ [aCell setEnabled:([self _isFieldTypeString:theRowType] && ![theRowType hasSuffix:@"BINARY"] && ![theRowType hasSuffix:@"BLOB"])];
+ }
+ // Only string fields allow collation settings and string field is not set to BINARY since BINARY sets the collation to *_bin
+ else if([[aTableColumn identifier] isEqualToString:@"collation"]){
+ [aCell setEnabled:([self _isFieldTypeString:theRowType] && [[theRow objectForKey:@"binary"] integerValue] == 0 && ![theRowType hasSuffix:@"BINARY"] && ![theRowType hasSuffix:@"BLOB"])];
+ }
+ // Check if UNSIGNED and ZEROFILL is allowed
+ else if([[aTableColumn identifier] isEqualToString:@"zerofill"] || [[aTableColumn identifier] isEqualToString:@"unsigned"]) {
+ [aCell setEnabled:([self _isFieldTypeNumeric:theRowType])];
+ }
+ // Check if BINARY is allowed
+ else if([[aTableColumn identifier] isEqualToString:@"binary"]) {
+ [aCell setEnabled:([self _isFieldTypeAllowBinary:theRowType])];
+ }
+ // TEXT, BLOB, and GEOMETRY fields don't allow a DEFAULT
+ else if([[aTableColumn identifier] isEqualToString:@"default"]) {
+ [aCell setEnabled:([theRowType hasSuffix:@"TEXT"] || [theRowType hasSuffix:@"BLOB"] || [self _isFieldTypeGeometry:theRowType]) ? NO : YES];
+ }
+ // Check allow NULL
+ else if([[aTableColumn identifier] isEqualToString:@"null"]) {
+ [aCell setEnabled:([[theRow objectForKey:@"Key"] isEqualToString:@"PRI"] || [[[theRow objectForKey:@"Extra"] uppercaseString] isEqualToString:@"AUTO_INCREMENT"]) ? NO : YES];
+ }
+ // TEXT, BLOB, date, and GEOMETRY fields don't allow a length
+ else if([[aTableColumn identifier] isEqualToString:@"length"]) {
+ [aCell setEnabled:([theRowType hasSuffix:@"TEXT"] || [theRowType hasSuffix:@"BLOB"] || [self _isFieldTypeDate:theRowType] || [self _isFieldTypeGeometry:theRowType]) ? NO : YES];
+ }
+ else {
+ [aCell setEnabled:YES];
+ }
+ }
+}
+
+#pragma mark -
+#pragma mark Split view delegate methods
+
+- (BOOL)splitView:(NSSplitView *)sender canCollapseSubview:(NSView *)subview
+{
+ return YES;
+}
+
+- (CGFloat)splitView:(NSSplitView *)sender constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)offset
+{
+ return proposedMax - 130;
+}
+
+- (CGFloat)splitView:(NSSplitView *)sender constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)offset
+{
+ return proposedMin + 130;
+}
+
+- (NSRect)splitView:(NSSplitView *)splitView additionalEffectiveRectOfDividerAtIndex:(NSInteger)dividerIndex
+{
+ return [structureGrabber convertRect:[structureGrabber bounds] toView:splitView];
+}
+
+- (void)splitViewDidResizeSubviews:(NSNotification *)aNotification
+{
+ if ([aNotification object] == tablesIndexesSplitView) {
+
+ NSView *indexesView = [[tablesIndexesSplitView subviews] objectAtIndex:1];
+
+ if ([tablesIndexesSplitView isSubviewCollapsed:indexesView]) {
+ [indexesShowButton setHidden:NO];
+ }
+ else {
+ [indexesShowButton setHidden:YES];
+ }
+ }
+}
+
+#pragma mark -
+#pragma mark Combo box delegate methods
+
+- (id)comboBoxCell:(NSComboBoxCell *)aComboBoxCell objectValueForItemAtIndex:(NSInteger)index
+{
+ return NSArrayObjectAtIndex(typeSuggestions, index);
+}
+
+- (NSInteger)numberOfItemsInComboBoxCell:(NSComboBoxCell *)aComboBoxCell
+{
+ return [typeSuggestions count];
+}
+
+/**
+ * Allow completion of field data types of lowercased input.
+ */
+- (NSString *)comboBoxCell:(NSComboBoxCell *)aComboBoxCell completedString:(NSString *)uncompletedString
+{
+ if ([uncompletedString hasPrefix:@"-"]) return @"";
+
+ NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF BEGINSWITH[c] %@", [uncompletedString uppercaseString]];
+ NSArray *result = [typeSuggestions filteredArrayUsingPredicate:predicate];
+
+ if (result && [result count]) return [result objectAtIndex:0];
+
+ return @"";
+}
+
+
+@end