aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorstuconnolly <stuart02@gmail.com>2012-03-20 22:43:34 +0000
committerstuconnolly <stuart02@gmail.com>2012-03-20 22:43:34 +0000
commit1f18d0eb40a1bc0551dbabdfe355c9632a47c6c6 (patch)
treef2983b3e79cb542b3a4297a14cf93d9847871b6c
parent008b8291ebaf3042c7229350ca1c77a110f4b65d (diff)
downloadsequelpro-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.
-rw-r--r--Source/SPCustomQuery.h2
-rw-r--r--Source/SPCustomQuery.m16
-rw-r--r--Source/SPDataAdditions.h6
-rw-r--r--Source/SPDataAdditions.m40
-rw-r--r--Source/SPExportInitializer.m7
-rw-r--r--Source/SPPrintController.m2
-rw-r--r--Source/SPTableContent.h136
-rw-r--r--Source/SPTableContent.m1019
-rw-r--r--Source/SPTableContentDataSource.h37
-rw-r--r--Source/SPTableContentDataSource.m183
-rw-r--r--Source/SPTableContentDelegate.h37
-rw-r--r--Source/SPTableContentDelegate.m778
-rw-r--r--sequel-pro.xcodeproj/project.pbxproj28
13 files changed, 1298 insertions, 993 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:@"&lt;BLOB&gt;"];
+ [tempRow addObject:hide ? @"&lt;BLOB&gt;" : [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
diff --git a/sequel-pro.xcodeproj/project.pbxproj b/sequel-pro.xcodeproj/project.pbxproj
index e5f1a578..84e362a3 100644
--- a/sequel-pro.xcodeproj/project.pbxproj
+++ b/sequel-pro.xcodeproj/project.pbxproj
@@ -41,6 +41,8 @@
172A65110F7BED7A001E861A /* SPConsoleMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 172A65100F7BED7A001E861A /* SPConsoleMessage.m */; };
173284EA1088FEDE0062E892 /* SPConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 173284E91088FEDE0062E892 /* SPConstants.m */; };
1734696B11C1167000AB3D16 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A7FEA54F5311CA2CBB /* Cocoa.framework */; };
+ 17386E0B15192526002DC206 /* SPTableContentDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 17386E0A15192526002DC206 /* SPTableContentDataSource.m */; };
+ 17386E0E1519257E002DC206 /* SPTableContentDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 17386E0D1519257E002DC206 /* SPTableContentDelegate.m */; };
173C4362104455CA001F3A30 /* QueryFavoriteManager.xib in Resources */ = {isa = PBXBuildFile; fileRef = 173C4360104455CA001F3A30 /* QueryFavoriteManager.xib */; };
173C4366104455E0001F3A30 /* SPQueryFavoriteManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 173C4365104455E0001F3A30 /* SPQueryFavoriteManager.m */; };
173C44D81044A6B0001F3A30 /* SPOutlineView.m in Sources */ = {isa = PBXBuildFile; fileRef = 173C44D71044A6B0001F3A30 /* SPOutlineView.m */; };
@@ -492,14 +494,14 @@
isa = PBXContainerItemProxy;
containerPortal = 584D876015140D3500F24774 /* SPMySQLFramework.xcodeproj */;
proxyType = 2;
- remoteGlobalIDString = 8DC2EF5B0486A6940098B216 /* SPMySQL.framework */;
+ remoteGlobalIDString = 8DC2EF5B0486A6940098B216;
remoteInfo = SPMySQL.framework;
};
584D877015140F5400F24774 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 584D876015140D3500F24774 /* SPMySQLFramework.xcodeproj */;
proxyType = 1;
- remoteGlobalIDString = 8DC2EF4F0486A6940098B216 /* SPMySQL.framework */;
+ remoteGlobalIDString = 8DC2EF4F0486A6940098B216;
remoteInfo = SPMySQL.framework;
};
586AAB0F14FAD3A2007F82BF /* PBXContainerItemProxy */ = {
@@ -613,6 +615,10 @@
172A65100F7BED7A001E861A /* SPConsoleMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPConsoleMessage.m; sourceTree = "<group>"; };
173284E81088FEDE0062E892 /* SPConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPConstants.h; sourceTree = "<group>"; };
173284E91088FEDE0062E892 /* SPConstants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPConstants.m; sourceTree = "<group>"; };
+ 17386E0915192526002DC206 /* SPTableContentDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTableContentDataSource.h; sourceTree = "<group>"; };
+ 17386E0A15192526002DC206 /* SPTableContentDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTableContentDataSource.m; sourceTree = "<group>"; };
+ 17386E0C1519257E002DC206 /* SPTableContentDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTableContentDelegate.h; sourceTree = "<group>"; };
+ 17386E0D1519257E002DC206 /* SPTableContentDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTableContentDelegate.m; sourceTree = "<group>"; };
173C4361104455CA001F3A30 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/QueryFavoriteManager.xib; sourceTree = "<group>"; };
173C4364104455E0001F3A30 /* SPQueryFavoriteManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPQueryFavoriteManager.h; sourceTree = "<group>"; };
173C4365104455E0001F3A30 /* SPQueryFavoriteManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPQueryFavoriteManager.m; sourceTree = "<group>"; };
@@ -1446,6 +1452,19 @@
name = "Bundle Support";
sourceTree = "<group>";
};
+ 17386E08151924E9002DC206 /* Table Content */ = {
+ isa = PBXGroup;
+ children = (
+ 17E6414E0EF01EF6001BC333 /* SPTableContent.h */,
+ 17E6414F0EF01EF6001BC333 /* SPTableContent.m */,
+ 17386E0C1519257E002DC206 /* SPTableContentDelegate.h */,
+ 17386E0D1519257E002DC206 /* SPTableContentDelegate.m */,
+ 17386E0915192526002DC206 /* SPTableContentDataSource.h */,
+ 17386E0A15192526002DC206 /* SPTableContentDataSource.m */,
+ );
+ name = "Table Content";
+ sourceTree = "<group>";
+ };
173C836C11AAD24300B8B084 /* Exporters */ = {
isa = PBXGroup;
children = (
@@ -1520,13 +1539,12 @@
387BBBA70FBCB6CB00B31746 /* SPTableRelations.m */,
29FA88211114619E00D1AF3D /* SPTableTriggers.h */,
29FA88221114619E00D1AF3D /* SPTableTriggers.m */,
- 17E6414E0EF01EF6001BC333 /* SPTableContent.h */,
- 17E6414F0EF01EF6001BC333 /* SPTableContent.m */,
17E641500EF01EF6001BC333 /* SPDatabaseDocument.h */,
17E641510EF01EF6001BC333 /* SPDatabaseDocument.m */,
17D3DC1E1281816E002A163A /* SPDatabaseViewController.h */,
17D3DC1F1281816E002A163A /* SPDatabaseViewController.m */,
17D38FC2127B0C9500672B13 /* Connection View */,
+ 17386E08151924E9002DC206 /* Table Content */,
17D38F691279E17D00672B13 /* Table Structure */,
1792C28910AE1C7200ABE758 /* Controller Categories */,
);
@@ -3203,6 +3221,8 @@
584D87921514101E00F24774 /* SPDatabaseStructure.m in Sources */,
584D88A91515034200F24774 /* NSNotificationCenterThreadingAdditions.m in Sources */,
584D899D15162CBE00F24774 /* SPDataBase64EncodingAdditions.m in Sources */,
+ 17386E0B15192526002DC206 /* SPTableContentDataSource.m in Sources */,
+ 17386E0E1519257E002DC206 /* SPTableContentDelegate.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};