diff options
Diffstat (limited to 'Source/SPCustomQuery.m')
-rw-r--r-- | Source/SPCustomQuery.m | 744 |
1 files changed, 379 insertions, 365 deletions
diff --git a/Source/SPCustomQuery.m b/Source/SPCustomQuery.m index 26810323..272c0ebf 100644 --- a/Source/SPCustomQuery.m +++ b/Source/SPCustomQuery.m @@ -37,6 +37,7 @@ #import "SPTooltip.h" #import "SPQueryFavoriteManager.h" #import "SPQueryController.h" +#import "SPQueryDocumentsController.h" #import "SPEncodingPopupAccessory.h" #import "SPDataStorage.h" #import "SPAlertSheets.h" @@ -49,8 +50,23 @@ #import <BWToolkitFramework/BWToolkitFramework.h> #endif +@interface SPCustomQuery (PrivateAPI) + +- (id)_resultDataItemAtRow:(NSInteger)row columnIndex:(NSUInteger)column; +- (id)_convertResultDataValueToDisplayableRepresentation:(id)value whilePreservingNULLs:(BOOL)preserveNULLs; + +@end + @implementation SPCustomQuery +#ifdef SP_REFACTOR +@synthesize textView; +@synthesize customQueryView; +@synthesize runAllButton; +@synthesize tableDocumentInstance; +@synthesize tablesListInstance; +#endif + @synthesize textViewWasChanged; #pragma mark IBAction methods @@ -110,9 +126,8 @@ [self performQueries:queries withCallback:@selector(runAllQueriesCallback)]; } -- (void) runAllQueriesCallback +- (void)runAllQueriesCallback { - // If no error was selected, reconstruct a given selection. This // may no longer be valid if the query text has changed in the // meantime, so error-checking is required. @@ -310,7 +325,7 @@ } } -/* +/** * Closes the sheet */ - (IBAction)closeSheet:(id)sender @@ -319,7 +334,7 @@ [[sender window] orderOut:self]; } -/* +/** * Perform simple actions (which don't require their own method), triggered by selecting the appropriate menu item * in the "gear" action menu displayed beneath the cusotm query view. */ @@ -466,7 +481,6 @@ - (IBAction)copyQueryHistory:(id)sender { - NSPasteboard *pb = [NSPasteboard generalPasteboard]; [pb declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil]; @@ -474,10 +488,11 @@ } -// "Clear History" menu item - clear query history +/** + * 'Clear History' menu item - clear query history + */ - (IBAction)clearQueryHistory:(id)sender { - NSString *infoString; #ifndef SP_REFACTOR /* if ([tableDocumentInstance isUntitled]) */ @@ -511,7 +526,7 @@ } -/* +/* * * Set font panel's valid modes */ - (NSUInteger)validModesForFontPanel:(NSFontPanel *)fontPanel @@ -532,18 +547,21 @@ #pragma mark - #pragma mark Query actions -/* +/** * Performs the mysql-query given by the user * sets the tableView columns corresponding to the mysql-result */ - (void)performQueries:(NSArray *)queries withCallback:(SEL)customQueryCallbackMethod; { NSString *taskString; + if ([queries count] > 1) { taskString = [NSString stringWithFormat:NSLocalizedString(@"Running query %i of %lu...", @"Running multiple queries string"), 1, (unsigned long)[queries count]]; - } else { + } + else { taskString = NSLocalizedString(@"Running query...", @"Running single query string"); } + [tableDocumentInstance startTaskWithDescription:taskString]; [errorText setString:taskString]; [affectedRowsText setStringValue:@""]; @@ -556,7 +574,8 @@ // If a helper thread is already running, execute inline - otherwise detach a new thread for the queries if ([NSThread isMainThread]) { [NSThread detachNewThreadSelector:@selector(performQueriesTask:) toTarget:self withObject:taskArguments]; - } else { + } + else { [self performQueriesTask:taskArguments]; } } @@ -584,11 +603,7 @@ #endif // Notify listeners that a query has started -#ifndef SP_REFACTOR [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryWillBePerformed" object:tableDocumentInstance]; -#else - [[NSNotificationCenter defaultCenter] sequelProPostNotificationOnMainThreadWithName:@"SMySQLQueryWillBePerformed" object:tableDocumentInstance]; -#endif #ifndef SP_REFACTOR /* growl */ // Start the notification timer to allow notifications to be shown even if frontmost for long queries @@ -855,11 +870,7 @@ [customQueryView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:YES]; // Notify any listeners that the query has completed -#ifndef SP_REFACTOR [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; -#else - [[NSNotificationCenter defaultCenter] sequelProPostNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; -#endif #ifndef SP_REFACTOR /* growl */ // Perform the Growl notification for query completion @@ -892,11 +903,7 @@ } //query finished -#ifndef SP_REFACTOR [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; -#else - [[NSNotificationCenter defaultCenter] sequelProPostNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; -#endif #ifndef SP_REFACTOR /* growl */ // Query finished Growl notification @@ -922,10 +929,9 @@ [[tableDocumentInstance parentWindow] makeFirstResponder:customQueryView]; [queryRunningPool release]; - } -/* +/** * Processes a supplied streaming result set, loading it into the data array. */ - (void)processResultIntoDataStorage:(MCPStreamingResult *)theResult @@ -978,7 +984,7 @@ [dataLoadingPool drain]; } -/* +/** * Retrieve the range of the query at a position specified * within the custom query text view. */ @@ -1103,7 +1109,7 @@ return queryRange; } -/* +/** * Retrieve the range of the query for the passed index seen from a start position * specified within the custom query text view. */ @@ -1144,7 +1150,7 @@ return theQueryRange; } -/* +/** * Retrieve the query at a position specified within the custom query * text view. This will return nil if the position specified is beyond * the available string or if an empty query would be returned. @@ -1167,13 +1173,12 @@ [textView setSelectedRange:currentQueryRange]; } -/* +/** * Add or remove "⁄* *⁄" for each line in the current query * a given selection */ - (void)commentOutCurrentQueryTakingSelection:(BOOL)takeSelection { - BOOL isUncomment = NO; NSRange oldRange = [textView selectedRange]; @@ -1216,10 +1221,9 @@ // something like /*!400000 or similar if(!isUncomment) [textView setSelectedRange:NSMakeRange(workingRange.location+2,0)]; - } -/* +/** * Add or remove "-- " for each line in the current query or selection, * if the selection is in-line wrap selection into ⁄* block comments and * place the caret after ⁄* to allow to enter !xxxxxx e.g. @@ -1269,12 +1273,10 @@ // allow a fast (un)commenting of lines [textView setSelectedRange:lineRange]; [textView insertText:n]; - } - } -/* +/** * Update the interface to reflect the query error state. * Should be performed on the main thread. */ @@ -1375,6 +1377,7 @@ - (void) initQueryLoadTimer { if (queryLoadTimer) [self clearQueryLoadTimer]; + queryLoadInterfaceUpdateInterval = 1; queryLoadLastRowCount = 0; queryLoadTimerTicksSinceLastUpdate = 0; @@ -1435,48 +1438,65 @@ queryLoadInterfaceUpdateInterval = 25; break; } + queryLoadTimerTicksSinceLastUpdate = 0; } #pragma mark - #pragma mark Accessors -/* - * Returns the current result (as shown in custom result view) as array, - * the first object containing the field names as array, - * the following objects containing the rows as array +/** + * Returns the current result (as shown in custom result view) as an array, the first object containing + * the field names as an array and the following objects containing the rows as arrays. */ - (NSArray *)currentResult +{ + return [self currentDataResultWithNULLs:NO]; +} + +/** + * Returns the current result (as shown in custom result view) as an array, the first object containing + * the field names as an array and the following objects containing the rows as arrays. + * + * @param includeNULLs Indicates whether to include NULLs as a native type + * or use the user's NULL string representation preference. + */ +- (NSArray *)currentDataResultWithNULLs:(BOOL)includeNULLs { - NSArray *tableColumns = [customQueryView tableColumns]; - NSEnumerator *enumerator = [tableColumns objectEnumerator]; + NSInteger i; id tableColumn; - NSMutableArray *currentResult = [NSMutableArray array]; - NSMutableArray *tempRow = [NSMutableArray array]; - NSInteger i; - - //set field names as first line - while ( (tableColumn = [enumerator nextObject]) ) { + NSMutableArray *tempRow = [[NSMutableArray alloc] init]; + + // Set field names as first line + for (tableColumn in [customQueryView tableColumns]) + { [tempRow addObject:[[tableColumn headerCell] stringValue]]; } - [currentResult addObject:[NSArray arrayWithArray:tempRow]]; + NSMutableArray *currentResult = [NSMutableArray array]; + + [currentResult addObject:[NSArray arrayWithArray:tempRow]]; + //add rows for ( i = 0 ; i < [self numberOfRowsInTableView:customQueryView] ; i++) { [tempRow removeAllObjects]; - enumerator = [tableColumns objectEnumerator]; + NSEnumerator *enumerator = [[customQueryView tableColumns] objectEnumerator]; while ( (tableColumn = [enumerator nextObject]) ) { - [tempRow addObject:[self tableView:customQueryView objectValueForTableColumn:tableColumn row:i]]; + id value = [self _resultDataItemAtRow:i columnIndex:[[tableColumn identifier] integerValue]]; + + [tempRow addObject:[self _convertResultDataValueToDisplayableRepresentation:value whilePreservingNULLs:YES]]; } [currentResult addObject:[NSArray arrayWithArray:tempRow]]; } + [tempRow release]; + return currentResult; } #pragma mark - #pragma mark Additional methods -/* +/** * Sets the connection (received from SPDatabaseDocument) and makes things that have to be done only once */ - (void)setConnection:(MCPConnection *)theConnection @@ -1511,7 +1531,7 @@ [runSelectionMenuItem setEnabled:NO]; } -/* +/** * Inserts the query in the textView and performs query */ - (void)doPerformQueryService:(NSString *)query @@ -1522,6 +1542,7 @@ [textView scrollRangeToVisible:NSMakeRange([query length], 0)]; [self runAllQueries:self]; } + - (void)doPerformLoadQueryService:(NSString *)query { [textView shouldChangeTextInRange:NSMakeRange(0, [[textView string] length]) replacementString:query]; @@ -1609,21 +1630,12 @@ [customQueryView addTableColumn:theCol]; [theCol release]; } - - [customQueryView sizeLastColumnToFit]; - - //tries to fix problem with last row (otherwise to small) - //sets last column to width of the first if smaller than 30 - //problem not fixed for resizing window - if ( [[customQueryView tableColumnWithIdentifier:[NSNumber numberWithInteger:[theColumns count]-1]] width] < 30 ) - [[customQueryView tableColumnWithIdentifier:[NSNumber numberWithInteger:[theColumns count]-1]] - setWidth:[[customQueryView tableColumnWithIdentifier:[NSNumber numberWithInteger:0]] width]]; } /** * Provide a getter for the custom query result table's selected rows index set */ -- (NSIndexSet *) resultSelectedRowIndexes +- (NSIndexSet *)resultSelectedRowIndexes { return [customQueryView selectedRowIndexes]; } @@ -1631,7 +1643,7 @@ /** * Provide a getter for the custom query result table's current viewport */ -- (NSRect) resultViewport +- (NSRect)resultViewport { return [customQueryView visibleRect]; } @@ -1647,7 +1659,7 @@ /** * Set the selected row indexes to restore on next custom query result table load */ -- (void) setResultSelectedRowIndexesToRestore:(NSIndexSet *)theIndexSet +- (void)setResultSelectedRowIndexesToRestore:(NSIndexSet *)theIndexSet { if (selectionIndexToRestore) [selectionIndexToRestore release], selectionIndexToRestore = nil; @@ -1657,7 +1669,7 @@ /** * Set the viewport to restore on next table load */ -- (void) setResultViewportToRestore:(NSRect)theViewport +- (void)setResultViewportToRestore:(NSRect)theViewport { selectionViewportToRestore = theViewport; } @@ -1665,7 +1677,7 @@ /** * Convenience method for storing all current settings for restoration */ -- (void) storeCurrentResultViewForRestoration +- (void)storeCurrentResultViewForRestoration { [self setResultSelectedRowIndexesToRestore:[self resultSelectedRowIndexes]]; [self setResultViewportToRestore:[self resultViewport]]; @@ -1674,7 +1686,7 @@ /** * Convenience method for clearing any settings to restore */ -- (void) clearResultViewDetailsToRestore +- (void)clearResultViewDetailsToRestore { [self setResultSelectedRowIndexesToRestore:nil]; [self setResultViewportToRestore:NSZeroRect]; @@ -1699,33 +1711,26 @@ // Otherwise set the column width NSTableColumn *aTableColumn = [customQueryView tableColumnWithIdentifier:[columnDefinition objectForKey:@"datacolumnindex"]]; - NSUInteger targetWidth = [[columnWidths objectForKey:[columnDefinition objectForKey:@"datacolumnindex"]] unsignedIntegerValue]; + NSUInteger targetWidth = [[columnWidths objectForKey:[columnDefinition objectForKey:@"datacolumnindex"]] integerValue]; [aTableColumn setWidth:targetWidth]; } + [customQueryView setDelegate:self]; } #pragma mark - #pragma mark Field Editing -/* +/** * Check if table cell is editable * Returns as array the minimum number of possible changes or * -1 if no table name can be found or multiple table origins * -2 for other errors * and the used WHERE clause to identify */ -- (NSArray*)fieldEditStatusForRow:(NSInteger)rowIndex andColumn:(NSNumber*)columnIndex +- (NSArray*)fieldEditStatusForRow:(NSInteger)rowIndex andColumn:(NSInteger)columnIndex { - NSDictionary *columnDefinition = nil; - - // Retrieve the column defintion - for(id c in cqColumnDefinition) { - if([[c objectForKey:@"datacolumnindex"] isEqualToNumber:columnIndex]) { - columnDefinition = [NSDictionary dictionaryWithDictionary:c]; - break; - } - } + NSDictionary *columnDefinition = [NSDictionary dictionaryWithDictionary:[cqColumnDefinition objectAtIndex:[[[[customQueryView tableColumns] objectAtIndex:columnIndex] identifier] integerValue]]]; if(!columnDefinition) return [NSArray arrayWithObjects:[NSNumber numberWithInteger:-2], @"", nil]; @@ -1786,7 +1791,6 @@ [tableDocumentInstance endTask]; return [NSArray arrayWithObjects:[NSNumber numberWithInteger:-1], @"", nil]; } - } [tableDocumentInstance endTask]; @@ -1795,10 +1799,9 @@ fieldIDQueryStr = @""; return [NSArray arrayWithObjects:[NSNumber numberWithInteger:[[tempRow objectAtIndex:0] integerValue]], fieldIDQueryStr, nil]; - } -/* +/** * Collect all columns for a given 'tableForColumn' table and * return a WHERE clause for identifying the field in question. */ @@ -1807,12 +1810,13 @@ NSArray *dataRow; NSDictionary *theRow; id field; + NSMutableArray *argumentParts = [NSMutableArray array]; - //Look for all columns which are coming from "tableForColumn" - NSMutableArray *columnsForFieldTableName = [NSMutableArray array]; + // Check the table/view columns and select only those coming from the supplied database and table + NSMutableArray *columnsInSpecifiedTable = [NSMutableArray array]; for(field in cqColumnDefinition) { - if([[field objectForKey:@"org_table"] isEqualToString:tableForColumn]) - [columnsForFieldTableName addObject:field]; + if([[field objectForKey:@"db"] isEqualToString:database] && [[field objectForKey:@"org_table"] isEqualToString:tableForColumn]) + [columnsInSpecifiedTable addObject:field]; } // Try to identify the field bijectively @@ -1822,34 +1826,32 @@ // --- Build WHERE clause --- dataRow = [resultData rowContentsAtIndex:rowIndex]; - // Get the primary key if there is one + // Get the primary key if there is one, using any columns present within it MCPResult *theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW COLUMNS FROM %@.%@", [database backtickQuotedString], [tableForColumn backtickQuotedString]]]; [theResult setReturnDataAsStrings:YES]; if ([theResult numOfRows]) [theResult dataSeek:0]; + NSMutableArray *primaryColumnsInSpecifiedTable = [NSMutableArray array]; NSUInteger i; for ( i = 0 ; i < [theResult numOfRows] ; i++ ) { theRow = [theResult fetchRowAsDictionary]; if ( [[theRow objectForKey:@"Key"] isEqualToString:@"PRI"] ) { - for(field in columnsForFieldTableName) { - id aValue = [dataRow objectAtIndex:[[field objectForKey:@"datacolumnindex"] integerValue]]; + for (field in columnsInSpecifiedTable) { if([[field objectForKey:@"org_name"] isEqualToString:[theRow objectForKey:@"Field"]]) { - [fieldIDQueryStr appendFormat:@"%@.%@.%@ = %@)", - [database backtickQuotedString], - [tableForColumn backtickQuotedString], - [[theRow objectForKey:@"Field"] backtickQuotedString], - [aValue description]]; - return fieldIDQueryStr; + [primaryColumnsInSpecifiedTable addObject:field]; } } } } - // If there is no primary key, all found fields belonging to the same table are used in the argument - for(field in columnsForFieldTableName) { + // Determine whether to use the primary keys list or fall back to all fields when building the query string + NSMutableArray *columnsToQuery = [primaryColumnsInSpecifiedTable count] ? primaryColumnsInSpecifiedTable : columnsInSpecifiedTable; + + // Build up the argument + for (field in columnsToQuery) { id aValue = [dataRow objectAtIndex:[[field objectForKey:@"datacolumnindex"] integerValue]]; if ([aValue isKindOfClass:[NSNull class]] || [aValue isNSNull]) { - [fieldIDQueryStr appendFormat:@"%@ IS NULL AND ", [[field objectForKey:@"org_name"] backtickQuotedString]]; + [argumentParts addObject:[NSString stringWithFormat:@"%@ IS NULL", [[field objectForKey:@"org_name"] backtickQuotedString]]]; } else { if ([[field objectForKey:@"typegrouping"] isEqualToString:@"textdata"]) { if(includeBlobs) { @@ -1862,25 +1864,126 @@ } } else if ([[field objectForKey:@"typegrouping"] isEqualToString:@"bit"]) { - [fieldIDQueryStr appendFormat:@"%@=b'%@' AND ", [[field objectForKey:@"org_name"] backtickQuotedString], [aValue description]]; + [argumentParts addObject:[NSString stringWithFormat:@"%@=b'%@'", [[field objectForKey:@"org_name"] backtickQuotedString], [aValue description]]]; } else if ([[field objectForKey:@"typegrouping"] isEqualToString:@"integer"]) { [fieldIDQueryStr appendFormat:@"%@=%@ AND ", [[field objectForKey:@"org_name"] backtickQuotedString], [aValue description]]; } else if ([[field objectForKey:@"typegrouping"] isEqualToString:@"geometry"]) { - [fieldIDQueryStr appendFormat:@"%@=X'%@' AND ", [[field objectForKey:@"org_name"] backtickQuotedString], [mySQLConnection prepareBinaryData:[aValue data]]]; + [argumentParts addObject:[NSString stringWithFormat:@"%@=X'%@'", [[field objectForKey:@"org_name"] backtickQuotedString], [mySQLConnection prepareBinaryData:[aValue data]]]]; + } + // BLOB/TEXT data + else if ([aValue isKindOfClass:[NSData class]]) { + [argumentParts addObject:[NSString stringWithFormat:@"%@=X'%@'", [[field objectForKey:@"org_name"] backtickQuotedString], [mySQLConnection prepareBinaryData:aValue]]]; } else { - [fieldIDQueryStr appendFormat:@"%@='%@' AND ", [[field objectForKey:@"org_name"] backtickQuotedString], [mySQLConnection prepareString:aValue]]; + [argumentParts addObject:[NSString stringWithFormat:@"%@='%@'", [[field objectForKey:@"org_name"] backtickQuotedString], [mySQLConnection prepareString:aValue]]]; } } } - // Remove last " AND " - if([fieldIDQueryStr length]>12) - [fieldIDQueryStr replaceCharactersInRange:NSMakeRange([fieldIDQueryStr length]-5,5) withString:@")"]; + // Check for empty strings + if (![argumentParts count]) return nil; - return fieldIDQueryStr; + return [NSString stringWithFormat:@"WHERE (%@)", [argumentParts componentsJoinedByString:@" AND "]]; +} + +- (void)saveCellValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSUInteger)rowIndex +{ + NSDictionary *columnDefinition = [cqColumnDefinition objectAtIndex:[[aTableColumn identifier] integerValue]]; + NSString *columnTypeGroup = [columnDefinition objectForKey:@"typegrouping"]; + + // Resolve the original table name for current column if AS was used + NSString *tableForColumn = [columnDefinition objectForKey:@"org_table"]; + + if(!tableForColumn || ![tableForColumn length]) { + [errorText setString:[NSString stringWithFormat:NSLocalizedString(@"Couldn't identify field origin unambiguously. The column '%@' contains data from more than one table.", @"Custom Query result editing error - could not identify a corresponding column"), [columnDefinition objectForKey:@"name"]]]; + NSBeep(); + return; + } + + // Resolve the original column name if AS was used + NSString *columnName = [columnDefinition objectForKey:@"org_name"]; + + // Check if the IDstring identifies the current field bijectively and get the WHERE clause + NSArray *editStatus = [self fieldEditStatusForRow:rowIndex andColumn:[[aTableColumn identifier] integerValue]]; + fieldIDQueryString = [editStatus objectAtIndex:1]; + NSInteger numberOfPossibleUpdateRows = [[editStatus objectAtIndex:0] integerValue]; + + if(numberOfPossibleUpdateRows == 1) { + + NSString *newObject = nil; + if ( [anObject isKindOfClass:[NSCalendarDate class]] ) { + newObject = [NSString stringWithFormat:@"'%@'", [mySQLConnection prepareString:[anObject description]]]; + } else if ( [anObject isKindOfClass:[NSNumber class]] ) { + newObject = [anObject stringValue]; + } else if ( [anObject isKindOfClass:[NSData class]] ) { + newObject = [NSString stringWithFormat:@"X'%@'", [mySQLConnection prepareBinaryData:anObject]]; + } else { + if ( [[anObject description] isEqualToString:@"CURRENT_TIMESTAMP"] ) { + newObject = @"CURRENT_TIMESTAMP"; + } else if ([anObject isEqualToString:[prefs stringForKey:SPNullValue]] + || (([columnTypeGroup isEqualToString:@"float"] || [columnTypeGroup isEqualToString:@"integer"]) + && [[anObject description] isEqualToString:@""])) + { + newObject = @"NULL"; + } else if ([columnTypeGroup isEqualToString:@"geometry"]) { + newObject = [(NSString*)anObject getGeomFromTextString]; + } else if ([columnTypeGroup isEqualToString:@"bit"]) { + newObject = [NSString stringWithFormat:@"b'%@'", ((![[anObject description] length] || [[anObject description] isEqualToString:@"0"]) ? @"0" : [anObject description])]; + } else if ([columnTypeGroup isEqualToString:@"date"] + && [[anObject description] isEqualToString:@"NOW()"]) { + newObject = @"NOW()"; + } else { + newObject = [NSString stringWithFormat:@"'%@'", [mySQLConnection prepareString:[anObject description]]]; + } + } + + [mySQLConnection queryString: + [NSString stringWithFormat:@"UPDATE %@.%@ SET %@.%@.%@ = %@ %@ LIMIT 1", + [[columnDefinition objectForKey:@"db"] backtickQuotedString], [[columnDefinition objectForKey:@"org_table"] backtickQuotedString], + [[columnDefinition objectForKey:@"db"] backtickQuotedString], [[columnDefinition objectForKey:@"org_table"] backtickQuotedString], [columnName backtickQuotedString], newObject, fieldIDQueryString]]; + + // Check for errors while UPDATE + if ([mySQLConnection queryErrored]) { + SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), NSLocalizedString(@"Cancel", @"cancel button"), nil, [tableDocumentInstance parentWindow], self, nil, nil, + [NSString stringWithFormat:NSLocalizedString(@"Couldn't write field.\nMySQL said: %@", @"message of panel when error while updating field to db"), [mySQLConnection getLastErrorMessage]]); + + return; + } + + // This shouldn't happen – for safety reasons + if ( ![mySQLConnection affectedRows] ) { +#ifndef SP_REFACTOR + if ( [prefs boolForKey:SPShowNoAffectedRowsError] ) { + SPBeginAlertSheet(NSLocalizedString(@"Warning", @"warning"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, + NSLocalizedString(@"The row was not written to the MySQL database. You probably haven't changed anything.\nReload the table to be sure that the row exists and use a primary key for your table.\n(This error can be turned off in the preferences.)", @"message of panel when no rows have been affected after writing to the db")); + } else { + NSBeep(); + } +#endif + return; + } + + // On success reload table data by executing the last query if reloading is enabled +#ifndef SP_REFACTOR + if ([prefs boolForKey:SPReloadAfterEditingRow]) { + reloadingExistingResult = YES; + [self storeCurrentResultViewForRestoration]; + [self performQueries:[NSArray arrayWithObject:lastExecutedQuery] withCallback:NULL]; + } else { +#endif + // otherwise, just update the data in the data storage + SPDataStorageReplaceObjectAtRowAndColumn(resultData, rowIndex, [[aTableColumn identifier] intValue], anObject); +#ifndef SP_REFACTOR + } +#endif + } else { + SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, + [NSString stringWithFormat:NSLocalizedString(@"Updating field content failed. Couldn't identify field origin unambiguously (%ld match%@). It's very likely that while editing this field of table `%@` was changed.", @"message of panel when error while updating field to db after enabling it"), + (numberOfPossibleUpdateRows<1)?0:numberOfPossibleUpdateRows, (numberOfPossibleUpdateRows>1)?@"es":@"", [columnDefinition objectForKey:@"org_table"]]); + + } } #pragma mark - @@ -1891,12 +1994,7 @@ */ - (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView { - if (aTableView == customQueryView) { - return (resultData == nil) ? 0 : resultDataCount; - } - else { - return 0; - } + return (aTableView == customQueryView) ? (resultData == nil) ? 0 : resultDataCount : 0; } /** @@ -1908,28 +2006,32 @@ // For NULL cell's display the user's NULL value placeholder in grey to easily distinguish it from other values if ([cell respondsToSelector:@selector(setTextColor:)]) { + + id value = nil; NSUInteger columnIndex = [[aTableColumn identifier] integerValue]; - id theValue = nil; // While the table is being loaded, additional validation is required - data // locks must be used to avoid crashes, and indexes higher than the available // rows or columns may be requested. Use gray to show loading in these cases. if (isWorking) { pthread_mutex_lock(&resultDataLock); + if (rowIndex < resultDataCount && columnIndex < [resultData columnCount]) { - theValue = SPDataStorageObjectAtRowAndColumn(resultData, rowIndex, columnIndex); + value = SPDataStorageObjectAtRowAndColumn(resultData, rowIndex, columnIndex); } + pthread_mutex_unlock(&resultDataLock); - if (!theValue) { + if (!value) { [cell setTextColor:[NSColor lightGrayColor]]; return; } - } else { - theValue = SPDataStorageObjectAtRowAndColumn(resultData, rowIndex, columnIndex); + } + else { + value = SPDataStorageObjectAtRowAndColumn(resultData, rowIndex, columnIndex); } - [cell setTextColor:[theValue isNSNull] ? [NSColor lightGrayColor] : [NSColor blackColor]]; + [cell setTextColor:[value isNSNull] ? [NSColor lightGrayColor] : [NSColor blackColor]]; } } } @@ -1937,160 +2039,36 @@ /** * Returns the object for the requested column and row index. */ -- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex +- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex { if (aTableView == customQueryView) { - NSUInteger columnIndex = [[aTableColumn identifier] integerValue]; - id theValue = nil; - - // While the table is being loaded, additional validation is required - data - // locks must be used to avoid crashes, and indexes higher than the available - // rows or columns may be requested. Return "..." to indicate loading in these - // cases. - if (isWorking) { - pthread_mutex_lock(&resultDataLock); - if (rowIndex < resultDataCount && columnIndex < [resultData columnCount]) { - theValue = [[SPDataStorageObjectAtRowAndColumn(resultData, rowIndex, columnIndex) copy] autorelease]; - } - pthread_mutex_unlock(&resultDataLock); - - if (!theValue) return @"..."; - } else { - theValue = SPDataStorageObjectAtRowAndColumn(resultData, rowIndex, columnIndex); - } - - if ([theValue isKindOfClass:[NSData class]]) - return [theValue shortStringRepresentationUsingEncoding:[mySQLConnection stringEncoding]]; - - if ([theValue isNSNull]) - return [prefs objectForKey:SPNullValue]; - - if ([theValue isKindOfClass:[MCPGeometryData class]]) - return [theValue wktString]; - - return theValue; - } - else { - return @""; + + return [self _convertResultDataValueToDisplayableRepresentation:[self _resultDataItemAtRow:rowIndex columnIndex:[[tableColumn identifier] integerValue]] whilePreservingNULLs:NO]; } + + return @""; } - (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex { if (aTableView == customQueryView) { - NSDictionary *columnDefinition; - NSString *columnTypeGroup; - - // Retrieve the column defintion - for(id c in cqColumnDefinition) { - if([[c objectForKey:@"datacolumnindex"] isEqualToNumber:[aTableColumn identifier]]) { - columnDefinition = [NSDictionary dictionaryWithDictionary:c]; - columnTypeGroup = [columnDefinition objectForKey:@"typegrouping"]; - break; - } - } - - // Resolve the original table name for current column if AS was used - NSString *tableForColumn = [columnDefinition objectForKey:@"org_table"]; - - if(!tableForColumn || ![tableForColumn length]) { - [errorText setString:[NSString stringWithFormat:NSLocalizedString(@"Couldn't identify field origin unambiguously. The column '%@' contains data from more than one table.", @"Custom Query result editing error - could not identify a corresponding column"), [columnDefinition objectForKey:@"name"]]]; - NSBeep(); + // If the current cell should have been edited in a sheet, do nothing - field closing will have already + // updated the field. + if ([customQueryView shouldUseFieldEditorForRow:rowIndex column:[[aTableColumn identifier] integerValue]]) { return; } - // Resolve the original column name if AS was used - NSString *columnName = [columnDefinition objectForKey:@"org_name"]; - - // Check if the IDstring identifies the current field bijectively and get the WHERE clause - NSArray *editStatus = [self fieldEditStatusForRow:rowIndex andColumn:[aTableColumn identifier]]; - fieldIDQueryString = [editStatus objectAtIndex:1]; - NSInteger numberOfPossibleUpdateRows = [[editStatus objectAtIndex:0] integerValue]; - - if(numberOfPossibleUpdateRows == 1) { - - NSString *newObject = nil; - if ( [anObject isKindOfClass:[NSCalendarDate class]] ) { - newObject = [NSString stringWithFormat:@"'%@'", [mySQLConnection prepareString:[anObject description]]]; - } else if ( [anObject isKindOfClass:[NSNumber class]] ) { - newObject = [anObject stringValue]; - } else if ( [anObject isKindOfClass:[NSData class]] ) { - newObject = [NSString stringWithFormat:@"X'%@'", [mySQLConnection prepareBinaryData:anObject]]; - } else { - if ( [[anObject description] isEqualToString:@"CURRENT_TIMESTAMP"] ) { - newObject = @"CURRENT_TIMESTAMP"; - } else if ([anObject isEqualToString:[prefs stringForKey:SPNullValue]] - || (([columnTypeGroup isEqualToString:@"float"] || [columnTypeGroup isEqualToString:@"integer"]) - && [[anObject description] isEqualToString:@""])) - { - newObject = @"NULL"; - } else if ([columnTypeGroup isEqualToString:@"geometry"]) { - newObject = [(NSString*)anObject getGeomFromTextString]; - } else if ([columnTypeGroup isEqualToString:@"bit"]) { - newObject = [NSString stringWithFormat:@"b'%@'", ((![[anObject description] length] || [[anObject description] isEqualToString:@"0"]) ? @"0" : [anObject description])]; - } else if ([columnTypeGroup isEqualToString:@"date"] - && [[anObject description] isEqualToString:@"NOW()"]) { - newObject = @"NOW()"; - } else { - newObject = [NSString stringWithFormat:@"'%@'", [mySQLConnection prepareString:[anObject description]]]; - } - } - - [mySQLConnection queryString: - [NSString stringWithFormat:@"UPDATE %@.%@ SET %@.%@.%@ = %@ %@ LIMIT 1", - [[columnDefinition objectForKey:@"db"] backtickQuotedString], [[columnDefinition objectForKey:@"org_table"] backtickQuotedString], - [[columnDefinition objectForKey:@"db"] backtickQuotedString], [[columnDefinition objectForKey:@"org_table"] backtickQuotedString], [columnName backtickQuotedString], newObject, fieldIDQueryString]]; - - // Check for errors while UPDATE - if ([mySQLConnection queryErrored]) { - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), NSLocalizedString(@"Cancel", @"cancel button"), nil, [tableDocumentInstance parentWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"Couldn't write field.\nMySQL said: %@", @"message of panel when error while updating field to db"), [mySQLConnection getLastErrorMessage]]); - - return; - } - - // This shouldn't happen – for safety reasons - if ( ![mySQLConnection affectedRows] ) { -#ifndef SP_REFACTOR - if ( [prefs boolForKey:SPShowNoAffectedRowsError] ) { - SPBeginAlertSheet(NSLocalizedString(@"Warning", @"warning"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - NSLocalizedString(@"The row was not written to the MySQL database. You probably haven't changed anything.\nReload the table to be sure that the row exists and use a primary key for your table.\n(This error can be turned off in the preferences.)", @"message of panel when no rows have been affected after writing to the db")); - } else { - NSBeep(); - } -#endif - return; - } - - // On success reload table data by executing the last query if reloading is enabled -#ifndef SP_REFACTOR - if ([prefs boolForKey:SPReloadAfterEditingRow]) { - reloadingExistingResult = YES; - [self storeCurrentResultViewForRestoration]; - [self performQueries:[NSArray arrayWithObject:lastExecutedQuery] withCallback:NULL]; - } else { -#endif - // otherwise, just update the data in the data storage - SPDataStorageReplaceObjectAtRowAndColumn(resultData, rowIndex, [[aTableColumn identifier] intValue], anObject); -#ifndef SP_REFACTOR - } -#endif - } else { - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"Updating field content failed. Couldn't identify field origin unambiguously (%ld match%@). It's very likely that while editing this field of table `%@` was changed.", @"message of panel when error while updating field to db after enabling it"), - (numberOfPossibleUpdateRows<1)?0:numberOfPossibleUpdateRows, (numberOfPossibleUpdateRows>1)?@"es":@"", [columnDefinition objectForKey:@"org_table"]]); - - } + // Otherwise trigger a save + [self saveCellValue:anObject forTableColumn:aTableColumn row:rowIndex]; } } -/* +/** * Change the sort order by clicking at a column header */ - (void)tableView:(NSTableView*)tableView didClickTableColumn:(NSTableColumn *)tableColumn { - // Prevent sorting while a query is running if ([tableDocumentInstance isWorking]) return; if (!cqColumnDefinition || ![cqColumnDefinition count]) return; @@ -2099,7 +2077,7 @@ // Sets column order as tri-state descending, ascending, no sort, descending, ascending etc. order if the same // header is clicked several times - if (sortField && [[tableColumn identifier] isEqualToNumber:sortField]) { + if (sortField && [[tableColumn identifier] integerValue] == [sortField integerValue]) { if(isDesc) { [sortField release]; sortField = nil; @@ -2110,7 +2088,7 @@ } } else { isDesc = NO; - [[customQueryView onMainThread] setIndicatorImage:nil inTableColumn:[customQueryView tableColumnWithIdentifier:sortField]]; + [[customQueryView onMainThread] setIndicatorImage:nil inTableColumn:[customQueryView tableColumnWithIdentifier:[NSString stringWithFormat:@"%lld", (long long)[sortField integerValue]]]]; if (sortField) [sortField release]; sortField = [[NSNumber alloc] initWithInteger:[[tableColumn identifier] integerValue]]; } @@ -2225,7 +2203,6 @@ } - #pragma mark - #pragma mark TableView Drag & Drop datasource methods @@ -2310,7 +2287,6 @@ */ - (NSString *)tableView:(NSTableView *)aTableView toolTipForCell:(SPTextAndLinkCell *)aCell rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)row mouseLocation:(NSPoint)mouseLocation { - if([[aCell stringValue] length] < 2 || [tableDocumentInstance isWorking]) return nil; // Suppress tooltip if another toolip is already visible, mainly displayed by a Bundle command @@ -2334,7 +2310,7 @@ // cases. if (isWorking) { pthread_mutex_lock(&resultDataLock); - if (row < resultDataCount && [[aTableColumn identifier] unsignedIntegerValue] < [resultData columnCount]) { + if (row < resultDataCount && (NSUInteger)[[aTableColumn identifier] integerValue] < [resultData columnCount]) { theValue = [[SPDataStorageObjectAtRowAndColumn(resultData, row, [[aTableColumn identifier] integerValue]) copy] autorelease]; } pthread_mutex_unlock(&resultDataLock); @@ -2378,46 +2354,39 @@ return nil; } -/* +/** * Double-click action on a field */ - (BOOL)tableView:(NSTableView *)aTableView shouldEditTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex { - // Only allow editing if a task is not active if ([tableDocumentInstance isWorking]) return NO; // Check if the field can identified bijectively if ( aTableView == customQueryView ) { - - NSDictionary *columnDefinition; - BOOL isBlob = NO; - - // Retrieve the column defintion - for(id c in cqColumnDefinition) { - if([[c objectForKey:@"datacolumnindex"] isEqualToNumber:[aTableColumn identifier]]) { - columnDefinition = [NSDictionary dictionaryWithDictionary:c]; - break; - } - } + NSDictionary *columnDefinition = [cqColumnDefinition objectAtIndex:[[aTableColumn identifier] integerValue]]; // Check if current field is a blob - if([[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"textdata"] - || [[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"blobdata"]) - isBlob = YES; - else - isBlob = NO; - - if ([multipleLineEditingButton state] == NSOnState || isBlob) { + BOOL isBlob = ([[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"textdata"] + || [[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"blobdata"]); - if(fieldEditor) [fieldEditor release], fieldEditor = nil; + // Open the editing sheet if required + if ([customQueryView shouldUseFieldEditorForRow:rowIndex column:[[aTableColumn identifier] integerValue]]) + { + if (fieldEditor) [fieldEditor release], fieldEditor = nil; fieldEditor = [[SPFieldEditorController alloc] init]; // Remember edited row for reselecting and setting the scroll view after reload editedRow = rowIndex; editedScrollViewRect = [customQueryScrollView documentVisibleRect]; - NSArray *editStatus = [self fieldEditStatusForRow:rowIndex andColumn:[aTableColumn identifier]]; + NSInteger editedColumn = 0; + for (NSTableColumn* col in [customQueryView tableColumns]) { + if([[col identifier] isEqualToString:[aTableColumn identifier]]) break; + editedColumn++; + } + + NSArray *editStatus = [self fieldEditStatusForRow:rowIndex andColumn:[[aTableColumn identifier] integerValue]]; isFieldEditable = ([[editStatus objectAtIndex:0] integerValue] == 1) ? YES : NO; NSString *fieldType = nil; @@ -2458,17 +2427,17 @@ withWindow:[tableDocumentInstance parentWindow] sender:self contextInfo:[NSDictionary dictionaryWithObjectsAndKeys: - [NSNumber numberWithInteger:rowIndex], @"row", - [aTableColumn identifier], @"column", + [NSNumber numberWithInteger:rowIndex], @"rowIndex", + [NSNumber numberWithInteger:editedColumn], @"columnIndex", [NSNumber numberWithBool:isFieldEditable], @"isFieldEditable", nil]]; return NO; - } + return YES; - - } else { + } + else { return YES; } } @@ -2623,37 +2592,42 @@ // Return the width, while the delegate is empty to prevent column resize notifications [customQueryView setDelegate:nil]; [customQueryView performSelector:@selector(setDelegate:) withObject:self afterDelay:0.1]; + return targetWidth; } #pragma mark - #pragma mark TextView delegate methods -/* +/** * Traps enter key and performs query instead of inserting a line break if aTextView == textView * closes valueSheet if aTextView == valueTextField */ - (BOOL)textView:(NSTextView *)aTextView doCommandBySelector:(SEL)aSelector { - if ( aTextView == textView ) { - if ( [aTextView methodForSelector:aSelector] == [aTextView methodForSelector:@selector(insertNewline:)] && - [[[NSApp currentEvent] characters] isEqualToString:@"\003"] ) - { + if (aTextView == textView) { + if ([aTextView methodForSelector:aSelector] == [aTextView methodForSelector:@selector(insertNewline:)] && + [[[NSApp currentEvent] characters] isEqualToString:@"\003"]) { [self runAllQueries:self]; + return YES; - } else { + } + else { return NO; } - } else if ( aTextView == valueTextField ) { - if ( [aTextView methodForSelector:aSelector] == [aTextView methodForSelector:@selector(insertNewline:)] ) - { + } + else if (aTextView == valueTextField) { + if ([aTextView methodForSelector:aSelector] == [aTextView methodForSelector:@selector(insertNewline:)]) { [self closeSheet:self]; + return YES; - } else { + } + else { return NO; } } + return NO; } @@ -2663,18 +2637,17 @@ - (NSRange)textView:(NSTextView *)aTextView willChangeSelectionFromCharacterRange:(NSRange)oldSelectedCharRange toCharacterRange:(NSRange)newSelectedCharRange { // Check if snippet session is still valid - if(!newSelectedCharRange.length && [textView isSnippetMode]) [textView checkForCaretInsideSnippet]; + if (!newSelectedCharRange.length && [textView isSnippetMode]) [textView checkForCaretInsideSnippet]; return newSelectedCharRange; } -/* +/** * A notification posted when the selection changes within the text view; * used to control the run-currentrun-selection button state and action. */ - (void)textViewDidChangeSelection:(NSNotification *)aNotification { - // Ensure that the notification is from the custom query text view if ( [aNotification object] != textView ) return; @@ -2690,7 +2663,7 @@ currentQueryRange = NSMakeRange(0, 0); [textView setQueryRange:qRange]; - [textView setNeedsDisplay:YES]; + [textView setNeedsDisplayInRect:[textView bounds]]; // disable "Comment Current Query" menu item if no current query is selectable [commentCurrentQueryMenuItem setEnabled:(currentQueryRange.length) ? YES : NO]; @@ -2760,15 +2733,21 @@ else if ([notification object] == queryHistorySearchField) { [self filterQueryHistory:nil]; } +} +#ifndef SP_REFACTOR +- (NSUndoManager *)undoManagerForTextView:(NSTextView *)aTextView +{ + return [tableDocumentInstance undoManager]; } +#endif #pragma mark - #pragma mark SplitView delegate methods #ifndef SP_REFACTOR /* splitview delegate methods */ -/* +/** * Tells the splitView that it can collapse views */ - (BOOL)splitView:(NSSplitView *)sender canCollapseSubview:(NSView *)subview @@ -2776,21 +2755,23 @@ return YES; } -/* +/** * Defines max position of splitView */ - (CGFloat)splitView:(NSSplitView *)sender constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)offset { if (sender != queryInfoPaneSplitView) return (offset == 0) ? (proposedMax - 100) : (proposedMax - 73); + return proposedMax; } -/* +/** * Defines min position of splitView */ - (CGFloat)splitView:(NSSplitView *)sender constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)offset { if (sender != queryInfoPaneSplitView) return proposedMin + 100; + return proposedMin; } @@ -2807,18 +2788,17 @@ #pragma mark - #pragma mark MySQL Help -/* +/** * Set the MySQL version as X.Y for Help window title and online search */ - (void)setMySQLversion:(NSString *)theVersion { mySQLversion = [[theVersion substringToIndex:3] retain]; [textView setConnection:mySQLConnection withVersion:[[[mySQLversion componentsSeparatedByString:@"."] objectAtIndex:0] integerValue]]; - } #ifndef SP_REFACTOR -/* +/** * Return the Help window. */ - (NSWindow *)helpWebViewWindow @@ -2826,12 +2806,11 @@ return helpWebViewWindow; } -/* +/** * Show the data for "HELP 'searchString'". */ - (void)showHelpFor:(NSString *)searchString addToHistory:(BOOL)addToHistory calledByAutoHelp:(BOOL)autoHelp { - if(![searchString length]) return; NSString *helpString = [self getHTMLformattedMySQLHelpFor:searchString calledByAutoHelp:autoHelp]; @@ -2865,7 +2844,6 @@ // order out Help window if Help is available if(![helpString isEqualToString:SP_HELP_NOT_AVAILABLE]) [helpWebViewWindow orderFront:helpWebView]; - } // close Help window if no Help available @@ -2888,11 +2866,9 @@ // load HTML formatted help into the webview [[helpWebView mainFrame] loadHTMLString:helpString baseURL:nil]; - } - -/* +/** * Show the data for "HELP 'search word'" according to helpTarget */ - (IBAction)showHelpForSearchString:(id)sender @@ -2915,7 +2891,7 @@ } } -/* +/** * Show the Help for the selected text in the webview */ - (IBAction)showHelpForWebViewSelection:(id)sender @@ -2938,7 +2914,7 @@ } -/* +/** * Show the data for "HELP 'currentWord'" */ - (IBAction)showHelpForCurrentWord:(id)sender @@ -2947,7 +2923,7 @@ [self showHelpFor:searchString addToHistory:YES calledByAutoHelp:NO]; } -/* +/** * Find Next/Previous in current page */ - (IBAction)helpSearchFindNextInPage:(id)sender @@ -2956,6 +2932,7 @@ if(![helpWebView searchFor:[[helpSearchField stringValue] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] direction:YES caseSensitive:NO wrap:YES]) NSBeep(); } + - (IBAction)helpSearchFindPreviousInPage:(id)sender { if(helpTarget == SP_HELP_SEARCH_IN_PAGE) @@ -2963,7 +2940,7 @@ NSBeep(); } -/* +/** * Navigation for back/TOC/forward */ - (IBAction)helpSegmentDispatcher:(id)sender @@ -2980,13 +2957,14 @@ [helpWebView goForward]; break; } + // validate goback and goforward buttons according history [helpNavigator setEnabled:[[helpWebView backForwardList] backListCount] forSegment:SP_HELP_GOBACK_BUTTON]; [helpNavigator setEnabled:[[helpWebView backForwardList] forwardListCount] forSegment:SP_HELP_GOFORWARD_BUTTON]; } -/* +/** * Set helpTarget according user choice via mouse and keyboard short-cuts. */ - (IBAction)helpSelectHelpTargetMySQL:(id)sender @@ -2995,18 +2973,21 @@ [helpTargetSelector setSelectedSegment:SP_HELP_SEARCH_IN_MYSQL]; [self helpTargetValidation]; } + - (IBAction)helpSelectHelpTargetPage:(id)sender { helpTarget = SP_HELP_SEARCH_IN_PAGE; [helpTargetSelector setSelectedSegment:SP_HELP_SEARCH_IN_PAGE]; [self helpTargetValidation]; } + - (IBAction)helpSelectHelpTargetWeb:(id)sender { helpTarget = SP_HELP_SEARCH_IN_WEB; [helpTargetSelector setSelectedSegment:SP_HELP_SEARCH_IN_WEB]; [self helpTargetValidation]; } + - (IBAction)helpTargetDispatcher:(id)sender { helpTarget = [helpTargetSelector selectedSegment]; @@ -3031,7 +3012,7 @@ } #ifndef SP_REFACTOR -/* +/** * Show the data for "HELP 'currentWord' invoked by autohelp" */ - (void)showAutoHelpForCurrentWord:(id)sender @@ -3040,7 +3021,7 @@ [self showHelpFor:searchString addToHistory:YES calledByAutoHelp:YES]; } -/* +/** * Control the help search field behaviour. */ - (void)helpTargetValidation @@ -3073,7 +3054,7 @@ stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding]]]; } -/* +/** * Return the help string HTML formatted from executing "HELP 'searchString'". * If more than one help topic was found return a link list. */ @@ -3207,14 +3188,12 @@ [tableDetails release]; return [NSString stringWithFormat:helpHTMLTemplate, theHelp]; - } -////////////////////////////// -// WebView delegate methods // -////////////////////////////// +#pragma mark - +#pragma mark WebView delegate methods -/* +/** * Link detector: If user clicked at an http link open it in the default browser, * otherwise search for it in the MySQL help. Additionally handle back/forward events from * keyboard and context menu. @@ -3251,7 +3230,7 @@ } } -/* +/** * Manage contextual menu in helpWebView * Ignore "Reload", "Open Link", "Open Link in new Window", "Download link" etc. */ @@ -3363,6 +3342,7 @@ for(id historyMenuItem in [[SPQueryController sharedQueryController] historyMenuItemsForFileURL:[tableDocumentInstance fileURL]]) [historyMenu addItem:historyMenuItem]; } + /** * Called by the query favorites manager whenever the query favorites have been updated. */ @@ -3507,7 +3487,7 @@ [[SPQueryController sharedQueryController] addHistory:entryString forFileURL:[tableDocumentInstance fileURL]]; } -/* +/** * This method is called as part of Key Value Observing which is used to watch for prefernce changes which effect the interface. */ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context @@ -3530,7 +3510,6 @@ */ - (void)sheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo { - if ([contextInfo isEqualToString:@"runAllContinueStopSheet"]) { runAllContinueStopSheetReturnCode = returnCode; return; @@ -3666,15 +3645,15 @@ NSInteger column = -1; if(contextInfo) { - row = [[contextInfo objectForKey:@"row"] integerValue]; - column = [[contextInfo objectForKey:@"column"] integerValue]; + row = [[contextInfo objectForKey:@"rowIndex"] integerValue]; + column = [[contextInfo objectForKey:@"columnIndex"] integerValue]; } if (data && contextInfo) { BOOL isResultFieldEditable = ([contextInfo objectForKey:@"isFieldEditable"]) ? YES : NO; if(isResultFieldEditable) { - [self tableView:customQueryView setObjectValue:[[data copy] autorelease] forTableColumn:[customQueryView tableColumnWithIdentifier:[contextInfo objectForKey:@"column"]] row:row]; + [self saveCellValue:[[data copy] autorelease] forTableColumn:[[customQueryView tableColumns] objectAtIndex:column] row:row]; } } @@ -3793,14 +3772,13 @@ } } -/* +/** * If user selected a table cell which is a blob field and tried to edit it * cancel the fieldEditor, display the field editor sheet instead for editing * and re-enable the fieldEditor after editing. */ - (BOOL)control:(NSControl *)control textShouldBeginEditing:(NSText *)aFieldEditor { - if(![control isKindOfClass:[SPCopyTable class]]) return YES; NSUInteger row, column; @@ -3810,18 +3788,12 @@ row = [customQueryView editedRow]; column = [customQueryView editedColumn]; - // Retrieve the column defintion - NSNumber *colIdentifier = [NSArrayObjectAtIndex([customQueryView tableColumns], column) identifier]; - for(id c in cqColumnDefinition) { - if([[c objectForKey:@"datacolumnindex"] isEqualToNumber:colIdentifier]) { - columnDefinition = [NSDictionary dictionaryWithDictionary:c]; - break; - } - } + // Retrieve the column definition + columnDefinition = [NSDictionary dictionaryWithDictionary:[cqColumnDefinition objectAtIndex:[[[[customQueryView tableColumns] objectAtIndex:column] identifier] integerValue]]]; if(!columnDefinition) return NO; - NSArray *editStatus = [self fieldEditStatusForRow:row andColumn:colIdentifier]; + NSArray *editStatus = [self fieldEditStatusForRow:row andColumn:column]; NSInteger numberOfPossibleUpdateRows = [NSArrayObjectAtIndex(editStatus, 0) integerValue]; NSPoint pos = [[tableDocumentInstance parentWindow] convertBaseToScreen:[customQueryView convertPoint:[customQueryView frameOfCellAtColumn:column row:row].origin toView:nil]]; pos.y -= 20; @@ -3850,19 +3822,10 @@ shouldBeginEditing = NO; } - BOOL isBlob = NO; - - // Check if current field is a blob - if([[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"textdata"] - || [[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"blobdata"]) - isBlob = YES; - else - isBlob = NO; - isFieldEditable = shouldBeginEditing; - // Check if current edited field is a blob or should be displayed in field editor sheet - if (isBlob || [multipleLineEditingButton state] == NSOnState) + // Open the field editor sheet if required + if ([customQueryView shouldUseFieldEditorForRow:row column:column]) { [customQueryView setFieldEditorSelectedRange:[aFieldEditor selectedRange]]; @@ -3885,7 +3848,6 @@ [aFieldEditor setTextColor:[NSColor blackColor]]; return shouldBeginEditing; - } /** @@ -3894,7 +3856,6 @@ */ - (BOOL)control:(NSControl*)control textView:(NSTextView*)aTextView doCommandBySelector:(SEL)command { - if(control == queryHistorySearchField || control == queryFavoritesSearchField) { if(command == @selector(moveDown:) || command == @selector(moveUp:)) { [queryHistorySearchField abortEditing]; @@ -3930,22 +3891,10 @@ return TRUE; } - - } return NO; } -// - (void)menu:(NSMenu *)menu willHighlightItem:(NSMenuItem *)item -// { -// // Set the focus at the search field -// // TODO : but no way out; always selecting first/last menu item -// // because after setting focus to search field NSMenu selectedItemIndex is -1 -// if(item == queryHistorySearchMenuItem) { -// [queryHistorySearchField selectText:nil]; -// } -// -// } /** * Setup various interface controls. @@ -3993,6 +3942,71 @@ } } +#pragma mark - +#pragma mark Private API + +/** + * Retrieves the value from the underlying data storage at the supplied row and column indices. + * + * @param row The row index + * @param column The column index + * + * @return The value from the data storage + */ +- (id)_resultDataItemAtRow:(NSInteger)row columnIndex:(NSUInteger)column +{ + id value = nil; + + // While the table is being loaded, additional validation is required - data + // locks must be used to avoid crashes, and indexes higher than the available + // rows or columns may be requested. Return "..." to indicate loading in these + // cases. + if (isWorking) { + pthread_mutex_lock(&resultDataLock); + + if (row < resultDataCount && column < [resultData columnCount]) { + value = [[SPDataStorageObjectAtRowAndColumn(resultData, row, column) copy] autorelease]; + } + + pthread_mutex_unlock(&resultDataLock); + + if (!value) value = @"..."; + } + else { + value = SPDataStorageObjectAtRowAndColumn(resultData, row, column); + } + + return value; +} + +/** + * Converts the supplied value into it's displayable representation. + * + * @param value The value to convert + * @param preserveNULLs Whether or not NULLs should be preserved or converted to the + * user's NULL placeholder preference. + * + * @return The converted value + */ +- (id)_convertResultDataValueToDisplayableRepresentation:(id)value whilePreservingNULLs:(BOOL)preserveNULLs +{ + if ([value isKindOfClass:[NSData class]]) { + value = [value shortStringRepresentationUsingEncoding:[mySQLConnection stringEncoding]]; + } + + if ([value isNSNull] && !preserveNULLs) { + value = [prefs objectForKey:SPNullValue]; + } + + if ([value isKindOfClass:[MCPGeometryData class]]) { + value = [value wktString]; + } + + return value; +} + +#pragma mark - + /** * Dealloc. */ |