diff options
author | rowanbeentje <rowan@beent.je> | 2009-10-20 23:45:16 +0000 |
---|---|---|
committer | rowanbeentje <rowan@beent.je> | 2009-10-20 23:45:16 +0000 |
commit | 951330376a7b1e85dea0c44825534a4ab598c100 (patch) | |
tree | 1f098f10b4eebd660c9e2fcf1ea2926f1efbc24b /Source | |
parent | 53ca17c5bba2d57ecaee3a8ccd005fe4d6a4abe3 (diff) | |
download | sequelpro-951330376a7b1e85dea0c44825534a4ab598c100.tar.gz sequelpro-951330376a7b1e85dea0c44825534a4ab598c100.tar.bz2 sequelpro-951330376a7b1e85dea0c44825534a4ab598c100.zip |
Initial work on threaded task, with an implementation of Table Content view data loading:
- Removal of AMIndeterminateProgressIndicatorCell, and addition of a custom fork of YRKSpinningProgressIndicator which fixes bugs, adds threaded drawing, and adds a determinate mode.
- Addition of a task system within TableDocument, triggering display of a large central progress indicator and stopping the window from being interacted with in any way that would cause a query while the task is running.
- Add threaded TableContent content fetching, including use of the new task system and determinate progress bar; make fixes to improve threading stability and interaction.
Diffstat (limited to 'Source')
-rw-r--r-- | Source/AMIndeterminateProgressIndicatorCell.h | 57 | ||||
-rw-r--r-- | Source/AMIndeterminateProgressIndicatorCell.m | 189 | ||||
-rw-r--r-- | Source/SPConstants.h | 2 | ||||
-rw-r--r-- | Source/SPConstants.m | 2 | ||||
-rw-r--r-- | Source/TableContent.h | 10 | ||||
-rw-r--r-- | Source/TableContent.m | 199 | ||||
-rw-r--r-- | Source/TableDocument.h | 21 | ||||
-rw-r--r-- | Source/TableDocument.m | 131 | ||||
-rw-r--r-- | Source/TablesList.h | 7 | ||||
-rw-r--r-- | Source/TablesList.m | 36 | ||||
-rw-r--r-- | Source/YRKSpinningProgressIndicator.h | 45 | ||||
-rw-r--r-- | Source/YRKSpinningProgressIndicator.m | 304 |
12 files changed, 700 insertions, 303 deletions
diff --git a/Source/AMIndeterminateProgressIndicatorCell.h b/Source/AMIndeterminateProgressIndicatorCell.h deleted file mode 100644 index 04f7f13b..00000000 --- a/Source/AMIndeterminateProgressIndicatorCell.h +++ /dev/null @@ -1,57 +0,0 @@ -// -// $Id$ -// -// AMIndeterminateProgressIndicatorCell.h -// sequel-pro -// -// Created by Andreas Mayer on January 23, 2007 -// Copyright 2007 Andreas Mayer (andreas@harmless.de). All rights reserved. -// -// License: http://www.opensource.org/licenses/bsd-license.php -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 2 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// More info at <http://code.google.com/p/sequel-pro/> - -#import <Cocoa/Cocoa.h> - -@interface AMIndeterminateProgressIndicatorCell : NSCell { - double doubleValue; - NSTimeInterval animationDelay; - BOOL displayedWhenStopped; - BOOL spinning; - NSColor *color; - float redComponent; - float greenComponent; - float blueComponent; -} - -- (NSColor *)color; -- (void)setColor:(NSColor *)value; - -- (double)doubleValue; -- (void)setDoubleValue:(double)value; - -- (NSTimeInterval)animationDelay; -- (void)setAnimationDelay:(NSTimeInterval)value; - -- (BOOL)isDisplayedWhenStopped; -- (void)setDisplayedWhenStopped:(BOOL)value; - -- (BOOL)isSpinning; -- (void)setSpinning:(BOOL)value; - - -@end diff --git a/Source/AMIndeterminateProgressIndicatorCell.m b/Source/AMIndeterminateProgressIndicatorCell.m deleted file mode 100644 index 66fe1a39..00000000 --- a/Source/AMIndeterminateProgressIndicatorCell.m +++ /dev/null @@ -1,189 +0,0 @@ -// -// $Id$ -// -// AMIndeterminateProgressIndicatorCell.m -// sequel-pro -// -// Created by Andreas Mayer on January 23, 2007 -// Copyright 2007 Andreas Mayer (andreas@harmless.de). All rights reserved. -// -// License: http://www.opensource.org/licenses/bsd-license.php -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 2 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// More info at <http://code.google.com/p/sequel-pro/> - -#import "AMIndeterminateProgressIndicatorCell.h" - -#define ConvertAngle(a) (fmod((90.0-(a)), 360.0)) - -#define DEG2RAD 0.017453292519943295 - -@implementation AMIndeterminateProgressIndicatorCell - -- (id)init -{ - if (self = [super initImageCell:nil]) { - [self setAnimationDelay:5.0/60.0]; - [self setDisplayedWhenStopped:YES]; - [self setDoubleValue:0.0]; - [self setColor:[NSColor blackColor]]; - } - return self; -} - -- (void)dealloc -{ - [NSColor release]; - [super dealloc]; -} - -- (NSColor *)color -{ - return [[color retain] autorelease]; -} - -- (void)setColor:(NSColor *)value -{ - float alphaComponent; - if (color != value) { - [color release]; - color = [value retain]; - [[color colorUsingColorSpaceName:@"NSCalibratedRGBColorSpace"] getRed:&redComponent green:&greenComponent blue:&blueComponent alpha:&alphaComponent]; - NSAssert((alphaComponent > 0.999), @"color must be opaque"); - } -} - -- (double)doubleValue -{ - return doubleValue; -} - -- (void)setDoubleValue:(double)value -{ - if (doubleValue != value) { - doubleValue = value; - if (doubleValue > 1.0) { - doubleValue = 1.0; - } else if (doubleValue < 0.0) { - doubleValue = 0.0; - } - } -} - -- (NSTimeInterval)animationDelay -{ - return animationDelay; -} - -- (void)setAnimationDelay:(NSTimeInterval)value -{ - if (animationDelay != value) { - animationDelay = value; - } -} - -- (BOOL)isDisplayedWhenStopped -{ - return displayedWhenStopped; -} - -- (void)setDisplayedWhenStopped:(BOOL)value -{ - if (displayedWhenStopped != value) { - displayedWhenStopped = value; - } -} - -- (BOOL)isSpinning -{ - return spinning; -} - -- (void)setSpinning:(BOOL)value -{ - if (spinning != value) { - spinning = value; - } -} - -- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView -{ - // cell has no border - [self drawInteriorWithFrame:cellFrame inView:controlView]; -} - -- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView -{ - //NSLog(@"cellFrame: %f %f %f %f", cellFrame.origin.x, cellFrame.origin.y, cellFrame.size.width, cellFrame.size.height); - if ([self isSpinning] || [self isDisplayedWhenStopped]) { - float flipFactor = ([controlView isFlipped] ? 1.0 : -1.0); - int step = round([self doubleValue]/(5.0/60.0)); - float cellSize = MIN(cellFrame.size.width, cellFrame.size.height); - NSPoint center = cellFrame.origin; - center.x += cellSize/2.0; - center.y += cellFrame.size.height/2.0; - float outerRadius; - float innerRadius; - float strokeWidth = cellSize*0.08; - if (cellSize >= 32.0) { - outerRadius = cellSize*0.38; - innerRadius = cellSize*0.23; - } else { - outerRadius = cellSize*0.48; - innerRadius = cellSize*0.27; - } - float a; // angle - NSPoint inner; - NSPoint outer; - // remember defaults - NSLineCapStyle previousLineCapStyle = [NSBezierPath defaultLineCapStyle]; - float previousLineWidth = [NSBezierPath defaultLineWidth]; - // new defaults for our loop - [NSBezierPath setDefaultLineCapStyle:NSRoundLineCapStyle]; - [NSBezierPath setDefaultLineWidth:strokeWidth]; - if ([self isSpinning]) { - a = (270+(step* 30))*DEG2RAD; - } else { - a = 270*DEG2RAD; - } - a = flipFactor*a; - int i; - for (i = 0; i < 12; i++) { -// [[NSColor colorWithCalibratedWhite:MIN(sqrt(i)*0.25, 0.8) alpha:1.0] set]; -// [[NSColor colorWithCalibratedWhite:0.0 alpha:1.0-sqrt(i)*0.25] set]; - [[NSColor colorWithCalibratedRed:redComponent green:greenComponent blue:blueComponent alpha:1.0-sqrt(i)*0.25] set]; - outer = NSMakePoint(center.x+cos(a)*outerRadius, center.y+sin(a)*outerRadius); - inner = NSMakePoint(center.x+cos(a)*innerRadius, center.y+sin(a)*innerRadius); - [NSBezierPath strokeLineFromPoint:inner toPoint:outer]; - a -= flipFactor*30*DEG2RAD; - } - // restore previous defaults - [NSBezierPath setDefaultLineCapStyle:previousLineCapStyle]; - [NSBezierPath setDefaultLineWidth:previousLineWidth]; - } -} - -- (void)setObjectValue:(id)value -{ - if ([value respondsToSelector:@selector(boolValue)]) { - [self setSpinning:[value boolValue]]; - } else { - [self setSpinning:NO]; - } -} - - -@end diff --git a/Source/SPConstants.h b/Source/SPConstants.h index 9e257894..420d1823 100644 --- a/Source/SPConstants.h +++ b/Source/SPConstants.h @@ -109,3 +109,5 @@ extern NSString *SPQueryFavorites; extern NSString *SPFavorites; extern NSString *SPTableColumnWidths; extern NSString *SPQueryHistory; +extern NSString *SPDocumentTaskStartNotification; +extern NSString *SPDocumentTaskEndNotification; diff --git a/Source/SPConstants.m b/Source/SPConstants.m index b2a88ee1..91ae17b1 100644 --- a/Source/SPConstants.m +++ b/Source/SPConstants.m @@ -92,3 +92,5 @@ NSString *SPQueryFavorites = @"queryFavorites"; NSString *SPFavorites = @"favorites"; NSString *SPTableColumnWidths = @"tableColumnWidths"; NSString *SPQueryHistory = @"queryHistory"; +NSString *SPDocumentTaskStartNotification = @"DocumentTaskStarted"; +NSString *SPDocumentTaskEndNotification = @"DocumentTaskEnded"; diff --git a/Source/TableContent.h b/Source/TableContent.h index 6ecc3a8e..09c50a0e 100644 --- a/Source/TableContent.h +++ b/Source/TableContent.h @@ -47,6 +47,7 @@ IBOutlet id addButton; IBOutlet id copyButton; IBOutlet id removeButton; + IBOutlet id reloadButton; IBOutlet id multipleLineEditingButton; IBOutlet id countText; IBOutlet id limitRowsField; @@ -60,6 +61,7 @@ NSString *selectedTable, *usedQuery; NSMutableArray *tableValues, *dataColumns, *keys, *oldRow; + NSUInteger tableValuesCount; NSString *compareType; NSNumber *sortCol; BOOL isEditingRow, isEditingNewRow, isSavingRow, isDesc, setLimit; @@ -82,7 +84,8 @@ // Table loading methods and information - (void) loadTable:(NSString *)aTable; -- (void) loadTableValues; +- (void) loadTableValuesWithCallback:(SEL)tableContentCallbackMethod; +- (void) loadTableValuesTaskWithCallback:(id)encodedTableContentCallbackMethod; - (NSString *) tableFilterString; - (void) updateCountText; @@ -102,6 +105,10 @@ - (NSArray *)currentResult; - (NSArray *)currentDataResult; +// Task interaction +- (void) startDocumentTaskForTab:(NSNotification *)aNotification; +- (void) endDocumentTaskForTab:(NSNotification *)aNotification; + // Additional methods - (void)setConnection:(MCPConnection *)theConnection; - (void)clickLinkArrow:(SPTextAndLinkCell *)theArrowCell; @@ -113,6 +120,7 @@ - (BOOL)tableContainsBlobOrTextColumns; - (NSString *)fieldListForQuery; - (void)sheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(NSString *)contextInfo; +- (void)finalizeRowDeletion; - (void)updateNumberOfRows; - (int)fetchNumberOfRows; - (BOOL)saveRowOnDeselect; diff --git a/Source/TableContent.m b/Source/TableContent.m index d21c8df5..36eaa2e2 100644 --- a/Source/TableContent.m +++ b/Source/TableContent.m @@ -59,6 +59,7 @@ if ((self == [super init])) { tableValues = [[NSMutableArray alloc] init]; + tableValuesCount = 0; dataColumns = [[NSMutableArray alloc] init]; oldRow = [[NSMutableArray alloc] init]; @@ -119,6 +120,16 @@ { // Set the table content view's vertical gridlines if required [tableContentView setGridStyleMask:([prefs boolForKey:SPDisplayTableViewVerticalGridlines]) ? NSTableViewSolidVerticalGridLineMask : NSTableViewGridNone]; + + // Add observers for document task activity + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(startDocumentTaskForTab:) + name:SPDocumentTaskStartNotification + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(endDocumentTaskForTab:) + name:SPDocumentTaskEndNotification + object:nil]; } #pragma mark - @@ -164,6 +175,7 @@ if ( [[[tableDataInstance statusValues] objectForKey:@"Rows"] isNSNull] || [aTable isEqualToString:@""] || !aTable || [tableDataInstance tableEncoding] == nil) { // Empty the stored data arrays + tableValuesCount = 0; [tableValues removeAllObjects]; [tableContentView reloadData]; isFiltered = NO; @@ -394,8 +406,15 @@ [removeButton setEnabled:NO]; // Trigger a data refresh - [self loadTableValues]; + [self loadTableValuesWithCallback:@selector(finalizeTableLoad)]; +} +/** + * Callback to finish setting up a freshly loaded table once + * the values are available. + */ +- (void) finalizeTableLoad +{ // Restore the view origin if appropriate if (!NSEqualRects(selectionViewportToRestore, NSZeroRect)) { @@ -430,17 +449,36 @@ * using filters and limits as appropriate. * Will not refresh the table view itself. */ -- (void) loadTableValues +- (void) loadTableValuesWithCallback:(SEL)tableContentCallbackMethod +{ + if (!selectedTable) return; + + [tableDocumentInstance startTaskWithDescription:[NSString stringWithFormat:NSLocalizedString(@"Loading %@...", @"Loading table string"), selectedTable]]; + + NSValue *encodedCallbackMethod = nil; + if (tableContentCallbackMethod) + encodedCallbackMethod = [NSValue valueWithBytes:&tableContentCallbackMethod objCType:@encode(SEL)]; + [NSThread detachNewThreadSelector:@selector(loadTableValuesTaskWithCallback:) toTarget:self withObject:encodedCallbackMethod]; +} + +- (void) loadTableValuesTaskWithCallback:(id)encodedTableContentCallbackMethod { // If no table is selected, return - if(!selectedTable) return; + if (!selectedTable) return; + NSAutoreleasePool *tableLoadPool = [[NSAutoreleasePool alloc] init]; NSMutableString *queryString; NSString *queryStringBeforeLimit = nil; NSString *filterString; MCPStreamingResult *streamingResult; + SEL callbackMethod = NULL; int rowsToLoad = [[tableDataInstance statusValueForKey:@"Rows"] intValue]; - + + // Remove all items from the table + tableValuesCount = 0; + [tableContentView performSelectorOnMainThread:@selector(noteNumberOfRowsChanged) withObject:nil waitUntilDone:YES]; + [tableValues removeAllObjects]; + // Notify any listeners that a query has started [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:self]; @@ -492,7 +530,7 @@ [streamingResult release]; // If the result is empty, and a limit is active, reset the limit - if ([prefs boolForKey:SPLimitResults] && queryStringBeforeLimit && ![tableValues count]) { + if ([prefs boolForKey:SPLimitResults] && queryStringBeforeLimit && !tableValuesCount) { [limitRowsField setStringValue:@"1"]; queryString = [NSMutableString stringWithFormat:@"%@ LIMIT 0,%d", queryStringBeforeLimit, [prefs integerForKey:SPLimitResultsValue]]; [self setUsedQuery:queryString]; @@ -503,7 +541,7 @@ if ([prefs boolForKey:SPLimitResults] && ([limitRowsField intValue] > 1 - || [tableValues count] == [prefs integerForKey:SPLimitResultsValue])) + || tableValuesCount == [prefs integerForKey:SPLimitResultsValue])) { isLimited = YES; } else { @@ -518,6 +556,17 @@ // Notify listenters that the query has finished [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:self]; + + // Set up the callback if present + if (encodedTableContentCallbackMethod) { + [encodedTableContentCallbackMethod getValue:&callbackMethod]; + [self performSelectorOnMainThread:callbackMethod withObject:nil waitUntilDone:NO]; + } else { + [tableContentView reloadData]; + } + + [tableDocumentInstance endTask]; + [tableLoadPool release]; } /** @@ -683,25 +732,25 @@ // If no filter or limit is active, show just the count of rows in the table if (!isFiltered && !isLimited) { - if ([tableValues count] == 1) - [countString appendFormat:NSLocalizedString(@"%d row in table", @"text showing a single row in the result"), [tableValues count]]; + if (tableValuesCount == 1) + [countString appendFormat:NSLocalizedString(@"%d row in table", @"text showing a single row in the result"), tableValuesCount]; else - [countString appendFormat:NSLocalizedString(@"%d rows in table", @"text showing how many rows are in the result"), [tableValues count]]; + [countString appendFormat:NSLocalizedString(@"%d rows in table", @"text showing how many rows are in the result"), tableValuesCount]; // If a limit is active, display a string suggesting a limit is active } else if (!isFiltered && isLimited) { - [countString appendFormat:NSLocalizedString(@"Rows %d-%d of %@%d from table", @"text showing how many rows are in the limited result"), [limitRowsField intValue], [limitRowsField intValue]+[tableValues count]-1, maxNumRowsIsEstimate?@"~":@"", maxNumRows]; + [countString appendFormat:NSLocalizedString(@"Rows %d-%d of %@%d from table", @"text showing how many rows are in the limited result"), [limitRowsField intValue], [limitRowsField intValue]+tableValuesCount-1, maxNumRowsIsEstimate?@"~":@"", maxNumRows]; // If just a filter is active, show a count and an indication a filter is active } else if (isFiltered && !isLimited) { - if ([tableValues count] == 1) - [countString appendFormat:NSLocalizedString(@"%d row of %@%d matches filter", @"text showing how a single rows matched filter"), [tableValues count], maxNumRowsIsEstimate?@"~":@"", maxNumRows]; + if (tableValuesCount == 1) + [countString appendFormat:NSLocalizedString(@"%d row of %@%d matches filter", @"text showing how a single rows matched filter"), tableValuesCount, maxNumRowsIsEstimate?@"~":@"", maxNumRows]; else - [countString appendFormat:NSLocalizedString(@"%d rows of %@%d match filter", @"text showing how many rows matched filter"), [tableValues count], maxNumRowsIsEstimate?@"~":@"", maxNumRows]; + [countString appendFormat:NSLocalizedString(@"%d rows of %@%d match filter", @"text showing how many rows matched filter"), tableValuesCount, maxNumRowsIsEstimate?@"~":@"", maxNumRows]; // If both a filter and limit is active, display full string } else { - [countString appendFormat:NSLocalizedString(@"Rows %d-%d from filtered matches", @"text showing how many rows are in the limited filter match"), [limitRowsField intValue], [limitRowsField intValue]+[tableValues count]-1]; + [countString appendFormat:NSLocalizedString(@"Rows %d-%d from filtered matches", @"text showing how many rows are in the limited filter match"), [limitRowsField intValue], [limitRowsField intValue]+tableValuesCount-1]; } // If rows are selected, append selection count @@ -743,6 +792,7 @@ */ - (IBAction)filterTable:(id)sender { + if ([tableDocumentInstance isWorking]) return; // Check whether a save of the current row is required. if (![self saveRowOnDeselect]) return; @@ -762,11 +812,8 @@ } // Reload data using the new filter settings - [self loadTableValues]; - - // Reset the table view [tableContentView scrollPoint:NSMakePoint(0.0, 0.0)]; - [tableContentView reloadData]; + [self loadTableValuesWithCallback:NULL]; } /** @@ -858,6 +905,7 @@ } } [tableValues addObject:newRow]; + tableValuesCount++; [tableContentView reloadData]; [tableContentView selectRowIndexes:[NSIndexSet indexSetWithIndex:[tableContentView numberOfRows]-1] byExtendingSelection:NO]; @@ -1096,6 +1144,8 @@ */ - (void)clickLinkArrow:(SPTextAndLinkCell *)theArrowCell { + if ([tableDocumentInstance isWorking]) return; + if ([theArrowCell getClickedColumn] == NSNotFound || [theArrowCell getClickedRow] == NSNotFound) return; int dataColumnIndex = [[[[tableContentView tableColumns] objectAtIndex:[theArrowCell getClickedColumn]] identifier] intValue]; @@ -1318,11 +1368,8 @@ NSMutableArray *columnBlobStatuses = [[NSMutableArray alloc] init]; NSUInteger i; - // Update the progress wheel every ~15% - NSUInteger loadingIndicatorDelta = 15; - - NSUInteger lastProgressValue = loadingIndicatorDelta; float relativeTargetRowCount = 100.0/targetRowCount; + NSUInteger nextTableDisplayBoundary = 50; long rowsProcessed = 0; long columnsCount = [dataColumns count]; @@ -1336,12 +1383,6 @@ [columnBlobStatuses addObject:[NSNumber numberWithBool:[tableDataInstance columnIsBlobOrText:[NSArrayObjectAtIndex(dataColumns, i) objectForKey:@"name"] ]]]; } - // Remove all items from the table and reset the progress indicator - [tableValues removeAllObjects]; - if (targetRowCount) [dataLoadingIndicator setIndeterminate:NO]; - [dataLoadingIndicator setDoubleValue:(int)loadingIndicatorDelta/2]; - [dataLoadingIndicator display]; - // Set up an autorelease pool for row processing dataLoadingPool = [[NSAutoreleasePool alloc] init]; @@ -1351,6 +1392,7 @@ // Add values for hidden blob and text fields if appropriate if ( prefsLoadBlobsAsNeeded ) { NSMutableArrayAddObject(tableValues, [NSMutableArray arrayWithCapacity:columnsCount]); + tableValuesCount++; newRow = NSArrayObjectAtIndex(tableValues, rowsProcessed); for ( i = 0 ; i < columnsCount ; i++ ) { if ( [NSArrayObjectAtIndex(columnBlobStatuses, i) boolValue] ) { @@ -1363,18 +1405,22 @@ // Otherwise just add the new row } else { NSMutableArrayAddObject(tableValues, [NSMutableArray arrayWithArray:tempRow]); + tableValuesCount++; } - // Update the progress bar as necessary, minimising updates + // Update the task interface as necessary rowsProcessed++; - if (rowsProcessed < targetRowCount) { - [dataLoadingIndicator setDoubleValue:(rowsProcessed*relativeTargetRowCount)]; - if ((int)[dataLoadingIndicator doubleValue] > lastProgressValue) { - [dataLoadingIndicator display]; - lastProgressValue = (int)[dataLoadingIndicator doubleValue] + loadingIndicatorDelta; + if (!isFiltered) { + if (rowsProcessed < targetRowCount) { + [tableDocumentInstance performSelectorOnMainThread:@selector(setTaskPercentage:) withObject:[NSNumber numberWithFloat:(rowsProcessed*relativeTargetRowCount)] waitUntilDone:NO]; + } else if (rowsProcessed == targetRowCount) { + [tableDocumentInstance performSelectorOnMainThread:@selector(setTaskProgressToIndeterminate) withObject:nil waitUntilDone:NO]; + } + + if (rowsProcessed > nextTableDisplayBoundary) { + [tableContentView reloadData]; + nextTableDisplayBoundary *= 3; } - } else if (rowsProcessed == targetRowCount) { - [dataLoadingIndicator setIndeterminate:YES]; } // Drain and reset the autorelease pool every ~1024 rows @@ -1520,9 +1566,8 @@ // New row created successfully if ( isEditingNewRow ) { if ( [prefs boolForKey:SPReloadAfterAddingRow] ) { - [self loadTableValues]; [tableWindow endEditingFor:nil]; - [tableContentView reloadData]; + [self loadTableValuesWithCallback:NULL]; } else { // Set the insertId for fields with auto_increment @@ -1539,9 +1584,8 @@ // Reload table if set to - otherwise no action required. if ( [prefs boolForKey:SPReloadAfterEditingRow] ) { - [self loadTableValues]; [tableWindow endEditingFor:nil]; - [tableContentView reloadData]; + [self loadTableValuesWithCallback:NULL]; } } currentlyEditingRow = -1; @@ -1749,6 +1793,7 @@ withObject:[NSMutableArray arrayWithArray:oldRow]]; isEditingRow = NO; } else { + tableValuesCount--; [tableValues removeObjectAtIndex:[tableContentView selectedRow]]; isEditingRow = NO; isEditingNewRow = NO; @@ -1963,20 +2008,25 @@ // Refresh table content if ( errors || reloadAfterRemovingRow ) { - [self loadTableValues]; - [tableContentView reloadData]; + [self loadTableValuesWithCallback:@selector(finalizeRowDeletion)]; } else { - for ( i = 0 ; i < [tableValues count] ; i++ ) { + for ( i = 0 ; i < tableValuesCount ; i++ ) { if ( ![selectedRows containsIndex:i] ) [tempResult addObject:NSArrayObjectAtIndex(tableValues, i)]; } + tableValuesCount = [tempResult count]; [tableValues setArray:tempResult]; + [tableContentView deselectAll:self]; [tableContentView reloadData]; } - [tableContentView deselectAll:self]; } } } +- (void) finalizeRowDeletion +{ + [tableContentView deselectAll:self]; + [tableContentView reloadData]; +} /** * Show Error sheet (can be called from inside of a endSheet selector) @@ -2176,7 +2226,7 @@ // For unfiltered and non-limited tables, use the result count - and update the status count if (!isLimited && !isFiltered) { - maxNumRows = [tableValues count]; + maxNumRows = tableValuesCount; maxNumRowsIsEstimate = NO; [tableDataInstance setStatusValue:[NSString stringWithFormat:@"%d", maxNumRows] forKey:@"Rows"]; [tableDataInstance setStatusValue:@"y" forKey:@"RowsCountAccurate"]; @@ -2209,7 +2259,7 @@ if (checkStatusCount) { NSInteger foundMaxRows; if ([prefs boolForKey:SPLimitResults]) { - foundMaxRows = [limitRowsField intValue] - 1 + [tableValues count]; + foundMaxRows = [limitRowsField intValue] - 1 + tableValuesCount; if (foundMaxRows > maxNumRows) { if (foundMaxRows == [limitRowsField intValue] - 1 + [prefs integerForKey:SPLimitResultsValue]) { maxNumRows = foundMaxRows + 1; @@ -2219,8 +2269,8 @@ maxNumRowsIsEstimate = NO; } } - } else if ([tableValues count] > maxNumRows) { - maxNumRows = [tableValues count]; + } else if (tableValuesCount > maxNumRows) { + maxNumRows = tableValuesCount; maxNumRowsIsEstimate = YES; } [tableDataInstance setStatusValue:[NSString stringWithFormat:@"%d", maxNumRows] forKey:@"Rows"]; @@ -2284,11 +2334,13 @@ - (int)numberOfRowsInTableView:(NSTableView *)aTableView { - return [tableValues count]; + return tableValuesCount; } - (id)tableView:(CMCopyTable *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex { + if (rowIndex >= tableValuesCount) return nil; + id theValue = NSArrayObjectAtIndex(NSArrayObjectAtIndex(tableValues, rowIndex), [[aTableColumn identifier] intValue]); if ([theValue isKindOfClass:[NSData class]]) @@ -2309,6 +2361,7 @@ - (void)tableView:(CMCopyTable *)aTableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn*)aTableColumn row:(int)row { + if (row >= tableValuesCount) return; if (![cell respondsToSelector:@selector(setTextColor:)]) return; // If user wants to edit 'cell' set text color to black and return to avoid @@ -2384,9 +2437,6 @@ if (sortCol) [sortCol release]; sortCol = [[NSNumber alloc] initWithInt:[[tableColumn identifier] intValue]]; - // Update data using the new sort order - [self loadTableValues]; - if ( ![[mySQLConnection getLastErrorMessage] isEqualToString:@""] ) { NSBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil, [NSString stringWithFormat:NSLocalizedString(@"Couldn't sort table. MySQL said: %@", @"message of panel when sorting of table failed"), [mySQLConnection getLastErrorMessage]]); @@ -2401,7 +2451,8 @@ [tableContentView setIndicatorImage:[NSImage imageNamed:@"NSAscendingSortIndicator"] inTableColumn:tableColumn]; } - [tableContentView reloadData]; + // Update data using the new sort order + [self loadTableValuesWithCallback:NULL]; } - (void)tableViewSelectionDidChange:(NSNotification *)aNotification @@ -2588,6 +2639,46 @@ } #pragma mark - +#pragma mark Task interaction + +/** + * Disable all content interactive elements during an ongoing task. + */ +- (void) startDocumentTaskForTab:(NSNotification *)aNotification +{ + + // Only disable elements if the current tab is the content view + if (![[aNotification object] isEqualToString:@"SwitchToTableContentToolbarItemIdentifier"]) return; + + [tableContentView setEnabled:NO]; + [addButton setEnabled:NO]; + [removeButton setEnabled:NO]; + [copyButton setEnabled:NO]; + [reloadButton setEnabled:NO]; + [filterButton setEnabled:NO]; +} + +/** + * Enable all content interactive elements after an ongoing task. + */ +- (void) endDocumentTaskForTab:(NSNotification *)aNotification +{ + + // Only enable elements if the current tab is the content view + if (![[aNotification object] isEqualToString:@"SwitchToTableContentToolbarItemIdentifier"]) return; + + [tableContentView setEnabled:YES]; + if ( ![[[tableDataInstance statusValues] objectForKey:@"Rows"] isNSNull] && selectedTable && [selectedTable length] && [tableDataInstance tableEncoding]) [addButton setEnabled:YES]; + if ([tableContentView numberOfSelectedRows] > 0) { + [removeButton setEnabled:YES]; + [copyButton setEnabled:YES]; + } + [reloadButton setEnabled:YES]; + [filterButton setEnabled:[fieldField isEnabled]]; + [tableContentView setNeedsDisplay:YES]; +} + +#pragma mark - #pragma mark Other methods /* @@ -2692,6 +2783,8 @@ // Last but not least - (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [tableValues release]; [dataColumns release]; [oldRow release]; diff --git a/Source/TableDocument.h b/Source/TableDocument.h index 465d49b7..6720527a 100644 --- a/Source/TableDocument.h +++ b/Source/TableDocument.h @@ -68,6 +68,11 @@ enum sp_current_query_mode IBOutlet id variablesSheet; IBOutlet id queryProgressBar; + IBOutlet NSBox *taskProgressLayer; + IBOutlet id taskProgressIndicator; + IBOutlet id taskDescriptionText; + IBOutlet NSButton *taskCancelButton; + IBOutlet id favoritesButton; IBOutlet id databaseNameField; @@ -129,9 +134,16 @@ enum sp_current_query_mode BOOL _encodingViaLatin1; BOOL _shouldOpenConnectionAutomatically; BOOL _isConnected; + BOOL _isWorking; BOOL _mainNibLoaded; int _queryMode; + BOOL taskDisplayIsIndeterminate; + float taskProgressValue; + float taskDisplayLastValue; + float taskProgressValueDisplayInterval; + NSTimer *taskDrawTimer; + NSToolbar *mainToolbar; NSToolbarItem *chooseDatabaseToolbarItem; @@ -171,6 +183,15 @@ enum sp_current_query_mode - (IBAction)openCurrentConnectionInNewWindow:(id)sender; - (NSArray *)allDatabaseNames; +// Task progress and notification methods +- (void) startTaskWithDescription:(NSString *)description; +- (void) showTaskProgressLayer:(NSTimer *)theTimer; +- (void) setTaskDescription:(NSString *)description; +- (void) setTaskPercentage:(NSNumber *)taskPercentage; +- (void) setTaskProgressToIndeterminate; +- (void) endTask; +- (BOOL) isWorking; + // Encoding methods - (void)setConnectionEncoding:(NSString *)mysqlEncoding reloadingViews:(BOOL)reloadViews; - (NSString *)databaseEncoding; diff --git a/Source/TableDocument.m b/Source/TableDocument.m index 6b9e5eb4..cec12d09 100644 --- a/Source/TableDocument.m +++ b/Source/TableDocument.m @@ -51,6 +51,7 @@ #import "SPUserManager.h" #import "SPEncodingPopupAccessory.h" #import "SPConstants.h" +#import "YRKSpinningProgressIndicator.h" // Used for printing #import "MGTemplateEngine.h" @@ -73,6 +74,7 @@ _mainNibLoaded = NO; _encoding = [[NSString alloc] initWithString:@"utf8"]; _isConnected = NO; + _isWorking = NO; _queryMode = SP_QUERYMODE_INTERFACE; chooseDatabaseButton = nil; chooseDatabaseToolbarItem = nil; @@ -92,6 +94,12 @@ spfPreferences = [[NSMutableDictionary alloc] init]; spfDocData = [[NSMutableDictionary alloc] init]; + taskDisplayIsIndeterminate = YES; + taskDisplayLastValue = 0; + taskProgressValue = 0; + taskProgressValueDisplayInterval = 5; + taskDrawTimer = nil; + keyChainID = nil; } @@ -196,6 +204,15 @@ if (![NSBundle loadNibNamed:@"ConnectionErrorDialog" owner:self]) { NSLog(@"Connection error dialog could not be loaded; connection failure handling will not function correctly."); } + if (![NSBundle loadNibNamed:@"ProgressIndicatorLayer" owner:self]) { + NSLog(@"Progress indicator layer could not be loaded; progress display will not function correctly."); + } + + // Set up the progress indicator layer - add to main window, change indicator color and size + [taskProgressLayer setHidden:YES]; + [taskProgressLayer setFrame:[contentViewSplitter frame]]; + [[tableWindow contentView] addSubview:taskProgressLayer]; + [taskProgressIndicator setForeColor:[NSColor whiteColor]]; } - (void)initWithConnectionFile:(NSString *)path @@ -1174,6 +1191,111 @@ } #pragma mark - +#pragma mark Task progress and notification methods + +/** + * Start a document-wide task, providing a short task description for + * display to the user. This sets the document into working mode, + * preventing many actions, and shows an indeterminate progress interface + * to the user. + */ +- (void) startTaskWithDescription:(NSString *)description +{ + + // Set flags and prevent further UI interaction in this window + _isWorking = YES; + [dbTablesTableView setEnabled:NO]; + [historyControl setEnabled:NO]; + [[NSNotificationCenter defaultCenter] postNotificationName:SPDocumentTaskStartNotification object:[mainToolbar selectedItemIdentifier]]; + + // Reset the progress indicator + taskDisplayIsIndeterminate = YES; + [taskProgressIndicator setIndeterminate:YES]; + [taskProgressIndicator animate:self]; + [taskProgressIndicator startAnimation:self]; + taskDisplayLastValue = 0; + + // Set the descriptive label and schedule appearance in the near future + [taskDescriptionText setStringValue:description]; + taskDrawTimer = [[NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:@selector(showTaskProgressLayer:) userInfo:nil repeats:NO] retain]; +} + +/** + * Show the task progress layer - after a small delay to minimise flicker. + */ +- (void) showTaskProgressLayer:(NSTimer *)theTimer +{ + [taskProgressLayer setHidden:NO]; + [taskProgressLayer display]; + [taskDrawTimer release], taskDrawTimer = nil; +} + +/** + * Updates the task description shown to the user. + */ +- (void) setTaskDescription:(NSString *)description +{ + [taskDescriptionText setStringValue:description]; +} + +/** + * Sets the task percentage progress - the first call to this automatically + * switches the progress display to determinate. + */ +- (void) setTaskPercentage:(NSNumber *)taskPercentage +{ + taskProgressValue = [taskPercentage floatValue]; + if (taskProgressValue > taskDisplayLastValue + taskProgressValueDisplayInterval + || taskProgressValue < taskDisplayLastValue - taskProgressValueDisplayInterval) + { + [taskProgressIndicator setDoubleValue:taskProgressValue]; + taskDisplayLastValue = taskProgressValue; + } +} + +/** + * Sets the task progress indicator back to indeterminate (also performed + * automatically whenever a new task is started) + */ +- (void) setTaskProgressToIndeterminate +{ + if (taskDisplayIsIndeterminate) return; + taskDisplayIsIndeterminate = YES; + [taskProgressIndicator setIndeterminate:YES]; + [taskProgressIndicator startAnimation:self]; + taskDisplayLastValue = 0; +} + +/** + * Hide the task progress and restore the document to allow actions again. + */ +- (void) endTask +{ + + // Cancel the draw timer if it exists + if (taskDrawTimer) [taskDrawTimer invalidate], [taskDrawTimer release], taskDrawTimer = nil; + + // Hide the task interface + if (taskDisplayIsIndeterminate) [taskProgressIndicator stopAnimation:self]; + [taskProgressLayer setHidden:YES]; + + // Re-enable window interface + _isWorking = NO; + [dbTablesTableView setEnabled:YES]; + [historyControl setEnabled:YES]; + [[NSNotificationCenter defaultCenter] postNotificationName:SPDocumentTaskEndNotification object:[mainToolbar selectedItemIdentifier]]; +} + +/** + * Returns whether the document is busy performing a task - allows UI or actions + * to be restricted as appropriate. + */ +- (BOOL) isWorking +{ + return _isWorking; +} + +#pragma mark - #pragma mark Encoding Methods /** @@ -2643,7 +2765,7 @@ */ - (BOOL)validateMenuItem:(NSMenuItem *)menuItem { - if (!_isConnected) { + if (!_isConnected || _isWorking) { return ([menuItem action] == @selector(newDocument:) || [menuItem action] == @selector(terminate:)); } @@ -3130,7 +3252,7 @@ */ - (BOOL)validateToolbarItem:(NSToolbarItem *)toolbarItem; { - if (!_isConnected) return NO; + if (!_isConnected || _isWorking) return NO; NSString *identifier = [toolbarItem itemIdentifier]; @@ -3222,7 +3344,9 @@ */ - (BOOL)windowShouldClose:(id)sender { - if ( ![tablesListInstance selectionShouldChangeInTableView:nil] ) { + if (_isWorking) { + return NO; + } else if ( ![tablesListInstance selectionShouldChangeInTableView:nil] ) { return NO; } else { @@ -3485,6 +3609,7 @@ if (selectedDatabase) [selectedDatabase release]; if (mySQLVersion) [mySQLVersion release]; [allDatabases release]; + if (taskDrawTimer) [taskDrawTimer release]; if(queryEditorInitString) [queryEditorInitString release]; if(spfSession) [spfSession release]; if(userManagerInstance) [userManagerInstance release]; diff --git a/Source/TablesList.h b/Source/TablesList.h index 9a9272a3..d0349531 100644 --- a/Source/TablesList.h +++ b/Source/TablesList.h @@ -69,6 +69,9 @@ enum sp_table_types IBOutlet id tableNameField; IBOutlet id tableEncodingButton; IBOutlet id tableTypeButton; + IBOutlet id toolbarAddButton; + IBOutlet id toolbarActionsButton; + IBOutlet id toolbarReloadButton; IBOutlet id addTableButton; IBOutlet id tableRenameSheet; IBOutlet id tableRenameField; @@ -148,4 +151,8 @@ enum sp_table_types - (void) clearFilter; - (IBAction) updateFilter:(id)sender; +// Task interaction +- (void) startDocumentTaskForTab:(NSNotification *)aNotification; +- (void) endDocumentTaskForTab:(NSNotification *)aNotification; + @end diff --git a/Source/TablesList.m b/Source/TablesList.m index 348fb033..4e2aecef 100644 --- a/Source/TablesList.m +++ b/Source/TablesList.m @@ -1535,6 +1535,31 @@ } #pragma mark - +#pragma mark Task interaction + +/** + * Disable all table list interactive elements during an ongoing task. + */ +- (void) startDocumentTaskForTab:(NSNotification *)aNotification +{ + [tablesListView setEnabled:NO]; + [toolbarAddButton setEnabled:NO]; + [toolbarActionsButton setEnabled:NO]; + [toolbarReloadButton setEnabled:NO]; +} + +/** + * Enable all table list interactive elements after an ongoing task. + */ +- (void) endDocumentTaskForTab:(NSNotification *)aNotification +{ + [tablesListView setEnabled:YES]; + [toolbarAddButton setEnabled:YES]; + [toolbarActionsButton setEnabled:YES]; + [toolbarReloadButton setEnabled:YES]; +} + +#pragma mark - #pragma mark SplitView Delegate Methods - (NSRect)splitView:(NSSplitView *)splitView effectiveRect:(NSRect)proposedEffectiveRect forDrawnRect:(NSRect)drawnRect ofDividerAtIndex:(NSInteger)dividerIndex @@ -1594,6 +1619,15 @@ [tableListFilterSplitView setCollapsibleSubviewCollapsed:YES]; } + // Add observers for document task activity + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(startDocumentTaskForTab:) + name:SPDocumentTaskStartNotification + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(endDocumentTaskForTab:) + name:SPDocumentTaskEndNotification + object:nil]; } /** @@ -1601,6 +1635,8 @@ */ - (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [tables release]; [tableTypes release]; if (isTableListFiltered && filteredTables) [filteredTables release]; diff --git a/Source/YRKSpinningProgressIndicator.h b/Source/YRKSpinningProgressIndicator.h new file mode 100644 index 00000000..fae46208 --- /dev/null +++ b/Source/YRKSpinningProgressIndicator.h @@ -0,0 +1,45 @@ +// +// YRKSpinningProgressIndicator.h +// +// Copyright 2009 Kelan Champagne. All rights reserved. +// + +#import <Cocoa/Cocoa.h> + + +@interface YRKSpinningProgressIndicator : NSView { + int _position; + int _numFins; + + BOOL _isIndeterminate; + double _currentValue; + double _maxValue; + + BOOL _isAnimating; + NSThread *_animationThread; + + NSColor *_foreColor; + NSColor *_backColor; + BOOL _drawBackground; +} +- (void)animate:(id)sender; +- (void)stopAnimation:(id)sender; +- (void)startAnimation:(id)sender; + +- (NSColor *)foreColor; +- (void)setForeColor:(NSColor *)value; + +- (NSColor *)backColor; +- (void)setBackColor:(NSColor *)value; + +- (BOOL)drawBackground; +- (void)setDrawBackground:(BOOL)value; + +- (BOOL)isIndeterminate; +- (void)setIndeterminate:(BOOL)isIndeterminate; + +- (double)doubleValue; +- (void)setDoubleValue:(double)doubleValue; +- (double)maxValue; +- (void)setMaxValue:(double)maxValue; +@end diff --git a/Source/YRKSpinningProgressIndicator.m b/Source/YRKSpinningProgressIndicator.m new file mode 100644 index 00000000..f3ccab87 --- /dev/null +++ b/Source/YRKSpinningProgressIndicator.m @@ -0,0 +1,304 @@ +// +// YRKSpinningProgressIndicator.m +// +// Original drawing code by Kelan Champagne; forked by Rowan Beentje +// for fixes, determinate mode, and threaded drawing. +// +// Copyright (c) 2009, Kelan Champagne (http://yeahrightkeller.com) +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of the <organization> nor the +// names of its contributors may be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY Kelan Champagne ''AS IS'' AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL Kelan Champagne BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#import "YRKSpinningProgressIndicator.h" + + +@interface YRKSpinningProgressIndicator (YRKSpinningProgressIndicatorPrivate) + +- (void) animateInBackgroundThread; + +@end + + +@implementation YRKSpinningProgressIndicator + +- (id)initWithFrame:(NSRect)frame +{ + self = [super initWithFrame:frame]; + if (self) { + _position = 0; + _numFins = 12; + _isAnimating = NO; + _animationThread = nil; + _foreColor = nil; + _backColor = nil; + _isIndeterminate = YES; + _currentValue = 0.0; + _maxValue = 100.0; + } + return self; +} + +- (void) dealloc { + if (_foreColor) [_foreColor release]; + if (_backColor) [_backColor release]; + if (_isAnimating) [self stopAnimation:self]; + [super dealloc]; +} + +- (void)viewDidMoveToWindow +{ + [super viewDidMoveToWindow]; + + if ([self window] == nil) { + // No window? View hierarchy may be going away. Ensure animation is stopped. + [self stopAnimation:self]; + } + else if (_isAnimating) { + [self stopAnimation:self]; + [self startAnimation:self]; + } +} + +- (void)drawRect:(NSRect)rect +{ + int i; + float alpha = 1.0; + + // Determine size based on current bounds + NSSize size = [self bounds].size; + float maxSize; + if(size.width >= size.height) + maxSize = size.height; + else + maxSize = size.width; + + // fill the background, if set + if(_drawBackground) { + [_backColor set]; + [NSBezierPath fillRect:[self bounds]]; + } + + CGContextRef currentContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; + [NSGraphicsContext saveGraphicsState]; + + // Move the CTM so 0,0 is at the center of our bounds + CGContextTranslateCTM(currentContext,[self bounds].size.width/2,[self bounds].size.height/2); + + if (_isIndeterminate) { + + // do initial rotation to start place + CGContextRotateCTM(currentContext, 3.14159*2/_numFins * _position); + + NSBezierPath *path = [[NSBezierPath alloc] init]; + float lineWidth = 0.08 * maxSize; // should be 2.75 for 32x32 + float lineStart = 0.234375 * maxSize; // should be 7.5 for 32x32 + float lineEnd = 0.421875 * maxSize; // should be 13.5 for 32x32 + [path setLineWidth:lineWidth]; + [path setLineCapStyle:NSRoundLineCapStyle]; + [path moveToPoint:NSMakePoint(0,lineStart)]; + [path lineToPoint:NSMakePoint(0,lineEnd)]; + + for (i=0; i<_numFins; i++) { + if(_isAnimating) { + [[_foreColor colorWithAlphaComponent:alpha] set]; + } else { + [[_foreColor colorWithAlphaComponent:0.2] set]; + } + + [path stroke]; + + // we draw all the fins by rotating the CTM, then just redraw the same segment again + CGContextRotateCTM(currentContext, 6.282185/_numFins); + alpha -= 1.0/_numFins; + } + [path release]; + + } else { + + float lineWidth = 1 + (0.01 * maxSize); + float circleRadius = (maxSize - lineWidth) / 2.1; + NSPoint circleCenter = NSMakePoint(0, 0); + [[_foreColor colorWithAlphaComponent:alpha] set]; + NSBezierPath *path = [[NSBezierPath alloc] init]; + [path setLineWidth:lineWidth]; + [path appendBezierPathWithOvalInRect:NSMakeRect(-circleRadius, -circleRadius, circleRadius*2, circleRadius*2)]; + [path stroke]; + [path release]; + path = [[NSBezierPath alloc] init]; + [path appendBezierPathWithArcWithCenter:circleCenter radius:circleRadius startAngle:90 endAngle:90-(360*(_currentValue/_maxValue)) clockwise:YES]; + [path lineToPoint:circleCenter] ; + [path fill]; + [path release]; + } + + [NSGraphicsContext restoreGraphicsState]; +} + +# pragma mark - +# pragma mark Subclass + +- (void)animate:(id)sender +{ + if(_position > 1) { + _position--; + } + else { + _position = _numFins; + } + [self display]; +} + +- (void) animateInBackgroundThread +{ + NSAutoreleasePool *animationPool = [[NSAutoreleasePool alloc] init]; + + // Set up the animation speed to subtly change with size > 32. + int animationDelay = 38000 + (2000 * ([self bounds].size.height / 32)); + int poolFlushCounter = 0; + + do { + [self animate:nil]; + usleep(animationDelay); + poolFlushCounter++; + if (poolFlushCounter > 256) { + [animationPool drain]; + animationPool = [[NSAutoreleasePool alloc] init]; + poolFlushCounter = 0; + } + } while (![[NSThread currentThread] isCancelled]); + + [animationPool release]; +} + +- (void)startAnimation:(id)sender +{ + _isAnimating = YES; + + _animationThread = [[NSThread alloc] initWithTarget:self selector:@selector(animateInBackgroundThread) object:nil]; + [_animationThread start]; +} + +- (void)stopAnimation:(id)sender +{ + _isAnimating = NO; + if (_animationThread) { + [_animationThread cancel]; + if (![_animationThread isFinished]) { + [[NSRunLoop currentRunLoop] runMode:NSModalPanelRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.05]]; + } + [_animationThread release], _animationThread = nil; + } + + [self setNeedsDisplay:YES]; +} + +# pragma mark Not Implemented + +- (void)setStyle:(NSProgressIndicatorStyle)style +{ + if (NSProgressIndicatorSpinningStyle != style) { + NSAssert(NO, @"Non-spinning styles not available."); + } +} + + +# pragma mark - +# pragma mark Accessors + +- (NSColor *)foreColor +{ + return [[_foreColor retain] autorelease]; +} + +- (void)setForeColor:(NSColor *)value +{ + if (_foreColor != value) { + [_foreColor release]; + _foreColor = [value copy]; + [self setNeedsDisplay:YES]; + } +} + +- (NSColor *)backColor +{ + return [[_backColor retain] autorelease]; +} + +- (void)setBackColor:(NSColor *)value +{ + if (_backColor != value) { + [_backColor release]; + _backColor = [value copy]; + [self setNeedsDisplay:YES]; + } +} + +- (BOOL)drawBackground +{ + return _drawBackground; +} + +- (void)setDrawBackground:(BOOL)value +{ + if (_drawBackground != value) { + _drawBackground = value; + } + [self setNeedsDisplay:YES]; +} + +- (BOOL)isIndeterminate +{ + return _isIndeterminate; +} + +- (void)setIndeterminate:(BOOL)isIndeterminate +{ + _isIndeterminate = isIndeterminate; + if (!_isIndeterminate && _isAnimating) [self stopAnimation:self]; + [self displayIfNeeded]; +} + +- (double)doubleValue +{ + return _currentValue; +} + +- (void)setDoubleValue:(double)doubleValue +{ + if (_isIndeterminate) _isIndeterminate = NO; + _currentValue = doubleValue; + [self displayIfNeeded]; +} + +- (double)maxValue +{ + return _maxValue; +} + +- (void)setMaxValue:(double)maxValue +{ + _maxValue = maxValue; + [self displayIfNeeded]; +} + +@end |