aboutsummaryrefslogtreecommitdiffstats
path: root/Source
diff options
context:
space:
mode:
authorrowanbeentje <rowan@beent.je>2009-10-20 23:45:16 +0000
committerrowanbeentje <rowan@beent.je>2009-10-20 23:45:16 +0000
commit951330376a7b1e85dea0c44825534a4ab598c100 (patch)
tree1f098f10b4eebd660c9e2fcf1ea2926f1efbc24b /Source
parent53ca17c5bba2d57ecaee3a8ccd005fe4d6a4abe3 (diff)
downloadsequelpro-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.h57
-rw-r--r--Source/AMIndeterminateProgressIndicatorCell.m189
-rw-r--r--Source/SPConstants.h2
-rw-r--r--Source/SPConstants.m2
-rw-r--r--Source/TableContent.h10
-rw-r--r--Source/TableContent.m199
-rw-r--r--Source/TableDocument.h21
-rw-r--r--Source/TableDocument.m131
-rw-r--r--Source/TablesList.h7
-rw-r--r--Source/TablesList.m36
-rw-r--r--Source/YRKSpinningProgressIndicator.h45
-rw-r--r--Source/YRKSpinningProgressIndicator.m304
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