aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Source/SPDatabaseDocument.m1
-rw-r--r--Source/SPIndexesController.m1
-rw-r--r--Source/SPTableStructure.h6
-rw-r--r--Source/SPTableStructure.m1147
-rw-r--r--Source/SPTableStructureDelegate.h44
-rw-r--r--Source/SPTableStructureDelegate.m868
-rw-r--r--Source/SPTableStructureLoading.h44
-rw-r--r--Source/SPTableStructureLoading.m368
-rw-r--r--sequel-pro.xcodeproj/project.pbxproj12
9 files changed, 1151 insertions, 1340 deletions
diff --git a/Source/SPDatabaseDocument.m b/Source/SPDatabaseDocument.m
index cdef75a1..1961cbe7 100644
--- a/Source/SPDatabaseDocument.m
+++ b/Source/SPDatabaseDocument.m
@@ -82,7 +82,6 @@
#import "SPTableTriggers.h"
#ifdef SP_CODA /* headers */
#import "SPTableStructure.h"
-#import "SPTableStructureLoading.h"
#endif
#import "SPPrintAccessory.h"
#import "MGTemplateEngine.h"
diff --git a/Source/SPIndexesController.m b/Source/SPIndexesController.m
index 661b1b7f..c5b5d14c 100644
--- a/Source/SPIndexesController.m
+++ b/Source/SPIndexesController.m
@@ -37,7 +37,6 @@
#import "SPTablesList.h"
#import "SPTableView.h"
#import "SPTableStructure.h"
-#import "SPTableStructureLoading.h"
#import "SPThreadAdditions.h"
#import "SPFunctions.h"
diff --git a/Source/SPTableStructure.h b/Source/SPTableStructure.h
index c8b0ec0a..ff71c0b6 100644
--- a/Source/SPTableStructure.h
+++ b/Source/SPTableStructure.h
@@ -175,4 +175,10 @@
+ (SPFieldTypeHelp *)helpForFieldType:(NSString *)typeName;
+#pragma mark - SPTableStructureLoading
+
+- (void)loadTable:(NSString *)aTable;
+- (IBAction)reloadTable:(id)sender;
+- (void)setTableDetails:(NSDictionary *)tableDetails;
+
@end
diff --git a/Source/SPTableStructure.m b/Source/SPTableStructure.m
index 4120dbf8..d3723f86 100644
--- a/Source/SPTableStructure.m
+++ b/Source/SPTableStructure.m
@@ -42,10 +42,13 @@
#import "SPIndexesController.h"
#import "RegexKitLite.h"
#import "SPTableFieldValidation.h"
-#import "SPTableStructureLoading.h"
#import "SPThreadAdditions.h"
#import "SPServerSupport.h"
#import "SPExtendedTableInfo.h"
+#import "SPFunctions.h"
+#import "SPPillAttachmentCell.h"
+#import "SPIdMenu.h"
+#import "SPComboBoxCell.h"
#import <SPMySQL/SPMySQL.h>
@@ -88,11 +91,33 @@ static inline SPFieldTypeHelp *MakeFieldTypeHelp(NSString *typeName,NSString *ty
return [obj autorelease];
}
-@interface SPTableStructure (PrivateAPI)
+struct _cmpMap {
+ NSString *title; // the title of the "pill"
+ NSString *tooltipPart; // the tooltip of the menuitem
+ NSString *cmpWith; // the string to match against
+};
+/**
+ * This function will compare the representedObject of every item in menu against
+ * every map->cmpWith. If they match it will append a pill-like (similar to a TokenFieldCell's token)
+ * element labelled map->title to the menu item's title. If map->tooltipPart is set,
+ * it will also be added to the menu item's tooltip.
+ *
+ * This is used with the encoding/collation popup menus to add visual indicators for the
+ * table-level and default encoding/collation.
+ */
+static void _BuildMenuWithPills(NSMenu *menu,struct _cmpMap *map,size_t mapEntries);
+
+@interface SPTableStructure ()
+
+- (void)sheetDidEnd:(id)sheet returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo;
- (void)_removeFieldAndForeignKey:(NSNumber *)removeForeignKey;
- (NSString *)_buildPartialColumnDefinitionString:(NSDictionary *)theRow;
+#pragma mark - SPTableStructureDelegate
+
+- (void)_displayFieldTypeHelpIfPossible:(SPComboBoxCell *)cell;
+
@end
@implementation SPTableStructure
@@ -1513,6 +1538,1092 @@ static inline SPFieldTypeHelp *MakeFieldTypeHelp(NSString *typeName,NSString *ty
}
#pragma mark -
+#pragma mark Table loading
+
+/**
+ * Loads aTable, puts it in an array, updates the tableViewColumns and reloads the tableView.
+ */
+- (void)loadTable:(NSString *)aTable
+{
+ NSMutableDictionary *theTableEnumLists = [NSMutableDictionary dictionary];
+
+ // Check whether a save of the current row is required.
+ if (![[self onMainThread] saveRowOnDeselect]) return;
+
+ // If no table is selected, reset the interface and return
+ if (!aTable || ![aTable length]) {
+ [[self onMainThread] setTableDetails:nil];
+ return;
+ }
+
+ NSMutableArray *theTableFields = [[NSMutableArray alloc] init];
+
+ // Make a mutable copy out of the cached [tableDataInstance columns] since we're adding infos
+ for (id col in [tableDataInstance columns])
+ {
+ [theTableFields addObject:[[col mutableCopy] autorelease]];
+ }
+
+ // Retrieve the indexes for the table
+ SPMySQLResult *indexResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW INDEX FROM %@", [aTable backtickQuotedString]]];
+
+ // If an error occurred, reset the interface and abort
+ if ([mySQLConnection queryErrored]) {
+ [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance];
+ [[self onMainThread] setTableDetails:nil];
+
+ if ([mySQLConnection isConnected]) {
+ SPOnewayAlertSheet(
+ NSLocalizedString(@"Error", @"error"),
+ [NSApp mainWindow],
+ [NSString stringWithFormat:NSLocalizedString(@"An error occurred while retrieving information.\nMySQL said: %@", @"message of panel when retrieving information failed"), [mySQLConnection lastErrorMessage]]
+ );
+ }
+
+ return;
+ }
+
+ // Process the indexes into a local array of dictionaries
+ NSArray *theTableIndexes = [self convertIndexResultToArray:indexResult];
+
+ // Set the Key column
+ for (NSDictionary* theIndex in theTableIndexes)
+ {
+ for (id field in theTableFields)
+ {
+ if ([[field objectForKey:@"name"] isEqualToString:[theIndex objectForKey:@"Column_name"]]) {
+ if ([[theIndex objectForKey:@"Key_name"] isEqualToString:@"PRIMARY"]) {
+ [field setObject:@"PRI" forKey:@"Key"];
+ }
+ else {
+ if ([[field objectForKey:@"typegrouping"] isEqualToString:@"geometry"]) {
+ [field setObject:@"SPA" forKey:@"Key"];
+ }
+ else {
+ [field setObject:(([[theIndex objectForKey:@"Non_unique"] isEqualToString:@"1"]) ? @"MUL" : @"UNI") forKey:@"Key"];
+ }
+ }
+
+ break;
+ }
+ }
+ }
+
+ // Set up the encoding PopUpButtonCell
+ NSArray *encodings = [databaseDataInstance getDatabaseCharacterSetEncodings];
+
+ SPMainQSync(^{
+ [encodingPopupCell removeAllItems];
+
+ if ([encodings count]) {
+
+ [encodingPopupCell addItemWithTitle:@"dummy"];
+ //copy the default attributes and add gray color
+ NSMutableDictionary *defaultAttrs = [NSMutableDictionary dictionaryWithDictionary:[[encodingPopupCell attributedTitle] attributesAtIndex:0 effectiveRange:NULL]];
+ [defaultAttrs setObject:[NSColor lightGrayColor] forKey:NSForegroundColorAttributeName];
+ [[encodingPopupCell lastItem] setTitle:@""];
+
+ for (NSDictionary *encoding in encodings)
+ {
+ NSString *encodingName = [encoding objectForKey:@"CHARACTER_SET_NAME"];
+ NSString *title = (![encoding objectForKey:@"DESCRIPTION"]) ? encodingName : [NSString stringWithFormat:@"%@ (%@)", [encoding objectForKey:@"DESCRIPTION"], encodingName];
+
+ [encodingPopupCell addItemWithTitle:title];
+ NSMenuItem *item = [encodingPopupCell lastItem];
+
+ [item setRepresentedObject:encodingName];
+
+ if ([encodingName isEqualToString:[tableDataInstance tableEncoding]]) {
+
+ NSAttributedString *itemString = [[NSAttributedString alloc] initWithString:[item title] attributes:defaultAttrs];
+
+ [item setAttributedTitle:[itemString autorelease]];
+ }
+ }
+ }
+ else {
+ [encodingPopupCell addItemWithTitle:NSLocalizedString(@"Not available", @"not available label")];
+ }
+ });
+
+ // Process all the fields to normalise keys and add additional information
+ for (id theField in theTableFields)
+ {
+ NSString *type = [[[theField objectForKey:@"type"] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] uppercaseString];
+
+ if([type isEqualToString:@"JSON"]) {
+ // MySQL 5.7 manual:
+ // "MySQL handles strings used in JSON context using the utf8mb4 character set and utf8mb4_bin collation.
+ // Strings in other character set are converted to utf8mb4 as necessary."
+ [theField setObject:@"utf8mb4" forKey:@"encodingName"];
+ [theField setObject:@"utf8mb4_bin" forKey:@"collationName"];
+ [theField setObject:@1 forKey:@"binary"];
+ }
+ else if ([fieldValidation isFieldTypeString:type]) {
+ // The MySQL 4.1 manual says:
+ //
+ // MySQL chooses the column character set and collation in the following manner:
+ // 1. If both CHARACTER SET X and COLLATE Y were specified, then character set X and collation Y are used.
+ // 2. If CHARACTER SET X was specified without COLLATE, then character set X and its default collation are used.
+ // 3. If COLLATE Y was specified without CHARACTER SET, then the character set associated with Y and collation Y.
+ // 4. Otherwise, the table character set and collation are used.
+ NSString *encoding = [theField objectForKey:@"encoding"];
+ NSString *collation = [theField objectForKey:@"collation"];
+ if(encoding) {
+ if(collation) {
+ // 1
+ }
+ else {
+ collation = [databaseDataInstance getDefaultCollationForEncoding:encoding]; // 2
+ }
+ }
+ else {
+ if(collation) {
+ encoding = [databaseDataInstance getEncodingFromCollation:collation]; // 3
+ }
+ else {
+ encoding = [tableDataInstance tableEncoding]; //4
+ collation = [tableDataInstance statusValueForKey:@"Collation"];
+ if(!collation) {
+ // should not happen, as the TABLE STATUS output always(?) includes the collation
+ collation = [databaseDataInstance getDefaultCollationForEncoding:encoding];
+ }
+ }
+ }
+
+ // MySQL < 4.1 does not support collations (they are part of the charset), it will be nil there
+
+ [theField setObject:encoding forKey:@"encodingName"];
+ [theField setObject:collation forKey:@"collationName"];
+
+ // Set BINARY if collation ends with _bin for convenience
+ if ([collation hasSuffix:@"_bin"]) {
+ [theField setObject:@1 forKey:@"binary"];
+ }
+ }
+
+ // Get possible values if the field is an enum or a set
+ if (([type isEqualToString:@"ENUM"] || [type isEqualToString:@"SET"]) && [theField objectForKey:@"values"]) {
+ [theTableEnumLists setObject:[NSArray arrayWithArray:[theField objectForKey:@"values"]] forKey:[theField objectForKey:@"name"]];
+ [theField setObject:[NSString stringWithFormat:@"'%@'", [[theField objectForKey:@"values"] componentsJoinedByString:@"','"]] forKey:@"length"];
+ }
+
+ // Join length and decimals if any
+ if ([theField objectForKey:@"decimals"])
+ [theField setObject:[NSString stringWithFormat:@"%@,%@", [theField objectForKey:@"length"], [theField objectForKey:@"decimals"]] forKey:@"length"];
+
+ // Normalize default
+ if (![theField objectForKey:@"default"]) {
+ [theField setObject:@"" forKey:@"default"];
+ }
+ else if ([[theField objectForKey:@"default"] isNSNull]) {
+ [theField setObject:[prefs stringForKey:SPNullValue] forKey:@"default"];
+ }
+
+ // Init Extra field
+ [theField setObject:@"None" forKey:@"Extra"];
+
+ // Check for auto_increment and set Extra accordingly
+ if ([[theField objectForKey:@"autoincrement"] integerValue]) {
+ [theField setObject:@"auto_increment" forKey:@"Extra"];
+ }
+
+ // For timestamps/datetime check to see whether "on update CURRENT_TIMESTAMP" and set Extra accordingly
+ else if ([type isInArray:@[@"TIMESTAMP",@"DATETIME"]] && [[theField objectForKey:@"onupdatetimestamp"] boolValue]) {
+ NSString *ouct = @"on update CURRENT_TIMESTAMP";
+ // restore a length parameter if the field has fractional seconds.
+ // the parameter of current_timestamp MUST match the field's length in that case, so we can just 'guess' it.
+ NSString *fieldLen = [theField objectForKey:@"length"];
+ if([fieldLen length] && ![fieldLen isEqualToString:@"0"]) {
+ ouct = [ouct stringByAppendingFormat:@"(%@)",fieldLen];
+ }
+ [theField setObject:ouct forKey:@"Extra"];
+ }
+ }
+
+ // Set up the table details for the new table, and request an data/interface update
+ NSDictionary *tableDetails = [NSDictionary dictionaryWithObjectsAndKeys:
+ aTable, @"name",
+ theTableFields, @"tableFields",
+ theTableIndexes, @"tableIndexes",
+ theTableEnumLists, @"enumLists",
+ nil];
+
+ [[self onMainThread] setTableDetails:tableDetails];
+
+ isCurrentExtraAutoIncrement = [tableDataInstance tableHasAutoIncrementField];
+ autoIncrementIndex = nil;
+
+ // Send the query finished/work complete notification
+ [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance];
+
+ [theTableFields release];
+}
+
+/**
+ * Reloads the table (performing a new query).
+ */
+- (IBAction)reloadTable:(id)sender
+{
+ // Check whether a save of the current row is required
+ if (![[self onMainThread] saveRowOnDeselect]) return;
+
+ [tableDataInstance resetAllData];
+ [tableDocumentInstance setStatusRequiresReload:YES];
+
+ // Query the structure of all databases in the background (mainly for completion)
+ [[tableDocumentInstance databaseStructureRetrieval] queryDbStructureInBackgroundWithUserInfo:@{@"forceUpdate" : @YES}];
+
+ [self loadTable:selectedTable];
+}
+
+/**
+ * Updates the stored table details and updates the interface to match.
+ *
+ * Should be called on the main thread.
+ */
+- (void)setTableDetails:(NSDictionary *)tableDetails
+{
+ NSString *newTableName = [tableDetails objectForKey:@"name"];
+ NSMutableDictionary *newDefaultValues;
+
+ BOOL enableInteraction =
+#ifndef SP_CODA /* patch */
+ ![[tableDocumentInstance selectedToolbarItemIdentifier] isEqualToString:SPMainToolbarTableStructure] ||
+#endif
+ ![tableDocumentInstance isWorking];
+
+ // Update the selected table name
+ if (selectedTable) SPClear(selectedTable);
+ if (newTableName) selectedTable = [[NSString alloc] initWithString:newTableName];
+
+ [indexesController setTable:selectedTable];
+
+ // Reset the table store and display
+ [tableSourceView deselectAll:self];
+ [tableFields removeAllObjects];
+ [enumFields removeAllObjects];
+ [indexesTableView deselectAll:self];
+ [addFieldButton setEnabled:NO];
+ [duplicateFieldButton setEnabled:NO];
+ [removeFieldButton setEnabled:NO];
+#ifndef SP_CODA
+ [addIndexButton setEnabled:NO];
+ [removeIndexButton setEnabled:NO];
+ [editTableButton setEnabled:NO];
+#endif
+
+ // If no table is selected, refresh the table/index display to blank and return
+ if (!selectedTable) {
+ [tableSourceView reloadData];
+ // Empty indexesController's fields and indices explicitly before reloading
+ [indexesController setFields:@[]];
+ [indexesController setIndexes:@[]];
+ [indexesTableView reloadData];
+
+ return;
+ }
+
+ // Update the fields and indexes stores
+ [tableFields setArray:[tableDetails objectForKey:@"tableFields"]];
+
+ [indexesController setFields:tableFields];
+ [indexesController setIndexes:[tableDetails objectForKey:@"tableIndexes"]];
+
+ if (defaultValues) SPClear(defaultValues);
+
+ newDefaultValues = [NSMutableDictionary dictionaryWithCapacity:[tableFields count]];
+
+ for (id theField in tableFields)
+ {
+ [newDefaultValues setObject:[theField objectForKey:@"default"] forKey:[theField objectForKey:@"name"]];
+ }
+
+ defaultValues = [[NSDictionary dictionaryWithDictionary:newDefaultValues] retain];
+
+#ifndef SP_CODA
+ // Enable the edit table button
+ [editTableButton setEnabled:enableInteraction];
+#endif
+
+ // If a view is selected, disable the buttons; otherwise enable.
+ BOOL editingEnabled = ([tablesListInstance tableType] == SPTableTypeTable) && enableInteraction;
+
+ [addFieldButton setEnabled:editingEnabled];
+#ifndef SP_CODA
+ [addIndexButton setEnabled:editingEnabled && ![[[tableDataInstance statusValueForKey:@"Engine"] uppercaseString] isEqualToString:@"CSV"]];
+#endif
+
+ // Reload the views
+ [indexesTableView reloadData];
+ [tableSourceView reloadData];
+}
+
+#pragma mark - SPTableStructureDelegate
+
+#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 ((NSUInteger)rowIndex >= [tableFields count]) return @"...";
+
+ NSDictionary *rowData = NSArrayObjectAtIndex(tableFields, rowIndex);
+
+ if ([[tableColumn identifier] isEqualToString:@"collation"]) {
+ NSString *tableEncoding = [tableDataInstance tableEncoding];
+ NSString *columnEncoding = [rowData objectForKey:@"encodingName"];
+ NSString *columnCollation = [rowData objectForKey:@"collationName"]; // loadTable: has already inferred it, if not set explicit
+
+#warning Building the collation menu here is a big performance hog. This should be done in menuNeedsUpdate: below!
+ NSPopUpButtonCell *collationCell = [tableColumn dataCell];
+ [collationCell removeAllItems];
+ [collationCell addItemWithTitle:@"dummy"];
+ //copy the default style of menu items and add gray color for default item
+ NSMutableDictionary *menuAttrs = [NSMutableDictionary dictionaryWithDictionary:[[collationCell attributedTitle] attributesAtIndex:0 effectiveRange:NULL]];
+ [menuAttrs setObject:[NSColor lightGrayColor] forKey:NSForegroundColorAttributeName];
+ [[collationCell lastItem] setTitle:@""];
+
+ //if this is not set the column either has no encoding (numeric etc.) or retrieval failed. Either way we can't provide collations
+ if([columnEncoding length]) {
+ collations = [databaseDataInstance getDatabaseCollationsForEncoding:columnEncoding];
+
+ if ([collations count] > 0) {
+ NSString *tableCollation = [[tableDataInstance statusValues] objectForKey:@"Collation"];
+
+ if (![tableCollation length]) {
+ tableCollation = [databaseDataInstance getDefaultCollationForEncoding:tableEncoding];
+ }
+
+ BOOL columnUsesTableDefaultEncoding = ([columnEncoding isEqualToString:tableEncoding]);
+ // Populate collation popup button
+ for (NSDictionary *collation in collations)
+ {
+ NSString *collationName = [collation objectForKey:@"COLLATION_NAME"];
+
+ [collationCell addItemWithTitle:collationName];
+ NSMenuItem *item = [collationCell lastItem];
+ [item setRepresentedObject:collationName];
+
+ // If this matches the table's collation, draw in gray
+ if (columnUsesTableDefaultEncoding && [collationName isEqualToString:tableCollation]) {
+ NSAttributedString *itemString = [[NSAttributedString alloc] initWithString:[item title] attributes:menuAttrs];
+ [item setAttributedTitle:[itemString autorelease]];
+ }
+ }
+
+ // the popup cell is subclassed to take the representedObject instead of the item index
+ return columnCollation;
+ }
+ }
+
+ return nil;
+ }
+ else if ([[tableColumn identifier] isEqualToString:@"encoding"]) {
+ // the encoding menu was already configured during setTableDetails:
+ NSString *columnEncoding = [rowData objectForKey:@"encodingName"];
+
+ if([columnEncoding length]) {
+ NSInteger idx = [encodingPopupCell indexOfItemWithRepresentedObject:columnEncoding];
+ if(idx > 0) return @(idx);
+ }
+
+ return @0;
+ }
+ else if ([[tableColumn identifier] isEqualToString:@"Extra"]) {
+ id dataCell = [tableColumn dataCell];
+
+ [dataCell removeAllItems];
+
+ // Populate Extra suggestion popup button
+ for (id item in extraFieldSuggestions)
+ {
+ if (!(isCurrentExtraAutoIncrement && [item isEqualToString:@"auto_increment"])) {
+ [dataCell addItemWithObjectValue:item];
+ }
+ }
+ }
+
+ return [rowData objectForKey:[tableColumn identifier]];
+}
+
+- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
+{
+ // Make sure that the operation is for the right table view
+ if (aTableView != tableSourceView) return;
+
+ NSMutableDictionary *currentRow = NSArrayObjectAtIndex(tableFields,rowIndex);
+
+ if (!isEditingRow) {
+ [oldRow setDictionary:currentRow];
+ isEditingRow = YES;
+ currentlyEditingRow = rowIndex;
+ }
+
+ // Reset collation if encoding was changed
+ if ([[aTableColumn identifier] isEqualToString:@"encoding"]) {
+ NSString *oldEncoding = [currentRow objectForKey:@"encodingName"];
+ NSString *newEncoding = [[encodingPopupCell itemAtIndex:[anObject integerValue]] representedObject];
+ if (![oldEncoding isEqualToString:newEncoding]) {
+ [currentRow removeObjectForKey:@"collationName"];
+ [tableSourceView reloadData];
+ }
+ if(!newEncoding)
+ [currentRow removeObjectForKey:@"encodingName"];
+ else
+ [currentRow setObject:newEncoding forKey:@"encodingName"];
+ return;
+ }
+ else if ([[aTableColumn identifier] isEqualToString:@"collation"]) {
+ //the popup button is subclassed to return the representedObject instead of the item index
+ NSString *newCollation = anObject;
+
+ if(!newCollation)
+ [currentRow removeObjectForKey:@"collationName"];
+ else
+ [currentRow setObject:newCollation forKey:@"collationName"];
+ return;
+ }
+ // Reset collation if BINARY was changed, as enabling BINARY sets collation to *_bin
+ else if ([[aTableColumn identifier] isEqualToString:@"binary"]) {
+ if ([[currentRow objectForKey:@"binary"] integerValue] != [anObject integerValue]) {
+ [currentRow removeObjectForKey:@"collationName"];
+
+ [tableSourceView reloadData];
+ }
+ }
+ // Set null field to "do not allow NULL" for auto_increment Extra and reset Extra suggestion list
+ else if ([[aTableColumn identifier] isEqualToString:@"Extra"]) {
+ if (![[currentRow objectForKey:@"Extra"] isEqualToString:anObject]) {
+
+ isCurrentExtraAutoIncrement = [[[anObject stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] uppercaseString] isEqualToString:@"AUTO_INCREMENT"];
+
+ if (isCurrentExtraAutoIncrement) {
+ [currentRow setObject:@0 forKey:@"null"];
+
+ // Asks the user to add an index to query if AUTO_INCREMENT is set and field isn't indexed
+ if ((![currentRow objectForKey:@"Key"] || [[currentRow objectForKey:@"Key"] isEqualToString:@""])) {
+#ifndef SP_CODA
+ [chooseKeyButton selectItemWithTag:SPPrimaryKeyMenuTag];
+
+ [NSApp beginSheet:keySheet
+ modalForWindow:[tableDocumentInstance parentWindow] modalDelegate:self
+ didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
+ contextInfo:@"autoincrementindex" ];
+#endif
+ }
+ }
+ else {
+ autoIncrementIndex = nil;
+ }
+
+ id dataCell = [aTableColumn dataCell];
+
+ [dataCell removeAllItems];
+ [dataCell addItemsWithObjectValues:extraFieldSuggestions];
+ [dataCell noteNumberOfItemsChanged];
+ [dataCell reloadData];
+
+ [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
+ else 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"] ||
+ [[currentRow objectForKey:@"type"] isEqualToString:@"JSON"] ||
+ [fieldValidation isFieldTypeGeometry:[currentRow objectForKey:@"type"]] ||
+ ([fieldValidation isFieldTypeDate:[currentRow objectForKey:@"type"]] && ![[currentRow objectForKey:@"type"] isEqualToString:@"YEAR"]))
+ {
+ [currentRow setObject:@"" forKey:@"default"];
+ [currentRow setObject:@"" forKey:@"length"];
+ }
+
+ [tableSourceView reloadData];
+ }
+ return;
+ }
+
+ [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:@[SPDefaultPasteboardDragType] owner:nil];
+ [pboard setString:[NSString stringWithFormat:@"%lu",[rows firstIndex]] forType:SPDefaultPasteboardDragType];
+
+ return YES;
+ }
+
+ 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 NSDragOperationNone;
+
+ NSArray *pboardTypes = [[info draggingPasteboard] types];
+ NSInteger originalRow;
+
+ // Ensure the drop is of the correct type
+ if (operation == NSTableViewDropAbove && row != -1 && [pboardTypes containsObject:SPDefaultPasteboardDragType]) {
+
+ // Ensure the drag originated within this table
+ if ([info draggingSource] == tableView) {
+ originalRow = [[[info draggingPasteboard] stringForType:SPDefaultPasteboardDragType] 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;
+
+ // Extract the original row position from the pasteboard and retrieve the details
+ NSInteger originalRowIndex = [[[info draggingPasteboard] stringForType:SPDefaultPasteboardDragType] integerValue];
+ NSDictionary *originalRow = [[NSDictionary alloc] initWithDictionary:[tableFields objectAtIndex:originalRowIndex]];
+
+ [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:tableDocumentInstance];
+
+ // Begin construction of the reordering query
+ NSMutableString *queryString = [NSMutableString stringWithFormat:@"ALTER TABLE %@ MODIFY COLUMN %@",
+ [selectedTable backtickQuotedString],
+ [self _buildPartialColumnDefinitionString:originalRow]];
+
+ [queryString appendString:@" "];
+ // 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]) {
+ SPOnewayAlertSheet(
+ NSLocalizedString(@"Error moving field", @"error moving field message"),
+ [tableDocumentInstance parentWindow],
+ [NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to move the field.\n\nMySQL said: %@", @"error moving field informative message"), [mySQLConnection lastErrorMessage]]
+ );
+ }
+ else {
+ [tableDataInstance resetAllData];
+ [tableDocumentInstance setStatusRequiresReload:YES];
+
+ [self loadTable:selectedTable];
+
+ // Mark the content table cache for refresh
+ [tableDocumentInstance setContentRequiresReload:YES];
+
+ [tableSourceView selectRowIndexes:[NSIndexSet indexSetWithIndex:destinationRowIndex - ((originalRowIndex < destinationRowIndex) ? 1 : 0)] 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
+{
+ // Check for which table view the selection changed
+ if ([aNotification 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;
+
+ [duplicateFieldButton 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];
+ [duplicateFieldButton 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:([tableSourceView numberOfColumns] - 1) row:row - 1 withEvent:nil select:YES];
+ }
+ else {
+ [tableSourceView selectRowIndexes:[NSIndexSet indexSetWithIndex:([tableFields count] - 1)] byExtendingSelection:NO];
+ [tableSourceView editColumn:([tableSourceView numberOfColumns] - 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;
+ }
+
+ 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 *)tableColumn 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
+ NSString *rowType;
+ NSDictionary *row = NSArrayObjectAtIndex(tableFields, rowIndex);
+
+ if ((rowType = [row objectForKey:@"type"])) {
+ rowType = [[rowType stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] uppercaseString];
+ }
+
+ // Only string fields allow encoding settings, but JSON only uses utf8mb4
+ if (([[tableColumn identifier] isEqualToString:@"encoding"])) {
+ [aCell setEnabled:(![rowType isEqualToString:@"JSON"] && [fieldValidation isFieldTypeString:rowType] && [[tableDocumentInstance serverSupport] supportsPost41CharacterSetHandling])];
+ }
+
+ // Only string fields allow collation settings and string field is not set to BINARY since BINARY sets the collation to *_bin
+ else if ([[tableColumn identifier] isEqualToString:@"collation"]) {
+ // JSON always uses utf8mb4_bin which is already covered by this logic
+ [aCell setEnabled:([fieldValidation isFieldTypeString:rowType] && [[row objectForKey:@"binary"] integerValue] == 0 && [[tableDocumentInstance serverSupport] supportsPost41CharacterSetHandling])];
+ }
+
+ // Check if UNSIGNED and ZEROFILL is allowed
+ else if ([[tableColumn identifier] isEqualToString:@"zerofill"] || [[tableColumn identifier] isEqualToString:@"unsigned"]) {
+ [aCell setEnabled:([fieldValidation isFieldTypeNumeric:rowType] && ![rowType isEqualToString:@"BIT"])];
+ }
+
+ // Check if BINARY is allowed
+ else if ([[tableColumn identifier] isEqualToString:@"binary"]) {
+ // JSON always uses utf8mb4_bin
+ [aCell setEnabled:(![rowType isEqualToString:@"JSON"] && [fieldValidation isFieldTypeAllowBinary:rowType])];
+ }
+
+ // TEXT, BLOB, GEOMETRY and JSON fields don't allow a DEFAULT
+ else if ([[tableColumn identifier] isEqualToString:@"default"]) {
+ [aCell setEnabled:([rowType hasSuffix:@"TEXT"] || [rowType hasSuffix:@"BLOB"] || [rowType isEqualToString:@"JSON"] || [fieldValidation isFieldTypeGeometry:rowType]) ? NO : YES];
+ }
+
+ // Check allow NULL
+ else if ([[tableColumn identifier] isEqualToString:@"null"]) {
+ [aCell setEnabled:([[row objectForKey:@"Key"] isEqualToString:@"PRI"] ||
+ [[[row objectForKey:@"Extra"] uppercaseString] isEqualToString:@"AUTO_INCREMENT"] ||
+ [[[tableDataInstance statusValueForKey:@"Engine"] uppercaseString] isEqualToString:@"CSV"]) ? NO : YES];
+ }
+
+ // TEXT, BLOB, date, GEOMETRY and JSON fields don't allow a length
+ else if ([[tableColumn identifier] isEqualToString:@"length"]) {
+ [aCell setEnabled:([rowType hasSuffix:@"TEXT"] ||
+ [rowType hasSuffix:@"BLOB"] ||
+ [rowType isEqualToString:@"JSON"] ||
+ ([fieldValidation isFieldTypeDate:rowType] && ![[tableDocumentInstance serverSupport] supportsFractionalSeconds] && ![rowType isEqualToString:@"YEAR"]) ||
+ [fieldValidation isFieldTypeGeometry:rowType]) ? NO : YES];
+ }
+ else {
+ [aCell setEnabled:YES];
+ }
+ }
+}
+
+#pragma mark -
+#pragma mark Split view delegate methods
+#ifndef SP_CODA /* 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];
+ }
+ }
+}
+#endif
+
+#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 count]) return [result objectAtIndex:0];
+
+ return @"";
+}
+
+- (void)comboBoxCell:(SPComboBoxCell *)cell willPopUpWindow:(NSWindow *)win
+{
+ // the selected item in the popup list is independent of the displayed text, we have to explicitly set it, too
+ NSInteger pos = [typeSuggestions indexOfObject:[cell stringValue]];
+ if(pos != NSNotFound) {
+ [cell selectItemAtIndex:pos];
+ [cell scrollItemAtIndexToTop:pos];
+ }
+
+ //set up the help window to the right position
+ NSRect listFrame = [win frame];
+ NSRect helpFrame = [structureHelpPanel frame];
+ helpFrame.origin.y = listFrame.origin.y;
+ helpFrame.size.height = listFrame.size.height;
+ [structureHelpPanel setFrame:helpFrame display:YES];
+
+ [self _displayFieldTypeHelpIfPossible:cell];
+}
+
+- (void)comboBoxCell:(SPComboBoxCell *)cell willDismissWindow:(NSWindow *)win
+{
+ //hide the window if it is still visible
+ [structureHelpPanel orderOut:nil];
+}
+
+- (void)comboBoxCellSelectionDidChange:(SPComboBoxCell *)cell
+{
+ [self _displayFieldTypeHelpIfPossible:cell];
+}
+
+- (void)_displayFieldTypeHelpIfPossible:(SPComboBoxCell *)cell
+{
+ NSString *selected = [typeSuggestions objectOrNilAtIndex:[cell indexOfSelectedItem]];
+
+ const SPFieldTypeHelp *help = [[self class] helpForFieldType:selected];
+
+ if(help) {
+ NSMutableAttributedString *as = [[NSMutableAttributedString alloc] init];
+
+ //title
+ {
+ NSDictionary *titleAttr = @{NSFontAttributeName: [NSFont boldSystemFontOfSize:[NSFont systemFontSize]]};
+ NSAttributedString *title = [[NSAttributedString alloc] initWithString:[help typeDefinition] attributes:titleAttr];
+ [as appendAttributedString:[title autorelease]];
+ [[as mutableString] appendString:@"\n"];
+ }
+
+ //range
+ if([[help typeRange] length]) {
+ NSDictionary *rangeAttr = @{NSFontAttributeName: [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]};
+ NSAttributedString *range = [[NSAttributedString alloc] initWithString:[help typeRange] attributes:rangeAttr];
+ [as appendAttributedString:[range autorelease]];
+ [[as mutableString] appendString:@"\n"];
+ }
+
+ [[as mutableString] appendString:@"\n"];
+
+ //description
+ {
+ NSDictionary *descAttr = @{NSFontAttributeName: [NSFont systemFontOfSize:[NSFont systemFontSize]]};
+ NSAttributedString *desc = [[NSAttributedString alloc] initWithString:[help typeDescription] attributes:descAttr];
+ [as appendAttributedString:[desc autorelease]];
+ }
+
+ [as addAttribute:NSParagraphStyleAttributeName value:[NSParagraphStyle defaultParagraphStyle] range:NSMakeRange(0, [as length])];
+
+ [[structureHelpText textStorage] setAttributedString:[as autorelease]];
+
+ NSRect rect = [as boundingRectWithSize:NSMakeSize([structureHelpText frame].size.width-2, CGFLOAT_MAX) options:NSStringDrawingUsesFontLeading|NSStringDrawingUsesLineFragmentOrigin];
+
+ NSRect winRect = [structureHelpPanel frame];
+
+ CGFloat winAddonSize = (winRect.size.height - [[structureHelpPanel contentView] frame].size.height) + (6*2);
+
+ NSRect popUpFrame = [[cell spPopUpWindow] frame];
+
+ //determine the side on which to add our window based on the space left on screen
+ NSPoint topRightCorner = NSMakePoint(popUpFrame.origin.x, NSMaxY(popUpFrame));
+ NSRect screenRect = [NSScreen rectOfScreenAtPoint:topRightCorner];
+
+ if(NSMaxX(popUpFrame)+10+winRect.size.width > NSMaxX(screenRect)-10) {
+ // exceeds right border, display on the left
+ winRect.origin.x = popUpFrame.origin.x - 10 - winRect.size.width;
+ }
+ else {
+ // display on the right
+ winRect.origin.x = NSMaxX(popUpFrame)+10;
+ }
+
+ winRect.size.height = rect.size.height + winAddonSize;
+ winRect.origin.y = NSMaxY(popUpFrame) - winRect.size.height;
+ [structureHelpPanel setFrame:winRect display:YES];
+
+ [structureHelpPanel orderFront:nil];
+ }
+ else {
+ [structureHelpPanel orderOut:nil];
+ }
+}
+
+#pragma mark -
+#pragma mark Menu delegate methods (encoding/collation dropdown menu)
+
+- (void)menuNeedsUpdate:(SPIdMenu *)menu
+{
+ if(![menu isKindOfClass:[SPIdMenu class]]) return;
+ //NOTE: NSTableView will usually copy the menu and call this method on the copy. Matching with == won't work!
+
+ //walk through the menu and clear the attributedTitle if set. This will remove the gray color from the default items
+ for(NSMenuItem *item in [menu itemArray]) {
+ if([item attributedTitle]) {
+ [item setAttributedTitle:nil];
+ }
+ }
+
+ NSDictionary *rowData = NSArrayObjectAtIndex(tableFields, [tableSourceView selectedRow]);
+
+ if([[menu menuId] isEqualToString:@"encodingPopupMenu"]) {
+ NSString *tableEncoding = [tableDataInstance tableEncoding];
+ //NSString *databaseEncoding = [databaseDataInstance getDatabaseDefaultCharacterSet];
+ //NSString *serverEncoding = [databaseDataInstance getServerDefaultCharacterSet];
+
+ struct _cmpMap defaultCmp[] = {
+ {
+ NSLocalizedString(@"Table",@"Table Structure : Encoding dropdown : 'item is table default' marker"),
+ [NSString stringWithFormat:NSLocalizedString(@"This is the default encoding of table “%@”.", @"Table Structure : Encoding dropdown : table marker tooltip"),selectedTable],
+ tableEncoding
+ },
+ /* //we could, but that might confuse users even more plus there is no inheritance between a columns charset and the db/server default
+ {
+ NSLocalizedString(@"Database",@"Table Structure : Encoding dropdown : 'item is database default' marker"),
+ [NSString stringWithFormat:NSLocalizedString(@"This is the default encoding of database “%@”.", @"Table Structure : Encoding dropdown : database marker tooltip"),[tableDocumentInstance database]],
+ databaseEncoding
+ },
+ {
+ NSLocalizedString(@"Server",@"Table Structure : Encoding dropdown : 'item is server default' marker"),
+ NSLocalizedString(@"This is the default encoding of this server.", @"Table Structure : Encoding dropdown : server marker tooltip"),
+ serverEncoding
+ } */
+ };
+
+ _BuildMenuWithPills(menu, defaultCmp, COUNT_OF(defaultCmp));
+ }
+ else if([[menu menuId] isEqualToString:@"collationPopupMenu"]) {
+ NSString *encoding = [rowData objectForKey:@"encodingName"];
+ NSString *encodingDefaultCollation = [databaseDataInstance getDefaultCollationForEncoding:encoding];
+ NSString *tableCollation = [tableDataInstance statusValueForKey:@"Collation"];
+ //NSString *databaseCollation = [databaseDataInstance getDatabaseDefaultCollation];
+ //NSString *serverCollation = [databaseDataInstance getServerDefaultCollation];
+
+ struct _cmpMap defaultCmp[] = {
+ {
+ NSLocalizedString(@"Default",@"Table Structure : Collation dropdown : 'item is the same as the default collation of the row's charset' marker"),
+ [NSString stringWithFormat:NSLocalizedString(@"This is the default collation of encoding “%@”.", @"Table Structure : Collation dropdown : default marker tooltip"),encoding],
+ encodingDefaultCollation
+ },
+ {
+ NSLocalizedString(@"Table",@"Table Structure : Collation dropdown : 'item is the same as the collation of table' marker"),
+ [NSString stringWithFormat:NSLocalizedString(@"This is the default collation of table “%@”.", @"Table Structure : Collation dropdown : table marker tooltip"),selectedTable],
+ tableCollation
+ },
+ /* // see the comment for charset above
+ {
+ NSLocalizedString(@"Database",@"Table Structure : Collation dropdown : 'item is the same as the collation of database' marker"),
+ [NSString stringWithFormat:NSLocalizedString(@"This is the default collation of database “%@”.", @"Table Structure : Collation dropdown : database marker tooltip"),[tableDocumentInstance database]],
+ databaseCollation
+ },
+ {
+ NSLocalizedString(@"Server",@"Table Structure : Collation dropdown : 'item is the same as the collation of server' marker"),
+ NSLocalizedString(@"This is the default collation of this server.", @"Table Structure : Collation dropdown : server marker tooltip"),
+ serverCollation
+ } */
+ };
+
+ _BuildMenuWithPills(menu, defaultCmp, COUNT_OF(defaultCmp));
+ }
+}
+
+#pragma mark -
- (void)dealloc
{
@@ -1834,3 +2945,35 @@ static inline SPFieldTypeHelp *MakeFieldTypeHelp(NSString *typeName,NSString *ty
}
@end
+
+#pragma mark -
+
+void _BuildMenuWithPills(NSMenu *menu,struct _cmpMap *map,size_t mapEntries)
+{
+ NSDictionary *baseAttrs = @{NSFontAttributeName:[menu font],NSParagraphStyleAttributeName: [NSParagraphStyle defaultParagraphStyle]};
+
+ for(NSMenuItem *item in [menu itemArray]) {
+ NSMutableAttributedString *itemStr = [[NSMutableAttributedString alloc] initWithString:[item title] attributes:baseAttrs];
+ NSString *value = [item representedObject];
+
+ NSMutableArray *tooltipParts = [NSMutableArray array];
+ for (unsigned int i = 0; i < mapEntries; ++i) {
+ struct _cmpMap *cmp = &map[i];
+ if([cmp->cmpWith isEqualToString:value]) {
+ SPPillAttachmentCell *cell = [[SPPillAttachmentCell alloc] init];
+ [cell setStringValue:cmp->title];
+ NSTextAttachment *attachment = [[NSTextAttachment alloc] init];
+ [attachment setAttachmentCell:[cell autorelease]];
+ NSAttributedString *attachmentString = [NSAttributedString attributedStringWithAttachment:[attachment autorelease]];
+
+ [[itemStr mutableString] appendString:@" "];
+ [itemStr appendAttributedString:attachmentString];
+
+ if(cmp->tooltipPart) [tooltipParts addObject:cmp->tooltipPart];
+ }
+ }
+ if([tooltipParts count]) [item setToolTip:[tooltipParts componentsJoinedByString:@" "]];
+
+ [item setAttributedTitle:[itemStr autorelease]];
+ }
+}
diff --git a/Source/SPTableStructureDelegate.h b/Source/SPTableStructureDelegate.h
deleted file mode 100644
index 2ddf8914..00000000
--- a/Source/SPTableStructureDelegate.h
+++ /dev/null
@@ -1,44 +0,0 @@
-//
-// SPTableStructureDelegate.h
-// sequel-pro
-//
-// Created by Stuart Connolly (stuconnolly.com) on October 26, 2010.
-// Copyright (c) 2010 Stuart Connolly. All rights reserved.
-//
-// Permission is hereby granted, free of charge, to any person
-// obtaining a copy of this software and associated documentation
-// files (the "Software"), to deal in the Software without
-// restriction, including without limitation the rights to use,
-// copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the
-// Software is furnished to do so, subject to the following
-// conditions:
-//
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
-// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
-// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-// OTHER DEALINGS IN THE SOFTWARE.
-//
-// More info at <https://github.com/sequelpro/sequelpro>
-
-#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
deleted file mode 100644
index 88c9673f..00000000
--- a/Source/SPTableStructureDelegate.m
+++ /dev/null
@@ -1,868 +0,0 @@
-//
-// SPTableStructureDelegate.m
-// sequel-pro
-//
-// Created by Stuart Connolly (stuconnolly.com) on October 26, 2010.
-// Copyright (c) 2010 Stuart Connolly. All rights reserved.
-//
-// Permission is hereby granted, free of charge, to any person
-// obtaining a copy of this software and associated documentation
-// files (the "Software"), to deal in the Software without
-// restriction, including without limitation the rights to use,
-// copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the
-// Software is furnished to do so, subject to the following
-// conditions:
-//
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
-// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
-// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-// OTHER DEALINGS IN THE SOFTWARE.
-//
-// More info at <https://github.com/sequelpro/sequelpro>
-
-#import "SPTableStructureDelegate.h"
-#import "SPAlertSheets.h"
-#import "SPDatabaseData.h"
-#import "SPDatabaseDocument.h"
-#import "SPTableData.h"
-#import "SPTableView.h"
-#import "SPTableFieldValidation.h"
-#import "SPTableStructureLoading.h"
-#import "SPServerSupport.h"
-#import "SPTablesList.h"
-#import "SPPillAttachmentCell.h"
-#import "SPIdMenu.h"
-#import "SPComboBoxCell.h"
-
-#import <SPMySQL/SPMySQL.h>
-
-struct _cmpMap {
- NSString *title; // the title of the "pill"
- NSString *tooltipPart; // the tooltip of the menuitem
- NSString *cmpWith; // the string to match against
-};
-
-/**
- * This function will compare the representedObject of every item in menu against
- * every map->cmpWith. If they match it will append a pill-like (similar to a TokenFieldCell's token)
- * element labelled map->title to the menu item's title. If map->tooltipPart is set,
- * it will also be added to the menu item's tooltip.
- *
- * This is used with the encoding/collation popup menus to add visual indicators for the
- * table-level and default encoding/collation.
- */
-static void _BuildMenuWithPills(NSMenu *menu,struct _cmpMap *map,size_t mapEntries);
-
-@interface SPTableStructure (PrivateAPI)
-
-- (void)sheetDidEnd:(id)sheet returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo;
-- (NSString *)_buildPartialColumnDefinitionString:(NSDictionary *)theRow;
-
-- (void)_displayFieldTypeHelpIfPossible:(SPComboBoxCell *)cell;
-
-@end
-
-@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 ((NSUInteger)rowIndex >= [tableFields count]) return @"...";
-
- NSDictionary *rowData = NSArrayObjectAtIndex(tableFields, rowIndex);
-
- if ([[tableColumn identifier] isEqualToString:@"collation"]) {
- NSString *tableEncoding = [tableDataInstance tableEncoding];
- NSString *columnEncoding = [rowData objectForKey:@"encodingName"];
- NSString *columnCollation = [rowData objectForKey:@"collationName"]; // loadTable: has already inferred it, if not set explicit
-
-#warning Building the collation menu here is a big performance hog. This should be done in menuNeedsUpdate: below!
- NSPopUpButtonCell *collationCell = [tableColumn dataCell];
- [collationCell removeAllItems];
- [collationCell addItemWithTitle:@"dummy"];
- //copy the default style of menu items and add gray color for default item
- NSMutableDictionary *menuAttrs = [NSMutableDictionary dictionaryWithDictionary:[[collationCell attributedTitle] attributesAtIndex:0 effectiveRange:NULL]];
- [menuAttrs setObject:[NSColor lightGrayColor] forKey:NSForegroundColorAttributeName];
- [[collationCell lastItem] setTitle:@""];
-
- //if this is not set the column either has no encoding (numeric etc.) or retrieval failed. Either way we can't provide collations
- if([columnEncoding length]) {
- collations = [databaseDataInstance getDatabaseCollationsForEncoding:columnEncoding];
-
- if ([collations count] > 0) {
- NSString *tableCollation = [[tableDataInstance statusValues] objectForKey:@"Collation"];
-
- if (![tableCollation length]) {
- tableCollation = [databaseDataInstance getDefaultCollationForEncoding:tableEncoding];
- }
-
- BOOL columnUsesTableDefaultEncoding = ([columnEncoding isEqualToString:tableEncoding]);
- // Populate collation popup button
- for (NSDictionary *collation in collations)
- {
- NSString *collationName = [collation objectForKey:@"COLLATION_NAME"];
-
- [collationCell addItemWithTitle:collationName];
- NSMenuItem *item = [collationCell lastItem];
- [item setRepresentedObject:collationName];
-
- // If this matches the table's collation, draw in gray
- if (columnUsesTableDefaultEncoding && [collationName isEqualToString:tableCollation]) {
- NSAttributedString *itemString = [[NSAttributedString alloc] initWithString:[item title] attributes:menuAttrs];
- [item setAttributedTitle:[itemString autorelease]];
- }
- }
-
- // the popup cell is subclassed to take the representedObject instead of the item index
- return columnCollation;
- }
- }
-
- return nil;
- }
- else if ([[tableColumn identifier] isEqualToString:@"encoding"]) {
- // the encoding menu was already configured during setTableDetails:
- NSString *columnEncoding = [rowData objectForKey:@"encodingName"];
-
- if([columnEncoding length]) {
- NSInteger idx = [encodingPopupCell indexOfItemWithRepresentedObject:columnEncoding];
- if(idx > 0) return @(idx);
- }
-
- return @0;
- }
- else if ([[tableColumn identifier] isEqualToString:@"Extra"]) {
- id dataCell = [tableColumn dataCell];
-
- [dataCell removeAllItems];
-
- // Populate Extra suggestion popup button
- for (id item in extraFieldSuggestions)
- {
- if (!(isCurrentExtraAutoIncrement && [item isEqualToString:@"auto_increment"])) {
- [dataCell addItemWithObjectValue:item];
- }
- }
- }
-
- return [rowData objectForKey:[tableColumn identifier]];
-}
-
-- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
-{
- // Make sure that the operation is for the right table view
- if (aTableView != tableSourceView) return;
-
- NSMutableDictionary *currentRow = NSArrayObjectAtIndex(tableFields,rowIndex);
-
- if (!isEditingRow) {
- [oldRow setDictionary:currentRow];
- isEditingRow = YES;
- currentlyEditingRow = rowIndex;
- }
-
- // Reset collation if encoding was changed
- if ([[aTableColumn identifier] isEqualToString:@"encoding"]) {
- NSString *oldEncoding = [currentRow objectForKey:@"encodingName"];
- NSString *newEncoding = [[encodingPopupCell itemAtIndex:[anObject integerValue]] representedObject];
- if (![oldEncoding isEqualToString:newEncoding]) {
- [currentRow removeObjectForKey:@"collationName"];
- [tableSourceView reloadData];
- }
- if(!newEncoding)
- [currentRow removeObjectForKey:@"encodingName"];
- else
- [currentRow setObject:newEncoding forKey:@"encodingName"];
- return;
- }
- else if ([[aTableColumn identifier] isEqualToString:@"collation"]) {
- //the popup button is subclassed to return the representedObject instead of the item index
- NSString *newCollation = anObject;
-
- if(!newCollation)
- [currentRow removeObjectForKey:@"collationName"];
- else
- [currentRow setObject:newCollation forKey:@"collationName"];
- return;
- }
- // Reset collation if BINARY was changed, as enabling BINARY sets collation to *_bin
- else if ([[aTableColumn identifier] isEqualToString:@"binary"]) {
- if ([[currentRow objectForKey:@"binary"] integerValue] != [anObject integerValue]) {
- [currentRow removeObjectForKey:@"collationName"];
-
- [tableSourceView reloadData];
- }
- }
- // Set null field to "do not allow NULL" for auto_increment Extra and reset Extra suggestion list
- else if ([[aTableColumn identifier] isEqualToString:@"Extra"]) {
- if (![[currentRow objectForKey:@"Extra"] isEqualToString:anObject]) {
-
- isCurrentExtraAutoIncrement = [[[anObject stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] uppercaseString] isEqualToString:@"AUTO_INCREMENT"];
-
- if (isCurrentExtraAutoIncrement) {
- [currentRow setObject:@0 forKey:@"null"];
-
- // Asks the user to add an index to query if AUTO_INCREMENT is set and field isn't indexed
- if ((![currentRow objectForKey:@"Key"] || [[currentRow objectForKey:@"Key"] isEqualToString:@""])) {
-#ifndef SP_CODA
- [chooseKeyButton selectItemWithTag:SPPrimaryKeyMenuTag];
-
- [NSApp beginSheet:keySheet
- modalForWindow:[tableDocumentInstance parentWindow] modalDelegate:self
- didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
- contextInfo:@"autoincrementindex" ];
-#endif
- }
- }
- else {
- autoIncrementIndex = nil;
- }
-
- id dataCell = [aTableColumn dataCell];
-
- [dataCell removeAllItems];
- [dataCell addItemsWithObjectValues:extraFieldSuggestions];
- [dataCell noteNumberOfItemsChanged];
- [dataCell reloadData];
-
- [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
- else 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"] ||
- [[currentRow objectForKey:@"type"] isEqualToString:@"JSON"] ||
- [fieldValidation isFieldTypeGeometry:[currentRow objectForKey:@"type"]] ||
- ([fieldValidation isFieldTypeDate:[currentRow objectForKey:@"type"]] && ![[currentRow objectForKey:@"type"] isEqualToString:@"YEAR"]))
- {
- [currentRow setObject:@"" forKey:@"default"];
- [currentRow setObject:@"" forKey:@"length"];
- }
-
- [tableSourceView reloadData];
- }
- return;
- }
-
- [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:@[SPDefaultPasteboardDragType] owner:nil];
- [pboard setString:[NSString stringWithFormat:@"%lu",[rows firstIndex]] forType:SPDefaultPasteboardDragType];
-
- return YES;
- }
-
- 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 NSDragOperationNone;
-
- NSArray *pboardTypes = [[info draggingPasteboard] types];
- NSInteger originalRow;
-
- // Ensure the drop is of the correct type
- if (operation == NSTableViewDropAbove && row != -1 && [pboardTypes containsObject:SPDefaultPasteboardDragType]) {
-
- // Ensure the drag originated within this table
- if ([info draggingSource] == tableView) {
- originalRow = [[[info draggingPasteboard] stringForType:SPDefaultPasteboardDragType] 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;
-
- // Extract the original row position from the pasteboard and retrieve the details
- NSInteger originalRowIndex = [[[info draggingPasteboard] stringForType:SPDefaultPasteboardDragType] integerValue];
- NSDictionary *originalRow = [[NSDictionary alloc] initWithDictionary:[tableFields objectAtIndex:originalRowIndex]];
-
- [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:tableDocumentInstance];
-
- // Begin construction of the reordering query
- NSMutableString *queryString = [NSMutableString stringWithFormat:@"ALTER TABLE %@ MODIFY COLUMN %@",
- [selectedTable backtickQuotedString],
- [self _buildPartialColumnDefinitionString:originalRow]];
-
- [queryString appendString:@" "];
- // 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]) {
- SPOnewayAlertSheet(
- NSLocalizedString(@"Error moving field", @"error moving field message"),
- [tableDocumentInstance parentWindow],
- [NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to move the field.\n\nMySQL said: %@", @"error moving field informative message"), [mySQLConnection lastErrorMessage]]
- );
- }
- else {
- [tableDataInstance resetAllData];
- [tableDocumentInstance setStatusRequiresReload:YES];
-
- [self loadTable:selectedTable];
-
- // Mark the content table cache for refresh
- [tableDocumentInstance setContentRequiresReload:YES];
-
- [tableSourceView selectRowIndexes:[NSIndexSet indexSetWithIndex:destinationRowIndex - ((originalRowIndex < destinationRowIndex) ? 1 : 0)] 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
-{
- // Check for which table view the selection changed
- if ([aNotification 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;
-
- [duplicateFieldButton 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];
- [duplicateFieldButton 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:([tableSourceView numberOfColumns] - 1) row:row - 1 withEvent:nil select:YES];
- }
- else {
- [tableSourceView selectRowIndexes:[NSIndexSet indexSetWithIndex:([tableFields count] - 1)] byExtendingSelection:NO];
- [tableSourceView editColumn:([tableSourceView numberOfColumns] - 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;
- }
-
- 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 *)tableColumn 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
- NSString *rowType;
- NSDictionary *row = NSArrayObjectAtIndex(tableFields, rowIndex);
-
- if ((rowType = [row objectForKey:@"type"])) {
- rowType = [[rowType stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] uppercaseString];
- }
-
- // Only string fields allow encoding settings, but JSON only uses utf8mb4
- if (([[tableColumn identifier] isEqualToString:@"encoding"])) {
- [aCell setEnabled:(![rowType isEqualToString:@"JSON"] && [fieldValidation isFieldTypeString:rowType] && [[tableDocumentInstance serverSupport] supportsPost41CharacterSetHandling])];
- }
-
- // Only string fields allow collation settings and string field is not set to BINARY since BINARY sets the collation to *_bin
- else if ([[tableColumn identifier] isEqualToString:@"collation"]) {
- // JSON always uses utf8mb4_bin which is already covered by this logic
- [aCell setEnabled:([fieldValidation isFieldTypeString:rowType] && [[row objectForKey:@"binary"] integerValue] == 0 && [[tableDocumentInstance serverSupport] supportsPost41CharacterSetHandling])];
- }
-
- // Check if UNSIGNED and ZEROFILL is allowed
- else if ([[tableColumn identifier] isEqualToString:@"zerofill"] || [[tableColumn identifier] isEqualToString:@"unsigned"]) {
- [aCell setEnabled:([fieldValidation isFieldTypeNumeric:rowType] && ![rowType isEqualToString:@"BIT"])];
- }
-
- // Check if BINARY is allowed
- else if ([[tableColumn identifier] isEqualToString:@"binary"]) {
- // JSON always uses utf8mb4_bin
- [aCell setEnabled:(![rowType isEqualToString:@"JSON"] && [fieldValidation isFieldTypeAllowBinary:rowType])];
- }
-
- // TEXT, BLOB, GEOMETRY and JSON fields don't allow a DEFAULT
- else if ([[tableColumn identifier] isEqualToString:@"default"]) {
- [aCell setEnabled:([rowType hasSuffix:@"TEXT"] || [rowType hasSuffix:@"BLOB"] || [rowType isEqualToString:@"JSON"] || [fieldValidation isFieldTypeGeometry:rowType]) ? NO : YES];
- }
-
- // Check allow NULL
- else if ([[tableColumn identifier] isEqualToString:@"null"]) {
- [aCell setEnabled:([[row objectForKey:@"Key"] isEqualToString:@"PRI"] ||
- [[[row objectForKey:@"Extra"] uppercaseString] isEqualToString:@"AUTO_INCREMENT"] ||
- [[[tableDataInstance statusValueForKey:@"Engine"] uppercaseString] isEqualToString:@"CSV"]) ? NO : YES];
- }
-
- // TEXT, BLOB, date, GEOMETRY and JSON fields don't allow a length
- else if ([[tableColumn identifier] isEqualToString:@"length"]) {
- [aCell setEnabled:([rowType hasSuffix:@"TEXT"] ||
- [rowType hasSuffix:@"BLOB"] ||
- [rowType isEqualToString:@"JSON"] ||
- ([fieldValidation isFieldTypeDate:rowType] && ![[tableDocumentInstance serverSupport] supportsFractionalSeconds] && ![rowType isEqualToString:@"YEAR"]) ||
- [fieldValidation isFieldTypeGeometry:rowType]) ? NO : YES];
- }
- else {
- [aCell setEnabled:YES];
- }
- }
-}
-
-#pragma mark -
-#pragma mark Split view delegate methods
-#ifndef SP_CODA /* 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];
- }
- }
-}
-#endif
-
-#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 count]) return [result objectAtIndex:0];
-
- return @"";
-}
-
-- (void)comboBoxCell:(SPComboBoxCell *)cell willPopUpWindow:(NSWindow *)win
-{
- // the selected item in the popup list is independent of the displayed text, we have to explicitly set it, too
- NSInteger pos = [typeSuggestions indexOfObject:[cell stringValue]];
- if(pos != NSNotFound) {
- [cell selectItemAtIndex:pos];
- [cell scrollItemAtIndexToTop:pos];
- }
-
- //set up the help window to the right position
- NSRect listFrame = [win frame];
- NSRect helpFrame = [structureHelpPanel frame];
- helpFrame.origin.y = listFrame.origin.y;
- helpFrame.size.height = listFrame.size.height;
- [structureHelpPanel setFrame:helpFrame display:YES];
-
- [self _displayFieldTypeHelpIfPossible:cell];
-}
-
-- (void)comboBoxCell:(SPComboBoxCell *)cell willDismissWindow:(NSWindow *)win
-{
- //hide the window if it is still visible
- [structureHelpPanel orderOut:nil];
-}
-
-- (void)comboBoxCellSelectionDidChange:(SPComboBoxCell *)cell
-{
- [self _displayFieldTypeHelpIfPossible:cell];
-}
-
-- (void)_displayFieldTypeHelpIfPossible:(SPComboBoxCell *)cell
-{
- NSString *selected = [typeSuggestions objectOrNilAtIndex:[cell indexOfSelectedItem]];
-
- const SPFieldTypeHelp *help = [[self class] helpForFieldType:selected];
-
- if(help) {
- NSMutableAttributedString *as = [[NSMutableAttributedString alloc] init];
-
- //title
- {
- NSDictionary *titleAttr = @{NSFontAttributeName: [NSFont boldSystemFontOfSize:[NSFont systemFontSize]]};
- NSAttributedString *title = [[NSAttributedString alloc] initWithString:[help typeDefinition] attributes:titleAttr];
- [as appendAttributedString:[title autorelease]];
- [[as mutableString] appendString:@"\n"];
- }
-
- //range
- if([[help typeRange] length]) {
- NSDictionary *rangeAttr = @{NSFontAttributeName: [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]};
- NSAttributedString *range = [[NSAttributedString alloc] initWithString:[help typeRange] attributes:rangeAttr];
- [as appendAttributedString:[range autorelease]];
- [[as mutableString] appendString:@"\n"];
- }
-
- [[as mutableString] appendString:@"\n"];
-
- //description
- {
- NSDictionary *descAttr = @{NSFontAttributeName: [NSFont systemFontOfSize:[NSFont systemFontSize]]};
- NSAttributedString *desc = [[NSAttributedString alloc] initWithString:[help typeDescription] attributes:descAttr];
- [as appendAttributedString:[desc autorelease]];
- }
-
- [as addAttribute:NSParagraphStyleAttributeName value:[NSParagraphStyle defaultParagraphStyle] range:NSMakeRange(0, [as length])];
-
- [[structureHelpText textStorage] setAttributedString:[as autorelease]];
-
- NSRect rect = [as boundingRectWithSize:NSMakeSize([structureHelpText frame].size.width-2, CGFLOAT_MAX) options:NSStringDrawingUsesFontLeading|NSStringDrawingUsesLineFragmentOrigin];
-
- NSRect winRect = [structureHelpPanel frame];
-
- CGFloat winAddonSize = (winRect.size.height - [[structureHelpPanel contentView] frame].size.height) + (6*2);
-
- NSRect popUpFrame = [[cell spPopUpWindow] frame];
-
- //determine the side on which to add our window based on the space left on screen
- NSPoint topRightCorner = NSMakePoint(popUpFrame.origin.x, NSMaxY(popUpFrame));
- NSRect screenRect = [NSScreen rectOfScreenAtPoint:topRightCorner];
-
- if(NSMaxX(popUpFrame)+10+winRect.size.width > NSMaxX(screenRect)-10) {
- // exceeds right border, display on the left
- winRect.origin.x = popUpFrame.origin.x - 10 - winRect.size.width;
- }
- else {
- // display on the right
- winRect.origin.x = NSMaxX(popUpFrame)+10;
- }
-
- winRect.size.height = rect.size.height + winAddonSize;
- winRect.origin.y = NSMaxY(popUpFrame) - winRect.size.height;
- [structureHelpPanel setFrame:winRect display:YES];
-
- [structureHelpPanel orderFront:nil];
- }
- else {
- [structureHelpPanel orderOut:nil];
- }
-}
-
-#pragma mark -
-#pragma mark Menu delegate methods (encoding/collation dropdown menu)
-
-- (void)menuNeedsUpdate:(SPIdMenu *)menu
-{
- if(![menu isKindOfClass:[SPIdMenu class]]) return;
- //NOTE: NSTableView will usually copy the menu and call this method on the copy. Matching with == won't work!
-
- //walk through the menu and clear the attributedTitle if set. This will remove the gray color from the default items
- for(NSMenuItem *item in [menu itemArray]) {
- if([item attributedTitle]) {
- [item setAttributedTitle:nil];
- }
- }
-
- NSDictionary *rowData = NSArrayObjectAtIndex(tableFields, [tableSourceView selectedRow]);
-
- if([[menu menuId] isEqualToString:@"encodingPopupMenu"]) {
- NSString *tableEncoding = [tableDataInstance tableEncoding];
- //NSString *databaseEncoding = [databaseDataInstance getDatabaseDefaultCharacterSet];
- //NSString *serverEncoding = [databaseDataInstance getServerDefaultCharacterSet];
-
- struct _cmpMap defaultCmp[] = {
- {
- NSLocalizedString(@"Table",@"Table Structure : Encoding dropdown : 'item is table default' marker"),
- [NSString stringWithFormat:NSLocalizedString(@"This is the default encoding of table “%@”.", @"Table Structure : Encoding dropdown : table marker tooltip"),selectedTable],
- tableEncoding
- },
- /* //we could, but that might confuse users even more plus there is no inheritance between a columns charset and the db/server default
- {
- NSLocalizedString(@"Database",@"Table Structure : Encoding dropdown : 'item is database default' marker"),
- [NSString stringWithFormat:NSLocalizedString(@"This is the default encoding of database “%@”.", @"Table Structure : Encoding dropdown : database marker tooltip"),[tableDocumentInstance database]],
- databaseEncoding
- },
- {
- NSLocalizedString(@"Server",@"Table Structure : Encoding dropdown : 'item is server default' marker"),
- NSLocalizedString(@"This is the default encoding of this server.", @"Table Structure : Encoding dropdown : server marker tooltip"),
- serverEncoding
- } */
- };
-
- _BuildMenuWithPills(menu, defaultCmp, COUNT_OF(defaultCmp));
- }
- else if([[menu menuId] isEqualToString:@"collationPopupMenu"]) {
- NSString *encoding = [rowData objectForKey:@"encodingName"];
- NSString *encodingDefaultCollation = [databaseDataInstance getDefaultCollationForEncoding:encoding];
- NSString *tableCollation = [tableDataInstance statusValueForKey:@"Collation"];
- //NSString *databaseCollation = [databaseDataInstance getDatabaseDefaultCollation];
- //NSString *serverCollation = [databaseDataInstance getServerDefaultCollation];
-
- struct _cmpMap defaultCmp[] = {
- {
- NSLocalizedString(@"Default",@"Table Structure : Collation dropdown : 'item is the same as the default collation of the row's charset' marker"),
- [NSString stringWithFormat:NSLocalizedString(@"This is the default collation of encoding “%@”.", @"Table Structure : Collation dropdown : default marker tooltip"),encoding],
- encodingDefaultCollation
- },
- {
- NSLocalizedString(@"Table",@"Table Structure : Collation dropdown : 'item is the same as the collation of table' marker"),
- [NSString stringWithFormat:NSLocalizedString(@"This is the default collation of table “%@”.", @"Table Structure : Collation dropdown : table marker tooltip"),selectedTable],
- tableCollation
- },
- /* // see the comment for charset above
- {
- NSLocalizedString(@"Database",@"Table Structure : Collation dropdown : 'item is the same as the collation of database' marker"),
- [NSString stringWithFormat:NSLocalizedString(@"This is the default collation of database “%@”.", @"Table Structure : Collation dropdown : database marker tooltip"),[tableDocumentInstance database]],
- databaseCollation
- },
- {
- NSLocalizedString(@"Server",@"Table Structure : Collation dropdown : 'item is the same as the collation of server' marker"),
- NSLocalizedString(@"This is the default collation of this server.", @"Table Structure : Collation dropdown : server marker tooltip"),
- serverCollation
- } */
- };
-
- _BuildMenuWithPills(menu, defaultCmp, COUNT_OF(defaultCmp));
- }
-}
-
-@end
-
-void _BuildMenuWithPills(NSMenu *menu,struct _cmpMap *map,size_t mapEntries)
-{
- NSDictionary *baseAttrs = @{NSFontAttributeName:[menu font],NSParagraphStyleAttributeName: [NSParagraphStyle defaultParagraphStyle]};
-
- for(NSMenuItem *item in [menu itemArray]) {
- NSMutableAttributedString *itemStr = [[NSMutableAttributedString alloc] initWithString:[item title] attributes:baseAttrs];
- NSString *value = [item representedObject];
-
- NSMutableArray *tooltipParts = [NSMutableArray array];
- for (unsigned int i = 0; i < mapEntries; ++i) {
- struct _cmpMap *cmp = &map[i];
- if([cmp->cmpWith isEqualToString:value]) {
- SPPillAttachmentCell *cell = [[SPPillAttachmentCell alloc] init];
- [cell setStringValue:cmp->title];
- NSTextAttachment *attachment = [[NSTextAttachment alloc] init];
- [attachment setAttachmentCell:[cell autorelease]];
- NSAttributedString *attachmentString = [NSAttributedString attributedStringWithAttachment:[attachment autorelease]];
-
- [[itemStr mutableString] appendString:@" "];
- [itemStr appendAttributedString:attachmentString];
-
- if(cmp->tooltipPart) [tooltipParts addObject:cmp->tooltipPart];
- }
- }
- if([tooltipParts count]) [item setToolTip:[tooltipParts componentsJoinedByString:@" "]];
-
- [item setAttributedTitle:[itemStr autorelease]];
- }
-}
diff --git a/Source/SPTableStructureLoading.h b/Source/SPTableStructureLoading.h
deleted file mode 100644
index 502c37ae..00000000
--- a/Source/SPTableStructureLoading.h
+++ /dev/null
@@ -1,44 +0,0 @@
-//
-// SPTableStructureLoading.h
-// sequel-pro
-//
-// Created by Stuart Connolly (stuconnolly.com) on July 4, 2012.
-// Copyright (c) 2012 Stuart Connolly. All rights reserved.
-//
-// Permission is hereby granted, free of charge, to any person
-// obtaining a copy of this software and associated documentation
-// files (the "Software"), to deal in the Software without
-// restriction, including without limitation the rights to use,
-// copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the
-// Software is furnished to do so, subject to the following
-// conditions:
-//
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
-// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
-// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-// OTHER DEALINGS IN THE SOFTWARE.
-
-#import "SPTableStructure.h"
-
-/**
- * @category SPTableStructureLoading SPTableStructureLoading.h
- *
- * @author Stuart Connolly http://stuconnolly.com/
- *
- * Contains all functionality related to loading a table's structure.
- */
-@interface SPTableStructure (SPTableStructureLoading)
-
-- (void)loadTable:(NSString *)aTable;
-- (IBAction)reloadTable:(id)sender;
-- (void)setTableDetails:(NSDictionary *)tableDetails;
-
-@end
diff --git a/Source/SPTableStructureLoading.m b/Source/SPTableStructureLoading.m
deleted file mode 100644
index 9aec78e7..00000000
--- a/Source/SPTableStructureLoading.m
+++ /dev/null
@@ -1,368 +0,0 @@
-//
-// SPTableStructureLoading.m
-// sequel-pro
-//
-// Created by Stuart Connolly (stuconnolly.com) on July 4, 2012.
-// Copyright (c) 2012 Stuart Connolly. All rights reserved.
-//
-// Permission is hereby granted, free of charge, to any person
-// obtaining a copy of this software and associated documentation
-// files (the "Software"), to deal in the Software without
-// restriction, including without limitation the rights to use,
-// copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the
-// Software is furnished to do so, subject to the following
-// conditions:
-//
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
-// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
-// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-// OTHER DEALINGS IN THE SOFTWARE.
-
-#import "SPTableStructureLoading.h"
-#import "SPTableData.h"
-#import "SPAlertSheets.h"
-#import "SPDatabaseData.h"
-#import "SPDatabaseStructure.h"
-#import "SPTableFieldValidation.h"
-#import "SPDatabaseDocument.h"
-#import "SPIndexesController.h"
-#import "SPTablesList.h"
-#import "SPThreadAdditions.h"
-#import "SPTableView.h"
-#import "SPFunctions.h"
-
-#import <SPMySQL/SPMySQL.h>
-
-@implementation SPTableStructure (SPTableStructureLoading)
-
-#pragma mark -
-#pragma mark Table loading
-
-/**
- * Loads aTable, puts it in an array, updates the tableViewColumns and reloads the tableView.
- */
-- (void)loadTable:(NSString *)aTable
-{
- NSMutableDictionary *theTableEnumLists = [NSMutableDictionary dictionary];
-
- // Check whether a save of the current row is required.
- if (![[self onMainThread] saveRowOnDeselect]) return;
-
- // If no table is selected, reset the interface and return
- if (!aTable || ![aTable length]) {
- [[self onMainThread] setTableDetails:nil];
- return;
- }
-
- NSMutableArray *theTableFields = [[NSMutableArray alloc] init];
-
- // Make a mutable copy out of the cached [tableDataInstance columns] since we're adding infos
- for (id col in [tableDataInstance columns])
- {
- [theTableFields addObject:[[col mutableCopy] autorelease]];
- }
-
- // Retrieve the indexes for the table
- SPMySQLResult *indexResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW INDEX FROM %@", [aTable backtickQuotedString]]];
-
- // If an error occurred, reset the interface and abort
- if ([mySQLConnection queryErrored]) {
- [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance];
- [[self onMainThread] setTableDetails:nil];
-
- if ([mySQLConnection isConnected]) {
- SPOnewayAlertSheet(
- NSLocalizedString(@"Error", @"error"),
- [NSApp mainWindow],
- [NSString stringWithFormat:NSLocalizedString(@"An error occurred while retrieving information.\nMySQL said: %@", @"message of panel when retrieving information failed"), [mySQLConnection lastErrorMessage]]
- );
- }
-
- return;
- }
-
- // Process the indexes into a local array of dictionaries
- NSArray *theTableIndexes = [self convertIndexResultToArray:indexResult];
-
- // Set the Key column
- for (NSDictionary* theIndex in theTableIndexes)
- {
- for (id field in theTableFields)
- {
- if ([[field objectForKey:@"name"] isEqualToString:[theIndex objectForKey:@"Column_name"]]) {
- if ([[theIndex objectForKey:@"Key_name"] isEqualToString:@"PRIMARY"]) {
- [field setObject:@"PRI" forKey:@"Key"];
- }
- else {
- if ([[field objectForKey:@"typegrouping"] isEqualToString:@"geometry"]) {
- [field setObject:@"SPA" forKey:@"Key"];
- }
- else {
- [field setObject:(([[theIndex objectForKey:@"Non_unique"] isEqualToString:@"1"]) ? @"MUL" : @"UNI") forKey:@"Key"];
- }
- }
-
- break;
- }
- }
- }
-
- // Set up the encoding PopUpButtonCell
- NSArray *encodings = [databaseDataInstance getDatabaseCharacterSetEncodings];
-
- SPMainQSync(^{
- [encodingPopupCell removeAllItems];
-
- if ([encodings count]) {
-
- [encodingPopupCell addItemWithTitle:@"dummy"];
- //copy the default attributes and add gray color
- NSMutableDictionary *defaultAttrs = [NSMutableDictionary dictionaryWithDictionary:[[encodingPopupCell attributedTitle] attributesAtIndex:0 effectiveRange:NULL]];
- [defaultAttrs setObject:[NSColor lightGrayColor] forKey:NSForegroundColorAttributeName];
- [[encodingPopupCell lastItem] setTitle:@""];
-
- for (NSDictionary *encoding in encodings)
- {
- NSString *encodingName = [encoding objectForKey:@"CHARACTER_SET_NAME"];
- NSString *title = (![encoding objectForKey:@"DESCRIPTION"]) ? encodingName : [NSString stringWithFormat:@"%@ (%@)", [encoding objectForKey:@"DESCRIPTION"], encodingName];
-
- [encodingPopupCell addItemWithTitle:title];
- NSMenuItem *item = [encodingPopupCell lastItem];
-
- [item setRepresentedObject:encodingName];
-
- if ([encodingName isEqualToString:[tableDataInstance tableEncoding]]) {
-
- NSAttributedString *itemString = [[NSAttributedString alloc] initWithString:[item title] attributes:defaultAttrs];
-
- [item setAttributedTitle:[itemString autorelease]];
- }
- }
- }
- else {
- [encodingPopupCell addItemWithTitle:NSLocalizedString(@"Not available", @"not available label")];
- }
- });
-
- // Process all the fields to normalise keys and add additional information
- for (id theField in theTableFields)
- {
- NSString *type = [[[theField objectForKey:@"type"] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] uppercaseString];
-
- if([type isEqualToString:@"JSON"]) {
- // MySQL 5.7 manual:
- // "MySQL handles strings used in JSON context using the utf8mb4 character set and utf8mb4_bin collation.
- // Strings in other character set are converted to utf8mb4 as necessary."
- [theField setObject:@"utf8mb4" forKey:@"encodingName"];
- [theField setObject:@"utf8mb4_bin" forKey:@"collationName"];
- [theField setObject:@1 forKey:@"binary"];
- }
- else if ([fieldValidation isFieldTypeString:type]) {
- // The MySQL 4.1 manual says:
- //
- // MySQL chooses the column character set and collation in the following manner:
- // 1. If both CHARACTER SET X and COLLATE Y were specified, then character set X and collation Y are used.
- // 2. If CHARACTER SET X was specified without COLLATE, then character set X and its default collation are used.
- // 3. If COLLATE Y was specified without CHARACTER SET, then the character set associated with Y and collation Y.
- // 4. Otherwise, the table character set and collation are used.
- NSString *encoding = [theField objectForKey:@"encoding"];
- NSString *collation = [theField objectForKey:@"collation"];
- if(encoding) {
- if(collation) {
- // 1
- }
- else {
- collation = [databaseDataInstance getDefaultCollationForEncoding:encoding]; // 2
- }
- }
- else {
- if(collation) {
- encoding = [databaseDataInstance getEncodingFromCollation:collation]; // 3
- }
- else {
- encoding = [tableDataInstance tableEncoding]; //4
- collation = [tableDataInstance statusValueForKey:@"Collation"];
- if(!collation) {
- // should not happen, as the TABLE STATUS output always(?) includes the collation
- collation = [databaseDataInstance getDefaultCollationForEncoding:encoding];
- }
- }
- }
-
- // MySQL < 4.1 does not support collations (they are part of the charset), it will be nil there
-
- [theField setObject:encoding forKey:@"encodingName"];
- [theField setObject:collation forKey:@"collationName"];
-
- // Set BINARY if collation ends with _bin for convenience
- if ([collation hasSuffix:@"_bin"]) {
- [theField setObject:@1 forKey:@"binary"];
- }
- }
-
- // Get possible values if the field is an enum or a set
- if (([type isEqualToString:@"ENUM"] || [type isEqualToString:@"SET"]) && [theField objectForKey:@"values"]) {
- [theTableEnumLists setObject:[NSArray arrayWithArray:[theField objectForKey:@"values"]] forKey:[theField objectForKey:@"name"]];
- [theField setObject:[NSString stringWithFormat:@"'%@'", [[theField objectForKey:@"values"] componentsJoinedByString:@"','"]] forKey:@"length"];
- }
-
- // Join length and decimals if any
- if ([theField objectForKey:@"decimals"])
- [theField setObject:[NSString stringWithFormat:@"%@,%@", [theField objectForKey:@"length"], [theField objectForKey:@"decimals"]] forKey:@"length"];
-
- // Normalize default
- if (![theField objectForKey:@"default"]) {
- [theField setObject:@"" forKey:@"default"];
- }
- else if ([[theField objectForKey:@"default"] isNSNull]) {
- [theField setObject:[prefs stringForKey:SPNullValue] forKey:@"default"];
- }
-
- // Init Extra field
- [theField setObject:@"None" forKey:@"Extra"];
-
- // Check for auto_increment and set Extra accordingly
- if ([[theField objectForKey:@"autoincrement"] integerValue]) {
- [theField setObject:@"auto_increment" forKey:@"Extra"];
- }
-
- // For timestamps/datetime check to see whether "on update CURRENT_TIMESTAMP" and set Extra accordingly
- else if ([type isInArray:@[@"TIMESTAMP",@"DATETIME"]] && [[theField objectForKey:@"onupdatetimestamp"] boolValue]) {
- NSString *ouct = @"on update CURRENT_TIMESTAMP";
- // restore a length parameter if the field has fractional seconds.
- // the parameter of current_timestamp MUST match the field's length in that case, so we can just 'guess' it.
- NSString *fieldLen = [theField objectForKey:@"length"];
- if([fieldLen length] && ![fieldLen isEqualToString:@"0"]) {
- ouct = [ouct stringByAppendingFormat:@"(%@)",fieldLen];
- }
- [theField setObject:ouct forKey:@"Extra"];
- }
- }
-
- // Set up the table details for the new table, and request an data/interface update
- NSDictionary *tableDetails = [NSDictionary dictionaryWithObjectsAndKeys:
- aTable, @"name",
- theTableFields, @"tableFields",
- theTableIndexes, @"tableIndexes",
- theTableEnumLists, @"enumLists",
- nil];
-
- [[self onMainThread] setTableDetails:tableDetails];
-
- isCurrentExtraAutoIncrement = [tableDataInstance tableHasAutoIncrementField];
- autoIncrementIndex = nil;
-
- // Send the query finished/work complete notification
- [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance];
-
- [theTableFields release];
-}
-
-/**
- * Reloads the table (performing a new query).
- */
-- (IBAction)reloadTable:(id)sender
-{
- // Check whether a save of the current row is required
- if (![[self onMainThread] saveRowOnDeselect]) return;
-
- [tableDataInstance resetAllData];
- [tableDocumentInstance setStatusRequiresReload:YES];
-
- // Query the structure of all databases in the background (mainly for completion)
- [[tableDocumentInstance databaseStructureRetrieval] queryDbStructureInBackgroundWithUserInfo:@{@"forceUpdate" : @YES}];
-
- [self loadTable:selectedTable];
-}
-
-/**
- * Updates the stored table details and updates the interface to match.
- *
- * Should be called on the main thread.
- */
-- (void)setTableDetails:(NSDictionary *)tableDetails
-{
- NSString *newTableName = [tableDetails objectForKey:@"name"];
- NSMutableDictionary *newDefaultValues;
-
- BOOL enableInteraction =
-#ifndef SP_CODA /* patch */
- ![[tableDocumentInstance selectedToolbarItemIdentifier] isEqualToString:SPMainToolbarTableStructure] ||
-#endif
- ![tableDocumentInstance isWorking];
-
- // Update the selected table name
- if (selectedTable) SPClear(selectedTable);
- if (newTableName) selectedTable = [[NSString alloc] initWithString:newTableName];
-
- [indexesController setTable:selectedTable];
-
- // Reset the table store and display
- [tableSourceView deselectAll:self];
- [tableFields removeAllObjects];
- [enumFields removeAllObjects];
- [indexesTableView deselectAll:self];
- [addFieldButton setEnabled:NO];
- [duplicateFieldButton setEnabled:NO];
- [removeFieldButton setEnabled:NO];
-#ifndef SP_CODA
- [addIndexButton setEnabled:NO];
- [removeIndexButton setEnabled:NO];
- [editTableButton setEnabled:NO];
-#endif
-
- // If no table is selected, refresh the table/index display to blank and return
- if (!selectedTable) {
- [tableSourceView reloadData];
- // Empty indexesController's fields and indices explicitly before reloading
- [indexesController setFields:@[]];
- [indexesController setIndexes:@[]];
- [indexesTableView reloadData];
-
- return;
- }
-
- // Update the fields and indexes stores
- [tableFields setArray:[tableDetails objectForKey:@"tableFields"]];
-
- [indexesController setFields:tableFields];
- [indexesController setIndexes:[tableDetails objectForKey:@"tableIndexes"]];
-
- if (defaultValues) SPClear(defaultValues);
-
- newDefaultValues = [NSMutableDictionary dictionaryWithCapacity:[tableFields count]];
-
- for (id theField in tableFields)
- {
- [newDefaultValues setObject:[theField objectForKey:@"default"] forKey:[theField objectForKey:@"name"]];
- }
-
- defaultValues = [[NSDictionary dictionaryWithDictionary:newDefaultValues] retain];
-
-#ifndef SP_CODA
- // Enable the edit table button
- [editTableButton setEnabled:enableInteraction];
-#endif
-
- // If a view is selected, disable the buttons; otherwise enable.
- BOOL editingEnabled = ([tablesListInstance tableType] == SPTableTypeTable) && enableInteraction;
-
- [addFieldButton setEnabled:editingEnabled];
-#ifndef SP_CODA
- [addIndexButton setEnabled:editingEnabled && ![[[tableDataInstance statusValueForKey:@"Engine"] uppercaseString] isEqualToString:@"CSV"]];
-#endif
-
- // Reload the views
- [indexesTableView reloadData];
- [tableSourceView reloadData];
-}
-
-@end
diff --git a/sequel-pro.xcodeproj/project.pbxproj b/sequel-pro.xcodeproj/project.pbxproj
index 9df83175..73c8cc6a 100644
--- a/sequel-pro.xcodeproj/project.pbxproj
+++ b/sequel-pro.xcodeproj/project.pbxproj
@@ -57,7 +57,6 @@
173C839411AAD32A00B8B084 /* SPSQLExporterDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 173C838D11AAD32A00B8B084 /* SPSQLExporterDelegate.m */; };
173C839511AAD32A00B8B084 /* SPXMLExporterDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 173C838F11AAD32A00B8B084 /* SPXMLExporterDelegate.m */; };
1740FABB0FC4372F00CF3699 /* SPDatabaseData.m in Sources */ = {isa = PBXBuildFile; fileRef = 1740FABA0FC4372F00CF3699 /* SPDatabaseData.m */; };
- 1748D50C15A4444F003562F2 /* SPTableStructureLoading.m in Sources */ = {isa = PBXBuildFile; fileRef = 1748D50B15A4444F003562F2 /* SPTableStructureLoading.m */; };
174CE11E10AB80B5008F892B /* DatabaseProcessList.xib in Resources */ = {isa = PBXBuildFile; fileRef = 174CE11C10AB80B5008F892B /* DatabaseProcessList.xib */; };
174CE14210AB9281008F892B /* SPProcessListController.m in Sources */ = {isa = PBXBuildFile; fileRef = 174CE14110AB9281008F892B /* SPProcessListController.m */; };
175EC63512733B36009A7C0F /* SPExportControllerDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 175EC63412733B36009A7C0F /* SPExportControllerDelegate.m */; };
@@ -110,7 +109,6 @@
17CC97F310B4ABE90034CD7A /* SPAboutController.m in Sources */ = {isa = PBXBuildFile; fileRef = 17CC97F210B4ABE90034CD7A /* SPAboutController.m */; };
17CC97F710B4AC6C0034CD7A /* AboutPanel.xib in Resources */ = {isa = PBXBuildFile; fileRef = 17CC97F510B4AC6C0034CD7A /* AboutPanel.xib */; };
17CC993B10B4C9C80034CD7A /* License.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 17CC993A10B4C9C80034CD7A /* License.rtf */; };
- 17D38EBC12771A1C00672B13 /* SPTableStructureDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D38EBB12771A1C00672B13 /* SPTableStructureDelegate.m */; };
17D38F701279E23A00672B13 /* SPTableFieldValidation.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D38F6F1279E23A00672B13 /* SPTableFieldValidation.m */; };
17D390C8127B65AF00672B13 /* SPGeneralPreferencePane.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D390C7127B65AF00672B13 /* SPGeneralPreferencePane.m */; };
17D390CB127B6BF800672B13 /* SPPreferencesUpgrade.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D390CA127B6BF800672B13 /* SPPreferencesUpgrade.m */; };
@@ -713,8 +711,6 @@
173C838F11AAD32A00B8B084 /* SPXMLExporterDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPXMLExporterDelegate.m; sourceTree = "<group>"; };
1740FAB90FC4372F00CF3699 /* SPDatabaseData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPDatabaseData.h; sourceTree = "<group>"; };
1740FABA0FC4372F00CF3699 /* SPDatabaseData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDatabaseData.m; sourceTree = "<group>"; };
- 1748D50A15A4444F003562F2 /* SPTableStructureLoading.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTableStructureLoading.h; sourceTree = "<group>"; };
- 1748D50B15A4444F003562F2 /* SPTableStructureLoading.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTableStructureLoading.m; sourceTree = "<group>"; };
174A345112DA4ED000DB0ADE /* create-test-stubs.pl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.perl; path = "create-test-stubs.pl"; sourceTree = "<group>"; };
174CE11D10AB80B5008F892B /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = Interfaces/English.lproj/DatabaseProcessList.xib; sourceTree = "<group>"; };
174CE14010AB9281008F892B /* SPProcessListController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPProcessListController.h; sourceTree = "<group>"; };
@@ -805,8 +801,6 @@
17CC97F210B4ABE90034CD7A /* SPAboutController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPAboutController.m; sourceTree = "<group>"; };
17CC97F610B4AC6C0034CD7A /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = Interfaces/English.lproj/AboutPanel.xib; sourceTree = "<group>"; };
17CC993A10B4C9C80034CD7A /* License.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = License.rtf; sourceTree = "<group>"; };
- 17D38EBA12771A1C00672B13 /* SPTableStructureDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTableStructureDelegate.h; sourceTree = "<group>"; };
- 17D38EBB12771A1C00672B13 /* SPTableStructureDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTableStructureDelegate.m; sourceTree = "<group>"; };
17D38F6E1279E23A00672B13 /* SPTableFieldValidation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTableFieldValidation.h; sourceTree = "<group>"; };
17D38F6F1279E23A00672B13 /* SPTableFieldValidation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTableFieldValidation.m; sourceTree = "<group>"; };
17D38FC3127B0CFC00672B13 /* SPConnectionControllerDelegateProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPConnectionControllerDelegateProtocol.h; sourceTree = "<group>"; };
@@ -1837,10 +1831,6 @@
children = (
17E641540EF01EF6001BC333 /* SPTableStructure.h */,
17E641550EF01EF6001BC333 /* SPTableStructure.m */,
- 1748D50A15A4444F003562F2 /* SPTableStructureLoading.h */,
- 1748D50B15A4444F003562F2 /* SPTableStructureLoading.m */,
- 17D38EBA12771A1C00672B13 /* SPTableStructureDelegate.h */,
- 17D38EBB12771A1C00672B13 /* SPTableStructureDelegate.m */,
17D38F6E1279E23A00672B13 /* SPTableFieldValidation.h */,
17D38F6F1279E23A00672B13 /* SPTableFieldValidation.m */,
);
@@ -3357,7 +3347,6 @@
BC2898F3125F4488001B50E1 /* SPGeometryDataView.m in Sources */,
17148565125F5FF500321285 /* SPDatabaseCharacterSets.m in Sources */,
175EC63512733B36009A7C0F /* SPExportControllerDelegate.m in Sources */,
- 17D38EBC12771A1C00672B13 /* SPTableStructureDelegate.m in Sources */,
17D38F701279E23A00672B13 /* SPTableFieldValidation.m in Sources */,
17D390C8127B65AF00672B13 /* SPGeneralPreferencePane.m in Sources */,
17D390CB127B6BF800672B13 /* SPPreferencesUpgrade.m in Sources */,
@@ -3402,7 +3391,6 @@
1798F19E15501892004B0AB8 /* SPFlippedView.m in Sources */,
17D5B49E1553059F00EF3BB3 /* SPViewCopy.m in Sources */,
176E14D115570FE300FAF326 /* SPBundleCommandRunner.m in Sources */,
- 1748D50C15A4444F003562F2 /* SPTableStructureLoading.m in Sources */,
58DF9F3315AB26C2003B4330 /* SPDateAdditions.m in Sources */,
58DF9F7315AB8509003B4330 /* SPSplitView.m in Sources */,
58DFC91615CB3501003B4330 /* BGHUDButtonCell.m in Sources */,