diff options
author | stuconnolly <stuart02@gmail.com> | 2012-03-20 22:43:34 +0000 |
---|---|---|
committer | stuconnolly <stuart02@gmail.com> | 2012-03-20 22:43:34 +0000 |
commit | 1f18d0eb40a1bc0551dbabdfe355c9632a47c6c6 (patch) | |
tree | f2983b3e79cb542b3a4297a14cf93d9847871b6c /Source | |
parent | 008b8291ebaf3042c7229350ca1c77a110f4b65d (diff) | |
download | sequelpro-1f18d0eb40a1bc0551dbabdfe355c9632a47c6c6.tar.gz sequelpro-1f18d0eb40a1bc0551dbabdfe355c9632a47c6c6.tar.bz2 sequelpro-1f18d0eb40a1bc0551dbabdfe355c9632a47c6c6.zip |
- When exporting a query result or filtered table view make sure we're including the entire content of BLOBs, not just what we display. Fixes issue #1124.
- Move SPTableContent's table view datasource and delegate methods to separate categories in order to reduce it's size.
Diffstat (limited to 'Source')
-rw-r--r-- | Source/SPCustomQuery.h | 2 | ||||
-rw-r--r-- | Source/SPCustomQuery.m | 16 | ||||
-rw-r--r-- | Source/SPDataAdditions.h | 6 | ||||
-rw-r--r-- | Source/SPDataAdditions.m | 40 | ||||
-rw-r--r-- | Source/SPExportInitializer.m | 7 | ||||
-rw-r--r-- | Source/SPPrintController.m | 2 | ||||
-rw-r--r-- | Source/SPTableContent.h | 136 | ||||
-rw-r--r-- | Source/SPTableContent.m | 1019 | ||||
-rw-r--r-- | Source/SPTableContentDataSource.h | 37 | ||||
-rw-r--r-- | Source/SPTableContentDataSource.m | 183 | ||||
-rw-r--r-- | Source/SPTableContentDelegate.h | 37 | ||||
-rw-r--r-- | Source/SPTableContentDelegate.m | 778 |
12 files changed, 1274 insertions, 989 deletions
diff --git a/Source/SPCustomQuery.h b/Source/SPCustomQuery.h index 1e330682..943c307d 100644 --- a/Source/SPCustomQuery.h +++ b/Source/SPCustomQuery.h @@ -237,7 +237,7 @@ // Accessors - (NSArray *)currentResult; -- (NSArray *)currentDataResultWithNULLs:(BOOL)includeNULLs; +- (NSArray *)currentDataResultWithNULLs:(BOOL)includeNULLs truncateDataFields:(BOOL)truncate; - (void)processResultIntoDataStorage:(SPMySQLFastStreamingResult *)theResult; // Retrieving and setting table state diff --git a/Source/SPCustomQuery.m b/Source/SPCustomQuery.m index da15da11..3388b035 100644 --- a/Source/SPCustomQuery.m +++ b/Source/SPCustomQuery.m @@ -57,7 +57,7 @@ @interface SPCustomQuery (PrivateAPI) - (id)_resultDataItemAtRow:(NSInteger)row columnIndex:(NSUInteger)column; -- (id)_convertResultDataValueToDisplayableRepresentation:(id)value whilePreservingNULLs:(BOOL)preserveNULLs; +- (id)_convertResultDataValueToDisplayableRepresentation:(id)value whilePreservingNULLs:(BOOL)preserveNULLs truncateDataFields:(BOOL)truncate; @end @@ -1455,7 +1455,7 @@ */ - (NSArray *)currentResult { - return [self currentDataResultWithNULLs:NO]; + return [self currentDataResultWithNULLs:NO truncateDataFields:YES]; } /** @@ -1464,8 +1464,9 @@ * * @param includeNULLs Indicates whether to include NULLs as a native type * or use the user's NULL string representation preference. + * @param truncate Indicates whether to truncate data fields for display purposes. */ -- (NSArray *)currentDataResultWithNULLs:(BOOL)includeNULLs +- (NSArray *)currentDataResultWithNULLs:(BOOL)includeNULLs truncateDataFields:(BOOL)truncate { NSInteger i; id tableColumn; @@ -1492,7 +1493,7 @@ { id value = [self _resultDataItemAtRow:i columnIndex:[[tableColumn identifier] integerValue]]; - [tempRow addObject:[self _convertResultDataValueToDisplayableRepresentation:value whilePreservingNULLs:YES]]; + [tempRow addObject:[self _convertResultDataValueToDisplayableRepresentation:value whilePreservingNULLs:includeNULLs truncateDataFields:truncate]]; } [currentResult addObject:[NSArray arrayWithArray:tempRow]]; @@ -2045,7 +2046,7 @@ { if (aTableView == customQueryView) { - return [self _convertResultDataValueToDisplayableRepresentation:[self _resultDataItemAtRow:rowIndex columnIndex:[[tableColumn identifier] integerValue]] whilePreservingNULLs:NO]; + return [self _convertResultDataValueToDisplayableRepresentation:[self _resultDataItemAtRow:rowIndex columnIndex:[[tableColumn identifier] integerValue]] whilePreservingNULLs:NO truncateDataFields:YES]; } return @""; @@ -3994,13 +3995,14 @@ * @param value The value to convert * @param preserveNULLs Whether or not NULLs should be preserved or converted to the * user's NULL placeholder preference. + * @param truncate Whether or not data fields should be truncates for display purposes. * * @return The converted value */ -- (id)_convertResultDataValueToDisplayableRepresentation:(id)value whilePreservingNULLs:(BOOL)preserveNULLs +- (id)_convertResultDataValueToDisplayableRepresentation:(id)value whilePreservingNULLs:(BOOL)preserveNULLs truncateDataFields:(BOOL)truncate { if ([value isKindOfClass:[NSData class]]) { - value = [value shortStringRepresentationUsingEncoding:[mySQLConnection stringEncoding]]; + value = truncate ? [value shortStringRepresentationUsingEncoding:[mySQLConnection stringEncoding]] : [value stringRepresentationUsingEncoding:[mySQLConnection stringEncoding]]; } if ([value isNSNull] && !preserveNULLs) { diff --git a/Source/SPDataAdditions.h b/Source/SPDataAdditions.h index 956e5b64..5ebbc356 100644 --- a/Source/SPDataAdditions.h +++ b/Source/SPDataAdditions.h @@ -24,11 +24,13 @@ @interface NSData (SPDataAdditions) -- (NSString *)dataToFormattedHexString; -- (NSString *)shortStringRepresentationUsingEncoding:(NSStringEncoding)encoding; - (NSData *)dataEncryptedWithPassword:(NSString *)password; - (NSData *)dataDecryptedWithPassword:(NSString *)password; - (NSData *)compress; - (NSData *)decompress; +- (NSString *)dataToFormattedHexString; +- (NSString *)stringRepresentationUsingEncoding:(NSStringEncoding)encoding; +- (NSString *)shortStringRepresentationUsingEncoding:(NSStringEncoding)encoding; + @end diff --git a/Source/SPDataAdditions.m b/Source/SPDataAdditions.m index 0329395c..b1aad389 100644 --- a/Source/SPDataAdditions.m +++ b/Source/SPDataAdditions.m @@ -76,7 +76,6 @@ - (NSData *)dataDecryptedWithPassword:(NSString *)password { - // Create the key from the password hash unsigned char passwordDigest[20]; SHA1((const unsigned char *)[password UTF8String], strlen([password UTF8String]), passwordDigest); @@ -190,10 +189,10 @@ return [NSData dataWithData: zipData]; } -- (NSString *)dataToFormattedHexString -/* - returns the hex representation of the given data +/** + * Returns the hex representation of the given data. */ +- (NSString *)dataToFormattedHexString { NSUInteger i, j; NSUInteger totalLength = [self length]; @@ -258,26 +257,33 @@ return retVal; } +/** + * Converts data instances to their string representation. + */ +- (NSString *)stringRepresentationUsingEncoding:(NSStringEncoding)encoding +{ + NSString *string = [[[NSString alloc] initWithData:self encoding:encoding] autorelease]; + + return !string ? [[[NSString alloc] initWithData:self encoding:NSASCIIStringEncoding] autorelease] : string; +} + /* * Convert data objects to their string representation (max 255 chars) * in the current encoding, falling back to ascii. (Mainly used for displaying * large blob data in a tableView) */ -- (NSString *) shortStringRepresentationUsingEncoding:(NSStringEncoding)encoding +- (NSString *)shortStringRepresentationUsingEncoding:(NSStringEncoding)encoding { - NSString *tmp = [[[NSString alloc] initWithData:self encoding:encoding] autorelease]; - - if (tmp == nil) - tmp = [[[NSString alloc] initWithData:self encoding:NSASCIIStringEncoding] autorelease]; - if (tmp == nil) - return @"- cannot be displayed -"; - else { - if([tmp length] > 255) - return [tmp substringToIndex:255]; - else - return tmp; + NSString *string = [self stringRepresentationUsingEncoding:encoding]; + + if (!string) { + string = @"-- cannot display --"; + } + else if ([string length] > 255) { + string = [string substringToIndex:255]; } - return @"- cannot be displayed -"; + + return string; } @end diff --git a/Source/SPExportInitializer.m b/Source/SPExportInitializer.m index b2f250c4..7751ddc5 100644 --- a/Source/SPExportInitializer.m +++ b/Source/SPExportInitializer.m @@ -98,10 +98,10 @@ switch (exportSource) { case SPFilteredExport: - dataArray = [tableContentInstance currentDataResultWithNULLs:YES]; + dataArray = [tableContentInstance currentDataResultWithNULLs:YES hideBLOBs:NO]; break; case SPQueryExport: - dataArray = [customQueryInstance currentResult]; + dataArray = [customQueryInstance currentDataResultWithNULLs:YES truncateDataFields:NO]; break; case SPTableExport: // Create an array of tables to export @@ -211,9 +211,10 @@ // export, create the single file now and assign it to all subsequently created exporters. if ((![self exportToMultipleFiles]) || (exportSource == SPFilteredExport) || (exportSource == SPQueryExport)) { NSString *selectedTableName = nil; + if (exportSource == SPTableExport && [exportTables count] == 1) selectedTableName = [exportTables objectAtIndex:0]; - [exportFilename setString:(createCustomFilename) ? [self expandCustomFilenameFormatUsingTableName:selectedTableName] : [self generateDefaultExportFilename]]; + [exportFilename setString:createCustomFilename ? [self expandCustomFilenameFormatUsingTableName:selectedTableName] : [self generateDefaultExportFilename]]; // Only append the extension if necessary if (![[exportFilename pathExtension] length]) { diff --git a/Source/SPPrintController.m b/Source/SPPrintController.m index 80d3c068..38071ba7 100644 --- a/Source/SPPrintController.m +++ b/Source/SPPrintController.m @@ -253,7 +253,7 @@ // Table content view else if (view == 1) { - NSArray *data = [tableContentInstance currentDataResultWithNULLs:NO]; + NSArray *data = [tableContentInstance currentDataResultWithNULLs:NO hideBLOBs:YES]; heading = NSLocalizedString(@"Table Content", @"table content print heading"); diff --git a/Source/SPTableContent.h b/Source/SPTableContent.h index a95dbae3..d8feee7e 100644 --- a/Source/SPTableContent.h +++ b/Source/SPTableContent.h @@ -25,8 +25,22 @@ // // More info at <http://code.google.com/p/sequel-pro/> -@class SPDatabaseDocument, SPCopyTable, SPTextAndLinkCell, SPHistoryController, SPTableInfo, SPDataStorage, SPTextView, SPFieldEditorController, SPMySQLConnection, SPMySQLFastStreamingResult; -@class SPTableData, SPDatabaseDocument, SPTablesList, SPTableStructure, SPTableList, SPContentFilterManager; +@class SPDatabaseDocument, + SPCopyTable, + SPTextAndLinkCell, + SPHistoryController, + SPTableInfo, + SPDataStorage, + SPTextView, + SPFieldEditorController, + SPMySQLConnection, + SPMySQLFastStreamingResult, + SPTableData, + SPDatabaseDocument, + SPTablesList, + SPTableStructure, + SPTableList, + SPContentFilterManager; @interface SPTableContent : NSObject #ifdef SP_REFACTOR @@ -155,35 +169,57 @@ NSRange fieldEditorSelectedRange; } +#ifdef SP_REFACTOR /* glue */ +@property (assign) id filterButton; +@property (assign) id fieldField; +@property (assign) id compareField; +@property (assign) id betweenTextField; +@property (assign) id firstBetweenField; +@property (assign) id secondBetweenField; +@property (assign) id argumentField; +@property (assign) NSButton* addButton; +@property (assign) NSButton* duplicateButton; +@property (assign) NSButton* removeButton; +@property (assign) NSButton* reloadButton; +@property (assign) NSButton* paginationNextButton; +@property (assign) NSButton* paginationPreviousButton; +@property (assign) NSTextField* paginationPageField; +@property (assign) SPDatabaseDocument* tableDocumentInstance; +@property (assign) SPTablesList* tablesListInstance; +@property (assign) SPCopyTable* tableContentView; +@property (assign) SPTableData* tableDataInstance; +@property (assign) SPTableStructure* tableSourceInstance; +#endif + - (void)setFieldEditorSelectedRange:(NSRange)aRange; - (NSRange)fieldEditorSelectedRange; // Table loading methods and information -- (void) loadTable:(NSString *)aTable; -- (void) clearTableValues; -- (void) loadTableValues; -- (NSString *) tableFilterString; -- (void) updateCountText; -- (void) initTableLoadTimer; -- (void) clearTableLoadTimer; -- (void) tableLoadUpdate:(NSTimer *)theTimer; +- (void)loadTable:(NSString *)aTable; +- (void)clearTableValues; +- (void)loadTableValues; +- (NSString *)tableFilterString; +- (void)updateCountText; +- (void)initTableLoadTimer; +- (void)clearTableLoadTimer; +- (void)tableLoadUpdate:(NSTimer *)theTimer; // Table interface actions -- (IBAction) reloadTable:(id)sender; -- (void) reloadTableTask; -- (IBAction) filterTable:(id)sender; +- (IBAction)reloadTable:(id)sender; +- (void)reloadTableTask; +- (IBAction)filterTable:(id)sender; - (void)filterTableTask; -- (IBAction) toggleFilterField:(id)sender; -- (NSString *) usedQuery; -- (void) setUsedQuery:(NSString *)query; +- (IBAction)toggleFilterField:(id)sender; +- (NSString *)usedQuery; +- (void)setUsedQuery:(NSString *)query; // Pagination -- (IBAction) navigatePaginationFromButton:(id)sender; +- (IBAction)navigatePaginationFromButton:(id)sender; #ifndef SP_REFACTOR -- (IBAction) togglePagination:(NSButton *)sender; +- (IBAction)togglePagination:(NSButton *)sender; #endif -- (void) setPaginationViewVisibility:(BOOL)makeVisible; -- (void) updatePaginationState; +- (void)setPaginationViewVisibility:(BOOL)makeVisible; +- (void)updatePaginationState; // Edit methods - (IBAction)addRow:(id)sender; @@ -204,11 +240,11 @@ // Data accessors - (NSArray *)currentResult; -- (NSArray *)currentDataResultWithNULLs:(BOOL)includeNULLs; +- (NSArray *)currentDataResultWithNULLs:(BOOL)includeNULLs hideBLOBs:(BOOL)hide; // Task interaction -- (void) startDocumentTaskForTab:(NSNotification *)aNotification; -- (void) endDocumentTaskForTab:(NSNotification *)aNotification; +- (void)startDocumentTaskForTab:(NSNotification *)aNotification; +- (void)endDocumentTaskForTab:(NSNotification *)aNotification; // Additional methods - (void)setConnection:(SPMySQLConnection *)theConnection; @@ -233,23 +269,23 @@ - (void)saveViewCellValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSUInteger)rowIndex; // Retrieving and setting table state -- (NSString *) sortColumnName; -- (BOOL) sortColumnIsAscending; -- (NSUInteger) pageNumber; -- (NSIndexSet *) selectedRowIndexes; -- (NSRect) viewport; -- (CGFloat) tablesListWidth; -- (NSDictionary *) filterSettings; +- (NSString *)sortColumnName; +- (BOOL)sortColumnIsAscending; +- (NSUInteger)pageNumber; +- (NSIndexSet *)selectedRowIndexes; +- (NSRect)viewport; +- (CGFloat)tablesListWidth; +- (NSDictionary *)filterSettings; - (NSArray *)dataColumnDefinitions; -- (void) setSortColumnNameToRestore:(NSString *)theSortColumnName isAscending:(BOOL)isAscending; -- (void) setPageToRestore:(NSUInteger)thePage; -- (void) setSelectedRowIndexesToRestore:(NSIndexSet *)theIndexSet; -- (void) setViewportToRestore:(NSRect)theViewport; -- (void) setFiltersToRestore:(NSDictionary *)filterSettings; -- (void) storeCurrentDetailsForRestoration; -- (void) clearDetailsToRestore; -- (void) setFilterTableData:(NSData*)arcData; -- (NSData*) filterTableData; +- (void)setSortColumnNameToRestore:(NSString *)theSortColumnName isAscending:(BOOL)isAscending; +- (void)setPageToRestore:(NSUInteger)thePage; +- (void)setSelectedRowIndexesToRestore:(NSIndexSet *)theIndexSet; +- (void)setViewportToRestore:(NSRect)theViewport; +- (void)setFiltersToRestore:(NSDictionary *)filterSettings; +- (void)storeCurrentDetailsForRestoration; +- (void)clearDetailsToRestore; +- (void)setFilterTableData:(NSData*)arcData; +- (NSData*)filterTableData; - (NSString *)escapeFilterArgument:(NSString *)argument againstClause:(NSString *)clause; - (void)openContentFilterManager; @@ -260,26 +296,4 @@ - (void)updateFilterTableClause:(id)currentValue; - (NSString*)escapeFilterTableDefaultOperator:(NSString*)anOperator; -#ifdef SP_REFACTOR /* glue */ -@property (assign) id filterButton; -@property (assign) id fieldField; -@property (assign) id compareField; -@property (assign) id betweenTextField; -@property (assign) id firstBetweenField; -@property (assign) id secondBetweenField; -@property (assign) id argumentField; -@property (assign) NSButton* addButton; -@property (assign) NSButton* duplicateButton; -@property (assign) NSButton* removeButton; -@property (assign) NSButton* reloadButton; -@property (assign) NSButton* paginationNextButton; -@property (assign) NSButton* paginationPreviousButton; -@property (assign) NSTextField* paginationPageField; -@property (assign) SPDatabaseDocument* tableDocumentInstance; -@property (assign) SPTablesList* tablesListInstance; -@property (assign) SPCopyTable* tableContentView; -@property (assign) SPTableData* tableDataInstance; -@property (assign) SPTableStructure* tableSourceInstance; -#endif - @end diff --git a/Source/SPTableContent.m b/Source/SPTableContent.m index 9ba7446a..492bb9ae 100644 --- a/Source/SPTableContent.m +++ b/Source/SPTableContent.m @@ -374,7 +374,7 @@ // reload the data in-place to maintain table state if possible. if ([selectedTable isEqualToString:newTableName]) { previousTableRowsCount = tableRowsCount; - + // Store the column widths for later restoration preservedColumnWidths = [NSMutableDictionary dictionaryWithCapacity:[[tableContentView tableColumns] count]]; for (NSTableColumn *eachColumn in [tableContentView tableColumns]) { @@ -538,12 +538,13 @@ ([columnDefinition objectForKey:@"values"]) ? [NSString stringWithFormat:@"(\n- %@\n)", [[columnDefinition objectForKey:@"values"] componentsJoinedByString:@"\n- "]] : @"", ([columnDefinition objectForKey:@"comment"] && [(NSString *)[columnDefinition objectForKey:@"comment"] length]) ? [NSString stringWithFormat:@"\n%@", [[columnDefinition objectForKey:@"comment"] stringByReplacingOccurrencesOfString:@"\\n" withString:@"\n"]] : @"" ]]; - [theCol setEditable:YES]; - + // Copy in the width if present in a reloaded table if ([preservedColumnWidths objectForKey:[columnDefinition objectForKey:@"name"]]) { [theCol setWidth:[[preservedColumnWidths objectForKey:[columnDefinition objectForKey:@"name"]] floatValue]]; } + + [theCol setEditable:YES]; #ifndef SP_REFACTOR // Set up column for filterTable @@ -1541,6 +1542,77 @@ usedQuery = [[NSString alloc] initWithString:query]; } +- (void)sortTableTaskWithColumn:(NSTableColumn *)tableColumn +{ + NSAutoreleasePool *sortPool = [[NSAutoreleasePool alloc] init]; + + // Check whether a save of the current row is required. + if (![[self onMainThread] saveRowOnDeselect]) { + [sortPool drain]; + return; + } + + // Sets column order as tri-state descending, ascending, no sort, descending, ascending etc. order if the same + // header is clicked several times + if (sortCol && [[tableColumn identifier] integerValue] == [sortCol integerValue]) { + if (isDesc) { + [sortCol release]; + sortCol = nil; + } + else { + if (sortCol) [sortCol release]; + + sortCol = [[NSNumber alloc] initWithInteger:[[tableColumn identifier] integerValue]]; + isDesc = !isDesc; + } + } + else { + isDesc = NO; + + [[tableContentView onMainThread] setIndicatorImage:nil inTableColumn:[tableContentView tableColumnWithIdentifier:[NSString stringWithFormat:@"%lld", (long long)[sortCol integerValue]]]]; + + if (sortCol) [sortCol release]; + + sortCol = [[NSNumber alloc] initWithInteger:[[tableColumn identifier] integerValue]]; + } + + if (sortCol) { + // Set the highlight and indicatorImage + [[tableContentView onMainThread] setHighlightedTableColumn:tableColumn]; + + if (isDesc) { + [[tableContentView onMainThread] setIndicatorImage:[NSImage imageNamed:@"NSDescendingSortIndicator"] inTableColumn:tableColumn]; + } + else { + [[tableContentView onMainThread] setIndicatorImage:[NSImage imageNamed:@"NSAscendingSortIndicator"] inTableColumn:tableColumn]; + } + } + else { + // If no sort order deselect column header and + // remove indicator image + [[tableContentView onMainThread] setHighlightedTableColumn:nil]; + [[tableContentView onMainThread] setIndicatorImage:nil inTableColumn:tableColumn]; + } + + // Update data using the new sort order + previousTableRowsCount = tableRowsCount; + + [self loadTableValues]; + + if ([mySQLConnection queryErrored] && ![mySQLConnection lastQueryWasCancelled]) { + SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, + [NSString stringWithFormat:NSLocalizedString(@"Couldn't sort table. MySQL said: %@", @"message of panel when sorting of table failed"), [mySQLConnection lastErrorMessage]]); + + [tableDocumentInstance endTask]; + [sortPool drain]; + + return; + } + + [tableDocumentInstance endTask]; + [sortPool drain]; +} + #pragma mark - #pragma mark Pagination @@ -2224,7 +2296,49 @@ * Returns the current result (as shown in table content view) as array, the first object containing the field * names as array, the following objects containing the rows as array. */ -- (NSArray *)currentDataResultWithNULLs:(BOOL)includeNULLs +- (NSArray *)currentResult +{ + NSInteger i; + NSArray *tableColumns; + NSMutableArray *currentResult = [NSMutableArray array]; + NSMutableArray *tempRow = [NSMutableArray array]; + + // Load the table if not already loaded + if (![tableDocumentInstance contentLoaded]) { + [self loadTable:[tableDocumentInstance table]]; + } + + tableColumns = [tableContentView tableColumns]; + + // Add the field names as the first line + for (NSTableColumn *tableColumn in tableColumns) + { + [tempRow addObject:[[tableColumn headerCell] stringValue]]; + } + + [currentResult addObject:[NSArray arrayWithArray:tempRow]]; + + // Add the rows + for (i = 0 ; i < [self numberOfRowsInTableView:tableContentView]; i++) + { + [tempRow removeAllObjects]; + + for (NSTableColumn *tableColumn in tableColumns) + { + [tempRow addObject:[self tableView:tableContentView objectValueForTableColumn:tableColumn row:i]]; + } + + [currentResult addObject:[NSArray arrayWithArray:tempRow]]; + } + + return currentResult; +} + +/** + * Returns the current result (as shown in table content view) as array, the first object containing the field + * names as array, the following objects containing the rows as array. + */ +- (NSArray *)currentDataResultWithNULLs:(BOOL)includeNULLs hideBLOBs:(BOOL)hide { NSInteger i; NSArray *tableColumns; @@ -2256,7 +2370,7 @@ id o = SPDataStorageObjectAtRowAndColumn(tableValues, i, [[aTableColumn identifier] integerValue]); if ([o isNSNull]) { - [tempRow addObject:(includeNULLs) ? [NSNull null] : [prefs objectForKey:SPNullValue]]; + [tempRow addObject:includeNULLs ? [NSNull null] : [prefs objectForKey:SPNullValue]]; } else if ([o isSPNotLoaded]) { [tempRow addObject:NSLocalizedString(@"(not loaded)", @"value shown for hidden blob and text fields")]; @@ -2269,7 +2383,7 @@ NSImage *image = [v thumbnailImage]; NSString *imageStr = @""; - if(image) { + if (image) { NSString *maxSizeValue = @"WIDTH"; NSInteger imageWidth = [image size].width; NSInteger imageHeight = [image size].height; @@ -2304,10 +2418,10 @@ [[image TIFFRepresentationUsingCompression:NSTIFFCompressionJPEG factor:0.01f] base64Encoding]]]; } else { - [tempRow addObject:@"<BLOB>"]; + [tempRow addObject:hide ? @"<BLOB>" : [o stringRepresentationUsingEncoding:[mySQLConnection stringEncoding]]]; } - if(image) [image release]; + if (image) [image release]; } } @@ -2317,42 +2431,6 @@ return currentResult; } -/** - * Returns the current result (as shown in table content view) as array, the first object containing the field - * names as array, the following objects containing the rows as array. - */ -- (NSArray *)currentResult -{ - NSArray *tableColumns; - NSMutableArray *currentResult = [NSMutableArray array]; - NSMutableArray *tempRow = [NSMutableArray array]; - NSInteger i; - - // Load the table if not already loaded - if ( ![tableDocumentInstance contentLoaded] ) { - [self loadTable:[tableDocumentInstance table]]; - } - - tableColumns = [tableContentView tableColumns]; - - // Add the field names as the first line - for (NSTableColumn *aTableColumn in tableColumns) { - [tempRow addObject:[[aTableColumn headerCell] stringValue]]; - } - [currentResult addObject:[NSArray arrayWithArray:tempRow]]; - - // Add the rows - for ( i = 0 ; i < [self numberOfRowsInTableView:tableContentView] ; i++) { - [tempRow removeAllObjects]; - for (NSTableColumn *aTableColumn in tableColumns) { - [tempRow addObject:[self tableView:tableContentView objectValueForTableColumn:aTableColumn row:i]]; - } - [currentResult addObject:[NSArray arrayWithArray:tempRow]]; - } - - return currentResult; -} - #pragma mark - // Additional methods @@ -3871,741 +3949,6 @@ } [tableContentView setDelegate:self]; } -#ifndef SP_REFACTOR - -#pragma mark - -#pragma mark TableView delegate methods - -/** - * Show the table cell content as tooltip - * - for text displays line breaks and tabs as well - * - if blob data can be interpret as image data display the image as transparent thumbnail - * (up to now using base64 encoded HTML data) - */ -- (NSString *)tableView:(NSTableView *)aTableView toolTipForCell:(id)aCell rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)row mouseLocation:(NSPoint)mouseLocation -{ - if (aTableView == filterTableView) { - return nil; - } - else if (aTableView == tableContentView) { - - if([[aCell stringValue] length] < 2 || [tableDocumentInstance isWorking]) return nil; - - // Suppress tooltip if another toolip is already visible, mainly displayed by a Bundle command - // TODO has to be improved - for(id win in [NSApp orderedWindows]) { - if([[[[win contentView] class] description] isEqualToString:@"WebView"]) { - return nil; - } - } - - NSImage *image; - - NSPoint pos = [NSEvent mouseLocation]; - pos.y -= 20; - - 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(&tableValuesLock); - if (row < (NSInteger)tableRowsCount && [[aTableColumn identifier] integerValue] < (NSInteger)[tableValues columnCount]) { - theValue = [[SPDataStorageObjectAtRowAndColumn(tableValues, row, [[aTableColumn identifier] integerValue]) copy] autorelease]; - } - pthread_mutex_unlock(&tableValuesLock); - - if (!theValue) theValue = @"..."; - } else { - theValue = SPDataStorageObjectAtRowAndColumn(tableValues, row, [[aTableColumn identifier] integerValue]); - } - - if(theValue == nil) return nil; - - if ([theValue isKindOfClass:[NSData class]]) { - image = [[[NSImage alloc] initWithData:theValue] autorelease]; - if(image) { - [SPTooltip showWithObject:image atLocation:pos ofType:@"image"]; - return nil; - } - } - else if ([theValue isKindOfClass:[SPMySQLGeometryData class]]) { - SPGeometryDataView *v = [[SPGeometryDataView alloc] initWithCoordinates:[theValue coordinates]]; - image = [v thumbnailImage]; - if(image) { - [SPTooltip showWithObject:image atLocation:pos ofType:@"image"]; - [v release]; - return nil; - } - [v release]; - } - - // Show the cell string value as tooltip (including line breaks and tabs) - // by using the cell's font - [SPTooltip showWithObject:[aCell stringValue] - atLocation:pos - ofType:@"text" - displayOptions:[NSDictionary dictionaryWithObjectsAndKeys: - [[aCell font] familyName], @"fontname", - [NSString stringWithFormat:@"%f",[[aCell font] pointSize]], @"fontsize", - nil]]; - - return nil; - } - - return nil; -} -#endif - -- (NSInteger)numberOfRowsInTableView:(SPCopyTable *)aTableView -{ -#ifndef SP_REFACTOR - if (aTableView == filterTableView) { - if (filterTableIsSwapped) - return [filterTableData count]; - else - return [[[filterTableData objectForKey:[NSNumber numberWithInteger:0]] objectForKey:@"filter"] count]; - } - else -#endif - if (aTableView == tableContentView) { - return tableRowsCount; - } - - return 0; -} - -- (id)tableView:(SPCopyTable *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex -{ -#ifndef SP_REFACTOR - if (aTableView == filterTableView) { - if (filterTableIsSwapped) - // First column shows the field names - if([[aTableColumn identifier] integerValue] == 0) { - NSTableHeaderCell *c = [[[NSTableHeaderCell alloc] initTextCell:[[filterTableData objectForKey:[NSNumber numberWithInteger:rowIndex]] objectForKey:@"name"]] autorelease]; - return c; - } else - return NSArrayObjectAtIndex([[filterTableData objectForKey:[NSNumber numberWithInteger:rowIndex]] objectForKey:@"filter"], [[aTableColumn identifier] integerValue]-1); - else { - return NSArrayObjectAtIndex([[filterTableData objectForKey:[aTableColumn identifier]] objectForKey:@"filter"], rowIndex); - } - } - else -#endif - if (aTableView == tableContentView) { - - 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(&tableValuesLock); - if (rowIndex < (NSInteger)tableRowsCount && columnIndex < [tableValues columnCount]) { - theValue = [[SPDataStorageObjectAtRowAndColumn(tableValues, rowIndex, columnIndex) copy] autorelease]; - } - pthread_mutex_unlock(&tableValuesLock); - - if (!theValue) return @"..."; - } else { - theValue = SPDataStorageObjectAtRowAndColumn(tableValues, rowIndex, columnIndex); - } - - if([theValue isKindOfClass:[SPMySQLGeometryData class]]) - return [theValue wktString]; - - if ([theValue isNSNull]) - return [prefs objectForKey:SPNullValue]; - - if ([theValue isKindOfClass:[NSData class]]) - return [theValue shortStringRepresentationUsingEncoding:[mySQLConnection stringEncoding]]; - - if ([theValue isSPNotLoaded]) - return NSLocalizedString(@"(not loaded)", @"value shown for hidden blob and text fields"); - - return theValue; - } - - return nil; -} - -/** - * This function changes the text color of text/blob fields which are null or not yet loaded to gray - */ -- (void)tableView:(SPCopyTable *)aTableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn*)aTableColumn row:(NSInteger)rowIndex -{ -#ifndef SP_REFACTOR - if(aTableView == filterTableView) { - if(filterTableIsSwapped && [[aTableColumn identifier] integerValue] == 0) { - [cell setDrawsBackground:YES]; - [cell setBackgroundColor:lightGrayColor]; - } else { - [cell setDrawsBackground:NO]; - } - return; - } - else -#endif - if(aTableView == tableContentView) { - - if (![cell respondsToSelector:@selector(setTextColor:)]) return; - - 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 indicate loading in these cases. - if (isWorking) { - pthread_mutex_lock(&tableValuesLock); - if (rowIndex < (NSInteger)tableRowsCount && columnIndex < [tableValues columnCount]) { - theValue = SPDataStorageObjectAtRowAndColumn(tableValues, rowIndex, columnIndex); - } - pthread_mutex_unlock(&tableValuesLock); - - if (!theValue) { - [cell setTextColor:[NSColor lightGrayColor]]; - return; - } - } else { - theValue = SPDataStorageObjectAtRowAndColumn(tableValues, rowIndex, columnIndex); - } - - // If user wants to edit 'cell' set text color to black and return to avoid - // writing in gray if value was NULL - if ([aTableView editedColumn] != -1 - && [aTableView editedRow] == rowIndex - && (NSUInteger)[[NSArrayObjectAtIndex([aTableView tableColumns], [aTableView editedColumn]) identifier] integerValue] == columnIndex) { - [cell setTextColor:blackColor]; - return; - } - - // For null cells and not loaded cells, display the contents in gray. - if ([theValue isNSNull] || [theValue isSPNotLoaded]) { - [cell setTextColor:lightGrayColor]; - - // Otherwise, set the color to black - required as NSTableView reuses NSCells. - } else { - [cell setTextColor:blackColor]; - } - } -} - -- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex -{ -#ifndef SP_REFACTOR - if(aTableView == filterTableView) { - if(filterTableIsSwapped) - [[[filterTableData objectForKey:[NSNumber numberWithInteger:rowIndex]] objectForKey:@"filter"] replaceObjectAtIndex:([[aTableColumn identifier] integerValue]-1) withObject:(NSString*)anObject]; - else - [[[filterTableData objectForKey:[aTableColumn identifier]] objectForKey:@"filter"] replaceObjectAtIndex:rowIndex withObject:(NSString*)anObject]; - [self updateFilterTableClause:nil]; - return; - } - else -#endif - if(aTableView == tableContentView) { - - // If the current cell should have been edited in a sheet, do nothing - field closing will have already - // updated the field. - if ([tableContentView shouldUseFieldEditorForRow:rowIndex column:[[aTableColumn identifier] integerValue]]) { - return; - } - - // If table data comes from a view, save back to the view - if([tablesListInstance tableType] == SPTableTypeView) { - [self saveViewCellValue:anObject forTableColumn:aTableColumn row:rowIndex]; - return; - } - - // Catch editing events in the row and if the row isn't currently being edited, - // start an edit. This allows edits including enum changes to save correctly. - if ( isEditingRow && [tableContentView selectedRow] != currentlyEditingRow ) - [self saveRowOnDeselect]; - if ( !isEditingRow ) { - [oldRow setArray:[tableValues rowContentsAtIndex:rowIndex]]; - isEditingRow = YES; - currentlyEditingRow = rowIndex; - } - - NSDictionary *column = NSArrayObjectAtIndex(dataColumns, [[aTableColumn identifier] integerValue]); - - if (anObject) { - - // Restore NULLs if necessary - if ([anObject isEqualToString:[prefs objectForKey:SPNullValue]] && [[column objectForKey:@"null"] boolValue]) - anObject = [NSNull null]; - - [tableValues replaceObjectInRow:rowIndex column:[[aTableColumn identifier] integerValue] withObject:anObject]; - } else { - [tableValues replaceObjectInRow:rowIndex column:[[aTableColumn identifier] integerValue] withObject:@""]; - } - } -} - -#pragma mark - -#pragma mark TableView delegate methods - -/** - * Sorts the tableView by the clicked column. - * If clicked twice, order is altered to descending. - * Performs the task in a new thread if necessary. - */ -- (void)tableView:(NSTableView*)tableView didClickTableColumn:(NSTableColumn *)tableColumn -{ - - if ( [selectedTable isEqualToString:@""] || !selectedTable || tableView != tableContentView ) - return; - - // Prevent sorting while the table is still loading - if ([tableDocumentInstance isWorking]) return; - - // Start the task - [tableDocumentInstance startTaskWithDescription:NSLocalizedString(@"Sorting table...", @"Sorting table task description")]; - if ([NSThread isMainThread]) { - [NSThread detachNewThreadSelector:@selector(sortTableTaskWithColumn:) toTarget:self withObject:tableColumn]; - } else { - [self sortTableTaskWithColumn:tableColumn]; - } -} - -- (void)sortTableTaskWithColumn:(NSTableColumn *)tableColumn -{ - NSAutoreleasePool *sortPool = [[NSAutoreleasePool alloc] init]; - - // Check whether a save of the current row is required. - if (![[self onMainThread] saveRowOnDeselect]) { - [sortPool drain]; - return; - } - - // Sets column order as tri-state descending, ascending, no sort, descending, ascending etc. order if the same - // header is clicked several times - if (sortCol && [[tableColumn identifier] integerValue] == [sortCol integerValue]) { - if(isDesc) { - [sortCol release]; - sortCol = nil; - } else { - if (sortCol) [sortCol release]; - sortCol = [[NSNumber alloc] initWithInteger:[[tableColumn identifier] integerValue]]; - isDesc = !isDesc; - } - } else { - isDesc = NO; - [[tableContentView onMainThread] setIndicatorImage:nil inTableColumn:[tableContentView tableColumnWithIdentifier:[NSString stringWithFormat:@"%lld", (long long)[sortCol integerValue]]]]; - if (sortCol) [sortCol release]; - sortCol = [[NSNumber alloc] initWithInteger:[[tableColumn identifier] integerValue]]; - } - - if (sortCol) { - // Set the highlight and indicatorImage - [[tableContentView onMainThread] setHighlightedTableColumn:tableColumn]; - if (isDesc) { - [[tableContentView onMainThread] setIndicatorImage:[NSImage imageNamed:@"NSDescendingSortIndicator"] inTableColumn:tableColumn]; - } else { - [[tableContentView onMainThread] setIndicatorImage:[NSImage imageNamed:@"NSAscendingSortIndicator"] inTableColumn:tableColumn]; - } - } else { - // If no sort order deselect column header and - // remove indicator image - [[tableContentView onMainThread] setHighlightedTableColumn:nil]; - [[tableContentView onMainThread] setIndicatorImage:nil inTableColumn:tableColumn]; - } - - // Update data using the new sort order - previousTableRowsCount = tableRowsCount; - [self loadTableValues]; - - if ([mySQLConnection queryErrored] && ![mySQLConnection lastQueryWasCancelled]) { - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"Couldn't sort table. MySQL said: %@", @"message of panel when sorting of table failed"), [mySQLConnection lastErrorMessage]]); - [tableDocumentInstance endTask]; - [sortPool drain]; - return; - } - - [tableDocumentInstance endTask]; - [sortPool drain]; -} - -- (void)tableViewSelectionDidChange:(NSNotification *)aNotification -{ - - // Check our notification object is our table content view - if ([aNotification object] != tableContentView) return; - - isFirstChangeInView = YES; - - [addButton setEnabled:([tablesListInstance tableType] == SPTableTypeTable)]; - - // If we are editing a row, attempt to save that row - if saving failed, reselect the edit row. - if (isEditingRow && [tableContentView selectedRow] != currentlyEditingRow && ![self saveRowOnDeselect]) return; - - if (![tableDocumentInstance isWorking]) { - // Update the row selection count - // and update the status of the delete/duplicate buttons - if([tablesListInstance tableType] == SPTableTypeTable) { - if ([tableContentView numberOfSelectedRows] > 0) { - [duplicateButton setEnabled:([tableContentView numberOfSelectedRows] == 1)]; - [removeButton setEnabled:YES]; - } - else { - [duplicateButton setEnabled:NO]; - [removeButton setEnabled:NO]; - } - } else { - [duplicateButton setEnabled:NO]; - [removeButton setEnabled:NO]; - } - } - - [self updateCountText]; - -#ifndef SP_REFACTOR /* triggered commands */ - NSArray *triggeredCommands = [[NSApp delegate] bundleCommandsForTrigger:SPBundleTriggerActionTableRowChanged]; - for(NSString* cmdPath in triggeredCommands) { - NSArray *data = [cmdPath componentsSeparatedByString:@"|"]; - NSMenuItem *aMenuItem = [[[NSMenuItem alloc] init] autorelease]; - [aMenuItem setTag:0]; - [aMenuItem setToolTip:[data objectAtIndex:0]]; - - // For HTML output check if corresponding window already exists - BOOL stopTrigger = NO; - if([(NSString *)[data objectAtIndex:2] length]) { - BOOL correspondingWindowFound = NO; - NSString *uuid = [data objectAtIndex:2]; - for(id win in [NSApp windows]) { - if([[[[win delegate] class] description] isEqualToString:@"SPBundleHTMLOutputController"]) { - if([[[win delegate] windowUUID] isEqualToString:uuid]) { - correspondingWindowFound = YES; - break; - } - } - } - if(!correspondingWindowFound) stopTrigger = YES; - } - if(!stopTrigger) { - if([[data objectAtIndex:1] isEqualToString:SPBundleScopeGeneral]) { - [[[NSApp delegate] onMainThread] executeBundleItemForApp:aMenuItem]; - } - else if([[data objectAtIndex:1] isEqualToString:SPBundleScopeDataTable]) { - if([[[[[NSApp mainWindow] firstResponder] class] description] isEqualToString:@"SPCopyTable"]) - [[[[NSApp mainWindow] firstResponder] onMainThread] executeBundleItemForDataTable:aMenuItem]; - } - else if([[data objectAtIndex:1] isEqualToString:SPBundleScopeInputField]) { - if([[[NSApp mainWindow] firstResponder] isKindOfClass:[NSTextView class]]) - [[[[NSApp mainWindow] firstResponder] onMainThread] executeBundleItemForInputField:aMenuItem]; - } - } - } -#endif -} - -/** - saves the new column size in the preferences - */ -- (void)tableViewColumnDidResize:(NSNotification *)aNotification -{ - - // Check our notification object is our table content view - if ([aNotification object] != tableContentView) return; - - // sometimes the column has no identifier. I can't figure out what is causing it, so we just skip over this item - if (![[[aNotification userInfo] objectForKey:@"NSTableColumn"] identifier]) - return; - - NSMutableDictionary *tableColumnWidths; - NSString *database = [NSString stringWithFormat:@"%@@%@", [tableDocumentInstance database], [tableDocumentInstance host]]; - NSString *table = [tablesListInstance tableName]; - - // get tableColumnWidths object -#ifndef SP_REFACTOR - if ( [prefs objectForKey:SPTableColumnWidths] != nil ) { - tableColumnWidths = [NSMutableDictionary dictionaryWithDictionary:[prefs objectForKey:SPTableColumnWidths]]; - } else { -#endif - tableColumnWidths = [NSMutableDictionary dictionary]; -#ifndef SP_REFACTOR - } -#endif - // get database object - if ( [tableColumnWidths objectForKey:database] == nil ) { - [tableColumnWidths setObject:[NSMutableDictionary dictionary] forKey:database]; - } else { - [tableColumnWidths setObject:[NSMutableDictionary dictionaryWithDictionary:[tableColumnWidths objectForKey:database]] forKey:database]; - - } - // get table object - if ( [[tableColumnWidths objectForKey:database] objectForKey:table] == nil ) { - [[tableColumnWidths objectForKey:database] setObject:[NSMutableDictionary dictionary] forKey:table]; - } else { - [[tableColumnWidths objectForKey:database] setObject:[NSMutableDictionary dictionaryWithDictionary:[[tableColumnWidths objectForKey:database] objectForKey:table]] forKey:table]; - - } - // save column size - [[[tableColumnWidths objectForKey:database] objectForKey:table] setObject:[NSNumber numberWithDouble:[(NSTableColumn *)[[aNotification userInfo] objectForKey:@"NSTableColumn"] width]] forKey:[[[[aNotification userInfo] objectForKey:@"NSTableColumn"] headerCell] stringValue]]; -#ifndef SP_REFACTOR - [prefs setObject:tableColumnWidths forKey:SPTableColumnWidths]; -#endif -} - -/** - * Confirm whether to allow editing of a row. Returns YES by default, unless the multipleLineEditingButton is in - * the ON state, or for blob or text fields - in those cases opens a sheet for editing instead and returns NO. - */ -- (BOOL)tableView:(NSTableView *)aTableView shouldEditTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex -{ - if ([tableDocumentInstance isWorking]) return NO; - -#ifndef SP_REFACTOR - if(aTableView == filterTableView) { - if(filterTableIsSwapped && [[aTableColumn identifier] integerValue] == 0) - return NO; - else - return YES; - } - else -#endif - if ( aTableView == tableContentView ) { - - // Ensure that row is editable since it could contain "(not loaded)" columns together with - // issue that the table has no primary key - NSString *wherePart = [NSString stringWithString:[self argumentForRow:[tableContentView selectedRow]]]; - if ([wherePart length] == 0) return NO; - - // If the selected cell hasn't been loaded, load it. - if ([[tableValues cellDataAtRow:rowIndex column:[[aTableColumn identifier] integerValue]] isSPNotLoaded]) { - - // Only get the data for the selected column, not all of them - NSString *query = [NSString stringWithFormat:@"SELECT %@ FROM %@ WHERE %@", [[[aTableColumn headerCell] stringValue] backtickQuotedString], [selectedTable backtickQuotedString], wherePart]; - - SPMySQLResult *tempResult = [mySQLConnection queryString:query]; - if (![tempResult numberOfRows]) { - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - NSLocalizedString(@"Couldn't load the row. Reload the table to be sure that the row exists and use a primary key for your table.", @"message of panel when loading of row failed")); - return NO; - } - - NSArray *tempRow = [tempResult getRowAsArray]; - [tableValues replaceObjectInRow:rowIndex column:[[tableContentView tableColumns] indexOfObject:aTableColumn] withObject:[tempRow objectAtIndex:0]]; - [tableContentView reloadData]; - } - - // Open the editing sheet if required - if ([tableContentView shouldUseFieldEditorForRow:rowIndex column:[[aTableColumn identifier] integerValue]]) - { - - // Retrieve the column definition - NSDictionary *columnDefinition = [cqColumnDefinition objectAtIndex:[[aTableColumn identifier] integerValue]]; - BOOL isBlob = [tableDataInstance columnIsBlobOrText:[[aTableColumn headerCell] stringValue]]; - - // A table is per definition editable - BOOL isFieldEditable = YES; - - // Check for Views if field is editable - if([tablesListInstance tableType] == SPTableTypeView) { - NSArray *editStatus = [self fieldEditStatusForRow:rowIndex andColumn:[[aTableColumn identifier] integerValue]]; - isFieldEditable = ([[editStatus objectAtIndex:0] integerValue] == 1) ? YES : NO; - } - - NSString *fieldType = nil; - NSUInteger fieldLength = 0; - NSString *fieldEncoding = nil; - BOOL allowNULL = YES; - - fieldType = [columnDefinition objectForKey:@"type"]; - if([columnDefinition objectForKey:@"char_length"]) - fieldLength = [[columnDefinition objectForKey:@"char_length"] integerValue]; - if([columnDefinition objectForKey:@"null"]) - allowNULL = (![[columnDefinition objectForKey:@"null"] integerValue]); - if([columnDefinition objectForKey:@"charset_name"] && ![[columnDefinition objectForKey:@"charset_name"] isEqualToString:@"binary"]) - fieldEncoding = [columnDefinition objectForKey:@"charset_name"]; - - if(fieldEditor) [fieldEditor release], fieldEditor = nil; - fieldEditor = [[SPFieldEditorController alloc] init]; - [fieldEditor setEditedFieldInfo:[NSDictionary dictionaryWithObjectsAndKeys: - [[aTableColumn headerCell] stringValue], @"colName", - [self usedQuery], @"usedQuery", - @"content", @"tableSource", - nil]]; - [fieldEditor setTextMaxLength:fieldLength]; - [fieldEditor setFieldType:(fieldType==nil) ? @"" : fieldType]; - [fieldEditor setFieldEncoding:(fieldEncoding==nil) ? @"" : fieldEncoding]; - [fieldEditor setAllowNULL:allowNULL]; - - id cellValue = [tableValues cellDataAtRow:rowIndex column:[[aTableColumn identifier] integerValue]]; - if ([cellValue isNSNull]) - cellValue = [NSString stringWithString:[prefs objectForKey:SPNullValue]]; - - NSInteger editedColumn = 0; - for(NSTableColumn* col in [tableContentView tableColumns]) { - if([[col identifier] isEqualToString:[aTableColumn identifier]]) break; - editedColumn++; - } - - [fieldEditor editWithObject:cellValue - fieldName:[[aTableColumn headerCell] stringValue] - usingEncoding:[mySQLConnection stringEncoding] - isObjectBlob:isBlob - isEditable:isFieldEditable - withWindow:[tableDocumentInstance parentWindow] - sender:self - contextInfo:[NSDictionary dictionaryWithObjectsAndKeys: - [NSNumber numberWithInteger:rowIndex], @"rowIndex", - [NSNumber numberWithInteger:editedColumn], @"columnIndex", - [NSNumber numberWithBool:isFieldEditable], @"isFieldEditable", - nil]]; - - return NO; - } - - return YES; - } - - return YES; -} - -/** - * Enable drag from tableview - */ -- (BOOL)tableView:(NSTableView *)aTableView writeRowsWithIndexes:(NSIndexSet *)rows toPasteboard:(NSPasteboard*)pboard -{ - if (aTableView == tableContentView) { - NSString *tmp; - - // By holding ⌘, ⇧, or/and ⌥ copies selected rows as SQL INSERTS - // otherwise \t delimited lines - if([[NSApp currentEvent] modifierFlags] & (NSCommandKeyMask|NSShiftKeyMask|NSAlternateKeyMask)) - tmp = [tableContentView rowsAsSqlInsertsOnlySelectedRows:YES]; - else - tmp = [tableContentView draggedRowsAsTabString]; - - if ( nil != tmp && [tmp length] ) - { - [pboard declareTypes:[NSArray arrayWithObjects: NSTabularTextPboardType, - NSStringPboardType, nil] - owner:nil]; - - [pboard setString:tmp forType:NSStringPboardType]; - [pboard setString:tmp forType:NSTabularTextPboardType]; - return YES; - } - } - - return NO; -} - -/** - * Disable row selection while the document is working. - */ -- (BOOL)tableView:(NSTableView *)aTableView shouldSelectRow:(NSInteger)rowIndex -{ -#ifndef SP_REFACTOR - - if(aTableView == filterTableView) - return YES; - else -#endif - if(aTableView == tableContentView) - return tableRowsSelectable; - else - return YES; - -} - -/** - * Resize a column when it's double-clicked. (10.6+) - */ -- (CGFloat)tableView:(NSTableView *)tableView sizeToFitWidthOfColumn:(NSInteger)columnIndex -{ - - NSTableColumn *theColumn = [[tableView tableColumns] objectAtIndex:columnIndex]; - NSDictionary *columnDefinition = [dataColumns objectAtIndex:[[theColumn identifier] integerValue]]; - - // Get the column width - NSUInteger targetWidth = [tableContentView autodetectWidthForColumnDefinition:columnDefinition maxRows:500]; - -#ifndef SP_REFACTOR - // Clear any saved widths for the column - NSString *dbKey = [NSString stringWithFormat:@"%@@%@", [tableDocumentInstance database], [tableDocumentInstance host]]; - NSString *tableKey = [tablesListInstance tableName]; - NSMutableDictionary *savedWidths = [NSMutableDictionary dictionaryWithDictionary:[prefs objectForKey:SPTableColumnWidths]]; - NSMutableDictionary *dbDict = [NSMutableDictionary dictionaryWithDictionary:[savedWidths objectForKey:dbKey]]; - NSMutableDictionary *tableDict = [NSMutableDictionary dictionaryWithDictionary:[dbDict objectForKey:tableKey]]; - if ([tableDict objectForKey:[columnDefinition objectForKey:@"name"]]) { - [tableDict removeObjectForKey:[columnDefinition objectForKey:@"name"]]; - if ([tableDict count]) { - [dbDict setObject:[NSDictionary dictionaryWithDictionary:tableDict] forKey:tableKey]; - } else { - [dbDict removeObjectForKey:tableKey]; - } - if ([dbDict count]) { - [savedWidths setObject:[NSDictionary dictionaryWithDictionary:dbDict] forKey:dbKey]; - } else { - [savedWidths removeObjectForKey:dbKey]; - } - [prefs setObject:[NSDictionary dictionaryWithDictionary:savedWidths] forKey:SPTableColumnWidths]; - } -#endif - - // Return the width, while the delegate is empty to prevent column resize notifications - [tableContentView setDelegate:nil]; - [tableContentView performSelector:@selector(setDelegate:) withObject:self afterDelay:0.1]; - return targetWidth; -} - -#ifndef SP_REFACTOR /* SplitView delegate methods */ -#pragma mark - -#pragma mark SplitView delegate methods - -- (BOOL)splitView:(NSSplitView *)sender canCollapseSubview:(NSView *)subview -{ - return NO; -} - -// Set a minimum size for the filter text area -- (CGFloat)splitView:(NSSplitView *)sender constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)offset -{ - return (proposedMax - 180); -} - -// Set a minimum size for the field list and action area -- (CGFloat)splitView:(NSSplitView *)sender constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)offset -{ - return (proposedMin + 200); -} - -// Improve default resizing and resize only the filter text area by default -- (void)splitView:(NSSplitView *)sender resizeSubviewsWithOldSize:(NSSize)oldSize -{ - NSSize newSize = [sender frame].size; - NSView *leftView = [[sender subviews] objectAtIndex:0]; - NSView *rightView = [[sender subviews] objectAtIndex:1]; - float dividerThickness = [sender dividerThickness]; - NSRect leftFrame = [leftView frame]; - NSRect rightFrame = [rightView frame]; - - // Resize height of both views - leftFrame.size.height = newSize.height; - rightFrame.size.height = newSize.height; - - // Only resize the right view's width - unless the constraint has been reached - if (rightFrame.size.width > 180 || newSize.width > oldSize.width) { - rightFrame.size.width = newSize.width - leftFrame.size.width - dividerThickness; - } else { - leftFrame.size.width = newSize.width - rightFrame.size.width - dividerThickness; - } - rightFrame.origin.x = leftFrame.size.width + dividerThickness; - - [leftView setFrame:leftFrame]; - [rightView setFrame:rightFrame]; -} -#endif - #pragma mark - #pragma mark Task interaction @@ -4669,124 +4012,6 @@ #pragma mark - #pragma mark Other methods -- (void)controlTextDidChange:(NSNotification *)notification -{ -#ifndef SP_REFACTOR - if ([notification object] == filterTableView) { - - NSString *str = [[[[notification userInfo] objectForKey:@"NSFieldEditor"] textStorage] string]; - if(str && [str length]) { - if(lastEditedFilterTableValue) [lastEditedFilterTableValue release]; - lastEditedFilterTableValue = [[NSString stringWithString:str] retain]; - } - [self updateFilterTableClause:str]; - - } -#endif -} -/** - * 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 != tableContentView) return YES; - - NSUInteger row, column; - BOOL shouldBeginEditing = YES; - - row = [tableContentView editedRow]; - column = [tableContentView editedColumn]; - - // If cell editing mode and editing request comes - // from the keyboard show an error tooltip - // or bypass if numberOfPossibleUpdateRows == 1 - if([tableContentView isCellEditingMode]) { - NSArray *editStatus = [self fieldEditStatusForRow:row andColumn:[[NSArrayObjectAtIndex([tableContentView tableColumns], column) identifier] integerValue]]; - NSInteger numberOfPossibleUpdateRows = [[editStatus objectAtIndex:0] integerValue]; - NSPoint pos = [[tableDocumentInstance parentWindow] convertBaseToScreen:[tableContentView convertPoint:[tableContentView frameOfCellAtColumn:column row:row].origin toView:nil]]; - pos.y -= 20; - switch(numberOfPossibleUpdateRows) { - case -1: - [SPTooltip showWithObject:kCellEditorErrorNoMultiTabDb - atLocation:pos - ofType:@"text"]; - shouldBeginEditing = NO; - break; - case 0: - [SPTooltip showWithObject:[NSString stringWithFormat:kCellEditorErrorNoMatch, selectedTable] - atLocation:pos - ofType:@"text"]; - shouldBeginEditing = NO; - break; - - case 1: - shouldBeginEditing = YES; - break; - - default: - [SPTooltip showWithObject:[NSString stringWithFormat:kCellEditorErrorTooManyMatches, (long)numberOfPossibleUpdateRows, (numberOfPossibleUpdateRows>1)?NSLocalizedString(@"es", @"Plural suffix for row count, eg 4 match*es*"):@""] - atLocation:pos - ofType:@"text"]; - shouldBeginEditing = NO; - } - - } - - // Open the field editor sheet if required - if ([tableContentView shouldUseFieldEditorForRow:row column:column]) - { - [tableContentView setFieldEditorSelectedRange:[aFieldEditor selectedRange]]; - - // Cancel editing - [control abortEditing]; - - // Call the field editor sheet - [self tableView:tableContentView shouldEditTableColumn:NSArrayObjectAtIndex([tableContentView tableColumns], column) row:row]; - - // send current event to field editor sheet - if([NSApp currentEvent]) - [NSApp sendEvent:[NSApp currentEvent]]; - - return NO; - - } - - return shouldBeginEditing; - -} - -/** - * Trap the enter, escape, tab and arrow keys, overriding default behaviour and continuing/ending editing, - * only within the current row. - */ -- (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)command -{ - - // Check firstly if SPCopyTable can handle command -#ifndef SP_REFACTOR - if([control control:control textView:textView doCommandBySelector:(SEL)command]) -#else - if([(id<NSControlTextEditingDelegate>)control control:control textView:textView doCommandBySelector:(SEL)command]) -#endif - return YES; - - // Trap the escape key - if ( [[control window] methodForSelector:command] == [[control window] methodForSelector:@selector(cancelOperation:)] ) - { - // Abort editing - [control abortEditing]; - if(control == tableContentView) - [self cancelRowEditing]; - return TRUE; - } - - return FALSE; - -} - /** * This method is called as part of Key Value Observing which is used to watch for prefernce changes which effect the interface. */ diff --git a/Source/SPTableContentDataSource.h b/Source/SPTableContentDataSource.h new file mode 100644 index 00000000..7276f736 --- /dev/null +++ b/Source/SPTableContentDataSource.h @@ -0,0 +1,37 @@ +// +// $Id$ +// +// SPTableContentDataSource.h +// Sequel Pro +// +// Created by Stuart Connolly (stuconnolly.com) on March 20, 2012 +// Copyright (c) 2012 Stuart Connolly. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + +#import "SPTableContent.h" + +@interface SPTableContent (SPTableContentDataSource) + +@end diff --git a/Source/SPTableContentDataSource.m b/Source/SPTableContentDataSource.m new file mode 100644 index 00000000..4289ade4 --- /dev/null +++ b/Source/SPTableContentDataSource.m @@ -0,0 +1,183 @@ +// +// $Id$ +// +// SPTableContentDataSource.m +// Sequel Pro +// +// Created by Stuart Connolly (stuconnolly.com) on March 20, 2012 +// Copyright (c) 2012 Stuart Connolly. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + +#import "SPTableContentDataSource.h" +#import "SPMySQLGeometryData.h" +#import "SPDataStorage.h" +#import "SPMySQL.h" +#import "SPCopyTable.h" +#import "SPTablesList.h" + +#import <pthread.h> + +@implementation SPTableContent (SPTableContentDataSource) + +#pragma mark - +#pragma mark TableView datasource methods + +- (NSInteger)numberOfRowsInTableView:(SPCopyTable *)tableView +{ +#ifndef SP_REFACTOR + if (tableView == filterTableView) { + return filterTableIsSwapped ? [filterTableData count] : [[[filterTableData objectForKey:[NSNumber numberWithInteger:0]] objectForKey:@"filter"] count]; + } + else +#endif + if (tableView == tableContentView) { + return tableRowsCount; + } + + return 0; +} + +- (id)tableView:(SPCopyTable *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex +{ +#ifndef SP_REFACTOR + if (tableView == filterTableView) { + if (filterTableIsSwapped) + + // First column shows the field names + if ([[tableColumn identifier] integerValue] == 0) { + return [[[NSTableHeaderCell alloc] initTextCell:[[filterTableData objectForKey:[NSNumber numberWithInteger:rowIndex]] objectForKey:@"name"]] autorelease]; + } + else { + return NSArrayObjectAtIndex([[filterTableData objectForKey:[NSNumber numberWithInteger:rowIndex]] objectForKey:@"filter"], [[tableColumn identifier] integerValue] - 1); + } + else { + return NSArrayObjectAtIndex([[filterTableData objectForKey:[tableColumn identifier]] objectForKey:@"filter"], rowIndex); + } + } + else +#endif + if (tableView == tableContentView) { + + id value = nil; + NSUInteger columnIndex = [[tableColumn identifier] integerValue]; + + // 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(&tableValuesLock); + + if (rowIndex < (NSInteger)tableRowsCount && columnIndex < [tableValues columnCount]) { + value = [[SPDataStorageObjectAtRowAndColumn(tableValues, rowIndex, columnIndex) copy] autorelease]; + } + + pthread_mutex_unlock(&tableValuesLock); + + if (!value) return @"..."; + } + else { + value = SPDataStorageObjectAtRowAndColumn(tableValues, rowIndex, columnIndex); + } + + if ([value isKindOfClass:[SPMySQLGeometryData class]]) + return [value wktString]; + + if ([value isNSNull]) + return [prefs objectForKey:SPNullValue]; + + if ([value isKindOfClass:[NSData class]]) + return [value shortStringRepresentationUsingEncoding:[mySQLConnection stringEncoding]]; + + if ([value isSPNotLoaded]) + return NSLocalizedString(@"(not loaded)", @"value shown for hidden blob and text fields"); + + return value; + } + + return nil; +} + +- (void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex +{ +#ifndef SP_REFACTOR + if(tableView == filterTableView) { + if (filterTableIsSwapped) { + [[[filterTableData objectForKey:[NSNumber numberWithInteger:rowIndex]] objectForKey:@"filter"] replaceObjectAtIndex:([[tableColumn identifier] integerValue] - 1) withObject:(NSString *)object]; + } + else { + [[[filterTableData objectForKey:[tableColumn identifier]] objectForKey:@"filter"] replaceObjectAtIndex:rowIndex withObject:(NSString *)object]; + } + + [self updateFilterTableClause:nil]; + + return; + } + else +#endif + if (tableView == tableContentView) { + + // If the current cell should have been edited in a sheet, do nothing - field closing will have already + // updated the field. + if ([tableContentView shouldUseFieldEditorForRow:rowIndex column:[[tableColumn identifier] integerValue]]) { + return; + } + + // If table data comes from a view, save back to the view + if ([tablesListInstance tableType] == SPTableTypeView) { + [self saveViewCellValue:object forTableColumn:tableColumn row:rowIndex]; + return; + } + + // Catch editing events in the row and if the row isn't currently being edited, + // start an edit. This allows edits including enum changes to save correctly. + if (isEditingRow && [tableContentView selectedRow] != currentlyEditingRow) { + [self saveRowOnDeselect]; + } + + if (!isEditingRow) { + [oldRow setArray:[tableValues rowContentsAtIndex:rowIndex]]; + + isEditingRow = YES; + currentlyEditingRow = rowIndex; + } + + NSDictionary *column = NSArrayObjectAtIndex(dataColumns, [[tableColumn identifier] integerValue]); + + if (object) { + // Restore NULLs if necessary + if ([object isEqualToString:[prefs objectForKey:SPNullValue]] && [[column objectForKey:@"null"] boolValue]) { + object = [NSNull null]; + } + + [tableValues replaceObjectInRow:rowIndex column:[[tableColumn identifier] integerValue] withObject:object]; + } + else { + [tableValues replaceObjectInRow:rowIndex column:[[tableColumn identifier] integerValue] withObject:@""]; + } + } +} + +@end diff --git a/Source/SPTableContentDelegate.h b/Source/SPTableContentDelegate.h new file mode 100644 index 00000000..25a19a50 --- /dev/null +++ b/Source/SPTableContentDelegate.h @@ -0,0 +1,37 @@ +// +// $Id$ +// +// SPTableContentDelegate.h +// Sequel Pro +// +// Created by Stuart Connolly (stuconnolly.com) on March 20, 2012 +// Copyright (c) 2012 Stuart Connolly. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + +#import "SPTableContent.h" + +@interface SPTableContent (SPTableContentDelegate) + +@end diff --git a/Source/SPTableContentDelegate.m b/Source/SPTableContentDelegate.m new file mode 100644 index 00000000..96c7bbec --- /dev/null +++ b/Source/SPTableContentDelegate.m @@ -0,0 +1,778 @@ +// +// $Id$ +// +// SPTableContentDelegate.m +// Sequel Pro +// +// Created by Stuart Connolly (stuconnolly.com) on March 20, 2012 +// Copyright (c) 2012 Stuart Connolly. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + +#import "SPTableContentDelegate.h" +#import "SPAppController.h" +#import "SPDatabaseDocument.h" +#import "SPDataStorage.h" +#import "SPGeometryDataView.h" +#import "SPTooltip.h" +#import "SPTablesList.h" +#import "SPMySQL.h" +#import "SPBundleHTMLOutputController.h" +#import "SPCopyTable.h" +#import "SPAlertSheets.h" +#import "SPTableData.h" +#import "SPFieldEditorController.h" + +#import <pthread.h> + +@interface SPTableContent (SPDeclaredAPI) + +- (BOOL)cancelRowEditing; + +@end + +@implementation SPTableContent (SPTableContentDelegate) + +#pragma mark - +#pragma mark TableView delegate methods + +/** + * Sorts the tableView by the clicked column. If clicked twice, order is altered to descending. + * Performs the task in a new thread if necessary. + */ +- (void)tableView:(NSTableView*)tableView didClickTableColumn:(NSTableColumn *)tableColumn +{ + if ([selectedTable isEqualToString:@""] || !selectedTable || tableView != tableContentView) return; + + // Prevent sorting while the table is still loading + if ([tableDocumentInstance isWorking]) return; + + // Start the task + [tableDocumentInstance startTaskWithDescription:NSLocalizedString(@"Sorting table...", @"Sorting table task description")]; + + if ([NSThread isMainThread]) { + [NSThread detachNewThreadSelector:@selector(sortTableTaskWithColumn:) toTarget:self withObject:tableColumn]; + } + else { + [self sortTableTaskWithColumn:tableColumn]; + } +} + +- (void)tableViewSelectionDidChange:(NSNotification *)aNotification +{ + // Check our notification object is our table content view + if ([aNotification object] != tableContentView) return; + + isFirstChangeInView = YES; + + [addButton setEnabled:([tablesListInstance tableType] == SPTableTypeTable)]; + + // If we are editing a row, attempt to save that row - if saving failed, reselect the edit row. + if (isEditingRow && [tableContentView selectedRow] != currentlyEditingRow && ![self saveRowOnDeselect]) return; + + if (![tableDocumentInstance isWorking]) { + // Update the row selection count + // and update the status of the delete/duplicate buttons + if([tablesListInstance tableType] == SPTableTypeTable) { + if ([tableContentView numberOfSelectedRows] > 0) { + [duplicateButton setEnabled:([tableContentView numberOfSelectedRows] == 1)]; + [removeButton setEnabled:YES]; + } + else { + [duplicateButton setEnabled:NO]; + [removeButton setEnabled:NO]; + } + } + else { + [duplicateButton setEnabled:NO]; + [removeButton setEnabled:NO]; + } + } + + [self updateCountText]; + +#ifndef SP_REFACTOR /* triggered commands */ + NSArray *triggeredCommands = [[NSApp delegate] bundleCommandsForTrigger:SPBundleTriggerActionTableRowChanged]; + + for (NSString *cmdPath in triggeredCommands) + { + NSArray *data = [cmdPath componentsSeparatedByString:@"|"]; + NSMenuItem *aMenuItem = [[[NSMenuItem alloc] init] autorelease]; + + [aMenuItem setTag:0]; + [aMenuItem setToolTip:[data objectAtIndex:0]]; + + // For HTML output check if corresponding window already exists + BOOL stopTrigger = NO; + + if ([(NSString *)[data objectAtIndex:2] length]) { + BOOL correspondingWindowFound = NO; + NSString *uuid = [data objectAtIndex:2]; + + for (id win in [NSApp windows]) + { + if ([[[[win delegate] class] description] isEqualToString:@"SPBundleHTMLOutputController"]) { + if ([[[win delegate] windowUUID] isEqualToString:uuid]) { + correspondingWindowFound = YES; + break; + } + } + } + + if (!correspondingWindowFound) stopTrigger = YES; + } + if (!stopTrigger) { + + if ([[data objectAtIndex:1] isEqualToString:SPBundleScopeGeneral]) { + [[[NSApp delegate] onMainThread] executeBundleItemForApp:aMenuItem]; + } + else if ([[data objectAtIndex:1] isEqualToString:SPBundleScopeDataTable]) { + if ([[[[[NSApp mainWindow] firstResponder] class] description] isEqualToString:@"SPCopyTable"]) { + [[[[NSApp mainWindow] firstResponder] onMainThread] executeBundleItemForDataTable:aMenuItem]; + } + } + else if ([[data objectAtIndex:1] isEqualToString:SPBundleScopeInputField]) { + if ([[[NSApp mainWindow] firstResponder] isKindOfClass:[NSTextView class]]) { + [[[[NSApp mainWindow] firstResponder] onMainThread] executeBundleItemForInputField:aMenuItem]; + } + } + } + } +#endif +} + +/** + * Saves the new column size in the preferences. + */ +- (void)tableViewColumnDidResize:(NSNotification *)notification +{ + // Check our notification object is our table content view + if ([notification object] != tableContentView) return; + + // Sometimes the column has no identifier. I can't figure out what is causing it, so we just skip over this item + if (![[[notification userInfo] objectForKey:@"NSTableColumn"] identifier]) return; + + NSMutableDictionary *tableColumnWidths; + NSString *database = [NSString stringWithFormat:@"%@@%@", [tableDocumentInstance database], [tableDocumentInstance host]]; + NSString *table = [tablesListInstance tableName]; + + // Get tableColumnWidths object +#ifndef SP_REFACTOR + if ([prefs objectForKey:SPTableColumnWidths] != nil ) { + tableColumnWidths = [NSMutableDictionary dictionaryWithDictionary:[prefs objectForKey:SPTableColumnWidths]]; + } + else { +#endif + tableColumnWidths = [NSMutableDictionary dictionary]; +#ifndef SP_REFACTOR + } +#endif + + // Get the database object + if ([tableColumnWidths objectForKey:database] == nil) { + [tableColumnWidths setObject:[NSMutableDictionary dictionary] forKey:database]; + } + else { + [tableColumnWidths setObject:[NSMutableDictionary dictionaryWithDictionary:[tableColumnWidths objectForKey:database]] forKey:database]; + } + + // Get the table object + if ([[tableColumnWidths objectForKey:database] objectForKey:table] == nil) { + [[tableColumnWidths objectForKey:database] setObject:[NSMutableDictionary dictionary] forKey:table]; + } + else { + [[tableColumnWidths objectForKey:database] setObject:[NSMutableDictionary dictionaryWithDictionary:[[tableColumnWidths objectForKey:database] objectForKey:table]] forKey:table]; + } + + // Save column size + [[[tableColumnWidths objectForKey:database] objectForKey:table] + setObject:[NSNumber numberWithDouble:[(NSTableColumn *)[[notification userInfo] objectForKey:@"NSTableColumn"] width]] + forKey:[[[[notification userInfo] objectForKey:@"NSTableColumn"] headerCell] stringValue]]; +#ifndef SP_REFACTOR + [prefs setObject:tableColumnWidths forKey:SPTableColumnWidths]; +#endif +} + +/** + * Confirm whether to allow editing of a row. Returns YES by default, unless the multipleLineEditingButton is in + * the ON state, or for blob or text fields - in those cases opens a sheet for editing instead and returns NO. + */ +- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex +{ + if ([tableDocumentInstance isWorking]) return NO; + +#ifndef SP_REFACTOR + if (tableView == filterTableView) { + return (filterTableIsSwapped && [[tableColumn identifier] integerValue] == 0) ? NO : YES; + } + else +#endif + if (tableView == tableContentView) { + + // Ensure that row is editable since it could contain "(not loaded)" columns together with + // issue that the table has no primary key + NSString *wherePart = [NSString stringWithString:[self argumentForRow:[tableContentView selectedRow]]]; + + if ([wherePart length] == 0) return NO; + + // If the selected cell hasn't been loaded, load it. + if ([[tableValues cellDataAtRow:rowIndex column:[[tableColumn identifier] integerValue]] isSPNotLoaded]) { + + // Only get the data for the selected column, not all of them + NSString *query = [NSString stringWithFormat:@"SELECT %@ FROM %@ WHERE %@", [[[tableColumn headerCell] stringValue] backtickQuotedString], [selectedTable backtickQuotedString], wherePart]; + + SPMySQLResult *tempResult = [mySQLConnection queryString:query]; + + if (![tempResult numberOfRows]) { + SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, + NSLocalizedString(@"Couldn't load the row. Reload the table to be sure that the row exists and use a primary key for your table.", @"message of panel when loading of row failed")); + return NO; + } + + NSArray *tempRow = [tempResult getRowAsArray]; + + [tableValues replaceObjectInRow:rowIndex column:[[tableContentView tableColumns] indexOfObject:tableColumn] withObject:[tempRow objectAtIndex:0]]; + [tableContentView reloadData]; + } + + // Open the editing sheet if required + if ([tableContentView shouldUseFieldEditorForRow:rowIndex column:[[tableColumn identifier] integerValue]]) { + + // Retrieve the column definition + NSDictionary *columnDefinition = [cqColumnDefinition objectAtIndex:[[tableColumn identifier] integerValue]]; + BOOL isBlob = [tableDataInstance columnIsBlobOrText:[[tableColumn headerCell] stringValue]]; + + // A table is per definition editable + BOOL isFieldEditable = YES; + + // Check for Views if field is editable + if ([tablesListInstance tableType] == SPTableTypeView) { + NSArray *editStatus = [self fieldEditStatusForRow:rowIndex andColumn:[[tableColumn identifier] integerValue]]; + isFieldEditable = [[editStatus objectAtIndex:0] integerValue] == 1; + } + + NSString *fieldType = nil; + NSUInteger fieldLength = 0; + NSString *fieldEncoding = nil; + BOOL allowNULL = YES; + + fieldType = [columnDefinition objectForKey:@"type"]; + + if ([columnDefinition objectForKey:@"char_length"]) { + fieldLength = [[columnDefinition objectForKey:@"char_length"] integerValue]; + } + + if ([columnDefinition objectForKey:@"null"]) { + allowNULL = (![[columnDefinition objectForKey:@"null"] integerValue]); + } + + if ([columnDefinition objectForKey:@"charset_name"] && ![[columnDefinition objectForKey:@"charset_name"] isEqualToString:@"binary"]) { + fieldEncoding = [columnDefinition objectForKey:@"charset_name"]; + } + + if(fieldEditor) [fieldEditor release], fieldEditor = nil; + + fieldEditor = [[SPFieldEditorController alloc] init]; + + [fieldEditor setEditedFieldInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [[tableColumn headerCell] stringValue], @"colName", + [self usedQuery], @"usedQuery", + @"content", @"tableSource", + nil]]; + + [fieldEditor setTextMaxLength:fieldLength]; + [fieldEditor setFieldType:(fieldType==nil) ? @"" : fieldType]; + [fieldEditor setFieldEncoding:(fieldEncoding==nil) ? @"" : fieldEncoding]; + [fieldEditor setAllowNULL:allowNULL]; + + id cellValue = [tableValues cellDataAtRow:rowIndex column:[[tableColumn identifier] integerValue]]; + + if ([cellValue isNSNull]) { + cellValue = [NSString stringWithString:[prefs objectForKey:SPNullValue]]; + } + + NSInteger editedColumn = 0; + + for (NSTableColumn* col in [tableContentView tableColumns]) + { + if ([[col identifier] isEqualToString:[tableColumn identifier]]) break; + + editedColumn++; + } + + [fieldEditor editWithObject:cellValue + fieldName:[[tableColumn headerCell] stringValue] + usingEncoding:[mySQLConnection stringEncoding] + isObjectBlob:isBlob + isEditable:isFieldEditable + withWindow:[tableDocumentInstance parentWindow] + sender:self + contextInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInteger:rowIndex], @"rowIndex", + [NSNumber numberWithInteger:editedColumn], @"columnIndex", + [NSNumber numberWithBool:isFieldEditable], @"isFieldEditable", + nil]]; + + return NO; + } + + return YES; + } + + return YES; +} + +/** + * Enable drag from tableview + */ +- (BOOL)tableView:(NSTableView *)tableView writeRowsWithIndexes:(NSIndexSet *)rows toPasteboard:(NSPasteboard*)pboard +{ + if (tableView == tableContentView) { + NSString *tmp; + + // By holding ⌘, ⇧, or/and ⌥ copies selected rows as SQL INSERTS + // otherwise \t delimited lines + if ([[NSApp currentEvent] modifierFlags] & (NSCommandKeyMask|NSShiftKeyMask|NSAlternateKeyMask)) { + tmp = [tableContentView rowsAsSqlInsertsOnlySelectedRows:YES]; + } + else { + tmp = [tableContentView draggedRowsAsTabString]; + } + + if (!tmp && [tmp length]) + { + [pboard declareTypes:[NSArray arrayWithObjects: NSTabularTextPboardType, NSStringPboardType, nil] owner:nil]; + + [pboard setString:tmp forType:NSStringPboardType]; + [pboard setString:tmp forType:NSTabularTextPboardType]; + + return YES; + } + } + + return NO; +} + +/** + * Disable row selection while the document is working. + */ +- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)rowIndex +{ +#ifndef SP_REFACTOR + if (tableView == filterTableView) { + return YES; + } + else +#endif + return tableView == tableContentView ? tableRowsSelectable : YES; +} + +/** + * Resize a column when it's double-clicked (10.6+ only). + */ +- (CGFloat)tableView:(NSTableView *)tableView sizeToFitWidthOfColumn:(NSInteger)columnIndex +{ + NSTableColumn *theColumn = [[tableView tableColumns] objectAtIndex:columnIndex]; + NSDictionary *columnDefinition = [dataColumns objectAtIndex:[[theColumn identifier] integerValue]]; + + // Get the column width + NSUInteger targetWidth = [tableContentView autodetectWidthForColumnDefinition:columnDefinition maxRows:500]; + +#ifndef SP_REFACTOR + // Clear any saved widths for the column + NSString *dbKey = [NSString stringWithFormat:@"%@@%@", [tableDocumentInstance database], [tableDocumentInstance host]]; + NSString *tableKey = [tablesListInstance tableName]; + NSMutableDictionary *savedWidths = [NSMutableDictionary dictionaryWithDictionary:[prefs objectForKey:SPTableColumnWidths]]; + NSMutableDictionary *dbDict = [NSMutableDictionary dictionaryWithDictionary:[savedWidths objectForKey:dbKey]]; + NSMutableDictionary *tableDict = [NSMutableDictionary dictionaryWithDictionary:[dbDict objectForKey:tableKey]]; + + if ([tableDict objectForKey:[columnDefinition objectForKey:@"name"]]) { + [tableDict removeObjectForKey:[columnDefinition objectForKey:@"name"]]; + + if ([tableDict count]) { + [dbDict setObject:[NSDictionary dictionaryWithDictionary:tableDict] forKey:tableKey]; + } + else { + [dbDict removeObjectForKey:tableKey]; + } + + if ([dbDict count]) { + [savedWidths setObject:[NSDictionary dictionaryWithDictionary:dbDict] forKey:dbKey]; + } + else { + [savedWidths removeObjectForKey:dbKey]; + } + + [prefs setObject:[NSDictionary dictionaryWithDictionary:savedWidths] forKey:SPTableColumnWidths]; + } +#endif + + // Return the width, while the delegate is empty to prevent column resize notifications + [tableContentView setDelegate:nil]; + [tableContentView performSelector:@selector(setDelegate:) withObject:self afterDelay:0.1]; + + return targetWidth; +} + +/** + * This function changes the text color of text/blob fields which are null or not yet loaded to gray + */ +- (void)tableView:(SPCopyTable *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex +{ +#ifndef SP_REFACTOR + if (tableView == filterTableView) { + if (filterTableIsSwapped && [[tableColumn identifier] integerValue] == 0) { + [cell setDrawsBackground:YES]; + [cell setBackgroundColor:lightGrayColor]; + } + else { + [cell setDrawsBackground:NO]; + } + + return; + } + else +#endif + if (tableView == tableContentView) { + + if (![cell respondsToSelector:@selector(setTextColor:)]) return; + + id theValue = nil; + NSUInteger columnIndex = [[tableColumn identifier] integerValue]; + + // 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 indicate loading in these cases. + if (isWorking) { + pthread_mutex_lock(&tableValuesLock); + + if (rowIndex < (NSInteger)tableRowsCount && columnIndex < [tableValues columnCount]) { + theValue = SPDataStorageObjectAtRowAndColumn(tableValues, rowIndex, columnIndex); + } + + pthread_mutex_unlock(&tableValuesLock); + + if (!theValue) { + [cell setTextColor:[NSColor lightGrayColor]]; + return; + } + } + else { + theValue = SPDataStorageObjectAtRowAndColumn(tableValues, rowIndex, columnIndex); + } + + // If user wants to edit 'cell' set text color to black and return to avoid + // writing in gray if value was NULL + if ([tableView editedColumn] != -1 + && [tableView editedRow] == rowIndex + && (NSUInteger)[[NSArrayObjectAtIndex([tableView tableColumns], [tableView editedColumn]) identifier] integerValue] == columnIndex) { + [cell setTextColor:blackColor]; + return; + } + + // For null cells and not loaded cells, display the contents in gray. + if ([theValue isNSNull] || [theValue isSPNotLoaded]) { + [cell setTextColor:lightGrayColor]; + + // Otherwise, set the color to black - required as NSTableView reuses NSCells. + } + else { + [cell setTextColor:blackColor]; + } + } +} + +#ifndef SP_REFACTOR +/** + * Show the table cell content as tooltip + * + * - for text displays line breaks and tabs as well + * - if blob data can be interpret as image data display the image as transparent thumbnail + * (up to now using base64 encoded HTML data). + */ +- (NSString *)tableView:(NSTableView *)tableView toolTipForCell:(id)aCell rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row mouseLocation:(NSPoint)mouseLocation +{ + if (tableView == filterTableView) { + return nil; + } + else if (tableView == tableContentView) { + + if ([[aCell stringValue] length] < 2 || [tableDocumentInstance isWorking]) return nil; + + // Suppress tooltip if another toolip is already visible, mainly displayed by a Bundle command + // TODO has to be improved + for (id win in [NSApp orderedWindows]) + { + if ([[[[win contentView] class] description] isEqualToString:@"WebView"]) return nil; + } + + NSImage *image; + + NSPoint pos = [NSEvent mouseLocation]; + pos.y -= 20; + + 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(&tableValuesLock); + + if (row < (NSInteger)tableRowsCount && [[tableColumn identifier] integerValue] < (NSInteger)[tableValues columnCount]) { + theValue = [[SPDataStorageObjectAtRowAndColumn(tableValues, row, [[tableColumn identifier] integerValue]) copy] autorelease]; + } + + pthread_mutex_unlock(&tableValuesLock); + + if (!theValue) theValue = @"..."; + } + else { + theValue = SPDataStorageObjectAtRowAndColumn(tableValues, row, [[tableColumn identifier] integerValue]); + } + + if (theValue == nil) return nil; + + if ([theValue isKindOfClass:[NSData class]]) { + image = [[[NSImage alloc] initWithData:theValue] autorelease]; + + if (image) { + [SPTooltip showWithObject:image atLocation:pos ofType:@"image"]; + return nil; + } + } + else if ([theValue isKindOfClass:[SPMySQLGeometryData class]]) { + SPGeometryDataView *v = [[SPGeometryDataView alloc] initWithCoordinates:[theValue coordinates]]; + image = [v thumbnailImage]; + + if (image) { + [SPTooltip showWithObject:image atLocation:pos ofType:@"image"]; + [v release]; + return nil; + } + + [v release]; + } + + // Show the cell string value as tooltip (including line breaks and tabs) + // by using the cell's font + [SPTooltip showWithObject:[aCell stringValue] + atLocation:pos + ofType:@"text" + displayOptions:[NSDictionary dictionaryWithObjectsAndKeys: + [[aCell font] familyName], @"fontname", + [NSString stringWithFormat:@"%f",[[aCell font] pointSize]], @"fontsize", + nil]]; + + return nil; + } + + return nil; +} +#endif + +#ifndef SP_REFACTOR /* SplitView delegate methods */ + +#pragma mark - +#pragma mark SplitView delegate methods + +- (BOOL)splitView:(NSSplitView *)sender canCollapseSubview:(NSView *)subview +{ + return NO; +} + +/** + * Set a minimum size for the filter text area. + */ +- (CGFloat)splitView:(NSSplitView *)sender constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)offset +{ + return proposedMax - 180; +} + +/** + * Set a minimum size for the field list and action area. + */ +- (CGFloat)splitView:(NSSplitView *)sender constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)offset +{ + return proposedMin + 200; +} + +/** + * Improve default resizing and resize only the filter text area by default. + */ +- (void)splitView:(NSSplitView *)sender resizeSubviewsWithOldSize:(NSSize)oldSize +{ + NSSize newSize = [sender frame].size; + NSView *leftView = [[sender subviews] objectAtIndex:0]; + NSView *rightView = [[sender subviews] objectAtIndex:1]; + float dividerThickness = [sender dividerThickness]; + NSRect leftFrame = [leftView frame]; + NSRect rightFrame = [rightView frame]; + + // Resize height of both views + leftFrame.size.height = newSize.height; + rightFrame.size.height = newSize.height; + + // Only resize the right view's width - unless the constraint has been reached + if (rightFrame.size.width > 180 || newSize.width > oldSize.width) { + rightFrame.size.width = newSize.width - leftFrame.size.width - dividerThickness; + } + else { + leftFrame.size.width = newSize.width - rightFrame.size.width - dividerThickness; + } + + rightFrame.origin.x = leftFrame.size.width + dividerThickness; + + [leftView setFrame:leftFrame]; + [rightView setFrame:rightFrame]; +} + +#endif + +#pragma mark - +#pragma mark Control delegate methods + +- (void)controlTextDidChange:(NSNotification *)notification +{ +#ifndef SP_REFACTOR + if ([notification object] == filterTableView) { + + NSString *string = [[[[notification userInfo] objectForKey:@"NSFieldEditor"] textStorage] string]; + + if (string && [string length]) { + if (lastEditedFilterTableValue) [lastEditedFilterTableValue release]; + + lastEditedFilterTableValue = [[NSString stringWithString:string] retain]; + } + + [self updateFilterTableClause:string]; + } +#endif +} + +/** + * If the 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 != tableContentView) return YES; + + NSUInteger row, column; + BOOL shouldBeginEditing = YES; + + row = [tableContentView editedRow]; + column = [tableContentView editedColumn]; + + // If cell editing mode and editing request comes + // from the keyboard show an error tooltip + // or bypass if numberOfPossibleUpdateRows == 1 + if ([tableContentView isCellEditingMode]) { + + NSArray *editStatus = [self fieldEditStatusForRow:row andColumn:[[NSArrayObjectAtIndex([tableContentView tableColumns], column) identifier] integerValue]]; + NSInteger numberOfPossibleUpdateRows = [[editStatus objectAtIndex:0] integerValue]; + NSPoint pos = [[tableDocumentInstance parentWindow] convertBaseToScreen:[tableContentView convertPoint:[tableContentView frameOfCellAtColumn:column row:row].origin toView:nil]]; + + pos.y -= 20; + + switch (numberOfPossibleUpdateRows) + { + case -1: + [SPTooltip showWithObject:kCellEditorErrorNoMultiTabDb + atLocation:pos + ofType:@"text"]; + shouldBeginEditing = NO; + break; + case 0: + [SPTooltip showWithObject:[NSString stringWithFormat:kCellEditorErrorNoMatch, selectedTable] + atLocation:pos + ofType:@"text"]; + shouldBeginEditing = NO; + break; + case 1: + shouldBeginEditing = YES; + break; + default: + [SPTooltip showWithObject:[NSString stringWithFormat:kCellEditorErrorTooManyMatches, (long)numberOfPossibleUpdateRows, (numberOfPossibleUpdateRows>1)?NSLocalizedString(@"es", @"Plural suffix for row count, eg 4 match*es*"):@""] + atLocation:pos + ofType:@"text"]; + shouldBeginEditing = NO; + } + + } + + // Open the field editor sheet if required + if ([tableContentView shouldUseFieldEditorForRow:row column:column]) + { + [tableContentView setFieldEditorSelectedRange:[aFieldEditor selectedRange]]; + + // Cancel editing + [control abortEditing]; + + // Call the field editor sheet + [self tableView:tableContentView shouldEditTableColumn:NSArrayObjectAtIndex([tableContentView tableColumns], column) row:row]; + + // send current event to field editor sheet + if ([NSApp currentEvent]) { + [NSApp sendEvent:[NSApp currentEvent]]; + } + + return NO; + } + + return shouldBeginEditing; +} + +/** + * Trap the enter, escape, tab and arrow keys, overriding default behaviour and continuing/ending editing, + * only within the current row. + */ +- (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)command +{ +#ifndef SP_REFACTOR + // Check firstly if SPCopyTable can handle command + if ([control control:control textView:textView doCommandBySelector:(SEL)command]) +#else + if ([(id<NSControlTextEditingDelegate>)control control:control textView:textView doCommandBySelector:(SEL)command]) +#endif + return YES; + + // Trap the escape key + if ([[control window] methodForSelector:command] == [[control window] methodForSelector:@selector(cancelOperation:)]) { + // Abort editing + [control abortEditing]; + + if (control == tableContentView) { + [self cancelRowEditing]; + } + + return YES; + } + + return NO; +} + +@end |