//
// $Id$
//
// SPFieldMapperController.m
// sequel-pro
//
// Created by Hans-Jörg Bibiko on February 01, 2010
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// More info at
#import "SPFieldMapperController.h"
#import "SPTableData.h"
#import "SPDataImport.h"
#import "SPTablesList.h"
#import "SPTextView.h"
#import "SPTableView.h"
#import "SPCategoryAdditions.h"
#import "RegexKitLite.h"
#import "SPDatabaseData.h"
#define SP_NUMBER_OF_RECORDS_STRING NSLocalizedString(@"%ld of %@%lu records", @"Label showing the index of the selected CSV row")
// Constants
static NSString *SPTableViewImportValueColumnID = @"import_value";
static NSString *SPTableViewTypeColumnID = @"type";
static NSString *SPTableViewTargetFieldColumnID = @"target_field";
static NSString *SPTableViewOperatorColumnID = @"operator";
static NSString *SPTableViewValueIndexColumnID = @"value_index";
static NSString *SPTableViewGlobalValueColumnID = @"global_value";
static NSString *SPTableViewSqlColumnID = @"sql";
@implementation SPFieldMapperController
@synthesize sourcePath;
#pragma mark -
#pragma mark Initialization
/**
* Initialize the field mapper
*/
- (id)initWithDelegate:(id)managerDelegate
{
if ((self = [super initWithWindowNibName:@"DataMigrationDialog"])) {
fieldMappingCurrentRow = 0;
if(managerDelegate == nil) {
NSBeep();
NSLog(@"FieldMapperController was called without a delegate.");
return nil;
}
theDelegate = managerDelegate;
fieldMappingTableColumnNames = [[NSMutableArray alloc] init];
fieldMappingTableDefaultValues = [[NSMutableArray alloc] init];
fieldMappingTableTypes = [[NSMutableArray alloc] init];
fieldMappingButtonOptions = [[NSMutableArray alloc] init];
fieldMappingOperatorOptions = [[NSMutableArray alloc] init];
fieldMappingOperatorArray = [[NSMutableArray alloc] init];
fieldMappingGlobalValues = [[NSMutableArray alloc] init];
defaultFieldTypesForComboBox = [[NSMutableArray alloc] init];
fieldMappingGlobalValuesSQLMarked = [[NSMutableArray alloc] init];
fieldMappingArray = nil;
lastDisabledCSVFieldcolumn = [NSNumber numberWithInteger:0];
doImport = [NSNumber numberWithInteger:0];
doNotImport = [NSNumber numberWithInteger:1];
isEqual = [NSNumber numberWithInteger:2];
doImportString = @"―";
doNotImportString = @" ";
isEqualString = @"=";
newTableMode = NO;
addGlobalSheetIsOpen = NO;
toBeEditedRowIndexes = [[NSMutableIndexSet alloc] init];
prefs = [NSUserDefaults standardUserDefaults];
tablesListInstance = [theDelegate valueForKeyPath:@"tablesListInstance"];
databaseDataInstance = [tablesListInstance valueForKeyPath:@"databaseDataInstance"];
if(![prefs objectForKey:SPLastImportIntoNewTableType])
[prefs setObject:@"Default" forKey:SPLastImportIntoNewTableType];
if(![prefs objectForKey:SPLastImportIntoNewTableEncoding])
[prefs setObject:@"Default" forKey:SPLastImportIntoNewTableEncoding];
}
return self;
}
- (void)awakeFromNib
{
// Set Context Menu
[[[fieldMapperTableView menu] itemAtIndex:0] setHidden:YES];
[[[fieldMapperTableView menu] itemAtIndex:1] setHidden:YES];
[[[fieldMapperTableView menu] itemAtIndex:2] setHidden:NO];
[[[fieldMapperTableView menu] itemAtIndex:3] setHidden:NO];
// [[[fieldMapperTableView menu] itemAtIndex:4] setHidden:NO];
// Set source path
// Note: [fileSourcePath setURL:[NSURL fileWithPath:sourcePath]] does NOT work
// if Sequel Pro runs localized. Reason unknown, it seems to be a NSPathControl bug.
// Ask HansJB for more info.
NSPathControl *pc = [[[NSPathControl alloc] initWithFrame:NSZeroRect] autorelease];
[pc setURL:[NSURL fileURLWithPath:sourcePath]];
if([pc pathComponentCells])
[fileSourcePath setPathComponentCells:[pc pathComponentCells]];
[fileSourcePath setDoubleAction:@selector(goBackToFileChooserFromPathControl:)];
[onupdateTextView setDelegate:theDelegate];
windowMinWidth = [[self window] minSize].width;
windowMinHeigth = [[self window] minSize].height;
[newTableNameTextField setHidden:YES];
[newTableNameLabel setHidden:YES];
[newTableNameInfoButton setHidden:YES];
[newTableButton setHidden:NO];
// Init table target popup menu
[tableTargetPopup removeAllItems];
[tableTargetPopup addItemWithTitle:NSLocalizedString(@"New Table", @"new table menu item")];
[tableTargetPopup addItemWithTitle:NSLocalizedString(@"Refresh List", @"refresh list menu item")];
[[tableTargetPopup menu] addItem:[NSMenuItem separatorItem]];
NSArray *allTableNames = [tablesListInstance allTableNames];
if(allTableNames) {
[tableTargetPopup addItemsWithTitles:allTableNames];
// Select either the currently selected table, or the first item in the list, or if no table in db switch to "New Table" mode
if ([[theDelegate valueForKeyPath:@"tableDocumentInstance"] table] != nil
&& ![[tablesListInstance tableName] isEqualToString:@""]
&& [allTableNames containsObject:[tablesListInstance tableName]]) {
[tableTargetPopup selectItemWithTitle:[tablesListInstance tableName]];
} else {
if([allTableNames count])
[tableTargetPopup selectItemAtIndex:3];
else
[tableTargetPopup selectItemAtIndex:0];
[newTableNameTextField selectText:nil];
}
}
[defaultFieldTypesForComboBox setArray:[NSArray arrayWithObjects:
@"VARCHAR(255)",
@"CHAR(63)",
@"TEXT",
@"LONGTEXT",
@"INT(11)",
@"BIGINT",
@"DATE",
@"DATETIME",
@"TIME",
@"TIMESTAMP",
nil
]];
[importFieldNamesHeaderSwitch setState:importFieldNamesHeader];
[addRemainingDataSwitch setState:NO];
[ignoreCheckBox setState:NO];
[ignoreUpdateCheckBox setState:NO];
[delayedCheckBox setState:NO];
[delayedReplaceCheckBox setState:NO];
[onupdateCheckBox setState:NO];
[lowPriorityCheckBox setState:NO];
[lowPriorityReplaceCheckBox setState:NO];
[lowPriorityUpdateCheckBox setState:NO];
[highPriorityCheckBox setState:NO];
[skipexistingRowsCheckBox setState:NO];
[skipexistingRowsCheckBox setEnabled:NO];
[advancedButton setState:NO];
[advancedBox setHidden:YES];
showAdvancedView = NO;
targetTableHasPrimaryKey = NO;
primaryKeyField = nil;
heightOffset = 0;
[advancedReplaceView setHidden:YES];
[advancedUpdateView setHidden:YES];
[advancedInsertView setHidden:YES];
[self changeHasHeaderCheckbox:self];
[self changeTableTarget:self];
[[self window] makeFirstResponder:fieldMapperTableView];
if([fieldMappingTableColumnNames count])
[fieldMapperTableView selectRowIndexes:[NSIndexSet indexSetWithIndex:0] byExtendingSelection:NO];
[removeGlobalValueButton setEnabled:([globalValuesTableView numberOfSelectedRows] > 0)];
[insertNULLValueButton setEnabled:([globalValuesTableView numberOfSelectedRows] == 1)];
[self updateFieldNameAlignment];
}
- (void)dealloc
{
if (mySQLConnection) [mySQLConnection release];
if (sourcePath) [sourcePath release];
if (fieldMappingTableColumnNames) [fieldMappingTableColumnNames release];
if (defaultFieldTypesForComboBox) [defaultFieldTypesForComboBox release];
if (fieldMappingTableTypes) [fieldMappingTableTypes release];
if (fieldMappingArray) [fieldMappingArray release];
if (fieldMappingButtonOptions) [fieldMappingButtonOptions release];
if (fieldMappingOperatorOptions) [fieldMappingOperatorOptions release];
if (fieldMappingOperatorArray) [fieldMappingOperatorArray release];
if (fieldMappingGlobalValues) [fieldMappingGlobalValues release];
if (fieldMappingGlobalValuesSQLMarked) [fieldMappingGlobalValuesSQLMarked release];
if (fieldMappingTableDefaultValues) [fieldMappingTableDefaultValues release];
if (primaryKeyField) [primaryKeyField release];
if (toBeEditedRowIndexes) [toBeEditedRowIndexes release];
[super dealloc];
}
#pragma mark -
#pragma mark Setter methods
- (void)setConnection:(MCPConnection *)theConnection
{
mySQLConnection = theConnection;
[mySQLConnection retain];
}
- (void)setImportDataArray:(id)theFieldMappingImportArray hasHeader:(BOOL)hasHeader isPreview:(BOOL)isPreview
{
numberOfImportColumns = 0;
[fieldMappingGlobalValues removeAllObjects];
fieldMappingImportArray = theFieldMappingImportArray;
importFieldNamesHeader = hasHeader;
fieldMappingImportArrayIsPreview = isPreview;
if([fieldMappingImportArray count])
numberOfImportColumns = [NSArrayObjectAtIndex(fieldMappingImportArray, 0) count];
NSInteger i;
for(i=0; i= numberOfImportColumns && NSArrayObjectAtIndex(fieldMappingOperatorArray, i) != doNotImport)
return YES;
i++;
}
return NO;
}
- (BOOL)importIntoNewTable
{
return newTableMode;
}
- (NSArray*)fieldMappingTableColumnNames
{
return fieldMappingTableColumnNames;
}
- (NSArray*)fieldMappingTableDefaultValues
{
return fieldMappingTableDefaultValues;
}
- (BOOL)importFieldNamesHeader
{
return ([importFieldNamesHeaderSwitch state] == NSOnState)?YES:NO;
}
- (BOOL)insertRemainingRowsAfterUpdate
{
return ([addRemainingDataSwitch state] == NSOnState)?YES:NO;
}
- (NSString*)importHeaderString
{
if([[importMethodPopup titleOfSelectedItem] isEqualToString:@"INSERT"]) {
return [NSString stringWithFormat:@"INSERT %@%@%@%@INTO ",
([lowPriorityCheckBox state] == NSOnState) ? @"LOW_PRIORITY " : @"",
([delayedCheckBox state] == NSOnState) ? @"DELAYED " : @"",
([highPriorityCheckBox state] == NSOnState) ? @"HIGH_PRIORITY " : @"",
([ignoreCheckBox state] == NSOnState) ? @"IGNORE " : @""
];
}
else if([[importMethodPopup titleOfSelectedItem] isEqualToString:@"REPLACE"]) {
return [NSString stringWithFormat:@"REPLACE %@%@INTO ",
([lowPriorityReplaceCheckBox state] == NSOnState) ? @"LOW_PRIORITY " : @"",
([delayedReplaceCheckBox state] == NSOnState) ? @"DELAYED " : @""
];
}
else if([[importMethodPopup titleOfSelectedItem] isEqualToString:@"UPDATE"]) {
return [NSString stringWithFormat:@"UPDATE %@%@%@ SET ",
([lowPriorityUpdateCheckBox state] == NSOnState) ? @"LOW_PRIORITY " : @"",
([ignoreUpdateCheckBox state] == NSOnState) ? @"IGNORE " : @"",
[[self selectedTableTarget] backtickQuotedString]
];
}
return @"";
}
- (NSString*)onupdateString
{
if([onupdateCheckBox state] == NSOnState && [[onupdateTextView string] length])
return [NSString stringWithFormat:@"ON DUPLICATE KEY UPDATE %@", [onupdateTextView string]];
else
return @"";
}
- (BOOL)canBeClosed
{
return [importButton isEnabled];
}
- (BOOL)isGlobalValueSheetOpen
{
return addGlobalSheetIsOpen;
}
#pragma mark -
#pragma mark IBAction methods
- (IBAction)closeInfoSheet:(id)sender
{
// Only save selection if the user selected 'OK'
if ([sender tag]) {
[prefs setObject:[newTableInfoEnginePopup titleOfSelectedItem] forKey:SPLastImportIntoNewTableType];
[prefs setObject:[newTableInfoEncodingPopup titleOfSelectedItem] forKey:SPLastImportIntoNewTableEncoding];
}
[NSApp endSheet:[sender window] returnCode:[sender tag]];
[[sender window] orderOut:self];
}
- (IBAction)closeSheet:(id)sender
{
// Try to add new columns first
if(!newTableMode && [toBeEditedRowIndexes count] && [sender tag] == 1) {
[[self window] endEditingFor:nil];
NSUInteger currentIndex = [toBeEditedRowIndexes firstIndex];
while (currentIndex != NSNotFound) {
NSMutableString *createString = [NSMutableString string];
[createString appendFormat:@"ALTER TABLE %@ ADD %@ %@",
[[tableTargetPopup titleOfSelectedItem] backtickQuotedString],
[[fieldMappingTableColumnNames objectAtIndex:currentIndex] backtickQuotedString],
[fieldMappingTableTypes objectAtIndex:currentIndex]];
[mySQLConnection queryString:createString];
if ([mySQLConnection queryErrored]) {
NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Error adding new column", @"error adding new column message")
defaultButton:NSLocalizedString(@"OK", @"OK button")
alternateButton:nil
otherButton:nil
informativeTextWithFormat:[NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to add the new column '%@' by\n\n%@.\n\nMySQL said: %@", @"error adding new column informative message"), [fieldMappingTableColumnNames objectAtIndex:currentIndex], createString, [mySQLConnection getLastErrorMessage]]];
[alert setAlertStyle:NSCriticalAlertStyle];
[alert beginSheetModalForWindow:[self window] modalDelegate:self didEndSelector:nil contextInfo:nil];
return;
} else {
[toBeEditedRowIndexes removeIndex:currentIndex];
}
currentIndex = [toBeEditedRowIndexes indexGreaterThanIndex:currentIndex];
}
}
// Try to create the new TABLE
else if(newTableMode && [sender tag] == 1) {
[[self window] endEditingFor:nil];
NSMutableString *createString = [NSMutableString string];
[createString appendFormat:@"CREATE TABLE %@ (\n", [[newTableNameTextField stringValue] backtickQuotedString]];
NSInteger columnIndex = 0;
NSInteger numberOfColumns = [fieldMappingTableColumnNames count];
for(columnIndex = 0; columnIndex < numberOfColumns; columnIndex++) {
// add to the new table only those fields which are markes as "Do Import"
if([fieldMappingOperatorArray objectAtIndex:columnIndex] == doImport) {
[createString appendFormat:@"\t%@ %@", [[fieldMappingTableColumnNames objectAtIndex:columnIndex] backtickQuotedString], [fieldMappingTableTypes objectAtIndex:columnIndex]];
if(columnIndex < numberOfColumns-1) [createString appendString:@", \n"];
}
}
[createString appendString:@")"];
if(![[prefs objectForKey:SPLastImportIntoNewTableType] isEqualToString:@"Default"])
[createString appendFormat:@" ENGINE=%@", [prefs objectForKey:SPLastImportIntoNewTableType]];
if(![[prefs objectForKey:SPLastImportIntoNewTableEncoding] isEqualToString:@"Default"]) {
NSString *encodingName = [[prefs objectForKey:SPLastImportIntoNewTableEncoding] stringByMatching:@"\\((.*)\\)" capture:1L];
if (!encodingName) encodingName = @"utf8";
[createString appendFormat:[NSString stringWithFormat:@" DEFAULT CHARACTER SET %@", [encodingName backtickQuotedString]]];
}
[mySQLConnection queryString:createString];
if ([mySQLConnection queryErrored]) {
NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Error adding new table", @"error adding new table message")
defaultButton:NSLocalizedString(@"OK", @"OK button")
alternateButton:nil
otherButton:nil
informativeTextWithFormat:[NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to add the new table '%@' by\n\n%@.\n\nMySQL said: %@", @"error adding new table informative message"), [newTableNameTextField stringValue], createString, [mySQLConnection getLastErrorMessage]]];
[alert setAlertStyle:NSCriticalAlertStyle];
[alert beginSheetModalForWindow:[self window] modalDelegate:self didEndSelector:nil contextInfo:nil];
return;
}
}
[advancedReplaceView setHidden:YES];
[advancedUpdateView setHidden:YES];
[advancedInsertView setHidden:YES];
[advancedBox setHidden:YES];
[self resizeWindowByHeightDelta:0];
[NSApp endSheet:[self window] returnCode:[sender tag]];
}
- (IBAction)changeTableTarget:(id)sender
{
NSArray *allTableNames = [tablesListInstance allTableNames];
NSUInteger i;
// Remove all indexes for new columns
[toBeEditedRowIndexes removeAllIndexes];
// Is Refresh List chosen?
if([tableTargetPopup selectedItem] == [tableTargetPopup itemAtIndex:1]) {
[tableTargetPopup removeAllItems];
[tableTargetPopup addItemWithTitle:NSLocalizedString(@"New Table", @"new table menu item")];
[tableTargetPopup addItemWithTitle:NSLocalizedString(@"Refresh List", @"refresh list menu item")];
[[tableTargetPopup menu] addItem:[NSMenuItem separatorItem]];
// Update tables list
[tablesListInstance updateTables:nil];
if(allTableNames) {
[tableTargetPopup addItemsWithTitles:allTableNames];
}
// Select either the currently selected table, or the first item in the list, or if no table in db switch to "New Table" mode
if ([[theDelegate valueForKeyPath:@"tableDocumentInstance"] table] != nil
&& ![[tablesListInstance tableName] isEqualToString:@""]
&& [allTableNames containsObject:[tablesListInstance tableName]]) {
[tableTargetPopup selectItemWithTitle:[tablesListInstance tableName]];
} else {
if([allTableNames count])
[tableTargetPopup selectItemAtIndex:3];
else
[tableTargetPopup selectItemAtIndex:0];
}
return;
}
// New Table was chosen
else if([tableTargetPopup selectedItem] == [tableTargetPopup itemAtIndex:0]) {
[self newTable:nil];
return;
}
// Remove all the current columns
[fieldMappingTableColumnNames removeAllObjects];
[fieldMappingTableDefaultValues removeAllObjects];
[fieldMappingTableTypes removeAllObjects];
// Retrieve the information for the newly selected table using a SPTableData instance
SPTableData *selectedTableData = [[SPTableData alloc] init];
[selectedTableData setConnection:mySQLConnection];
NSDictionary *tableDetails = [selectedTableData informationForTable:[tableTargetPopup titleOfSelectedItem]];
targetTableHasPrimaryKey = NO;
BOOL isReplacePossible = NO;
if (tableDetails) {
for (NSDictionary *column in [tableDetails objectForKey:@"columns"]) {
[fieldMappingTableColumnNames addObject:[NSString stringWithString:[column objectForKey:@"name"]]];
NSMutableString *type = [NSMutableString string];
if([column objectForKey:@"type"])
[type appendString:[column objectForKey:@"type"]];
if([column objectForKey:@"length"])
[type appendFormat:@"(%@)", [column objectForKey:@"length"]];
if([column objectForKey:@"values"])
[type appendFormat:@"(%@)", [[column objectForKey:@"values"] componentsJoinedByString:@"¦"]];
if([column objectForKey:@"isprimarykey"]) {
[type appendFormat:@",%@",@"PRIMARY"];
if([[[column objectForKey:@"autoincrement"] description] isEqualToString:@"1"]) {
[fieldMappingTableDefaultValues addObject:@"auto_increment"];
} else {
[fieldMappingTableDefaultValues addObject:@"0"];
}
targetTableHasPrimaryKey = YES;
if (primaryKeyField) [primaryKeyField release];
primaryKeyField = [[tableDetails objectForKey:@"primarykeyfield"] retain];
} else {
if([column objectForKey:@"unique"]) {
[type appendFormat:@",%@",@"UNIQUE"];
isReplacePossible = YES;
}
// if([[[column objectForKey:@"onupdatetimestamp"] description] isEqualToString:@"1"]) {
// [fieldMappingTableDefaultValues addObject:@"CURRENT_TIMESTAMP"];
// } else {
if ([column objectForKey:@"default"])
[fieldMappingTableDefaultValues addObject:[column objectForKey:@"default"]];
else
[fieldMappingTableDefaultValues addObject:[NSNull null]];
// }
}
[fieldMappingTableTypes addObject:[NSString stringWithString:type]];
}
}
[selectedTableData release];
[[importMethodPopup menu] setAutoenablesItems:NO];
[[importMethodPopup itemWithTitle:@"REPLACE"] setEnabled:(targetTableHasPrimaryKey|isReplacePossible)];
[skipexistingRowsCheckBox setEnabled:targetTableHasPrimaryKey];
// Update the table view
fieldMappingCurrentRow = 0;
if (fieldMappingArray) [fieldMappingArray release], fieldMappingArray = nil;
[self setupFieldMappingArray];
[rowDownButton setEnabled:NO];
[rowUpButton setEnabled:([fieldMappingImportArray count] > 1)];
[recordCountLabel setStringValue:[NSString stringWithFormat:SP_NUMBER_OF_RECORDS_STRING, (long)(fieldMappingCurrentRow+1), fieldMappingImportArrayIsPreview?@"first ":@"", (unsigned long)[fieldMappingImportArray count]]];
[self updateFieldMappingButtonCell];
[self updateFieldMappingOperatorOptions];
// Set all operators to doNotImport
[fieldMappingOperatorArray removeAllObjects];
for(i=0; i < [fieldMappingTableColumnNames count]; i++)
[fieldMappingOperatorArray addObject:doNotImport];
// Set the first n operators to doImport
if([fieldMappingImportArray count]) {
NSUInteger possibleImports = ([NSArrayObjectAtIndex(fieldMappingImportArray, 0) count] > [fieldMappingTableColumnNames count]) ? [fieldMappingTableColumnNames count] : [NSArrayObjectAtIndex(fieldMappingImportArray, 0) count];
for(i=0; i < possibleImports; i++)
[fieldMappingOperatorArray replaceObjectAtIndex:i withObject:doImport];
}
// Disable Import button if no fields are available
[importButton setEnabled:([fieldMappingTableColumnNames count] > 0)];
// Disable UPDATE import method if target table has less than 2 fields
// and fall back to INSERT if UPDATE was selected
if([fieldMappingTableColumnNames count] > 1) {
[[importMethodPopup itemWithTitle:@"UPDATE"] setEnabled:YES];
} else {
[[importMethodPopup itemWithTitle:@"UPDATE"] setEnabled:NO];
if([[importMethodPopup titleOfSelectedItem] isEqualToString:@"UPDATE"]) {
[importMethodPopup selectItemWithTitle:@"INSERT"];
[self changeImportMethod:nil];
}
}
[self updateFieldNameAlignment];
[fieldMapperTableView reloadData];
}
- (IBAction)changeImportMethod:(id)sender
{
NSUInteger i;
[onupdateTextView setBackgroundColor:[NSColor lightGrayColor]];
[onupdateTextView setEditable:NO];
[ignoreCheckBox setState:NO];
[ignoreUpdateCheckBox setState:NO];
[delayedCheckBox setState:NO];
[delayedReplaceCheckBox setState:NO];
[onupdateCheckBox setState:NO];
[lowPriorityCheckBox setState:NO];
[lowPriorityReplaceCheckBox setState:NO];
[lowPriorityUpdateCheckBox setState:NO];
[highPriorityCheckBox setState:NO];
[advancedReplaceView setHidden:YES];
[advancedUpdateView setHidden:YES];
[advancedInsertView setHidden:YES];
if(showAdvancedView) {
[advancedBox setHidden:NO];
if([[importMethodPopup titleOfSelectedItem] isEqualToString:@"UPDATE"]) {
[self resizeWindowByHeightDelta:[advancedUpdateView frame].size.height-10];
[advancedUpdateView setHidden:NO];
[advancedInsertView setHidden:YES];
[advancedReplaceView setHidden:YES];
}
else if([[importMethodPopup titleOfSelectedItem] isEqualToString:@"INSERT"]) {
[self resizeWindowByHeightDelta:[advancedInsertView frame].size.height-20];
[advancedInsertView setHidden:NO];
[advancedUpdateView setHidden:YES];
[advancedReplaceView setHidden:YES];
}
else if([[importMethodPopup titleOfSelectedItem] isEqualToString:@"REPLACE"]) {
[self resizeWindowByHeightDelta:[advancedReplaceView frame].size.height-10];
[advancedReplaceView setHidden:NO];
[advancedUpdateView setHidden:YES];
[advancedInsertView setHidden:YES];
}
} else {
[advancedBox setHidden:YES];
}
// If operator is set to = for UPDATE method replace it by doNotImport
if(![[importMethodPopup titleOfSelectedItem] isEqualToString:@"UPDATE"]) {
[advancedButton setEnabled:YES];
for(i=0; i<[fieldMappingTableColumnNames count]; i++) {
if([fieldMappingOperatorArray objectAtIndex:i] == isEqual) {
[fieldMappingOperatorArray replaceObjectAtIndex:i withObject:doNotImport];
}
}
} else {
[advancedButton setEnabled:YES];
}
[self validateImportButton];
[self updateFieldMappingOperatorOptions];
[fieldMapperTableView reloadData];
}
- (IBAction)changeFieldAlignment:(id)sender
{
if(![fieldMappingImportArray count]) return;
NSUInteger i;
NSInteger j;
NSInteger possibleImports = ([NSArrayObjectAtIndex(fieldMappingImportArray, 0) count] > [fieldMappingTableColumnNames count]) ? [fieldMappingTableColumnNames count] : [NSArrayObjectAtIndex(fieldMappingImportArray, 0) count];
if(possibleImports < 1) return;
// Set all operators to doNotImport
[fieldMappingOperatorArray removeAllObjects];
for(i=0; i < [fieldMappingTableColumnNames count]; i++)
[fieldMappingOperatorArray addObject:doNotImport];
switch([[alignByPopup selectedItem] tag]) {
case 0: // file order
for(j=0; j=0; j--) {
[fieldMappingArray replaceObjectAtIndex:possibleImports-j withObject:[NSNumber numberWithInteger:j]];
[fieldMappingOperatorArray replaceObjectAtIndex:possibleImports-j withObject:doImport];
}
break;
case 2: // try to align header and table target field names via Levenshtein distance
[self matchHeaderNames];
break;
}
[fieldMapperTableView reloadData];
// Remember last field alignment if not "custom order"
if([[alignByPopup selectedItem] tag] != 3)
[prefs setInteger:[[alignByPopup selectedItem] tag] forKey:SPCSVFieldImportMappingAlignment];
}
/*
* Displays next/previous row in fieldMapping tableView
*/
- (IBAction)stepRow:(id)sender
{
if ( [sender tag] == 0 ) {
fieldMappingCurrentRow--;
} else {
fieldMappingCurrentRow++;
}
[self updateFieldMappingButtonCell];
[fieldMapperTableView reloadData];
[recordCountLabel setStringValue:[NSString stringWithFormat:SP_NUMBER_OF_RECORDS_STRING, (long)(fieldMappingCurrentRow+1), fieldMappingImportArrayIsPreview?@"first ":@"", (unsigned long)[fieldMappingImportArray count]]];
// enable/disable buttons
[rowDownButton setEnabled:(fieldMappingCurrentRow != 0)];
[rowUpButton setEnabled:(fieldMappingCurrentRow != (NSInteger)([fieldMappingImportArray count]-1))];
}
- (IBAction)changeHasHeaderCheckbox:(id)sender
{
[matchingNameMenuItem setEnabled:([importFieldNamesHeaderSwitch state] == NSOnState)?YES:NO];
// In New Table mode reset new field name according to importFieldNamesHeaderSwitch's state
if(newTableMode) {
[fieldMappingTableColumnNames removeAllObjects];
if([importFieldNamesHeaderSwitch state] == NSOnState) {
for(id h in NSArrayObjectAtIndex(fieldMappingImportArray, 0)) {
[fieldMappingTableColumnNames addObject:h];
}
} else {
NSInteger i = 0;
for(id h in NSArrayObjectAtIndex(fieldMappingImportArray, 0)) {
[fieldMappingTableColumnNames addObject:[NSString stringWithFormat:@"col_%ld", i++]];
}
}
[fieldMapperTableView reloadData];
}
}
- (IBAction)goBackToFileChooserFromPathControl:(id)sender
{
[gobackButton performSelector:@selector(performClick:) withObject:nil afterDelay:0.0f];
}
- (IBAction)goBackToFileChooser:(id)sender
{
[NSApp endSheet:[self window] returnCode:[sender tag]];
if([sourcePath hasPrefix:SPImportClipboardTempFileNamePrefix])
[theDelegate importFromClipboard];
else
[theDelegate importFile];
}
- (IBAction)newTable:(id)sender
{
newTableMode = YES;
// Set Context Menu
[[[fieldMapperTableView menu] itemAtIndex:0] setHidden:NO];
[[[fieldMapperTableView menu] itemAtIndex:1] setHidden:YES];
[[[fieldMapperTableView menu] itemAtIndex:2] setHidden:YES];
[[[fieldMapperTableView menu] itemAtIndex:3] setHidden:YES];
// [[[fieldMapperTableView menu] itemAtIndex:4] setHidden:YES];
[importMethodPopup selectItemWithTitle:@"INSERT"];
[[importMethodPopup itemWithTitle:@"UPDATE"] setEnabled:NO];
[[importMethodPopup itemWithTitle:@"REPLACE"] setEnabled:NO];
[tableTargetPopup setHidden:YES];
[newTableNameTextField setHidden:NO];
[newTableNameLabel setHidden:NO];
[newTableNameInfoButton setHidden:NO];
[newTableButton setHidden:YES];
[newTableNameTextField selectText:nil];
// Check length and type of fieldMappingImportArray 65,535
NSInteger maxLengthOfSourceColumns [numberOfImportColumns];
NSInteger typeOfSourceColumns [numberOfImportColumns]; // 0=text 1=integer
NSInteger columnCounter;
for(columnCounter = 0; columnCounter < numberOfImportColumns; columnCounter++) {
maxLengthOfSourceColumns[columnCounter] = 0;
typeOfSourceColumns[columnCounter] = 1;
}
BOOL skipFirstRow = importFieldNamesHeader;
for(NSArray* row in fieldMappingImportArray) {
if(skipFirstRow) {
skipFirstRow = NO;
continue;
}
columnCounter = 0;
for(id col in row) {
if(col && col != [NSNull null]) {
if([col isKindOfClass:[NSString class]] && maxLengthOfSourceColumns[columnCounter] < (NSInteger)[col length]) {
maxLengthOfSourceColumns[columnCounter] = [col length];
}
if(typeOfSourceColumns[columnCounter] == 1) {
if(![[[NSNumber numberWithLongLong:[col longLongValue]] stringValue] isEqualToString:col])
typeOfSourceColumns[columnCounter] = 0;
}
}
columnCounter++;
}
}
columnCounter = 0;
[fieldMappingTableColumnNames removeAllObjects];
[fieldMappingTableDefaultValues removeAllObjects];
[fieldMappingTableTypes removeAllObjects];
BOOL serverGreaterThanVersion4 = ([mySQLConnection serverMajorVersion] >= 5) ? YES : NO;
if([importFieldNamesHeaderSwitch state] == NSOnState) {
for(id h in NSArrayObjectAtIndex(fieldMappingImportArray, 0)) {
[fieldMappingTableColumnNames addObject:h];
[fieldMappingTableDefaultValues addObject:@""];
if(typeOfSourceColumns[columnCounter] == 1) { // integer type
if(maxLengthOfSourceColumns[columnCounter] < 9)
[fieldMappingTableTypes addObject:@"INT(11)"];
else
[fieldMappingTableTypes addObject:@"BIGINT(11)"];
} else {
if(serverGreaterThanVersion4) {
if(maxLengthOfSourceColumns[columnCounter] < 256)
[fieldMappingTableTypes addObject:@"VARCHAR(255)"];
else if(maxLengthOfSourceColumns[columnCounter] < 32768)
[fieldMappingTableTypes addObject:@"VARCHAR(32767)"];
else
[fieldMappingTableTypes addObject:@"TEXT"];
} else {
if(maxLengthOfSourceColumns[columnCounter] < 256)
[fieldMappingTableTypes addObject:@"VARCHAR(255)"];
else
[fieldMappingTableTypes addObject:@"TEXT"];
}
}
columnCounter++;
}
} else {
NSInteger i = 0;
for(id h in NSArrayObjectAtIndex(fieldMappingImportArray, 0)) {
[fieldMappingTableColumnNames addObject:[NSString stringWithFormat:@"col_%ld", i++]];
[fieldMappingTableDefaultValues addObject:@""];
if(typeOfSourceColumns[columnCounter] == 1) { // integer type
if(maxLengthOfSourceColumns[columnCounter] < 9)
[fieldMappingTableTypes addObject:@"INT(11)"];
else
[fieldMappingTableTypes addObject:@"BIGINT(11)"];
} else {
if(serverGreaterThanVersion4) {
if(maxLengthOfSourceColumns[columnCounter] < 256)
[fieldMappingTableTypes addObject:@"VARCHAR(255)"];
else if(maxLengthOfSourceColumns[columnCounter] < 32768)
[fieldMappingTableTypes addObject:@"VARCHAR(32767)"];
else
[fieldMappingTableTypes addObject:@"TEXT"];
} else {
if(maxLengthOfSourceColumns[columnCounter] < 256)
[fieldMappingTableTypes addObject:@"VARCHAR(255)"];
else
[fieldMappingTableTypes addObject:@"TEXT"];
}
}
columnCounter++;
}
}
// Update the table view
NSUInteger i;
fieldMappingCurrentRow = 0;
if (fieldMappingArray) [fieldMappingArray release], fieldMappingArray = nil;
[self setupFieldMappingArray];
[rowDownButton setEnabled:NO];
[rowUpButton setEnabled:([fieldMappingImportArray count] > 1)];
[recordCountLabel setStringValue:[NSString stringWithFormat:SP_NUMBER_OF_RECORDS_STRING, (long)(fieldMappingCurrentRow+1), fieldMappingImportArrayIsPreview?@"first ":@"", (unsigned long)[fieldMappingImportArray count]]];
[self updateFieldMappingButtonCell];
[self updateFieldMappingOperatorOptions];
// Set all operators to doNotImport
[fieldMappingOperatorArray removeAllObjects];
for(i=0; i < [fieldMappingTableColumnNames count]; i++)
[fieldMappingOperatorArray addObject:doImport];
[fieldMapperTableView reloadData];
[self validateImportButton];
}
/*
* Add new column to the selected table (processed after pressing 'Import' button)
*/
- (IBAction)addNewColumn:(id)sender
{
[fieldMappingOperatorArray addObject:doNotImport];
[fieldMappingTableColumnNames addObject:NSLocalizedString(@"New Column Name", @"new column name placeholder string")];
[fieldMappingTableTypes addObject:@"VARCHAR(255)"];
[fieldMappingTableDefaultValues addObject:@""];
NSInteger newIndex = [fieldMappingTableTypes count]-1;
[fieldMappingArray addObject:[NSNumber numberWithInteger:newIndex]];
[toBeEditedRowIndexes addIndex:newIndex];
[fieldMapperTableView reloadData];
[fieldMapperTableView editColumn:2 row:newIndex withEvent:nil select:YES];
}
/*
* Remove currently new added column
*/
- (IBAction)removeNewColumn:(id)sender
{
NSInteger toBeRemovedIndex = [fieldMapperTableView selectedRow];
if(![toBeEditedRowIndexes containsIndex:toBeRemovedIndex]) {
NSBeep();
return;
}
[fieldMappingOperatorArray removeObjectAtIndex:toBeRemovedIndex];
[fieldMappingTableColumnNames removeObjectAtIndex:toBeRemovedIndex];
[fieldMappingTableTypes removeObjectAtIndex:toBeRemovedIndex];
[fieldMappingTableDefaultValues removeObjectAtIndex:toBeRemovedIndex];
[fieldMappingArray removeObjectAtIndex:toBeRemovedIndex];
[toBeEditedRowIndexes removeIndex:toBeRemovedIndex];
// Renumber indexes greater than toBeRemovedIndex
NSInteger currentIndex = [toBeEditedRowIndexes firstIndex];
while(currentIndex != NSNotFound) {
if(currentIndex > toBeRemovedIndex) {
[toBeEditedRowIndexes addIndex:currentIndex-1];
[toBeEditedRowIndexes removeIndex:currentIndex];
}
currentIndex = [toBeEditedRowIndexes indexGreaterThanIndex:currentIndex];
}
[fieldMapperTableView reloadData];
}
// - (IBAction)editColumn:(id)sender
// {
// toBeEditedRowIndexes = [fieldMapperTableView selectedRow];
// [fieldMapperTableView reloadData];
// [fieldMapperTableView editColumn:3 row:[fieldMapperTableView selectedRow] withEvent:nil select:YES];
// }
/*
* Set all table target field types to that one of the current selected type
*/
- (IBAction)setAllTypesTo:(id)sender
{
NSInteger row = [fieldMapperTableView selectedRow];
if(row<0 || row>=(NSInteger)([fieldMappingTableColumnNames count])) {
NSBeep();
return;
}
NSString *type = [[fieldMappingTableTypes objectAtIndex:row] retain];
[fieldMappingTableTypes removeAllObjects];
NSUInteger i;
for(i=0; i<[fieldMappingTableColumnNames count]; i++)
[fieldMappingTableTypes addObject:type];
[fieldMapperTableView reloadData];
[type release];
}
/*
* Show sheet to set up encoding and engine for the new to be created table
*/
- (IBAction)newTableInfo:(id)sender
{
[[self window] endEditingFor:nil];
// Populate the table type (engine) popup button
[newTableInfoEnginePopup removeAllItems];
NSArray *engines = [databaseDataInstance getDatabaseStorageEngines];
// Add default menu item
[newTableInfoEnginePopup addItemWithTitle:@"Default"];
[[newTableInfoEnginePopup menu] addItem:[NSMenuItem separatorItem]];
for (NSDictionary *engine in engines)
{
[newTableInfoEnginePopup addItemWithTitle:[engine objectForKey:@"Engine"]];
}
[newTableInfoEnginePopup selectItemWithTitle:[prefs objectForKey:SPLastImportIntoNewTableType]];
// Populate the table encoding popup button with a default menu item
[newTableInfoEncodingPopup removeAllItems];
[newTableInfoEncodingPopup addItemWithTitle:@"Default"];
// Retrieve the server-supported encodings and add them to the menu
NSArray *encodings = [databaseDataInstance getDatabaseCharacterSetEncodings];
NSString *utf8MenuItemTitle = nil;
if ([encodings count] > 0
&& ([mySQLConnection serverMajorVersion] > 4
|| ([mySQLConnection serverMajorVersion] == 4 && [mySQLConnection serverMinorVersion] >= 1)))
{
[[newTableInfoEncodingPopup menu] addItem:[NSMenuItem separatorItem]];
for (NSDictionary *encoding in encodings) {
NSString *menuItemTitle = (![encoding objectForKey:@"DESCRIPTION"]) ? [encoding objectForKey:@"CHARACTER_SET_NAME"] : [NSString stringWithFormat:@"%@ (%@)", [encoding objectForKey:@"DESCRIPTION"], [encoding objectForKey:@"CHARACTER_SET_NAME"]];
[newTableInfoEncodingPopup addItemWithTitle:menuItemTitle];
// If the UTF8 entry has been encountered, store the menu title
if ([[encoding objectForKey:@"CHARACTER_SET_NAME"] isEqualToString:@"utf8"]) {
utf8MenuItemTitle = [NSString stringWithString:menuItemTitle];
}
}
// If a UTF8 entry was found, promote it to the top of the list
if (utf8MenuItemTitle) {
[[newTableInfoEncodingPopup menu] insertItem:[NSMenuItem separatorItem] atIndex:2];
[newTableInfoEncodingPopup insertItemWithTitle:utf8MenuItemTitle atIndex:2];
}
[newTableInfoEncodingPopup selectItemWithTitle:[prefs objectForKey:SPLastImportIntoNewTableEncoding]];
}
[NSApp beginSheet:newTableInfoWindow
modalForWindow:[self window]
modalDelegate:self
didEndSelector:nil
contextInfo:nil];
}
#pragma mark -
#pragma mark Global Value Sheet
- (IBAction)addGlobalSourceVariable:(id)sender
{
// Since it can be called via keyboard short-cut as well bail the call if sheet is already open
if(addGlobalSheetIsOpen) return;
addGlobalSheetIsOpen = YES;
// Init insert pulldown menu
// Remove all dynamic menu items
while([insertPullDownButton numberOfItems] > (([[self selectedImportMethod] isEqualToString:@"UPDATE"]) ? 6 : 5))
[insertPullDownButton removeItemAtIndex:[insertPullDownButton numberOfItems]-1];
// Add recent global value menu
if([prefs objectForKey:SPGlobalValueHistory] && [[prefs objectForKey:SPGlobalValueHistory] isKindOfClass:[NSArray class]] && [[prefs objectForKey:SPGlobalValueHistory] count])
for(id item in [prefs objectForKey:SPGlobalValueHistory])
[recentGlobalValueMenu addItemWithTitle:item action:@selector(insertRecentGlobalValue:) keyEquivalent:@""];
// Add column placeholder
NSInteger i = 0;
if([fieldMappingImportArray count] && [[fieldMappingImportArray objectAtIndex:0] count]) {
for(id item in [fieldMappingImportArray objectAtIndex:0]) {
i++;
if ([item isNSNull]) {
[insertPullDownButton addItemWithTitle:[NSString stringWithFormat:@"%i. <%@>", i, [prefs objectForKey:SPNullValue]]];
} else if ([item isSPNotLoaded]) {
[insertPullDownButton addItemWithTitle:[NSString stringWithFormat:@"%i. <%@>", i, @"DEFAULT"]];
} else {
if([item length] > 20)
[insertPullDownButton addItemWithTitle:[NSString stringWithFormat:@"%i. %@…", i, [item substringToIndex:20]]];
else
[insertPullDownButton addItemWithTitle:[NSString stringWithFormat:@"%i. %@", i, item]];
}
}
}
[NSApp beginSheet:globalValuesSheet
modalForWindow:[self window]
modalDelegate:self
didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:nil];
[self addGlobalValue:nil];
}
- (IBAction)addGlobalValue:(id)sender
{
[fieldMappingGlobalValues addObject:@""];
[fieldMappingGlobalValuesSQLMarked addObject:[NSNumber numberWithBool:NO]];
[globalValuesTableView reloadData];
[globalValuesTableView selectRowIndexes:[NSIndexSet indexSetWithIndex:[fieldMappingGlobalValues count]-1-numberOfImportColumns] byExtendingSelection:NO];
[globalValuesTableView editColumn:1 row:[fieldMappingGlobalValues count]-1-numberOfImportColumns withEvent:nil select:YES];
}
- (IBAction)removeGlobalValue:(id)sender
{
[globalValuesTableView abortEditing];
NSIndexSet *indexes = [globalValuesTableView selectedRowIndexes];
// get last index
NSUInteger currentIndex = [indexes lastIndex];
while (currentIndex != NSNotFound) {
[fieldMappingGlobalValues removeObjectAtIndex:currentIndex+numberOfImportColumns];
[fieldMappingGlobalValuesSQLMarked removeObjectAtIndex:currentIndex+numberOfImportColumns];
// get next index (beginning from the end)
currentIndex = [indexes indexLessThanIndex:currentIndex];
}
[globalValuesTableView reloadData];
// Set focus to favorite list to avoid an unstable state
[globalValuesSheet makeFirstResponder:globalValuesTableView];
[removeGlobalValueButton setEnabled:([globalValuesTableView numberOfSelectedRows] > 0)];
[insertNULLValueButton setEnabled:([globalValuesTableView numberOfSelectedRows] == 1)];
}
- (IBAction)insertNULLValue:(id)sender;
{
if([globalValuesTableView numberOfSelectedRows] != 1) return;
[globalValuesTableView abortEditing];
[fieldMappingGlobalValues replaceObjectAtIndex:[globalValuesTableView selectedRow]+numberOfImportColumns withObject:[NSNull null]];
[globalValuesTableView reloadData];
}
- (IBAction)closeGlobalValuesSheet:(id)sender
{
// Ensure all changes are stored before ordering out
[globalValuesTableView validateEditing];
if ([globalValuesTableView numberOfSelectedRows] == 1)
[globalValuesSheet makeFirstResponder:globalValuesTableView];
// Replace the current map pair with the last selected global value
if([replaceAfterSavingCheckBox state] == NSOnState && [globalValuesTableView numberOfSelectedRows] == 1) {
[fieldMappingArray replaceObjectAtIndex:[fieldMapperTableView selectedRow] withObject:[NSNumber numberWithInteger:[globalValuesTableView selectedRow]+numberOfImportColumns]];
// Set corresponding operator to doImport if not set to isEqual
if([fieldMappingOperatorArray objectAtIndex:[fieldMapperTableView selectedRow]] != isEqual)
[fieldMappingOperatorArray replaceObjectAtIndex:[fieldMapperTableView selectedRow] withObject:doImport];
[fieldMapperTableView reloadData];
// Set alignment popup to "custom order"
[alignByPopup selectItemWithTag:3];
}
[NSApp endSheet:globalValuesSheet returnCode:[sender tag]];
}
#pragma mark -
#pragma mark Advanced Sheet
- (IBAction)openAdvancedSheet:(id)sender
{
showAdvancedView = !showAdvancedView;
if(showAdvancedView) {
[advancedButton setState:NSOnState];
[self changeImportMethod:nil];
} else {
[advancedButton setState:NSOffState];
[advancedBox setHidden:YES];
[advancedReplaceView setHidden:YES];
[advancedUpdateView setHidden:YES];
[advancedInsertView setHidden:YES];
[self resizeWindowByHeightDelta:0];
}
}
- (IBAction)advancedCheckboxValidation:(id)sender
{
if(sender == lowPriorityReplaceCheckBox && [lowPriorityReplaceCheckBox state] == NSOnState) {
[delayedReplaceCheckBox setState:NO];
return;
}
if(sender == delayedReplaceCheckBox && [delayedReplaceCheckBox state] == NSOnState) {
[lowPriorityReplaceCheckBox setState:NO];
return;
}
if(sender == skipexistingRowsCheckBox) {
if([skipexistingRowsCheckBox state] == NSOnState) {
[delayedCheckBox setState:NO];
[delayedCheckBox setEnabled:NO];
[onupdateCheckBox setState:YES];
[onupdateCheckBox setEnabled:NO];
[onupdateTextView setEditable:YES];
[onupdateTextView setSelectedRange:NSMakeRange(0,[[onupdateTextView string] length])];
[onupdateTextView insertText:[NSString stringWithFormat:@"%@ = %@", [primaryKeyField backtickQuotedString], [primaryKeyField backtickQuotedString]]];
[onupdateTextView setBackgroundColor:[NSColor lightGrayColor]];
[onupdateTextView setEditable:NO];
} else {
[delayedCheckBox setEnabled:YES];
[onupdateCheckBox setState:NO];
[onupdateCheckBox setEnabled:YES];
BOOL oldEditableState = [onupdateTextView isEditable];
[onupdateTextView setEditable:YES];
[onupdateTextView setSelectedRange:NSMakeRange(0,[[onupdateTextView string] length])];
[onupdateTextView insertText:@""];
[onupdateTextView setEditable:oldEditableState];
}
}
if(sender == lowPriorityCheckBox && [lowPriorityCheckBox state] == NSOnState) {
[highPriorityCheckBox setState:NO];
[delayedCheckBox setState:NO];
if([skipexistingRowsCheckBox state] == NSOffState)
[onupdateCheckBox setEnabled:YES];
}
if(sender == highPriorityCheckBox && [highPriorityCheckBox state] == NSOnState) {
[lowPriorityCheckBox setState:NO];
[delayedCheckBox setState:NO];
if([skipexistingRowsCheckBox state] == NSOffState)
[onupdateCheckBox setEnabled:YES];
}
if(sender == delayedCheckBox) {
if([delayedCheckBox state] == NSOnState) {
[lowPriorityCheckBox setState:NO];
[highPriorityCheckBox setState:NO];
[onupdateCheckBox setState:NO];
[onupdateCheckBox setEnabled:NO];
} else {
[onupdateCheckBox setEnabled:YES];
}
}
if(sender == onupdateCheckBox && [onupdateCheckBox state] == NSOnState) {
[onupdateTextView setBackgroundColor:[NSColor whiteColor]];
[onupdateTextView setEditable:YES];
[[self window] makeFirstResponder:onupdateTextView];
}
if([onupdateCheckBox state] == NSOffState && [skipexistingRowsCheckBox state] == NSOffState) {
[onupdateTextView setBackgroundColor:[NSColor lightGrayColor]];
[onupdateTextView setEditable:NO];
}
}
- (IBAction)insertPulldownValue:(id)sender
{
if([globalValuesTableView numberOfSelectedRows] != 1 || [globalValuesTableView editedRow] < 0) return;
NSInteger selectedIndex = [sender indexOfItem:[sender selectedItem]] - 4;
if([[[NSApp keyWindow] firstResponder] respondsToSelector:@selector(insertText:)])
[[[NSApp keyWindow] firstResponder] insertText:[NSString stringWithFormat:@"$%ld", selectedIndex]];
}
- (IBAction)insertRecentGlobalValue:(id)sender
{
if([globalValuesTableView numberOfSelectedRows] != 1 || [globalValuesTableView editedRow] < 0) return;
if([[[NSApp keyWindow] firstResponder] respondsToSelector:@selector(insertText:)])
[[[NSApp keyWindow] firstResponder] insertText:[sender title]];
}
#pragma mark -
#pragma mark Others
- (void)resizeWindowByHeightDelta:(NSInteger)delta
{
NSUInteger tableMask = [fieldMapperTableScrollView autoresizingMask];
NSUInteger headerSwitchMask = [importFieldNamesHeaderSwitch autoresizingMask];
NSUInteger alignPopupMask = [alignByPopup autoresizingMask];
NSUInteger alignPopupLabelMask = [alignByPopupLabel autoresizingMask];
NSUInteger importMethodLabelMask = [importMethodLabel autoresizingMask];
NSUInteger importMethodMask = [importMethodPopup autoresizingMask];
NSUInteger advancedButtonMask = [advancedButton autoresizingMask];
NSUInteger advancedLabelMask = [advancedLabel autoresizingMask];
NSUInteger insertViewMask = [advancedInsertView autoresizingMask];
NSUInteger updateViewMask = [advancedUpdateView autoresizingMask];
NSUInteger replaceViewMask = [advancedReplaceView autoresizingMask];
NSRect frame = [[self window] frame];
if(frame.size.height>600 && delta > heightOffset) {
frame.origin.y += [advancedInsertView frame].size.height;
frame.size.height -= [advancedInsertView frame].size.height;
[[self window] setFrame:frame display:YES animate:YES];
}
[fieldMapperTableScrollView setAutoresizingMask:NSViewNotSizable|NSViewMinYMargin];
[importFieldNamesHeaderSwitch setAutoresizingMask:NSViewNotSizable|NSViewMinYMargin];
[alignByPopup setAutoresizingMask:NSViewNotSizable|NSViewMinYMargin];
[alignByPopupLabel setAutoresizingMask:NSViewNotSizable|NSViewMinYMargin];
[importMethodLabel setAutoresizingMask:NSViewNotSizable|NSViewMinYMargin];
[importMethodPopup setAutoresizingMask:NSViewNotSizable|NSViewMinYMargin];
[advancedButton setAutoresizingMask:NSViewNotSizable|NSViewMinYMargin];
[advancedLabel setAutoresizingMask:NSViewNotSizable|NSViewMinYMargin];
[advancedInsertView setAutoresizingMask:NSViewNotSizable|NSViewMinYMargin];
[advancedUpdateView setAutoresizingMask:NSViewNotSizable|NSViewMinYMargin];
[advancedReplaceView setAutoresizingMask:NSViewNotSizable|NSViewMinYMargin];
[advancedBox setAutoresizingMask:NSViewNotSizable|NSViewWidthSizable|NSViewHeightSizable|NSViewMaxXMargin|NSViewMinXMargin];
NSInteger newMinHeight = (windowMinHeigth-heightOffset+delta < windowMinHeigth) ? windowMinHeigth : windowMinHeigth-heightOffset+delta;
[[self window] setMinSize:NSMakeSize(windowMinWidth, newMinHeight)];
frame.origin.y += heightOffset;
frame.size.height -= heightOffset;
heightOffset = delta;
frame.origin.y -= heightOffset;
frame.size.height += heightOffset;
[[self window] setFrame:frame display:YES animate:YES];
[fieldMapperTableScrollView setAutoresizingMask:tableMask];
[importFieldNamesHeaderSwitch setAutoresizingMask:headerSwitchMask];
[alignByPopup setAutoresizingMask:alignPopupMask];
[alignByPopupLabel setAutoresizingMask:alignPopupLabelMask];
[importMethodLabel setAutoresizingMask:importMethodLabelMask];
[importMethodPopup setAutoresizingMask:importMethodMask];
[advancedButton setAutoresizingMask:advancedButtonMask];
[advancedLabel setAutoresizingMask:advancedLabelMask];
[advancedReplaceView setAutoresizingMask:replaceViewMask];
[advancedUpdateView setAutoresizingMask:updateViewMask];
[advancedInsertView setAutoresizingMask:insertViewMask];
[advancedBox setAutoresizingMask:NSViewNotSizable|NSViewWidthSizable|NSViewMaxYMargin|NSViewMaxXMargin|NSViewMinXMargin];
}
- (void)sheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo
{
if ([sheet respondsToSelector:@selector(orderOut:)]) [sheet orderOut:nil];
if (sheet == globalValuesSheet) {
addGlobalSheetIsOpen = NO;
[self updateFieldMappingButtonCell];
}
}
- (void)matchHeaderNames
{
if(![fieldMappingImportArray count]) return;
NSMutableArray *fileHeaderNames = [NSMutableArray array];
[fileHeaderNames setArray:NSArrayObjectAtIndex(fieldMappingImportArray, 0)];
NSMutableArray *tableHeaderNames = [NSMutableArray array];
[tableHeaderNames setArray:fieldMappingTableColumnNames];
// Create a distance matrix for each file-table name
// distance will be calculated by using Levenshtein distance minus common prefix and suffix length
// and minus the length of a fuzzy regex search for a common sequence of characters
NSUInteger i,j,k;
NSMutableArray *distMatrix = [NSMutableArray array];
for(i=0; i < [tableHeaderNames count]; i++) {
CGFloat dist = 1e6f;
for(j=0; j < [fileHeaderNames count]; j++) {
id fileHeaderName = NSArrayObjectAtIndex(fileHeaderNames,j);
if([fileHeaderName isKindOfClass:[NSNull class]] || [fileHeaderName isSPNotLoaded]) continue;
NSString *headerName = [(NSString*)fileHeaderName lowercaseString];
NSString *tableHeadName = [NSArrayObjectAtIndex(tableHeaderNames,i) lowercaseString];
dist = [tableHeadName levenshteinDistanceWithWord:headerName];
// if dist > 0 subtract the length of common prefixes, suffixes, and in common sequence characters
if(dist > 0.0) {
dist -= [[tableHeadName commonPrefixWithString:headerName options:NSCaseInsensitiveSearch] length];
dist -= [[tableHeadName commonPrefixWithString:headerName options:NSCaseInsensitiveSearch|NSBackwardsSearch] length];
NSMutableString *fuzzyRegexp = [[NSMutableString alloc] initWithCapacity:3];
unichar c;
for(k=0; k<[headerName length]; k++) {
c = [headerName characterAtIndex:k];
if (c == '.' || c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}')
[fuzzyRegexp appendFormat:@".*?\\%c",c];
else
[fuzzyRegexp appendFormat:@".*?%c",c];
}
dist -= [tableHeadName rangeOfRegex:fuzzyRegexp].length;
[fuzzyRegexp release];
} else {
// Levenshtein distance == 0 means that both names are equal set dist to
// a large negative number since dist can be negative due to search for in common chars
dist = -1e6f;
}
[distMatrix addObject:[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithFloat:dist], @"dist",
NSStringFromRange(NSMakeRange(i,j)), @"match",
(NSString*)fileHeaderName, @"file",
NSArrayObjectAtIndex(tableHeaderNames,i), @"table",
nil]];
}
}
// Sort the matrix according distance
NSSortDescriptor *sortByDistance = [[[NSSortDescriptor alloc] initWithKey:@"dist" ascending:TRUE] autorelease];
[distMatrix sortUsingDescriptors:[NSArray arrayWithObjects:sortByDistance, nil]];
NSMutableArray *matchedFile = [NSMutableArray array];
NSMutableArray *matchedTable = [NSMutableArray array];
NSUInteger cnt = 0;
for(NSDictionary* m in distMatrix) {
if(![matchedFile containsObject:[m objectForKey:@"file"]] && ![matchedTable containsObject:[m objectForKey:@"table"]]) {
NSRange match = NSRangeFromString([m objectForKey:@"match"]);
// Set best match
[fieldMappingArray replaceObjectAtIndex:match.location withObject:[NSNumber numberWithInteger:match.length]];
[fieldMappingOperatorArray replaceObjectAtIndex:match.location withObject:doImport];
// Remember matched pair
[matchedTable addObject:[m objectForKey:@"table"]];
[matchedFile addObject:[m objectForKey:@"file"]];
cnt++;
}
// break if all file names are mapped
if(cnt >= [fileHeaderNames count]) break;
}
}
/*
* Sets up the fieldMapping array to be shown in the tableView
*/
- (void)setupFieldMappingArray
{
NSUInteger i, value;
if (!fieldMappingArray) {
fieldMappingArray = [[NSMutableArray alloc] init];
for (i = 0; i < [fieldMappingTableColumnNames count]; i++) {
if (i < [NSArrayObjectAtIndex(fieldMappingImportArray, fieldMappingCurrentRow) count]
&& ![NSArrayObjectAtIndex(NSArrayObjectAtIndex(fieldMappingImportArray, fieldMappingCurrentRow), i) isKindOfClass:[NSNull class]]) {
value = i;
} else {
value = 0;
}
[fieldMappingArray addObject:[NSNumber numberWithUnsignedInteger:value]];
}
}
[fieldMapperTableView reloadData];
}
/*
* Update the NSButtonCell items for use in the import_value mapping display
*/
- (void)updateFieldMappingButtonCell
{
NSUInteger i;
if([fieldMappingImportArray count] == 0) return;
[fieldMappingButtonOptions setArray:[fieldMappingImportArray objectAtIndex:fieldMappingCurrentRow]];
for (i = 0; i < [fieldMappingButtonOptions count]; i++) {
if ([[fieldMappingButtonOptions objectAtIndex:i] isNSNull])
[fieldMappingButtonOptions replaceObjectAtIndex:i withObject:[NSString stringWithFormat:@"%i. <%@>", i+1, [prefs objectForKey:SPNullValue]]];
else if ([[fieldMappingButtonOptions objectAtIndex:i] isSPNotLoaded])
[fieldMappingButtonOptions replaceObjectAtIndex:i withObject:[NSString stringWithFormat:@"%i. <%@>", i+1, @"DEFAULT"]];
else
[fieldMappingButtonOptions replaceObjectAtIndex:i withObject:[NSString stringWithFormat:@"%i. %@", i+1, NSArrayObjectAtIndex(fieldMappingButtonOptions, i)]];
}
// Add global values if any
if((NSInteger)[fieldMappingGlobalValues count]>numberOfImportColumns)
for( ; i < [fieldMappingGlobalValues count]; i++) {
if ([NSArrayObjectAtIndex(fieldMappingGlobalValues, i) isNSNull])
[fieldMappingButtonOptions addObject:[NSString stringWithFormat:@"%i. <%@>", i+1, [prefs objectForKey:SPNullValue]]];
else
[fieldMappingButtonOptions addObject:[NSString stringWithFormat:@"%i. %@", i+1, NSArrayObjectAtIndex(fieldMappingGlobalValues, i)]];
}
[fieldMapperTableView reloadData];
}
/*
* Update the NSButtonCell items for use in the operator mapping display
*/
- (void)updateFieldMappingOperatorOptions
{
if(![[importMethodPopup titleOfSelectedItem] isEqualToString:@"UPDATE"]) {
[fieldMappingOperatorOptions setArray:[NSArray arrayWithObjects:doImportString, doNotImportString, nil]];
} else {
[fieldMappingOperatorOptions setArray:[NSArray arrayWithObjects:doImportString, doNotImportString, isEqualString, nil]];
}
}
/*
* Set field name alignment to default
*/
- (void)updateFieldNameAlignment
{
NSInteger alignment = 0;
if([prefs integerForKey:SPCSVFieldImportMappingAlignment]
&& [prefs integerForKey:SPCSVFieldImportMappingAlignment] >= 0
&& [prefs integerForKey:SPCSVFieldImportMappingAlignment] < 4) {
alignment = [prefs integerForKey:SPCSVFieldImportMappingAlignment];
}
// Set matching names only if csv file has an header
if(importFieldNamesHeader && alignment == 2)
[alignByPopup selectItemWithTag:2];
else if(!importFieldNamesHeader && alignment == 2)
[alignByPopup selectItemWithTag:0];
else
[alignByPopup selectItemWithTag:alignment];
[self changeFieldAlignment:nil];
}
- (void)validateImportButton
{
BOOL enableImportButton = YES;
if(newTableMode) {
if(![tablesListInstance isTableNameValid:[newTableNameTextField stringValue] forType:SPTableTypeTable ignoringSelectedTable:NO]) {
[importButton setEnabled:NO];
return;
}
for(NSString* fieldName in fieldMappingTableColumnNames) {
if(![fieldName length]) {
[importButton setEnabled:NO];
return;
}
}
for(NSString* fieldType in fieldMappingTableTypes) {
if(![fieldType length]) {
[importButton setEnabled:NO];
return;
}
}
}
if([[self selectedImportMethod] isEqualToString:@"UPDATE"]) {
enableImportButton = NO;
for(id op in fieldMappingOperatorArray) {
if(op == isEqual) {
enableImportButton = YES;
break;
}
}
}
[importButton setEnabled:enableImportButton];
}
/**
* Menu item interface validation
*/
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
{
NSInteger row = [fieldMapperTableView selectedRow];
// Hide/display Remove New Column menu item
[[[fieldMapperTableView menu] itemAtIndex:3] setHidden:([toBeEditedRowIndexes containsIndex:row]) ? NO : YES];
if (newTableMode && [menuItem action] == @selector(setAllTypesTo:)) {
NSString *orgTitle = [[menuItem title] substringToIndex:[[menuItem title] rangeOfString:@":"].location];
[menuItem setTitle:[NSString stringWithFormat:@"%@: %@", orgTitle, [fieldMappingTableTypes objectAtIndex:row]]];
}
else if (!newTableMode && [menuItem action] == @selector(insertNULLValue:)) {
return ([[globalValuesTableView selectedRowIndexes] count] == 1) ? YES : NO;
}
else if (!newTableMode && [menuItem action] == @selector(editColumn:)) {
NSString *orgTitle = [[menuItem title] substringToIndex:[[menuItem title] rangeOfString:@":"].location];
[menuItem setTitle:[NSString stringWithFormat:@"%@: %@", orgTitle, [fieldMappingTableColumnNames objectAtIndex:row]]];
}
else if (!newTableMode && [menuItem action] == @selector(removeNewColumn:)) {
if([toBeEditedRowIndexes containsIndex:row]) {
NSString *orgTitle = [[menuItem title] substringToIndex:[[menuItem title] rangeOfString:@":"].location];
[menuItem setTitle:[NSString stringWithFormat:@"%@: %@", orgTitle, [fieldMappingTableColumnNames objectAtIndex:row]]];
return YES;
} else {
NSString *orgTitle = [[menuItem title] substringToIndex:[[menuItem title] rangeOfString:@":"].location];
[menuItem setTitle:[NSString stringWithFormat:@"%@:", orgTitle]];
return NO;
}
}
return YES;
}
#pragma mark -
#pragma mark Table view datasource methods
- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView;
{
if(aTableView == fieldMapperTableView)
return [fieldMappingTableColumnNames count];
else if(aTableView == globalValuesTableView)
return [fieldMappingGlobalValues count] - numberOfImportColumns;
return 0;
}
- (void)tableView:(NSTableView *)aTableView willDisplayCell:(id)aCell forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
[aCell setFont:([prefs boolForKey:SPUseMonospacedFonts]) ? [NSFont fontWithName:SPDefaultMonospacedFontName size:[NSFont smallSystemFontSize]] : [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
}
- (void)tableView:(NSTableView*)aTableView didClickTableColumn:(NSTableColumn *)aTableColumn
{
if(aTableView == fieldMapperTableView) {
// A click at the operator column's header toggle all operators
if ([[aTableColumn identifier] isEqualToString:SPTableViewOperatorColumnID]
&& [self numberOfRowsInTableView:aTableView]
&& [fieldMappingOperatorArray count]
&& [fieldMappingTableColumnNames count]) {
NSUInteger i;
NSNumber *globalValue = doImport;
if([fieldMappingOperatorArray objectAtIndex:0] == doImport)
globalValue = doNotImport;
[fieldMappingOperatorArray removeAllObjects];
for(i=0; i < [fieldMappingTableColumnNames count]; i++)
[fieldMappingOperatorArray addObject:globalValue];
[self validateImportButton];
[fieldMapperTableView reloadData];
}
}
}
- (NSString *)tableView:(NSTableView *)aTableView toolTipForCell:(NSCell *)aCell rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex mouseLocation:(NSPoint)mouseLocation
{
if(aTableView == fieldMapperTableView) {
if ([fieldMappingOperatorArray objectAtIndex:rowIndex] == doNotImport) return [NSString stringWithFormat:@"DEFAULT: %@", [fieldMappingTableDefaultValues objectAtIndex:rowIndex]];
if([[aTableColumn identifier] isEqualToString:SPTableViewImportValueColumnID] && [importFieldNamesHeaderSwitch state] == NSOnState) {
if([NSArrayObjectAtIndex(fieldMappingArray, rowIndex) unsignedIntegerValue]>=[NSArrayObjectAtIndex(fieldMappingImportArray, 0) count])
return [NSString stringWithFormat:@"%@: %@", NSLocalizedString(@"User-defined value", @"user-defined value"), NSArrayObjectAtIndex(fieldMappingGlobalValues, [NSArrayObjectAtIndex(fieldMappingArray, rowIndex) integerValue])];
if(fieldMappingCurrentRow)
return [NSString stringWithFormat:@"%@: %@",
[NSArrayObjectAtIndex(NSArrayObjectAtIndex(fieldMappingImportArray, 0), [NSArrayObjectAtIndex(fieldMappingArray, rowIndex) integerValue]) description],
[NSArrayObjectAtIndex(NSArrayObjectAtIndex(fieldMappingImportArray, fieldMappingCurrentRow), [NSArrayObjectAtIndex(fieldMappingArray, rowIndex) integerValue]) description]];
else
return [NSArrayObjectAtIndex(NSArrayObjectAtIndex(fieldMappingImportArray, 0), [NSArrayObjectAtIndex(fieldMappingArray, rowIndex) integerValue]) description];
}
else if([[aTableColumn identifier] isEqualToString:SPTableViewImportValueColumnID] && [importFieldNamesHeaderSwitch state] == NSOffState) {
if([NSArrayObjectAtIndex(fieldMappingArray, rowIndex) unsignedIntegerValue]>=[NSArrayObjectAtIndex(fieldMappingImportArray, 0) count])
return NSArrayObjectAtIndex(fieldMappingGlobalValues, [NSArrayObjectAtIndex(fieldMappingArray, rowIndex) integerValue]);
else
return NSArrayObjectAtIndex(NSArrayObjectAtIndex(fieldMappingImportArray, fieldMappingCurrentRow), [NSArrayObjectAtIndex(fieldMappingArray, rowIndex) integerValue]);
}
else if([[aTableColumn identifier] isEqualToString:SPTableViewOperatorColumnID]) {
if([aCell objectValue] == doImport)
return NSLocalizedString(@"Import field", @"import field operator tooltip");
else if([aCell objectValue] == doNotImport)
return NSLocalizedString(@"Ignore field", @"ignore field label");
else if([aCell objectValue] == isEqual)
return NSLocalizedString(@"Do UPDATE where field contents match", @"do update operator tooltip");
else
return @"";
}
else if([[aTableColumn identifier] isEqualToString:SPTableViewTargetFieldColumnID])
return [fieldMappingTableColumnNames objectAtIndex:rowIndex];
}
else if(aTableView == globalValuesTableView) {
if ([[aTableColumn identifier] isEqualToString:SPTableViewGlobalValueColumnID])
return [fieldMappingGlobalValues objectAtIndex:numberOfImportColumns + rowIndex];
}
return @"";
}
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
if(aTableView == fieldMapperTableView) {
if ([[aTableColumn identifier] isEqualToString:SPTableViewTargetFieldColumnID]) {
if([toBeEditedRowIndexes containsIndex:rowIndex]) {
NSTextFieldCell *b = [[[NSTextFieldCell alloc] initTextCell:[fieldMappingTableColumnNames objectAtIndex:rowIndex]] autorelease];
[b setEditable:YES];
[b setFont:[NSFont systemFontOfSize:12]];
[aTableColumn setDataCell:b];
return b;
}
if(newTableMode) {
NSTextFieldCell *b = [[[NSTextFieldCell alloc] initTextCell:[fieldMappingTableColumnNames objectAtIndex:rowIndex]] autorelease];
[b setEditable:YES];
[b setFont:[NSFont systemFontOfSize:12]];
[aTableColumn setDataCell:b];
return b;
} else {
if ([[aTableColumn dataCell] isKindOfClass:[NSPopUpButtonCell class]]) {
[(NSPopUpButton *)[aTableColumn dataCell] removeAllItems];
[(NSPopUpButtonCell *)[aTableColumn dataCell] addItemWithTitle:[fieldMappingTableColumnNames objectAtIndex:rowIndex]];
}
return [fieldMappingTableColumnNames objectAtIndex:rowIndex];
}
}
else if ([[aTableColumn identifier] isEqualToString:SPTableViewTypeColumnID]) {
if([toBeEditedRowIndexes containsIndex:rowIndex]) {
[aTableColumn setDataCell:typeComboxBox];
return [fieldMappingTableTypes objectAtIndex:rowIndex];
}
if(newTableMode) {
[aTableColumn setDataCell:typeComboxBox];
return [fieldMappingTableTypes objectAtIndex:rowIndex];
} else {
NSTokenFieldCell *b = [[[NSTokenFieldCell alloc] initTextCell:[fieldMappingTableTypes objectAtIndex:rowIndex]] autorelease];
[b setEditable:NO];
[b setAlignment:NSLeftTextAlignment];
[b setWraps:NO];
[b setFont:[NSFont systemFontOfSize:9]];
[b setDelegate:self];
[aTableColumn setDataCell:b];
return [fieldMappingTableTypes objectAtIndex:rowIndex];
}
}
else if ([[aTableColumn identifier] isEqualToString:SPTableViewImportValueColumnID]) {
// Check if all global value was deleted, if so set assigned field as doNotImport
if([[fieldMappingArray objectAtIndex:rowIndex] unsignedIntegerValue] >= [fieldMappingButtonOptions count]) {
[fieldMappingOperatorArray replaceObjectAtIndex:rowIndex withObject:doNotImport];
}
if ([[aTableColumn dataCell] isKindOfClass:[NSPopUpButtonCell class]]) {
NSPopUpButtonCell *c = [aTableColumn dataCell];
NSMenu *m = [c menu];
[m setAutoenablesItems:NO];
[c removeAllItems];
[c addItemsWithTitles:fieldMappingButtonOptions];
[m addItem:[NSMenuItem separatorItem]];
[c addItemWithTitle:NSLocalizedString(@"Ignore Field", @"ignore field label")];
[c addItemWithTitle:NSLocalizedString(@"Ignore all Fields", @"ignore all fields menu item")];
[c addItemWithTitle:NSLocalizedString(@"Import all Fields", @"import all fields menu item")];
if([[self selectedImportMethod] isEqualToString:@"UPDATE"])
[c addItemWithTitle:NSLocalizedString(@"Match Field", @"match field menu item")];
[m addItem:[NSMenuItem separatorItem]];
NSMenuItem *menuItem = [m addItemWithTitle:NSLocalizedString(@"Add Value or Expression…", @"add global value or expression menu item") action:@selector(addGlobalSourceVariable:) keyEquivalent:@"g"];
[menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)];
[c addItemWithTitle:[NSString stringWithFormat:@"DEFAULT: %@", [fieldMappingTableDefaultValues objectAtIndex:rowIndex]]];
[[m itemAtIndex:[c numberOfItems]-1] setEnabled:NO];
// If user doesn't want to import it show its DEFAULT value if not
// UPDATE was chosen otherwise hide it.
if([fieldMappingOperatorArray objectAtIndex:rowIndex] != doNotImport)
return [fieldMappingArray objectAtIndex:rowIndex];
else if(![[self selectedImportMethod] isEqualToString:@"UPDATE"])
return [NSNumber numberWithInteger:[c numberOfItems]-1];
}
}
else if ([[aTableColumn identifier] isEqualToString:SPTableViewOperatorColumnID]) {
if ([[aTableColumn dataCell] isKindOfClass:[NSPopUpButtonCell class]]) {
[(NSPopUpButtonCell *)[aTableColumn dataCell] removeAllItems];
[(NSPopUpButtonCell *)[aTableColumn dataCell] addItemsWithTitles:fieldMappingOperatorOptions];
}
return [fieldMappingOperatorArray objectAtIndex:rowIndex];
}
}
else if(aTableView == globalValuesTableView) {
if ([[aTableColumn identifier] isEqualToString:SPTableViewValueIndexColumnID]) {
return [NSString stringWithFormat:@"%ld.", numberOfImportColumns + rowIndex + 1];
}
else if ([[aTableColumn identifier] isEqualToString:SPTableViewGlobalValueColumnID]) {
return [fieldMappingGlobalValues objectAtIndex:numberOfImportColumns + rowIndex];
}
else if ([[aTableColumn identifier] isEqualToString:SPTableViewSqlColumnID])
return [fieldMappingGlobalValuesSQLMarked objectAtIndex:numberOfImportColumns + rowIndex];
}
return nil;
}
- (BOOL)tableView:(NSTableView *)aTableView shouldEditTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
if(aTableView == globalValuesTableView) return YES;
if([toBeEditedRowIndexes containsIndex:rowIndex]) return YES;
if(!newTableMode) return NO;
return YES;
}
- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
if(aTableView == fieldMapperTableView) {
if ([[aTableColumn identifier] isEqualToString:SPTableViewImportValueColumnID]) {
if([anObject integerValue] > (NSInteger)[fieldMappingButtonOptions count]) {
// Ignore field - set operator to doNotImport
if([anObject integerValue] == (NSInteger)[fieldMappingButtonOptions count]+1) {
lastDisabledCSVFieldcolumn = [fieldMappingArray objectAtIndex:rowIndex];
[fieldMappingOperatorArray replaceObjectAtIndex:rowIndex withObject:doNotImport];
[aTableView performSelector:@selector(reloadData) withObject:nil afterDelay:0.0];
}
// Ignore all field - set all operator to doNotImport
else if([anObject integerValue] == (NSInteger)[fieldMappingButtonOptions count]+2) {
NSUInteger i;
NSNumber *globalValue = doNotImport;
[fieldMappingOperatorArray removeAllObjects];
for(i=0; i < [fieldMappingTableColumnNames count]; i++)
[fieldMappingOperatorArray addObject:globalValue];
[aTableView performSelector:@selector(reloadData) withObject:nil afterDelay:0.0];
}
// Import all field - set all operator to doImport
else if([anObject integerValue] == (NSInteger)[fieldMappingButtonOptions count]+3) {
NSUInteger i;
NSNumber *globalValue = doImport;
[fieldMappingOperatorArray removeAllObjects];
for(i=0; i < [fieldMappingTableColumnNames count]; i++)
[fieldMappingOperatorArray addObject:globalValue];
[aTableView performSelector:@selector(reloadData) withObject:nil afterDelay:0.0];
}
else if([[self selectedImportMethod] isEqualToString:@"UPDATE"] && [anObject integerValue] == (NSInteger)[fieldMappingButtonOptions count]+4) {
[fieldMappingOperatorArray replaceObjectAtIndex:rowIndex withObject:isEqual];
[aTableView performSelector:@selector(reloadData) withObject:nil afterDelay:0.0];
}
// Add global value
else if([anObject integerValue] == ([[self selectedImportMethod] isEqualToString:@"UPDATE"]) ? [fieldMappingButtonOptions count]+6 : [fieldMappingButtonOptions count]+5) {
[aTableView performSelector:@selector(reloadData) withObject:nil afterDelay:0.0];
[self addGlobalSourceVariable:nil];
}
[self validateImportButton];
return;
}
// If user changed the order set alignment popup to "custom order"
if([fieldMappingArray objectAtIndex:rowIndex] != anObject)
[alignByPopup selectItemWithTag:3];
[fieldMappingArray replaceObjectAtIndex:rowIndex withObject:anObject];
// If user _changed_ the csv file column set the operator to doImport if not set to =
if([(NSNumber*)anObject integerValue] > -1 && NSArrayObjectAtIndex(fieldMappingOperatorArray, rowIndex) != isEqual)
[fieldMappingOperatorArray replaceObjectAtIndex:rowIndex withObject:doImport];
[self validateImportButton];
}
else if ([[aTableColumn identifier] isEqualToString:SPTableViewTargetFieldColumnID]) {
if(newTableMode || [toBeEditedRowIndexes containsIndex:rowIndex]) {
if([(NSString*)anObject length]) {
[fieldMappingTableColumnNames replaceObjectAtIndex:rowIndex withObject:anObject];
}
}
}
else if ([[aTableColumn identifier] isEqualToString:SPTableViewTypeColumnID]) {
if(newTableMode || [toBeEditedRowIndexes containsIndex:rowIndex]) {
if([(NSString*)anObject length]) {
[fieldMappingTableTypes replaceObjectAtIndex:rowIndex withObject:anObject];
if(![defaultFieldTypesForComboBox containsObject:anObject])
[defaultFieldTypesForComboBox insertObject:anObject atIndex:0];
}
} else {
}
}
else if ([[aTableColumn identifier] isEqualToString:SPTableViewOperatorColumnID]) {
if([fieldMappingOperatorArray objectAtIndex:rowIndex] == doNotImport) {
[fieldMappingOperatorArray replaceObjectAtIndex:rowIndex withObject:anObject];
[fieldMappingArray replaceObjectAtIndex:rowIndex withObject:lastDisabledCSVFieldcolumn];
} else {
if(anObject == doNotImport) lastDisabledCSVFieldcolumn = [fieldMappingArray objectAtIndex:rowIndex];
[fieldMappingOperatorArray replaceObjectAtIndex:rowIndex withObject:anObject];
}
[self validateImportButton];
}
}
else if(aTableView == globalValuesTableView) {
if ([[aTableColumn identifier] isEqualToString:SPTableViewGlobalValueColumnID]) {
[fieldMappingGlobalValues replaceObjectAtIndex:(numberOfImportColumns + rowIndex) withObject:anObject];
// If anObject contains $1 etc. enable SQL checkbox
if([anObject isMatchedByRegex:@"(? 20)
[recents removeObjectAtIndex:[recents count]-1];
if([recents count])
[prefs setObject:recents forKey:SPGlobalValueHistory];
// Re-init recent menu
[recentGlobalValueMenu compatibleRemoveAllItems];
for(id item in recents)
[recentGlobalValueMenu addItemWithTitle:item action:@selector(insertRecentGlobalValue:) keyEquivalent:@""];
} else if ([[aTableColumn identifier] isEqualToString:SPTableViewSqlColumnID]) {
[fieldMappingGlobalValuesSQLMarked replaceObjectAtIndex:(numberOfImportColumns + rowIndex) withObject:anObject];
}
}
}
- (void)tableViewSelectionDidChange:(NSNotification *)aNotification
{
id object = [aNotification object];
if (object == globalValuesTableView) {
[removeGlobalValueButton setEnabled:([globalValuesTableView numberOfSelectedRows] > 0)];
[insertNULLValueButton setEnabled:([globalValuesTableView numberOfSelectedRows] == 1)];
}
}
/*
* Trap the enter, escape, tab and arrow keys, overriding default behaviour and continuing/ending editing,
* only within the current row of the tableView only in newTableMode.
*/
- (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)command
{
if((!newTableMode || addGlobalSheetIsOpen) && ![toBeEditedRowIndexes containsIndex:[fieldMapperTableView selectedRow]]) return NO;
NSInteger row, column;
row = [fieldMapperTableView editedRow];
column = [fieldMapperTableView editedColumn];
BOOL isCellComplex = ([[fieldMapperTableView preparedCellAtColumn:column row:row] isKindOfClass:[NSComboBoxCell class]]) ? YES : NO;
// Trap tab key
// -- for handling of blob fields and to check if it's editable look at [[self delegate] control:textShouldBeginEditing:]
if ( [textView methodForSelector:command] == [textView methodForSelector:@selector(insertTab:)] )
{
[[control window] makeFirstResponder:control];
// Save the current line if it's the last field in the table
if ( [fieldMapperTableView numberOfColumns] - 1 == column) {
[[fieldMapperTableView window] makeFirstResponder:fieldMapperTableView];
} else {
// Select the next field for editing
[fieldMapperTableView editColumn:column+1 row:row withEvent:nil select:YES];
}
return YES;
}
// Trap shift-tab key
else if ( [textView methodForSelector:command] == [textView methodForSelector:@selector(insertBacktab:)] )
{
[[control window] makeFirstResponder:control];
// Save the current line if it's the last field in the table
if ( column < 1 ) {
[[fieldMapperTableView window] makeFirstResponder:fieldMapperTableView];
} else {
// Select the previous field for editing
[fieldMapperTableView editColumn:column-1 row:row withEvent:nil select:YES];
}
return YES;
}
// Trap enter key
else if ( [textView methodForSelector:command] == [textView methodForSelector:@selector(insertNewline:)] )
{
if(isCellComplex && newTableMode) return NO;
// If newTableNameTextField is active enter key closes the sheet
if(control == newTableNameTextField) {
NSButton *b = [[[NSButton alloc] init] autorelease];
[b setTag:1];
[self closeSheet:b];
return YES;
}
[[self window] endEditingFor:nil];
[[control window] makeFirstResponder:control];
return YES;
}
// Trap down arrow key
else if ( [textView methodForSelector:command] == [textView methodForSelector:@selector(moveDown:)] )
{
if(isCellComplex) return NO;
NSInteger newRow = row+1;
if (newRow>=[self numberOfRowsInTableView:fieldMapperTableView]) return YES; //check if we're already at the end of the list
[[control window] makeFirstResponder:control];
[fieldMapperTableView selectRowIndexes:[NSIndexSet indexSetWithIndex:newRow] byExtendingSelection:NO];
[fieldMapperTableView editColumn:column row:newRow withEvent:nil select:YES];
return YES;
}
// Trap up arrow key
else if ( [textView methodForSelector:command] == [textView methodForSelector:@selector(moveUp:)] )
{
if(isCellComplex) return NO;
if (row==0) return YES; //already at the beginning of the list
NSUInteger newRow = row-1;
[[control window] makeFirstResponder:control];
if(![toBeEditedRowIndexes containsIndex:newRow]) return NO;
[fieldMapperTableView selectRowIndexes:[NSIndexSet indexSetWithIndex:newRow] byExtendingSelection:NO];
[fieldMapperTableView editColumn:column row:newRow withEvent:nil select:YES];
return YES;
}
// Trap the escape key
else if ( [[control window] methodForSelector:command] == [[control window] methodForSelector:@selector(cancelOperation:)] )
{
// Abort editing
[control abortEditing];
// Preserve the focus
[[fieldMapperTableView window] makeFirstResponder:fieldMapperTableView];
return TRUE;
}
return FALSE;
}
#pragma mark -
#pragma mark NSTextField delegates
/*
* Validate some user input in newTableMode
*/
- (void)controlTextDidChange:(NSNotification *)notification
{
if(!newTableMode) return;
[self validateImportButton];
}
#pragma mark -
#pragma mark NSComboBox delegates
- (id)comboBoxCell:(NSComboBoxCell *)aComboBoxCell objectValueForItemAtIndex:(NSInteger)anIndex
{
return [defaultFieldTypesForComboBox objectAtIndex:anIndex];
}
- (NSInteger)numberOfItemsInComboBoxCell:(NSComboBoxCell *)aComboBoxCell
{
return [defaultFieldTypesForComboBox count];
}
@end