aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Source/CMCopyTable.h36
-rw-r--r--Source/CMCopyTable.m95
-rw-r--r--Source/SPTableContent.h7
-rw-r--r--Source/SPTableContent.m183
4 files changed, 294 insertions, 27 deletions
diff --git a/Source/CMCopyTable.h b/Source/CMCopyTable.h
index 6baa2527..948607bc 100644
--- a/Source/CMCopyTable.h
+++ b/Source/CMCopyTable.h
@@ -25,6 +25,8 @@
#import <AppKit/AppKit.h>
#import "SPTableView.h"
+#define SP_MAX_CELL_WIDTH 200
+
@class SPDataStorage;
/*!
@@ -112,6 +114,40 @@
*/
- (void)setTableData:(SPDataStorage *)theTableStorage;
+/*!
+ @method autodetectColumnWidthsForFont:
+ @abstract Autodetect and return column widths based on contents
+ @discussion Support autocalculating column widths for the represented data.
+ This uses the underlying table storage, calculates string widths,
+ and eventually returns an array of table column widths.
+ Suitable for calling on background threads, but ensure that the
+ data storage range in use (currently rows 1-200) won't be altered
+ while this accesses it.
+ @param The font to use when calculating widths
+ @result A dictionary - mapped by column identifier - of the column widths to use
+*/
+- (NSDictionary *) autodetectColumnWidthsForFont:(NSFont *)theFont;
+
+/*!
+ @method autodetectWidthForColumnDefinition:usingFont:maxRows:
+ @abstract Autodetect and return column width based on contents
+ @discussion Support autocalculating column width for the represented data.
+ This uses the underlying table storage, and the supplied column definition,
+ iterating through the data and returning a reasonable column width to
+ display that data.
+ Suitable for calling on background threads, but ensure that the data
+ storage range in use won't be altered while being accessed.
+ @param A column definition for a represented column; the column to use is derived
+ @param The font to use when calculating widths
+ @param The maximum number of rows to process when looking at string lengths
+ @result A reasonable column width to use when displaying data
+*/
+/**
+ * Autodetect the column width for a specified column - derived from the supplied
+ * column definition, using the stored data and the specified font.
+ */
+- (NSUInteger)autodetectWidthForColumnDefinition:(NSDictionary *)columnDefinition usingFont:(NSFont *)theFont maxRows:(NSUInteger)rowsToCheck;
+
@end
extern NSInteger MENU_EDIT_COPY;
diff --git a/Source/CMCopyTable.m b/Source/CMCopyTable.m
index 8ce03586..5eab896d 100644
--- a/Source/CMCopyTable.m
+++ b/Source/CMCopyTable.m
@@ -446,6 +446,101 @@ NSInteger MENU_EDIT_COPY_AS_SQL = 2003;
tableStorage = theTableStorage;
}
+/**
+ * Autodetect column widths for a specified font.
+ */
+- (NSDictionary *) autodetectColumnWidthsForFont:(NSFont *)theFont;
+{
+ NSMutableDictionary *columnWidths = [NSMutableDictionary dictionaryWithCapacity:[columnDefinitions count]];
+ NSUInteger columnWidth;
+
+ for (NSDictionary *columnDefinition in columnDefinitions) {
+ if ([[NSThread currentThread] isCancelled]) return nil;
+
+ columnWidth = [self autodetectWidthForColumnDefinition:columnDefinition usingFont:theFont maxRows:100];
+ [columnWidths setObject:[NSNumber numberWithUnsignedInteger:columnWidth] forKey:[columnDefinition objectForKey:@"datacolumnindex"]];
+ }
+
+ return columnWidths;
+}
+
+/**
+ * Autodetect the column width for a specified column - derived from the supplied
+ * column definition, using the stored data and the specified font.
+ */
+- (NSUInteger)autodetectWidthForColumnDefinition:(NSDictionary *)columnDefinition usingFont:(NSFont *)theFont maxRows:(NSUInteger)rowsToCheck
+{
+ CGFloat columnBaseWidth;
+ id contentString;
+ NSUInteger cellWidth, maxCellWidth, i;
+ NSRange linebreakRange;
+ double rowStep;
+ NSUInteger columnIndex = [[columnDefinition objectForKey:@"datacolumnindex"] unsignedIntegerValue];
+ NSDictionary *stringAttributes = [NSDictionary dictionaryWithObject:theFont forKey:NSFontAttributeName];
+
+ // Check the number of rows available to check, sampling every n rows
+ if ([tableStorage count] < rowsToCheck) {
+ rowsToCheck = [tableStorage count];
+ rowStep = 1;
+ } else {
+ rowStep = floor([tableStorage count] / rowsToCheck);
+ }
+
+ // Set a default padding for this column
+ columnBaseWidth = 24;
+
+ // Iterate through the data store rows, checking widths
+ maxCellWidth = 0;
+ for (i = 0; i < rowsToCheck; i += rowStep) {
+
+ // Retrieve the cell's content
+ contentString = [tableStorage cellDataAtRow:i column:columnIndex];
+
+ // Replace NULLs with their placeholder string
+ if ([contentString isNSNull]) {
+ contentString = [prefs objectForKey:SPNullValue];
+
+ } else {
+
+ // Otherwise, ensure the cell is represented as a short string
+ if ([contentString isKindOfClass:[NSData class]]) {
+ contentString = [contentString shortStringRepresentationUsingEncoding:[mySQLConnection encoding]];
+ } else if ([contentString length] > 500) {
+ contentString = [contentString substringToIndex:500];
+ }
+
+ // If any linebreaks are present, use only the visible part of the string
+ linebreakRange = [contentString rangeOfCharacterFromSet:[NSCharacterSet newlineCharacterSet]];
+ if (linebreakRange.location != NSNotFound) {
+ contentString = [contentString substringToIndex:linebreakRange.location];
+ }
+ }
+
+ // Calculate the width, using it if it's higher than the current stored width
+ cellWidth = [contentString sizeWithAttributes:stringAttributes].width;
+ if (cellWidth > maxCellWidth) maxCellWidth = cellWidth;
+ if (maxCellWidth > SP_MAX_CELL_WIDTH) {
+ maxCellWidth = SP_MAX_CELL_WIDTH;
+ break;
+ }
+ }
+
+ // If the column has a foreign key link, expand the width; and also for enums
+ if ([columnDefinition objectForKey:@"foreignkeyreference"]) {
+ maxCellWidth += 18;
+ } else if ([[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"enum"]) {
+ maxCellWidth += 8;
+ }
+
+ // Add the padding
+ maxCellWidth += columnBaseWidth;
+
+ // If the header width is wider than this expanded width, use it instead
+ cellWidth = [[columnDefinition objectForKey:@"name"] sizeWithAttributes:[NSDictionary dictionaryWithObject:[NSFont labelFontOfSize:[NSFont smallSystemFontSize]] forKey:NSFontAttributeName]].width;
+ if (cellWidth + 10 > maxCellWidth) maxCellWidth = cellWidth + 10;
+
+ return maxCellWidth;
+}
- (void)keyDown:(NSEvent *)theEvent
{
diff --git a/Source/SPTableContent.h b/Source/SPTableContent.h
index 7b87bc32..1899d6a6 100644
--- a/Source/SPTableContent.h
+++ b/Source/SPTableContent.h
@@ -99,6 +99,9 @@
NSString *filterFieldToRestore, *filterComparisonToRestore, *filterValueToRestore, *firstBetweenValueToRestore, *secondBetweenValueToRestore;
NSInteger paginationViewHeight;
+
+ NSTimer *tableLoadTimer;
+ NSUInteger tableLoadInterfaceUpdateInterval, tableLoadTimerTicksSinceLastUpdate, tableLoadLastRowCount;
}
// Table loading methods and information
@@ -107,6 +110,9 @@
- (void) loadTableValues;
- (NSString *) tableFilterString;
- (void) updateCountText;
+- (void) initTableLoadTimer;
+- (void) clearTableLoadTimer;
+- (void) tableLoadUpdate:(NSTimer *)theTimer;
// Table interface actions
- (IBAction) reloadTable:(id)sender;
@@ -148,6 +154,7 @@
- (NSString *)fieldListForQuery;
- (void)updateNumberOfRows;
- (NSInteger)fetchNumberOfRows;
+- (void)autosizeColumns;
- (BOOL)saveRowOnDeselect;
- (void)sortTableTaskWithColumn:(NSTableColumn *)tableColumn;
diff --git a/Source/SPTableContent.m b/Source/SPTableContent.m
index bf03c66e..26eeb568 100644
--- a/Source/SPTableContent.m
+++ b/Source/SPTableContent.m
@@ -99,6 +99,8 @@
prefs = [NSUserDefaults standardUserDefaults];
usedQuery = [[NSString alloc] initWithString:@""];
+
+ tableLoadTimer = nil;
// Init default filters for Content Browser
contentFilters = nil;
@@ -208,7 +210,10 @@
[tableDataInstance getConstraints], @"constraints",
nil];
[self performSelectorOnMainThread:@selector(setTableDetails:) withObject:tableDetails waitUntilDone:YES];
-
+
+ // Init copyTable with necessary information for copying selected rows as SQL INSERT
+ [tableContentView setTableInstance:self withTableData:tableValues withColumns:dataColumns withTableName:selectedTable withConnection:mySQLConnection];
+
// Trigger a data refresh
[self loadTableValues];
@@ -231,8 +236,6 @@
// Update display if necessary
[[tableContentView onMainThread] setNeedsDisplay:YES];
- // Init copyTable with necessary information for copying selected rows as SQL INSERT
- [tableContentView setTableInstance:self withTableData:tableValues withColumns:dataColumns withTableName:selectedTable withConnection:mySQLConnection];
// Post the notification that the query is finished
[[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance];
@@ -689,14 +692,13 @@
NSUInteger dataColumnsCount = [dataColumns count];
BOOL *columnBlobStatuses = malloc(dataColumnsCount * sizeof(BOOL));
+ // Set up the table updates timer
+ [[self onMainThread] initTableLoadTimer];
+
// Set the column count on the data store
[tableValues setColumnCount:dataColumnsCount];
CGFloat relativeTargetRowCount = 100.0/targetRowCount;
- NSUInteger nextTableDisplayBoundary = 50;
- BOOL tableViewRedrawn = NO;
-
- NSUInteger rowsProcessed = 0;
NSAutoreleasePool *dataLoadingPool;
NSProgressIndicator *dataLoadingIndicator = [tableDocumentInstance valueForKey:@"queryProgressBar"];
@@ -711,11 +713,12 @@
dataLoadingPool = [[NSAutoreleasePool alloc] init];
// Loop through the result rows as they become available
+ tableRowsCount = 0;
while (tempRow = [theResult fetchNextRowAsArray]) {
pthread_mutex_lock(&tableValuesLock);
- if (rowsProcessed < previousTableRowsCount) {
- SPDataStorageReplaceRow(tableValues, rowsProcessed, tempRow);
+ if (tableRowsCount < previousTableRowsCount) {
+ SPDataStorageReplaceRow(tableValues, tableRowsCount, tempRow);
} else {
SPDataStorageAddRow(tableValues, tempRow);
}
@@ -724,42 +727,36 @@
if ( prefsLoadBlobsAsNeeded ) {
for ( i = 0 ; i < dataColumnsCount ; i++ ) {
if (columnBlobStatuses[i]) {
- SPDataStorageReplaceObjectAtRowAndColumn(tableValues, rowsProcessed, i, [SPNotLoaded notLoaded]);
+ SPDataStorageReplaceObjectAtRowAndColumn(tableValues, tableRowsCount, i, [SPNotLoaded notLoaded]);
}
}
}
- rowsProcessed++;
+ tableRowsCount++;
pthread_mutex_unlock(&tableValuesLock);
// Update the task interface as necessary
if (!isFiltered) {
- if (rowsProcessed < targetRowCount) {
- [tableDocumentInstance setTaskPercentage:(rowsProcessed*relativeTargetRowCount)];
- } else if (rowsProcessed == targetRowCount) {
+ if (tableRowsCount < targetRowCount) {
+ [tableDocumentInstance setTaskPercentage:(tableRowsCount*relativeTargetRowCount)];
+ } else if (tableRowsCount == targetRowCount) {
[tableDocumentInstance setTaskPercentage:100.0];
[[tableDocumentInstance onMainThread] setTaskProgressToIndeterminateAfterDelay:YES];
}
}
- // Update the table view with new results every now and then
- if (rowsProcessed > nextTableDisplayBoundary) {
- if (rowsProcessed > tableRowsCount) tableRowsCount = rowsProcessed;
- [[tableContentView onMainThread] noteNumberOfRowsChanged];
- if (!tableViewRedrawn) {
- [[tableContentView onMainThread] setNeedsDisplay:YES];
- tableViewRedrawn = YES;
- }
- nextTableDisplayBoundary *= 2;
- }
-
// Drain and reset the autorelease pool every ~1024 rows
- if (!(rowsProcessed % 1024)) {
+ if (!(tableRowsCount % 1024)) {
[dataLoadingPool drain];
dataLoadingPool = [[NSAutoreleasePool alloc] init];
}
}
- tableRowsCount = rowsProcessed;
+
+ // Clean up the interface update timer
+ [[self onMainThread] clearTableLoadTimer];
+
+ // If the final column autoresize wasn't performed, perform it
+ if (tableLoadLastRowCount < 200) [[self onMainThread] autosizeColumns];
// If the reloaded table is shorter than the previous table, remove the extra values from the storage
if (tableRowsCount < [tableValues count]) {
@@ -1015,6 +1012,77 @@
[[countText onMainThread] setStringValue:countString];
}
+/**
+ * Set up the table loading interface update timer.
+ * This should be called on the main thread.
+ */
+- (void) initTableLoadTimer
+{
+ if (tableLoadTimer) [self clearTableLoadTimer];
+ tableLoadInterfaceUpdateInterval = 1;
+ tableLoadLastRowCount = 0;
+ tableLoadTimerTicksSinceLastUpdate = 0;
+
+ tableLoadTimer = [[NSTimer scheduledTimerWithTimeInterval:0.02 target:self selector:@selector(tableLoadUpdate:) userInfo:nil repeats:YES] retain];
+}
+
+/**
+ * Invalidate and release the table loading interface update timer.
+ * This should be called on the main thread.
+ */
+- (void) clearTableLoadTimer
+{
+ if (tableLoadTimer) {
+ [tableLoadTimer invalidate];
+ [tableLoadTimer release];
+ tableLoadTimer = nil;
+ }
+}
+
+/**
+ * Perform table interface updates when loading tables, based on timer
+ * ticks. As data becomes available, the table should be redrawn to
+ * show new rows - quickly at the start of the table, and then slightly
+ * slower after some time to avoid needless updates.
+ */
+- (void) tableLoadUpdate:(NSTimer *)theTimer
+{
+ if (tableLoadTimerTicksSinceLastUpdate < tableLoadInterfaceUpdateInterval) {
+ tableLoadTimerTicksSinceLastUpdate++;
+ return;
+ }
+
+ // Check whether a table update is required, based on whether new rows are
+ // available to display.
+ if (tableRowsCount == tableLoadLastRowCount) {
+ return;
+ }
+
+ // Update the table display
+ [tableContentView noteNumberOfRowsChanged];
+ if (!tableLoadLastRowCount) [tableContentView setNeedsDisplay:YES];
+
+ // Update column widths in two cases: on very first rows displayed, and once
+ // more than 200 rows are present.
+ if (tableLoadInterfaceUpdateInterval || (tableRowsCount >= 200 && tableLoadLastRowCount < 200)) {
+ [self autosizeColumns];
+ }
+
+ tableLoadLastRowCount = tableRowsCount;
+
+ // Determine whether to decrease the update frequency
+ switch (tableLoadInterfaceUpdateInterval) {
+ case 1:
+ tableLoadInterfaceUpdateInterval = 10;
+ break;
+ case 10:
+ tableLoadInterfaceUpdateInterval = 25;
+ break;
+ }
+ tableLoadTimerTicksSinceLastUpdate = 0;
+}
+
+
#pragma mark -
#pragma mark Table interface actions
@@ -2749,6 +2817,27 @@
return [[[[mySQLConnection queryString:[NSString stringWithFormat:@"SELECT COUNT(1) FROM %@", [selectedTable backtickQuotedString]]] fetchRowAsArray] objectAtIndex:0] integerValue];
}
+/**
+ * Autosize all columns based on their content.
+ * Should be called on the main thread.
+ */
+- (void)autosizeColumns
+{
+ NSDictionary *columnWidths = [tableContentView autodetectColumnWidthsForFont:[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPGlobalResultTableFont]]];
+ [tableContentView setDelegate:nil];
+ for (NSDictionary *columnDefinition in dataColumns) {
+
+ // Skip columns with saved widths
+ if ([[[[prefs objectForKey:SPTableColumnWidths] objectForKey:[NSString stringWithFormat:@"%@@%@", [tableDocumentInstance database], [tableDocumentInstance host]]] objectForKey:[tablesListInstance tableName]] objectForKey:[columnDefinition objectForKey:@"name"]]) continue;
+
+ // Otherwise set the column width
+ NSTableColumn *aTableColumn = [tableContentView tableColumnWithIdentifier:[columnDefinition objectForKey:@"datacolumnindex"]];
+ NSUInteger targetWidth = [[columnWidths objectForKey:[columnDefinition objectForKey:@"datacolumnindex"]] unsignedIntegerValue];
+ [aTableColumn setWidth:targetWidth];
+ }
+ [tableContentView setDelegate:self];
+}
+
#pragma mark -
#pragma mark TableView delegate methods
@@ -3157,6 +3246,44 @@
return tableRowsSelectable;
}
+/**
+ * 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 usingFont:[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPGlobalResultTableFont]] maxRows:500];
+
+ // 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];
+ }
+
+ // 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;
+}
+
#pragma mark -
#pragma mark SplitView delegate methods
@@ -3406,7 +3533,9 @@
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
+ [NSObject cancelPreviousPerformRequestsWithTarget:tableContentView];
+ [self clearTableLoadTimer];
[tableValues release];
pthread_mutex_destroy(&tableValuesLock);
[dataColumns release];