diff options
author | rowanbeentje <rowan@beent.je> | 2010-08-12 01:15:44 +0000 |
---|---|---|
committer | rowanbeentje <rowan@beent.je> | 2010-08-12 01:15:44 +0000 |
commit | e5aa4302f8655a08d7fa7542893db009a6920689 (patch) | |
tree | 72a12fcf45eaa76e72a2350576a65a0ace6af08e /Source/SPTableContent.m | |
parent | 8b1962153814426bfb7e4ab38056ffa955d7c3f0 (diff) | |
download | sequelpro-e5aa4302f8655a08d7fa7542893db009a6920689.tar.gz sequelpro-e5aa4302f8655a08d7fa7542893db009a6920689.tar.bz2 sequelpro-e5aa4302f8655a08d7fa7542893db009a6920689.zip |
Implement column autosizing for the Content View:
- Add automatic column sizing (for columns without saved widths) as part of the value loading process
- Rework table updates to be timer based, for time-based and more regular updates. This improves speed and allows tables to update more consistently.
This results in overall smoother table loads, faster table loads, and autosizing columns. This partially implements Issues #271 and #272.
Column autosizing will likely be tweaked, and this will all also be extended to Custom Query views in a future patch.
Diffstat (limited to 'Source/SPTableContent.m')
-rw-r--r-- | Source/SPTableContent.m | 183 |
1 files changed, 156 insertions, 27 deletions
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]; |