From 105715893e83c8cfa07ecbc65d54cbef183667b3 Mon Sep 17 00:00:00 2001 From: mrsteveman1 Date: Sat, 4 Jun 2011 00:50:24 -0400 Subject: [PATCH 01/46] needs to be created here --- BitTicker/BitTickerAppDelegate.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BitTicker/BitTickerAppDelegate.m b/BitTicker/BitTickerAppDelegate.m index c49da18..d9888fb 100644 --- a/BitTicker/BitTickerAppDelegate.m +++ b/BitTicker/BitTickerAppDelegate.m @@ -368,7 +368,7 @@ -(void)bitcoinMarket:(BitcoinMarket*)market didReceiveTicker:(Ticker*)ticker { double ask = [ticker.sell doubleValue]; double bid = [ticker.buy doubleValue]; - spread = [NSNumber numberWithDouble:ask-bid]; + NSNumber *spread = [NSNumber numberWithDouble:ask-bid]; [spreadValue setStringValue:[currencyFormatter stringFromNumber:spread]]; } From 6e2e72cd7ca366d37a4f99d76ae8c0648d659612 Mon Sep 17 00:00:00 2001 From: mrsteveman1 Date: Sat, 4 Jun 2011 01:30:06 -0400 Subject: [PATCH 02/46] singleton for settings --- BitTicker.xcodeproj/project.pbxproj | 6 ++ BitTicker/BitTickerAppDelegate.h | 11 +-- BitTicker/BitTickerAppDelegate.m | 51 ++------------ BitTicker/BitcoinMarket.h | 3 +- BitTicker/BitcoinMarket.m | 4 +- BitTicker/MtGoxMarket.m | 6 +- BitTicker/SharedSettings.h | 21 ++++++ BitTicker/SharedSettings.m | 104 ++++++++++++++++++++++++++++ BitTicker/en.lproj/MainMenu.xib | 101 +++++++++++++++++++-------- 9 files changed, 216 insertions(+), 91 deletions(-) create mode 100644 BitTicker/SharedSettings.h create mode 100644 BitTicker/SharedSettings.m diff --git a/BitTicker.xcodeproj/project.pbxproj b/BitTicker.xcodeproj/project.pbxproj index 0d31b75..c2ba630 100644 --- a/BitTicker.xcodeproj/project.pbxproj +++ b/BitTicker.xcodeproj/project.pbxproj @@ -35,6 +35,7 @@ AA6C46D6137E10B2000A1DCB /* SBJsonTokeniser.m in Sources */ = {isa = PBXBuildFile; fileRef = AA6C46CC137E10B2000A1DCB /* SBJsonTokeniser.m */; }; AA6C46D7137E10B2000A1DCB /* SBJsonWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = AA6C46CE137E10B2000A1DCB /* SBJsonWriter.m */; }; AA6E6C4D1399DC3E003A4224 /* Credits.html in Resources */ = {isa = PBXBuildFile; fileRef = AA6E6C4C1399DC3E003A4224 /* Credits.html */; }; + AA6E6C831399F2EB003A4224 /* SharedSettings.m in Sources */ = {isa = PBXBuildFile; fileRef = AA6E6C821399F2EB003A4224 /* SharedSettings.m */; }; AAD172641399BA1E00B505B0 /* EMKeychainItem.m in Sources */ = {isa = PBXBuildFile; fileRef = AAD172631399BA1D00B505B0 /* EMKeychainItem.m */; }; AAD1726D1399BD3500B505B0 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AAD1726C1399BD3500B505B0 /* Security.framework */; }; AAD64420137B680E00589EAC /* Logo.icns in Resources */ = {isa = PBXBuildFile; fileRef = AAD6441F137B680E00589EAC /* Logo.icns */; }; @@ -96,6 +97,8 @@ AA6C46CD137E10B2000A1DCB /* SBJsonWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonWriter.h; sourceTree = ""; }; AA6C46CE137E10B2000A1DCB /* SBJsonWriter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonWriter.m; sourceTree = ""; }; AA6E6C4C1399DC3E003A4224 /* Credits.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = Credits.html; sourceTree = ""; }; + AA6E6C811399F2EB003A4224 /* SharedSettings.h */ = {isa = PBXFileReference; fileEncoding = 4; path = SharedSettings.h; sourceTree = ""; }; + AA6E6C821399F2EB003A4224 /* SharedSettings.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SharedSettings.m; sourceTree = ""; }; AAD172621399BA1D00B505B0 /* EMKeychainItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EMKeychainItem.h; sourceTree = ""; }; AAD172631399BA1D00B505B0 /* EMKeychainItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EMKeychainItem.m; sourceTree = ""; }; AAD1726C1399BD3500B505B0 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; @@ -239,6 +242,8 @@ AA4BBA3E1379E31C005CE351 /* BitTickerAppDelegate.h */, AA4BBA3F1379E31C005CE351 /* BitTickerAppDelegate.m */, AA4BBA411379E31C005CE351 /* MainMenu.xib */, + AA6E6C811399F2EB003A4224 /* SharedSettings.h */, + AA6E6C821399F2EB003A4224 /* SharedSettings.m */, AA4BBA331379E31B005CE351 /* Supporting Files */, ); path = BitTicker; @@ -341,6 +346,7 @@ 83405B3B137F6DDF0060CAD4 /* BitcoinMarket.m in Sources */, 83405B3C137F6DDF0060CAD4 /* MtGoxMarket.m in Sources */, AAD172641399BA1E00B505B0 /* EMKeychainItem.m in Sources */, + AA6E6C831399F2EB003A4224 /* SharedSettings.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/BitTicker/BitTickerAppDelegate.h b/BitTicker/BitTickerAppDelegate.h index cec0fef..ba8ee36 100644 --- a/BitTicker/BitTickerAppDelegate.h +++ b/BitTicker/BitTickerAppDelegate.h @@ -18,7 +18,7 @@ */ #import - +#import "SharedSettings.h" @class Ticker; @class StatusItemView; @class RequestHandler; @@ -28,6 +28,7 @@ #import "BitcoinMarketDelegate.h" @interface BitTickerAppDelegate : NSObject { + SharedSettings *sharedSettingManager; MtGoxMarket *market; NSTimer *tickerTimer; @@ -63,11 +64,6 @@ NSNumberFormatter *currencyFormatter; - //Settings - NSString *username; - NSString *password; - NSString *selected_market; - //User interface stuff IBOutlet NSWindow *settings_window; IBOutlet NSTextField *username_field; @@ -79,9 +75,6 @@ - (void)refreshTicker:(id)sender; - (IBAction)showSettings:(id)sender; - (IBAction)showAbout:(id)sender; -@property (retain) NSString *username; -@property (retain) NSString *password; -@property (retain) NSString *selected_market; @property (retain, nonatomic) NSNumber *tickerValue; @property (retain) NSMutableArray *stats; @property (nonatomic) NSInteger cancelThread; diff --git a/BitTicker/BitTickerAppDelegate.m b/BitTicker/BitTickerAppDelegate.m index d9888fb..28d209d 100644 --- a/BitTicker/BitTickerAppDelegate.m +++ b/BitTicker/BitTickerAppDelegate.m @@ -20,15 +20,15 @@ #import "BitTickerAppDelegate.h" #import "Ticker.h" #import "StatusItemView.h" -#import "EMKeychainItem.h" #import "MtGoxMarket.h" +#import "SharedSettings.h" @implementation BitTickerAppDelegate @synthesize stats; @synthesize cancelThread; @synthesize tickerValue; -@dynamic username,password,selected_market; + - (void)awakeFromNib { _statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength]; @@ -240,18 +240,8 @@ - (void)awakeFromNib { currencyFormatter.hasThousandSeparators = YES; currencyFormatter.minimumFractionDigits = 4; // TODO: Configurable - - if ([[NSUserDefaults standardUserDefaults] boolForKey:@"AlreadyRan"]) { - - } else { - [[NSUserDefaults standardUserDefaults] setBool:TRUE forKey:@"AlreadyRan"]; - [[NSUserDefaults standardUserDefaults] setObject:@"ChangeMe" forKey:@"username"]; - [[NSUserDefaults standardUserDefaults] setObject:@"MtGox" forKey:@"selected_market"]; - [[NSUserDefaults standardUserDefaults] synchronize]; - if (!self.password) { - self.password = @"ChangeMe"; - } - } + sharedSettingManager = [SharedSettings sharedSettingManager]; + MSLog(@"Starting"); market = [[MtGoxMarket alloc] initWithDelegate:self]; @@ -297,39 +287,6 @@ - (IBAction)showSettings:(id)sender { [NSApp activateIgnoringOtherApps:YES]; } -- (NSString *)username { - return [[NSUserDefaults standardUserDefaults] objectForKey:@"username"]; -} - -- (void)setUsername:(NSString *)newusername { - [[NSUserDefaults standardUserDefaults] setObject:newusername forKey:@"username"]; - [[NSUserDefaults standardUserDefaults] synchronize]; - NSLog(@"Set Username: %@",newusername); -} - -- (NSString *)password { - EMGenericKeychainItem *keychainItem = [EMGenericKeychainItem genericKeychainItemForService:@"BitTicker-MtGox" withUsername:self.username]; - return keychainItem.password; -} - -- (void)setPassword:(NSString *)newpassword { - EMGenericKeychainItem *keychainItem = [EMGenericKeychainItem genericKeychainItemForService:@"BitTicker-MtGox" withUsername:self.username]; - keychainItem.password = newpassword; - NSLog(@"Set password: %@",newpassword); -} - -- (NSString *)selected_market { - return [[NSUserDefaults standardUserDefaults] objectForKey:@"selected_market"]; -} - -- (void)setSelected_market:(NSString *)newmarket { - [[NSUserDefaults standardUserDefaults] setObject:newmarket forKey:@"selected_market"]; - [[NSUserDefaults standardUserDefaults] synchronize]; - NSLog(@"Set Market: %@",newmarket); -} - - - #pragma mark Actions - (void)quitProgram:(id)sender { [NSApp terminate:self]; diff --git a/BitTicker/BitcoinMarket.h b/BitTicker/BitcoinMarket.h index 8cb90cc..3d15e05 100644 --- a/BitTicker/BitcoinMarket.h +++ b/BitTicker/BitcoinMarket.h @@ -10,7 +10,7 @@ #import "BitcoinMarketDelegate.h" #import "RequestHandlerDelegate.h" - +#import "SharedSettings.h" @class RequestHandler; @interface BitcoinMarket : NSObject { @@ -18,6 +18,7 @@ id _delegate; NSMutableDictionary *_selectorMap; + SharedSettings *sharedSettingManager; } -(id)initWithDelegate:(id)delegate; diff --git a/BitTicker/BitcoinMarket.m b/BitTicker/BitcoinMarket.m index 2045dee..0d58e45 100644 --- a/BitTicker/BitcoinMarket.m +++ b/BitTicker/BitcoinMarket.m @@ -11,7 +11,7 @@ #import "RequestHandler.h" #import "JSON.h" - +#import "SharedSettings.h" @implementation BitcoinMarket @synthesize delegate=_delegate; @@ -23,7 +23,7 @@ -(id)initWithDelegate:(id)delegate { self.delegate = delegate; _selectorMap = [[NSMutableDictionary alloc] init]; - + sharedSettingManager = [SharedSettings sharedSettingManager]; return self; } diff --git a/BitTicker/MtGoxMarket.m b/BitTicker/MtGoxMarket.m index d73305c..e5a7e32 100644 --- a/BitTicker/MtGoxMarket.m +++ b/BitTicker/MtGoxMarket.m @@ -10,7 +10,6 @@ #import "Trade.h" #import "Ticker.h" -#import "EMKeychainItem.h" #define MTGOX_TICKER_URL @"https://mtgox.com/code/data/ticker.php" #define MTGOX_TRADES_URL @"https://mtgox.com/code/data/getTrades.php" @@ -41,10 +40,7 @@ -(void)fetchMarketDepth { -(void)fetchWallet { MSLog(@"Fetching wallet..."); - NSString *username = [[NSUserDefaults standardUserDefaults] objectForKey:@"username"]; - EMGenericKeychainItem *keychainItem = [EMGenericKeychainItem genericKeychainItemForService:@"BitTicker-MtGox" withUsername:username]; - NSString *pass = keychainItem.password; - NSDictionary *post = [NSDictionary dictionaryWithObjectsAndKeys:username,@"name",pass,@"pass",nil]; + NSDictionary *post = [NSDictionary dictionaryWithObjectsAndKeys:sharedSettingManager.username,@"name",sharedSettingManager.password,@"pass",nil]; [self downloadJsonDataFromURL:[NSURL URLWithString:MTGOX_WALLET_URL] withPostData:post callback:@selector(didFetchWallet:)]; } diff --git a/BitTicker/SharedSettings.h b/BitTicker/SharedSettings.h new file mode 100644 index 0000000..678f7ab --- /dev/null +++ b/BitTicker/SharedSettings.h @@ -0,0 +1,21 @@ +// +// SharedSettings.h +// +// Copyright 2011 Stephen Oliver . All rights reserved. +// + +#import + +@interface SharedSettings : NSObject { + + //Settings + NSString *username; + NSString *password; + NSString *selected_market; +} + ++ (id)sharedSettingManager; +@property (retain) NSString *username; +@property (retain) NSString *password; +@property (retain) NSString *selected_market; +@end diff --git a/BitTicker/SharedSettings.m b/BitTicker/SharedSettings.m new file mode 100644 index 0000000..be60054 --- /dev/null +++ b/BitTicker/SharedSettings.m @@ -0,0 +1,104 @@ +// +// SharedSettings.m +// +// Copyright 2011 Stephen Oliver . All rights reserved. +// + +#import "SharedSettings.h" +#import "EMKeychainItem.h" + +static SharedSettings *sharedSettingManager = nil; + +@implementation SharedSettings + +@dynamic username,password,selected_market; + +- (id)init { + NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init]; + if ((self = [super init])) { + + } + [autoreleasepool release]; + return self; +} + +- (void) checkDefaults { + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"AlreadyRan"]) { + + } else { + [[NSUserDefaults standardUserDefaults] setBool:TRUE forKey:@"AlreadyRan"]; + [[NSUserDefaults standardUserDefaults] setObject:@"ChangeMe" forKey:@"username"]; + [[NSUserDefaults standardUserDefaults] setObject:@"MtGox" forKey:@"selected_market"]; + [[NSUserDefaults standardUserDefaults] synchronize]; + if (!self.password) { + self.password = @"ChangeMe"; + } + } +} + +- (NSString *)username { + return [[NSUserDefaults standardUserDefaults] objectForKey:@"username"]; +} + +- (void)setUsername:(NSString *)newusername { + [[NSUserDefaults standardUserDefaults] setObject:newusername forKey:@"username"]; + [[NSUserDefaults standardUserDefaults] synchronize]; + NSLog(@"Set Username: %@",newusername); +} + +- (NSString *)password { + EMGenericKeychainItem *keychainItem = [EMGenericKeychainItem genericKeychainItemForService:@"BitTicker-MtGox" withUsername:self.username]; + return keychainItem.password; +} + +- (void)setPassword:(NSString *)newpassword { + EMGenericKeychainItem *keychainItem = [EMGenericKeychainItem genericKeychainItemForService:@"BitTicker-MtGox" withUsername:self.username]; + keychainItem.password = newpassword; + NSLog(@"Set password: %@",newpassword); +} + +- (NSString *)selected_market { + return [[NSUserDefaults standardUserDefaults] objectForKey:@"selected_market"]; +} + +- (void)setSelected_market:(NSString *)newmarket { + [[NSUserDefaults standardUserDefaults] setObject:newmarket forKey:@"selected_market"]; + [[NSUserDefaults standardUserDefaults] synchronize]; + NSLog(@"Set Market: %@",newmarket); +} + ++ (id)sharedSettingManager { + @synchronized(self) { + if(sharedSettingManager == nil) + sharedSettingManager = [[super allocWithZone:NULL] init]; + } + return sharedSettingManager; +} + ++ (id)allocWithZone:(NSZone *)zone { + return [[self sharedSettingManager] retain]; +} +- (id)copyWithZone:(NSZone *)zone { + return self; +} +- (id)retain { + return self; +} +- (NSUInteger)retainCount { + return UINT_MAX; //denotes an object that cannot be released +} + +- (void)release { + // never release +} +- (id)autorelease { + return self; +} + + +-(void)dealloc { + [super dealloc]; +} + +@end + diff --git a/BitTicker/en.lproj/MainMenu.xib b/BitTicker/en.lproj/MainMenu.xib index a6ae037..c0730c8 100644 --- a/BitTicker/en.lproj/MainMenu.xib +++ b/BitTicker/en.lproj/MainMenu.xib @@ -362,6 +362,9 @@ YES + + SharedSettings + @@ -424,55 +427,63 @@ - value: self.password - - + value: self.selected_market + + - - - value: self.password + + + value: self.selected_market value - self.password + self.selected_market + + NSNullPlaceholder + MtGox + 2 - 713 + 721 value: self.username - + - + value: self.username value self.username + + NSNullPlaceholder + ChangeMe + 2 - 716 + 726 - value: self.selected_market - - + value: self.password + + - - - value: self.selected_market + + + value: self.password value - self.selected_market + self.password NSNullPlaceholder - MtGox + ChangeMe 2 - 718 + 727 @@ -677,6 +688,11 @@ + + 719 + + + @@ -725,6 +741,7 @@ 700.IBComboBoxObjectValuesKey.objectValues 700.IBPluginDependency 705.IBPluginDependency + 719.IBPluginDependency YES @@ -773,6 +790,7 @@ com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin @@ -787,7 +805,7 @@ - 718 + 727 @@ -796,14 +814,35 @@ BitTickerAppDelegate NSObject - showSettings: - id + YES + + YES + showAbout: + showSettings: + + + YES + id + id + - showSettings: - - showSettings: - id + YES + + YES + showAbout: + showSettings: + + + YES + + showAbout: + id + + + showSettings: + id + @@ -857,6 +896,14 @@ ./Classes/BitTickerAppDelegate.h + + SharedSettings + NSObject + + IBProjectSource + ./Classes/SharedSettings.h + + 0 From f909419de37edabb81141b320828252cfd93fe11 Mon Sep 17 00:00:00 2001 From: mrsteveman1 Date: Sat, 4 Jun 2011 01:34:22 -0400 Subject: [PATCH 03/46] method def for default check, run it at start --- BitTicker/BitTickerAppDelegate.m | 1 + BitTicker/SharedSettings.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/BitTicker/BitTickerAppDelegate.m b/BitTicker/BitTickerAppDelegate.m index 28d209d..170e894 100644 --- a/BitTicker/BitTickerAppDelegate.m +++ b/BitTicker/BitTickerAppDelegate.m @@ -241,6 +241,7 @@ - (void)awakeFromNib { currencyFormatter.minimumFractionDigits = 4; // TODO: Configurable sharedSettingManager = [SharedSettings sharedSettingManager]; + [sharedSettingManager checkDefaults]; MSLog(@"Starting"); market = [[MtGoxMarket alloc] initWithDelegate:self]; diff --git a/BitTicker/SharedSettings.h b/BitTicker/SharedSettings.h index 678f7ab..098d24b 100644 --- a/BitTicker/SharedSettings.h +++ b/BitTicker/SharedSettings.h @@ -13,7 +13,7 @@ NSString *password; NSString *selected_market; } - +- (void) checkDefaults; + (id)sharedSettingManager; @property (retain) NSString *username; @property (retain) NSString *password; From 2c870b263392a3d1e0ad61b681ab84235fe06414 Mon Sep 17 00:00:00 2001 From: mrsteveman1 Date: Sat, 4 Jun 2011 13:33:09 -0400 Subject: [PATCH 04/46] make urls a readonly property of the BitcoinMarket class, subclasses set their local instance variable on creation, then urls can be accessed with market.tickerURL regardless of which market is in use --- BitTicker/BitcoinMarket.h | 10 ++++++++++ BitTicker/BitcoinMarket.m | 4 ++++ BitTicker/MtGoxMarket.h | 4 +++- BitTicker/MtGoxMarket.m | 18 +++++++++++++----- 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/BitTicker/BitcoinMarket.h b/BitTicker/BitcoinMarket.h index 3d15e05..3abe921 100644 --- a/BitTicker/BitcoinMarket.h +++ b/BitTicker/BitcoinMarket.h @@ -19,6 +19,11 @@ NSMutableDictionary *_selectorMap; SharedSettings *sharedSettingManager; + + NSString *_tradeURL; + NSString *_tickerURL; + NSString *_depthURL; + NSString *_walletURL; } -(id)initWithDelegate:(id)delegate; @@ -36,4 +41,9 @@ @property (nonatomic, assign) id delegate; +@property (readonly,nonatomic,retain) NSString *tradeURL; +@property (readonly,nonatomic,retain) NSString *tickerURL; +@property (readonly,nonatomic,retain) NSString *depthURL; +@property (readonly,nonatomic,retain) NSString *walletURL; + @end diff --git a/BitTicker/BitcoinMarket.m b/BitTicker/BitcoinMarket.m index 0d58e45..3159c26 100644 --- a/BitTicker/BitcoinMarket.m +++ b/BitTicker/BitcoinMarket.m @@ -15,6 +15,10 @@ @implementation BitcoinMarket @synthesize delegate=_delegate; +@synthesize tickerURL = _tickerURL; +@synthesize tradeURL = _tradeURL; +@synthesize walletURL = _walletURL; +@synthesize depthURL = _depthURL; -(id)initWithDelegate:(id)delegate { if (!(self = [super init])) return self; diff --git a/BitTicker/MtGoxMarket.h b/BitTicker/MtGoxMarket.h index 444a1f2..34a8a57 100644 --- a/BitTicker/MtGoxMarket.h +++ b/BitTicker/MtGoxMarket.h @@ -11,7 +11,9 @@ #import "BitcoinMarket.h" @interface MtGoxMarket : BitcoinMarket { - + } +-(id)initWithDelegate:(id)delegate; + @end diff --git a/BitTicker/MtGoxMarket.m b/BitTicker/MtGoxMarket.m index e5a7e32..dbc75c4 100644 --- a/BitTicker/MtGoxMarket.m +++ b/BitTicker/MtGoxMarket.m @@ -10,38 +10,46 @@ #import "Trade.h" #import "Ticker.h" - #define MTGOX_TICKER_URL @"https://mtgox.com/code/data/ticker.php" #define MTGOX_TRADES_URL @"https://mtgox.com/code/data/getTrades.php" #define MTGOX_MARKETDEPTH_URL @"https://mtgox.com/code/data/getDepth.php" #define MTGOX_WALLET_URL @"https://mtgox.com/code/getFunds.php" + @interface MtGoxMarket (Private) -(NSString*)makeURLStringWithSuffix:(NSString*)suffix; @end @implementation MtGoxMarket +-(id)initWithDelegate:(id)delegate { + if (!(self = [super initWithDelegate:delegate])) return self; + _tickerURL = MTGOX_TICKER_URL; + _tradeURL = MTGOX_TRADES_URL; + _depthURL = MTGOX_MARKETDEPTH_URL; + _walletURL = MTGOX_WALLET_URL; + return self; +} -(void)fetchRecentTrades { MSLog(@"Fetching recent trades..."); - [self downloadJsonDataFromURL:[NSURL URLWithString:MTGOX_TRADES_URL] callback:@selector(didFetchRecentTrades:)]; + [self downloadJsonDataFromURL:[NSURL URLWithString:self.tradeURL] callback:@selector(didFetchRecentTrades:)]; } -(void)fetchTicker { MSLog(@"Fetching ticker..."); - [self downloadJsonDataFromURL:[NSURL URLWithString:MTGOX_TICKER_URL] callback:@selector(didFetchTickerData:)]; + [self downloadJsonDataFromURL:[NSURL URLWithString:self.tickerURL] callback:@selector(didFetchTickerData:)]; } -(void)fetchMarketDepth { MSLog(@"Fetching market depth..."); - [self downloadJsonDataFromURL:[NSURL URLWithString:MTGOX_MARKETDEPTH_URL] callback:@selector(didFetchMarketDepth:)]; + [self downloadJsonDataFromURL:[NSURL URLWithString:self.depthURL] callback:@selector(didFetchMarketDepth:)]; } -(void)fetchWallet { MSLog(@"Fetching wallet..."); NSDictionary *post = [NSDictionary dictionaryWithObjectsAndKeys:sharedSettingManager.username,@"name",sharedSettingManager.password,@"pass",nil]; - [self downloadJsonDataFromURL:[NSURL URLWithString:MTGOX_WALLET_URL] withPostData:post callback:@selector(didFetchWallet:)]; + [self downloadJsonDataFromURL:[NSURL URLWithString:self.walletURL] withPostData:post callback:@selector(didFetchWallet:)]; } -(void)didFetchRecentTrades:(NSArray*)tradeData { From 5655d36fe69426f681b561b0cbd290b5868ef28e Mon Sep 17 00:00:00 2001 From: mrsteveman1 Date: Sat, 4 Jun 2011 20:49:54 -0400 Subject: [PATCH 05/46] fix for posting data in the bitcoinmarket class, turn on wallet fetching (still not exposed in UI) --- BitTicker/BitTickerAppDelegate.m | 4 ++-- BitTicker/BitcoinMarket.m | 4 +++- BitTicker/MtGoxMarket.m | 1 - 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/BitTicker/BitTickerAppDelegate.m b/BitTicker/BitTickerAppDelegate.m index 170e894..849e50e 100644 --- a/BitTicker/BitTickerAppDelegate.m +++ b/BitTicker/BitTickerAppDelegate.m @@ -247,10 +247,10 @@ - (void)awakeFromNib { market = [[MtGoxMarket alloc] initWithDelegate:self]; tickerTimer = [[NSTimer scheduledTimerWithTimeInterval:30 target:market selector:@selector(fetchTicker) userInfo:nil repeats:YES] retain]; - //walletTimer = [[NSTimer scheduledTimerWithTimeInterval:30 target:market selector:@selector(fetchWallet) userInfo:nil repeats:YES] retain]; + walletTimer = [[NSTimer scheduledTimerWithTimeInterval:60 target:market selector:@selector(fetchWallet) userInfo:nil repeats:YES] retain]; [market fetchTicker]; - //[market fetchWallet]; + [market fetchWallet]; } #pragma mark Application delegate diff --git a/BitTicker/BitcoinMarket.m b/BitTicker/BitcoinMarket.m index 3159c26..e45b37b 100644 --- a/BitTicker/BitcoinMarket.m +++ b/BitTicker/BitcoinMarket.m @@ -53,9 +53,11 @@ -(void)downloadJsonDataFromURL:(NSURL *)url withPostData:(NSDictionary*)postData BOOL appendAmpersand = NO; for(NSString *key in postData) { NSString *value = [postData objectForKey:key]; - [body appendFormat:@"%@=%@%@",key,value,appendAmpersand?@"&":@""]; + [body appendFormat:@"%@=%@%@",key,value,appendAmpersand?@"":@"&"]; if (!appendAmpersand) appendAmpersand = YES; } + + [urlRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"content-type"]; } else { [urlRequest setHTTPMethod:@"GET"]; } diff --git a/BitTicker/MtGoxMarket.m b/BitTicker/MtGoxMarket.m index dbc75c4..4a9604f 100644 --- a/BitTicker/MtGoxMarket.m +++ b/BitTicker/MtGoxMarket.m @@ -100,7 +100,6 @@ -(void)didFetchMarketDepth:(NSDictionary*)marketDepth { } -(void)didFetchWallet:(NSDictionary *)wallet { - NSLog(@"Dict: %@",wallet); [_delegate bitcoinMarket:self didReceiveWallet:wallet]; } From 13cd0b4f4bfa2115285f23aee9190e3328967ba2 Mon Sep 17 00:00:00 2001 From: mrsteveman1 Date: Sat, 4 Jun 2011 20:55:45 -0400 Subject: [PATCH 06/46] dont fetch wallet if username or password are defaults or null --- BitTicker/MtGoxMarket.m | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/BitTicker/MtGoxMarket.m b/BitTicker/MtGoxMarket.m index 4a9604f..c4bc10d 100644 --- a/BitTicker/MtGoxMarket.m +++ b/BitTicker/MtGoxMarket.m @@ -48,8 +48,16 @@ -(void)fetchMarketDepth { -(void)fetchWallet { MSLog(@"Fetching wallet..."); - NSDictionary *post = [NSDictionary dictionaryWithObjectsAndKeys:sharedSettingManager.username,@"name",sharedSettingManager.password,@"pass",nil]; - [self downloadJsonDataFromURL:[NSURL URLWithString:self.walletURL] withPostData:post callback:@selector(didFetchWallet:)]; + NSString *username = sharedSettingManager.username; + NSString *password = sharedSettingManager.password; + if ([username isEqualToString:@"ChangeMe"] || username == nil) { + return; + } + if ([password isEqualToString:@"ChangeMe"] || password == nil) { + return; + } + NSDictionary *post = [NSDictionary dictionaryWithObjectsAndKeys:username,@"name",password,@"pass",nil]; + [self downloadJsonDataFromURL:[NSURL URLWithString:self.walletURL] withPostData:post callback:@selector(didFetchWallet:)]; } -(void)didFetchRecentTrades:(NSArray*)tradeData { From 0483d827d4126d8d8727b87952aefa29ce17344f Mon Sep 17 00:00:00 2001 From: mrsteveman1 Date: Sat, 4 Jun 2011 21:59:17 -0400 Subject: [PATCH 07/46] add wallet class, show wallet info in the menu --- BitTicker.xcodeproj/project.pbxproj | 8 +- BitTicker/BitTickerAppDelegate.h | 11 +- BitTicker/BitTickerAppDelegate.m | 160 +++++++++++++++++++++++++++- BitTicker/BitcoinMarketDelegate.h | 3 +- BitTicker/MtGoxMarket.m | 9 +- BitTicker/Wallet.h | 22 ++++ BitTicker/Wallet.m | 18 ++++ 7 files changed, 222 insertions(+), 9 deletions(-) create mode 100644 BitTicker/Wallet.h create mode 100644 BitTicker/Wallet.m diff --git a/BitTicker.xcodeproj/project.pbxproj b/BitTicker.xcodeproj/project.pbxproj index c2ba630..cf38b51 100644 --- a/BitTicker.xcodeproj/project.pbxproj +++ b/BitTicker.xcodeproj/project.pbxproj @@ -36,6 +36,7 @@ AA6C46D7137E10B2000A1DCB /* SBJsonWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = AA6C46CE137E10B2000A1DCB /* SBJsonWriter.m */; }; AA6E6C4D1399DC3E003A4224 /* Credits.html in Resources */ = {isa = PBXBuildFile; fileRef = AA6E6C4C1399DC3E003A4224 /* Credits.html */; }; AA6E6C831399F2EB003A4224 /* SharedSettings.m in Sources */ = {isa = PBXBuildFile; fileRef = AA6E6C821399F2EB003A4224 /* SharedSettings.m */; }; + AAB399B3139B0D8500B9438F /* Wallet.m in Sources */ = {isa = PBXBuildFile; fileRef = AAB399B2139B0D8500B9438F /* Wallet.m */; }; AAD172641399BA1E00B505B0 /* EMKeychainItem.m in Sources */ = {isa = PBXBuildFile; fileRef = AAD172631399BA1D00B505B0 /* EMKeychainItem.m */; }; AAD1726D1399BD3500B505B0 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AAD1726C1399BD3500B505B0 /* Security.framework */; }; AAD64420137B680E00589EAC /* Logo.icns in Resources */ = {isa = PBXBuildFile; fileRef = AAD6441F137B680E00589EAC /* Logo.icns */; }; @@ -97,8 +98,10 @@ AA6C46CD137E10B2000A1DCB /* SBJsonWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonWriter.h; sourceTree = ""; }; AA6C46CE137E10B2000A1DCB /* SBJsonWriter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonWriter.m; sourceTree = ""; }; AA6E6C4C1399DC3E003A4224 /* Credits.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = Credits.html; sourceTree = ""; }; - AA6E6C811399F2EB003A4224 /* SharedSettings.h */ = {isa = PBXFileReference; fileEncoding = 4; path = SharedSettings.h; sourceTree = ""; }; + AA6E6C811399F2EB003A4224 /* SharedSettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SharedSettings.h; sourceTree = ""; }; AA6E6C821399F2EB003A4224 /* SharedSettings.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SharedSettings.m; sourceTree = ""; }; + AAB399B1139B0D8500B9438F /* Wallet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Wallet.h; path = ../../../SourceCache/BitTicker/BitTicker/Wallet.h; sourceTree = ""; }; + AAB399B2139B0D8500B9438F /* Wallet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Wallet.m; path = ../../../SourceCache/BitTicker/BitTicker/Wallet.m; sourceTree = ""; }; AAD172621399BA1D00B505B0 /* EMKeychainItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EMKeychainItem.h; sourceTree = ""; }; AAD172631399BA1D00B505B0 /* EMKeychainItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EMKeychainItem.m; sourceTree = ""; }; AAD1726C1399BD3500B505B0 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; @@ -182,6 +185,8 @@ 83405B36137F6DDF0060CAD4 /* BitcoinMarket.m */, 83405B37137F6DDF0060CAD4 /* MtGoxMarket.h */, 83405B38137F6DDF0060CAD4 /* MtGoxMarket.m */, + AAB399B1139B0D8500B9438F /* Wallet.h */, + AAB399B2139B0D8500B9438F /* Wallet.m */, ); name = Models; sourceTree = ""; @@ -347,6 +352,7 @@ 83405B3C137F6DDF0060CAD4 /* MtGoxMarket.m in Sources */, AAD172641399BA1E00B505B0 /* EMKeychainItem.m in Sources */, AA6E6C831399F2EB003A4224 /* SharedSettings.m in Sources */, + AAB399B3139B0D8500B9438F /* Wallet.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/BitTicker/BitTickerAppDelegate.h b/BitTicker/BitTickerAppDelegate.h index ba8ee36..de6b034 100644 --- a/BitTicker/BitTickerAppDelegate.h +++ b/BitTicker/BitTickerAppDelegate.h @@ -46,13 +46,22 @@ NSTextField *sellValue; NSTextField *lastValue; NSTextField *spreadValue; + + //wallet stuff + NSTextField *BTCValue; + NSTextField *BTCxUSDValue; + NSTextField *USDValue; + NSTextField *walletUSDValue; NSView *statsView; NSMenuItem *statsItem; NSView *technicalsView; NSMenuItem *technicalsItem; - + + NSView *walletView; + NSMenuItem *walletItem; + // below the line NSMenuItem *quitItem; NSMenuItem *aboutItem; diff --git a/BitTicker/BitTickerAppDelegate.m b/BitTicker/BitTickerAppDelegate.m index 849e50e..d2a2399 100644 --- a/BitTicker/BitTickerAppDelegate.m +++ b/BitTicker/BitTickerAppDelegate.m @@ -19,6 +19,7 @@ #import "BitTickerAppDelegate.h" #import "Ticker.h" +#import "Wallet.h" #import "StatusItemView.h" #import "MtGoxMarket.h" #import "SharedSettings.h" @@ -40,6 +41,9 @@ - (void)awakeFromNib { @"Status Item Tooltip")]; [_statusItem setView:statusItemView]; + sharedSettingManager = [SharedSettings sharedSettingManager]; + [sharedSettingManager checkDefaults]; + // menu stuff trayMenu = [[NSMenu alloc] initWithTitle:@"Ticker"]; //graphItem = [[NSMenuItem alloc] init]; @@ -50,8 +54,12 @@ - (void)awakeFromNib { NSString *menuFont = @"LucidaGrande"; NSInteger menuFontSize = 12; + NSString *headerFont = @"LucidaGrande-Bold"; + NSInteger headerFontSize = 13; + NSInteger menuHeight = 15; NSInteger labelWidth = 70; + NSInteger headerWidth = 120; NSInteger valueWidth = 60; NSInteger labelOffset = 20; @@ -211,6 +219,132 @@ - (void)awakeFromNib { [spreadLabel release]; [trayMenu addItem:[NSMenuItem separatorItem]]; + + + + walletItem = [[NSMenuItem alloc] init]; + walletView = [[NSView alloc] initWithFrame:CGRectMake(0,0,180,75)]; + [walletItem setView:walletView]; + [trayMenu addItem:walletItem]; + + + + //section header + NSTextField *walletSectionLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,64,headerWidth,menuHeight)]; + [walletSectionLabel setEditable:FALSE]; + [walletSectionLabel setBordered:NO]; + [walletSectionLabel setAlignment:NSLeftTextAlignment]; + [walletSectionLabel setBackgroundColor:[NSColor clearColor]]; + [walletSectionLabel setStringValue:[NSString stringWithFormat:@"%@ Wallet",sharedSettingManager.selected_market]]; + [walletSectionLabel setTextColor:[NSColor blueColor]]; + [walletSectionLabel setFont:[NSFont fontWithName:headerFont size:headerFontSize]]; + [walletView addSubview:walletSectionLabel]; + [walletSectionLabel release]; + + + + + + + BTCValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 45, valueWidth, menuHeight)]; + [BTCValue setEditable:FALSE]; + [BTCValue setBordered:NO]; + [BTCValue setAlignment:NSRightTextAlignment]; + [BTCValue setBackgroundColor:[NSColor clearColor]]; + [BTCValue setTextColor:[NSColor blackColor]]; + [BTCValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [walletView addSubview:BTCValue]; + + NSTextField *BTCLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,45,labelWidth,menuHeight)]; + [BTCLabel setEditable:FALSE]; + [BTCLabel setBordered:NO]; + [BTCLabel setAlignment:NSLeftTextAlignment]; + [BTCLabel setBackgroundColor:[NSColor clearColor]]; + [BTCLabel setStringValue:@"BTC:"]; + [BTCLabel setTextColor:[NSColor blackColor]]; + [BTCLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [walletView addSubview:BTCLabel]; + [BTCLabel release]; + + BTCxUSDValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 30, valueWidth, menuHeight)]; + [BTCxUSDValue setEditable:FALSE]; + [BTCxUSDValue setBordered:NO]; + [BTCxUSDValue setAlignment:NSRightTextAlignment]; + [BTCxUSDValue setBackgroundColor:[NSColor clearColor]]; + [BTCxUSDValue setTextColor:[NSColor blackColor]]; + [BTCxUSDValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [walletView addSubview:BTCxUSDValue]; + + NSTextField *BTCxUSDLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,30,labelWidth,menuHeight)]; + [BTCxUSDLabel setEditable:FALSE]; + [BTCxUSDLabel setBordered:NO]; + [BTCxUSDLabel setAlignment:NSLeftTextAlignment]; + [BTCxUSDLabel setBackgroundColor:[NSColor clearColor]]; + [BTCxUSDLabel setStringValue:@"BTC * Last:"]; + [BTCxUSDLabel setTextColor:[NSColor blackColor]]; + [BTCxUSDLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [walletView addSubview:BTCxUSDLabel]; + [BTCxUSDLabel release]; + + + + USDValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 15, valueWidth, menuHeight)]; + [USDValue setEditable:FALSE]; + [USDValue setBordered:NO]; + [USDValue setAlignment:NSRightTextAlignment]; + [USDValue setBackgroundColor:[NSColor clearColor]]; + [USDValue setTextColor:[NSColor blackColor]]; + [USDValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [walletView addSubview:USDValue]; + + NSTextField *USDLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,15,labelWidth,menuHeight)]; + [USDLabel setEditable:FALSE]; + [USDLabel setBordered:NO]; + [USDLabel setAlignment:NSLeftTextAlignment]; + [USDLabel setBackgroundColor:[NSColor clearColor]]; + [USDLabel setStringValue:@"USD:"]; + [USDLabel setTextColor:[NSColor blackColor]]; + [USDLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [walletView addSubview:USDLabel]; + [USDLabel release]; + + + + walletUSDValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 0, valueWidth, menuHeight)]; + [walletUSDValue setEditable:FALSE]; + [walletUSDValue setBordered:NO]; + [walletUSDValue setAlignment:NSRightTextAlignment]; + [walletUSDValue setBackgroundColor:[NSColor clearColor]]; + [walletUSDValue setTextColor:[NSColor blackColor]]; + [walletUSDValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [walletView addSubview:walletUSDValue]; + + NSTextField *walletLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,0,labelWidth,menuHeight)]; + [walletLabel setEditable:FALSE]; + [walletLabel setBordered:NO]; + [walletLabel setAlignment:NSLeftTextAlignment]; + [walletLabel setBackgroundColor:[NSColor clearColor]]; + [walletLabel setStringValue:@"Total:"]; + [walletLabel setTextColor:[NSColor blackColor]]; + [walletLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [walletView addSubview:walletLabel]; + [walletLabel release]; + + + + + + + [trayMenu addItem:[NSMenuItem separatorItem]]; + + + + + + + + + refreshItem = [trayMenu addItemWithTitle:@"Refresh" @@ -240,8 +374,7 @@ - (void)awakeFromNib { currencyFormatter.hasThousandSeparators = YES; currencyFormatter.minimumFractionDigits = 4; // TODO: Configurable - sharedSettingManager = [SharedSettings sharedSettingManager]; - [sharedSettingManager checkDefaults]; + MSLog(@"Starting"); market = [[MtGoxMarket alloc] initWithDelegate:self]; @@ -265,6 +398,7 @@ - (void)applicationWillTerminate:(NSNotification *)notification { [statsItem release]; [currencyFormatter release]; + //ticker stuff [highValue release]; [lowValue release]; [volValue release]; @@ -272,6 +406,12 @@ - (void)applicationWillTerminate:(NSNotification *)notification { [sellValue release]; [lastValue release]; [spreadValue release]; + + //wallet stuff + [BTCValue release]; + [BTCxUSDValue release]; + [USDValue release]; + [walletUSDValue release]; [technicalsItem release]; [technicalsView release]; @@ -309,6 +449,7 @@ -(void)bitcoinMarket:(BitcoinMarket*)market didReceiveInvalidResponse:(NSData*)d -(void)bitcoinMarket:(BitcoinMarket*)market didReceiveTicker:(Ticker*)ticker { [statusItemView setTickerValue:ticker.last]; + self.tickerValue = ticker.last; MSLog(@"Got mah ticker: %@",ticker); [highValue setStringValue:[currencyFormatter stringFromNumber:ticker.high]]; @@ -334,8 +475,19 @@ -(void)bitcoinMarket:(BitcoinMarket*)market didReceiveRecentTradesData:(NSArray* } --(void)bitcoinMarket:(BitcoinMarket*)market didReceiveWallet:(NSDictionary*)wallet { - +-(void)bitcoinMarket:(BitcoinMarket*)market didReceiveWallet:(Wallet*)wallet { + double btc = [wallet.btc doubleValue]; + double usd = [wallet.usd doubleValue]; + double last = [self.tickerValue doubleValue]; + [BTCValue setStringValue:[NSString stringWithFormat:@"%f.04",[wallet.btc floatValue]]]; + [USDValue setStringValue:[currencyFormatter stringFromNumber:wallet.usd]]; + + + NSNumber *BTCxRate = [NSNumber numberWithDouble:btc*last]; + [BTCxUSDValue setStringValue:[currencyFormatter stringFromNumber:BTCxRate]]; + + NSNumber *walletUSD = [NSNumber numberWithDouble:[BTCxRate doubleValue] + usd]; + [walletUSDValue setStringValue: [currencyFormatter stringFromNumber:walletUSD]]; } @end diff --git a/BitTicker/BitcoinMarketDelegate.h b/BitTicker/BitcoinMarketDelegate.h index 3bd0fd7..d01e922 100644 --- a/BitTicker/BitcoinMarketDelegate.h +++ b/BitTicker/BitcoinMarketDelegate.h @@ -10,6 +10,7 @@ @class BitcoinMarket; @class Ticker; +@class Wallet; @protocol BitcoinMarketDelegate @@ -25,5 +26,5 @@ -(void)bitcoinMarket:(BitcoinMarket*)market didReceiveTicker:(Ticker*)ticker; -(void)bitcoinMarket:(BitcoinMarket*)market didReceiveRecentTradesData:(NSArray*)trades; --(void)bitcoinMarket:(BitcoinMarket*)market didReceiveWallet:(NSDictionary*)wallet; +-(void)bitcoinMarket:(BitcoinMarket*)market didReceiveWallet:(Wallet*)wallet; @end diff --git a/BitTicker/MtGoxMarket.m b/BitTicker/MtGoxMarket.m index c4bc10d..448c19e 100644 --- a/BitTicker/MtGoxMarket.m +++ b/BitTicker/MtGoxMarket.m @@ -10,6 +10,8 @@ #import "Trade.h" #import "Ticker.h" +#import "Wallet.h" + #define MTGOX_TICKER_URL @"https://mtgox.com/code/data/ticker.php" #define MTGOX_TRADES_URL @"https://mtgox.com/code/data/getTrades.php" #define MTGOX_MARKETDEPTH_URL @"https://mtgox.com/code/data/getDepth.php" @@ -107,8 +109,11 @@ -(void)didFetchMarketDepth:(NSDictionary*)marketDepth { MSLog(@"Got %i asks and %i bids",[[marketDepth objectForKey:@"asks"] count],[[marketDepth objectForKey:@"bids"] count]); } --(void)didFetchWallet:(NSDictionary *)wallet { - [_delegate bitcoinMarket:self didReceiveWallet:wallet]; +-(void)didFetchWallet:(NSDictionary *)dictwallet { + Wallet *newwallet = [[Wallet alloc] init]; + newwallet.btc = [dictwallet objectForKey:@"btcs"]; + newwallet.usd = [dictwallet objectForKey:@"usds"]; + [_delegate bitcoinMarket:self didReceiveWallet:newwallet]; } @end diff --git a/BitTicker/Wallet.h b/BitTicker/Wallet.h new file mode 100644 index 0000000..7b847dc --- /dev/null +++ b/BitTicker/Wallet.h @@ -0,0 +1,22 @@ +// +// Wallet.h +// BitTicker +// +// Created by steve on 6/4/11. +// Copyright 2011 none. All rights reserved. +// + +#import + + +@interface Wallet : NSObject { + NSInteger _mid; // Market ID, 0 = MtGox + NSNumber *_btc; // Wallet BTC contents + NSNumber *_usd; // Wallet USD contents +} + +@property (nonatomic) NSInteger mid; +@property (nonatomic, retain) NSNumber *btc; +@property (nonatomic, retain) NSNumber *usd; + +@end diff --git a/BitTicker/Wallet.m b/BitTicker/Wallet.m new file mode 100644 index 0000000..c98027d --- /dev/null +++ b/BitTicker/Wallet.m @@ -0,0 +1,18 @@ +// +// Wallet.m +// BitTicker +// +// Created by steve on 6/4/11. +// Copyright 2011 none. All rights reserved. +// + +#import "Wallet.h" + + +@implementation Wallet + +@synthesize mid = _mid; +@synthesize btc = _btc; +@synthesize usd = _usd; + +@end From e39776239239701524f191417017e35aaf4eb8a2 Mon Sep 17 00:00:00 2001 From: Matt Stith Date: Sat, 4 Jun 2011 22:10:23 -0400 Subject: [PATCH 08/46] Fix file references --- BitTicker.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BitTicker.xcodeproj/project.pbxproj b/BitTicker.xcodeproj/project.pbxproj index cf38b51..6a1f41a 100644 --- a/BitTicker.xcodeproj/project.pbxproj +++ b/BitTicker.xcodeproj/project.pbxproj @@ -100,8 +100,8 @@ AA6E6C4C1399DC3E003A4224 /* Credits.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = Credits.html; sourceTree = ""; }; AA6E6C811399F2EB003A4224 /* SharedSettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SharedSettings.h; sourceTree = ""; }; AA6E6C821399F2EB003A4224 /* SharedSettings.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SharedSettings.m; sourceTree = ""; }; - AAB399B1139B0D8500B9438F /* Wallet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Wallet.h; path = ../../../SourceCache/BitTicker/BitTicker/Wallet.h; sourceTree = ""; }; - AAB399B2139B0D8500B9438F /* Wallet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Wallet.m; path = ../../../SourceCache/BitTicker/BitTicker/Wallet.m; sourceTree = ""; }; + AAB399B1139B0D8500B9438F /* Wallet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Wallet.h; sourceTree = ""; }; + AAB399B2139B0D8500B9438F /* Wallet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Wallet.m; sourceTree = ""; }; AAD172621399BA1D00B505B0 /* EMKeychainItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EMKeychainItem.h; sourceTree = ""; }; AAD172631399BA1D00B505B0 /* EMKeychainItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EMKeychainItem.m; sourceTree = ""; }; AAD1726C1399BD3500B505B0 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; From 34cb3fb0c39421d9eb2a1dd9177f4dd3108922ef Mon Sep 17 00:00:00 2001 From: Matt Stith Date: Sat, 4 Jun 2011 22:13:00 -0400 Subject: [PATCH 09/46] Follow naming conventions --- BitTicker/BitTickerAppDelegate.m | 2 +- BitTicker/SharedSettings.h | 4 ++-- BitTicker/SharedSettings.m | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/BitTicker/BitTickerAppDelegate.m b/BitTicker/BitTickerAppDelegate.m index d2a2399..e22ae85 100644 --- a/BitTicker/BitTickerAppDelegate.m +++ b/BitTicker/BitTickerAppDelegate.m @@ -235,7 +235,7 @@ - (void)awakeFromNib { [walletSectionLabel setBordered:NO]; [walletSectionLabel setAlignment:NSLeftTextAlignment]; [walletSectionLabel setBackgroundColor:[NSColor clearColor]]; - [walletSectionLabel setStringValue:[NSString stringWithFormat:@"%@ Wallet",sharedSettingManager.selected_market]]; + [walletSectionLabel setStringValue:[NSString stringWithFormat:@"%@ Wallet",sharedSettingManager.selectedMarket]]; [walletSectionLabel setTextColor:[NSColor blueColor]]; [walletSectionLabel setFont:[NSFont fontWithName:headerFont size:headerFontSize]]; [walletView addSubview:walletSectionLabel]; diff --git a/BitTicker/SharedSettings.h b/BitTicker/SharedSettings.h index 098d24b..394cc26 100644 --- a/BitTicker/SharedSettings.h +++ b/BitTicker/SharedSettings.h @@ -11,11 +11,11 @@ //Settings NSString *username; NSString *password; - NSString *selected_market; + NSString *selectedMarket; } - (void) checkDefaults; + (id)sharedSettingManager; @property (retain) NSString *username; @property (retain) NSString *password; -@property (retain) NSString *selected_market; +@property (retain) NSString *selectedMarket; @end diff --git a/BitTicker/SharedSettings.m b/BitTicker/SharedSettings.m index be60054..47c68fc 100644 --- a/BitTicker/SharedSettings.m +++ b/BitTicker/SharedSettings.m @@ -11,7 +11,7 @@ @implementation SharedSettings -@dynamic username,password,selected_market; +@dynamic username,password,selectedMarket; - (id)init { NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init]; @@ -28,7 +28,7 @@ - (void) checkDefaults { } else { [[NSUserDefaults standardUserDefaults] setBool:TRUE forKey:@"AlreadyRan"]; [[NSUserDefaults standardUserDefaults] setObject:@"ChangeMe" forKey:@"username"]; - [[NSUserDefaults standardUserDefaults] setObject:@"MtGox" forKey:@"selected_market"]; + [[NSUserDefaults standardUserDefaults] setObject:@"MtGox" forKey:@"selectedMarket"]; [[NSUserDefaults standardUserDefaults] synchronize]; if (!self.password) { self.password = @"ChangeMe"; @@ -57,12 +57,12 @@ - (void)setPassword:(NSString *)newpassword { NSLog(@"Set password: %@",newpassword); } -- (NSString *)selected_market { - return [[NSUserDefaults standardUserDefaults] objectForKey:@"selected_market"]; +- (NSString *)selectedMarket { + return [[NSUserDefaults standardUserDefaults] objectForKey:@"selectedMarket"]; } -- (void)setSelected_market:(NSString *)newmarket { - [[NSUserDefaults standardUserDefaults] setObject:newmarket forKey:@"selected_market"]; +- (void)setSelectedMarket:(NSString *)newmarket { + [[NSUserDefaults standardUserDefaults] setObject:newmarket forKey:@"selectedMarket"]; [[NSUserDefaults standardUserDefaults] synchronize]; NSLog(@"Set Market: %@",newmarket); } From a4f76b2b8ff2c10960bd75e3a3d6aab7c87322b6 Mon Sep 17 00:00:00 2001 From: Matt Stith Date: Sat, 4 Jun 2011 22:15:34 -0400 Subject: [PATCH 10/46] Separate property declarations to prevent merging conflicts (pet peeve :D ) --- BitTicker/SharedSettings.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/BitTicker/SharedSettings.m b/BitTicker/SharedSettings.m index 47c68fc..ebb169c 100644 --- a/BitTicker/SharedSettings.m +++ b/BitTicker/SharedSettings.m @@ -11,7 +11,9 @@ @implementation SharedSettings -@dynamic username,password,selectedMarket; +@dynamic username; +@dynamic password; +@dynamic selectedMarket; - (id)init { NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init]; From 366de811b28e494f85764eec81b7a9f2ed6a3802 Mon Sep 17 00:00:00 2001 From: Matt Stith Date: Sat, 4 Jun 2011 22:16:47 -0400 Subject: [PATCH 11/46] EMKeychainItem is a model, move it to Models group --- BitTicker.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BitTicker.xcodeproj/project.pbxproj b/BitTicker.xcodeproj/project.pbxproj index 6a1f41a..f309357 100644 --- a/BitTicker.xcodeproj/project.pbxproj +++ b/BitTicker.xcodeproj/project.pbxproj @@ -187,6 +187,8 @@ 83405B38137F6DDF0060CAD4 /* MtGoxMarket.m */, AAB399B1139B0D8500B9438F /* Wallet.h */, AAB399B2139B0D8500B9438F /* Wallet.m */, + AAD172621399BA1D00B505B0 /* EMKeychainItem.h */, + AAD172631399BA1D00B505B0 /* EMKeychainItem.m */, ); name = Models; sourceTree = ""; @@ -236,8 +238,6 @@ AA4BBA321379E31B005CE351 /* BitTicker */ = { isa = PBXGroup; children = ( - AAD172621399BA1D00B505B0 /* EMKeychainItem.h */, - AAD172631399BA1D00B505B0 /* EMKeychainItem.m */, 83405B2F137F6CFE0060CAD4 /* Models */, 83405B2B137F68870060CAD4 /* Additions */, 83405B20137F684E0060CAD4 /* Networking */, From 1ef5fb7f5aa325b800372e776e209734f2b4a4ff Mon Sep 17 00:00:00 2001 From: Matt Stith Date: Sat, 4 Jun 2011 22:23:30 -0400 Subject: [PATCH 12/46] Fix selected_market reference in xib --- BitTicker/en.lproj/MainMenu.xib | 46 ++++++++++++++++----------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/BitTicker/en.lproj/MainMenu.xib b/BitTicker/en.lproj/MainMenu.xib index c0730c8..2cc501e 100644 --- a/BitTicker/en.lproj/MainMenu.xib +++ b/BitTicker/en.lproj/MainMenu.xib @@ -3,12 +3,12 @@ 1060 10J869 - 1306 + 1305 1038.35 461.00 com.apple.InterfaceBuilder.CocoaPlugin - 1306 + 1305 YES @@ -425,26 +425,6 @@ 704 - - - value: self.selected_market - - - - - - value: self.selected_market - value - self.selected_market - - NSNullPlaceholder - MtGox - - 2 - - - 721 - value: self.username @@ -485,6 +465,26 @@ 727 + + + value: self.selectedMarket + + + + + + value: self.selectedMarket + value + self.selectedMarket + + NSNullPlaceholder + MtGox + + 2 + + + 728 + @@ -805,7 +805,7 @@ - 727 + 728 From 08e15f3a0e65667b164ec03fe382d02bdeea0667 Mon Sep 17 00:00:00 2001 From: Matt Stith Date: Sat, 4 Jun 2011 22:32:02 -0400 Subject: [PATCH 13/46] Get rid of technicals section for now --- BitTicker/BitTickerAppDelegate.h | 4 ---- BitTicker/BitTickerAppDelegate.m | 39 +------------------------------- 2 files changed, 1 insertion(+), 42 deletions(-) diff --git a/BitTicker/BitTickerAppDelegate.h b/BitTicker/BitTickerAppDelegate.h index de6b034..62d18bc 100644 --- a/BitTicker/BitTickerAppDelegate.h +++ b/BitTicker/BitTickerAppDelegate.h @@ -45,7 +45,6 @@ NSTextField *buyValue; NSTextField *sellValue; NSTextField *lastValue; - NSTextField *spreadValue; //wallet stuff NSTextField *BTCValue; @@ -55,9 +54,6 @@ NSView *statsView; NSMenuItem *statsItem; - - NSView *technicalsView; - NSMenuItem *technicalsItem; NSView *walletView; NSMenuItem *walletItem; diff --git a/BitTicker/BitTickerAppDelegate.m b/BitTicker/BitTickerAppDelegate.m index e22ae85..a89c6bf 100644 --- a/BitTicker/BitTickerAppDelegate.m +++ b/BitTicker/BitTickerAppDelegate.m @@ -192,35 +192,7 @@ - (void)awakeFromNib { [trayMenu addItem:[NSMenuItem separatorItem]]; - - technicalsItem = [[NSMenuItem alloc] init]; - technicalsView = [[NSView alloc] initWithFrame:CGRectMake(0,0,180,13)]; - [technicalsItem setView:technicalsView]; - [trayMenu addItem:technicalsItem]; - - spreadValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 0, valueWidth, menuHeight)]; - [spreadValue setEditable:FALSE]; - [spreadValue setBordered:NO]; - [spreadValue setAlignment:NSRightTextAlignment]; - [spreadValue setBackgroundColor:[NSColor clearColor]]; - [spreadValue setTextColor:[NSColor blackColor]]; - [spreadValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [technicalsView addSubview:spreadValue]; - - NSTextField *spreadLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,0,labelWidth,menuHeight)]; - [spreadLabel setEditable:FALSE]; - [spreadLabel setBordered:NO]; - [spreadLabel setAlignment:NSLeftTextAlignment]; - [spreadLabel setBackgroundColor:[NSColor clearColor]]; - [spreadLabel setStringValue:@"Spread:"]; - [spreadLabel setTextColor:[NSColor blackColor]]; - [spreadLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [technicalsView addSubview:spreadLabel]; - [spreadLabel release]; - - [trayMenu addItem:[NSMenuItem separatorItem]]; - - + walletItem = [[NSMenuItem alloc] init]; walletView = [[NSView alloc] initWithFrame:CGRectMake(0,0,180,75)]; @@ -405,7 +377,6 @@ - (void)applicationWillTerminate:(NSNotification *)notification { [buyValue release]; [sellValue release]; [lastValue release]; - [spreadValue release]; //wallet stuff [BTCValue release]; @@ -413,9 +384,6 @@ - (void)applicationWillTerminate:(NSNotification *)notification { [USDValue release]; [walletUSDValue release]; - [technicalsItem release]; - [technicalsView release]; - } - (IBAction)showAbout:(id)sender { @@ -464,11 +432,6 @@ -(void)bitcoinMarket:(BitcoinMarket*)market didReceiveTicker:(Ticker*)ticker { [volValue setStringValue:[volumeFormatter stringFromNumber:ticker.volume]]; [volumeFormatter release]; - - double ask = [ticker.sell doubleValue]; - double bid = [ticker.buy doubleValue]; - NSNumber *spread = [NSNumber numberWithDouble:ask-bid]; - [spreadValue setStringValue:[currencyFormatter stringFromNumber:spread]]; } -(void)bitcoinMarket:(BitcoinMarket*)market didReceiveRecentTradesData:(NSArray*)trades { From d20ce41ddae9180bdf164ba26a4f5f73172588c5 Mon Sep 17 00:00:00 2001 From: Matt Stith Date: Sat, 4 Jun 2011 23:20:26 -0400 Subject: [PATCH 14/46] Get rid of implicit conversion warnings --- BitTicker/EMKeychainItem.m | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/BitTicker/EMKeychainItem.m b/BitTicker/EMKeychainItem.m index 6795066..b35e4be 100644 --- a/BitTicker/EMKeychainItem.m +++ b/BitTicker/EMKeychainItem.m @@ -137,7 +137,7 @@ - (void)setPassword:(NSString *)newPassword mPassword = [newPassword copy]; const char *newPasswordCString = [newPassword UTF8String]; - SecKeychainItemModifyAttributesAndData(mCoreKeychainItem, NULL, strlen(newPasswordCString), (void *)newPasswordCString); + SecKeychainItemModifyAttributesAndData(mCoreKeychainItem, NULL, (UInt32)strlen(newPasswordCString), (void *)newPasswordCString); } } @@ -162,7 +162,7 @@ - (void)setUsername:(NSString *)newUsername mUsername = [newUsername copy]; const char *newUsernameCString = [newUsername UTF8String]; - [self _modifyAttributeWithTag:kSecAccountItemAttr toBeValue:(void *)newUsernameCString ofLength:strlen(newUsernameCString)]; + [self _modifyAttributeWithTag:kSecAccountItemAttr toBeValue:(void *)newUsernameCString ofLength:(UInt32)strlen(newUsernameCString)]; } } @@ -187,7 +187,7 @@ - (void)setLabel:(NSString *)newLabel mLabel = [newLabel copy]; const char *newLabelCString = [newLabel UTF8String]; - [self _modifyAttributeWithTag:kSecLabelItemAttr toBeValue:(void *)newLabelCString ofLength:strlen(newLabelCString)]; + [self _modifyAttributeWithTag:kSecLabelItemAttr toBeValue:(void *)newLabelCString ofLength:(UInt32)strlen(newLabelCString)]; } } @@ -258,7 +258,7 @@ + (EMGenericKeychainItem *)genericKeychainItemForService:(NSString *)serviceName char *password = nil; SecKeychainItemRef item = nil; - OSStatus returnStatus = SecKeychainFindGenericPassword(NULL, strlen(serviceNameCString), serviceNameCString, strlen(usernameCString), usernameCString, &passwordLength, (void **)&password, &item); + OSStatus returnStatus = SecKeychainFindGenericPassword(NULL, (UInt32)strlen(serviceNameCString), serviceNameCString, (UInt32)strlen(usernameCString), usernameCString, &passwordLength, (void **)&password, &item); if (returnStatus != noErr || !item) { if (_logsErrors) @@ -283,7 +283,7 @@ + (EMGenericKeychainItem *)addGenericKeychainItemForService:(NSString *)serviceN const char *passwordCString = [password UTF8String]; SecKeychainItemRef item = nil; - OSStatus returnStatus = SecKeychainAddGenericPassword(NULL, strlen(serviceNameCString), serviceNameCString, strlen(usernameCString), usernameCString, strlen(passwordCString), (void *)passwordCString, &item); + OSStatus returnStatus = SecKeychainAddGenericPassword(NULL, (UInt32)strlen(serviceNameCString), serviceNameCString, (UInt32)strlen(usernameCString), usernameCString, (UInt32)strlen(passwordCString), (void *)passwordCString, &item); if (returnStatus != noErr || !item) { @@ -316,7 +316,7 @@ - (void)setServiceName:(NSString *)newServiceName mServiceName = [newServiceName copy]; const char *newServiceNameCString = [newServiceName UTF8String]; - [self _modifyAttributeWithTag:kSecServiceItemAttr toBeValue:(void *)newServiceNameCString ofLength:strlen(newServiceNameCString)]; + [self _modifyAttributeWithTag:kSecServiceItemAttr toBeValue:(void *)newServiceNameCString ofLength:(UInt32)strlen(newServiceNameCString)]; } } @@ -392,13 +392,13 @@ + (EMInternetKeychainItem *)internetKeychainItemForServer:(NSString *)server SecKeychainItemRef item = nil; //0 is kSecAuthenticationTypeAny - OSStatus returnStatus = SecKeychainFindInternetPassword(NULL, strlen(serverCString), serverCString, 0, NULL, strlen(usernameCString), usernameCString, strlen(pathCString), pathCString, port, protocol, 0, &passwordLength, (void **)&password, &item); + OSStatus returnStatus = SecKeychainFindInternetPassword(NULL, (UInt32)strlen(serverCString), serverCString, 0, NULL, (UInt32)strlen(usernameCString), usernameCString, (UInt32)strlen(pathCString), pathCString, port, protocol, 0, &passwordLength, (void **)&password, &item); if (returnStatus != noErr && protocol == kSecProtocolTypeFTP) { //Some clients (like Transmit) still save passwords with kSecProtocolTypeFTPAccount, which was deprecated. Let's check for that. protocol = kSecProtocolTypeFTPAccount; - returnStatus = SecKeychainFindInternetPassword(NULL, strlen(serverCString), serverCString, 0, NULL, strlen(usernameCString), usernameCString, strlen(pathCString), pathCString, port, protocol, 0, &passwordLength, (void **)&password, &item); + returnStatus = SecKeychainFindInternetPassword(NULL, (UInt32)strlen(serverCString), serverCString, 0, NULL, (UInt32)strlen(usernameCString), usernameCString, (UInt32)strlen(pathCString), pathCString, port, protocol, 0, &passwordLength, (void **)&password, &item); } if (returnStatus != noErr || !item) @@ -432,7 +432,7 @@ + (EMInternetKeychainItem *)addInternetKeychainItemForServer:(NSString *)server pathCString = ""; SecKeychainItemRef item = nil; - OSStatus returnStatus = SecKeychainAddInternetPassword(NULL, strlen(serverCString), serverCString, 0, NULL, strlen(usernameCString), usernameCString, strlen(pathCString), pathCString, port, protocol, kSecAuthenticationTypeDefault, strlen(passwordCString), (void *)passwordCString, &item); + OSStatus returnStatus = SecKeychainAddInternetPassword(NULL, (UInt32)strlen(serverCString), serverCString, 0, NULL, (UInt32)strlen(usernameCString), usernameCString, (UInt32)strlen(pathCString), pathCString, port, protocol, kSecAuthenticationTypeDefault, (UInt32)strlen(passwordCString), (void *)passwordCString, &item); if (returnStatus != noErr || !item) { @@ -465,7 +465,7 @@ - (void)setServer:(NSString *)newServer mServer = [newServer copy]; const char *newServerCString = [newServer UTF8String]; - [self _modifyAttributeWithTag:kSecServerItemAttr toBeValue:(void *)newServerCString ofLength:strlen(newServerCString)]; + [self _modifyAttributeWithTag:kSecServerItemAttr toBeValue:(void *)newServerCString ofLength:(UInt32)strlen(newServerCString)]; } } @@ -488,7 +488,7 @@ - (void)setPath:(NSString *)newPath mPath = [newPath copy]; const char *newPathCString = [newPath UTF8String]; - [self _modifyAttributeWithTag:kSecPathItemAttr toBeValue:(void *)newPathCString ofLength:strlen(newPathCString)]; + [self _modifyAttributeWithTag:kSecPathItemAttr toBeValue:(void *)newPathCString ofLength:(UInt32)strlen(newPathCString)]; } #pragma mark - @@ -510,7 +510,7 @@ - (void)setPort:(NSInteger)newPort mPort = newPort; - UInt32 newPortValue = newPort; + UInt32 newPortValue = (UInt32)newPort; [self _modifyAttributeWithTag:kSecPortItemAttr toBeValue:&newPortValue ofLength:sizeof(newPortValue)]; } } From 7b5a11c533e5a9562dda7edbf2fc5b75a38d1383 Mon Sep 17 00:00:00 2001 From: Matt Stith Date: Sun, 5 Jun 2011 06:47:54 -0400 Subject: [PATCH 15/46] Rework settings window Window now enumerates all available markets, and is cleanly separated into its own classes. --- BitTicker.xcodeproj/project.pbxproj | 38 +- BitTicker/BitTickerAppDelegate.h | 7 +- BitTicker/BitTickerAppDelegate.m | 17 +- BitTicker/BitcoinMarket.h | 7 + BitTicker/MtGoxMarket.m | 4 +- BitTicker/SettingsWindow.h | 31 + BitTicker/SettingsWindow.m | 21 + BitTicker/SettingsWindow.xib | 856 +++++++++++++++++++++++++++ BitTicker/SettingsWindowController.h | 27 + BitTicker/SettingsWindowController.m | 93 +++ BitTicker/SharedSettings.h | 18 +- BitTicker/SharedSettings.m | 60 +- BitTicker/en.lproj/MainMenu.xib | 555 +---------------- 13 files changed, 1145 insertions(+), 589 deletions(-) create mode 100644 BitTicker/SettingsWindow.h create mode 100644 BitTicker/SettingsWindow.m create mode 100644 BitTicker/SettingsWindow.xib create mode 100644 BitTicker/SettingsWindowController.h create mode 100644 BitTicker/SettingsWindowController.m diff --git a/BitTicker.xcodeproj/project.pbxproj b/BitTicker.xcodeproj/project.pbxproj index f309357..1d69c7e 100644 --- a/BitTicker.xcodeproj/project.pbxproj +++ b/BitTicker.xcodeproj/project.pbxproj @@ -14,6 +14,9 @@ 83405B3A137F6DDF0060CAD4 /* Ticker.m in Sources */ = {isa = PBXBuildFile; fileRef = 83405B33137F6DDF0060CAD4 /* Ticker.m */; }; 83405B3B137F6DDF0060CAD4 /* BitcoinMarket.m in Sources */ = {isa = PBXBuildFile; fileRef = 83405B36137F6DDF0060CAD4 /* BitcoinMarket.m */; }; 83405B3C137F6DDF0060CAD4 /* MtGoxMarket.m in Sources */ = {isa = PBXBuildFile; fileRef = 83405B38137F6DDF0060CAD4 /* MtGoxMarket.m */; }; + 837DF12D139B2E97009987F3 /* SettingsWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 837DF12C139B2E97009987F3 /* SettingsWindow.xib */; }; + 837DF131139B319F009987F3 /* SettingsWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 837DF130139B319F009987F3 /* SettingsWindow.m */; }; + 837DF135139B8E19009987F3 /* SettingsWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 837DF134139B8E18009987F3 /* SettingsWindowController.m */; }; AA239F511379E67300150707 /* StatusItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = AA239F4E1379E67300150707 /* StatusItemView.m */; }; AA239F761379F17200150707 /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA239F731379F17200150707 /* CoreServices.framework */; }; AA239F771379F17200150707 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = AA239F741379F17200150707 /* libz.dylib */; }; @@ -59,6 +62,11 @@ 83405B36137F6DDF0060CAD4 /* BitcoinMarket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BitcoinMarket.m; sourceTree = ""; }; 83405B37137F6DDF0060CAD4 /* MtGoxMarket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MtGoxMarket.h; sourceTree = ""; }; 83405B38137F6DDF0060CAD4 /* MtGoxMarket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MtGoxMarket.m; sourceTree = ""; }; + 837DF12C139B2E97009987F3 /* SettingsWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SettingsWindow.xib; sourceTree = ""; }; + 837DF12F139B319E009987F3 /* SettingsWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SettingsWindow.h; sourceTree = ""; }; + 837DF130139B319F009987F3 /* SettingsWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SettingsWindow.m; sourceTree = ""; }; + 837DF133139B8E18009987F3 /* SettingsWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SettingsWindowController.h; sourceTree = ""; }; + 837DF134139B8E18009987F3 /* SettingsWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SettingsWindowController.m; sourceTree = ""; }; AA239F4D1379E67300150707 /* StatusItemView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StatusItemView.h; sourceTree = ""; }; AA239F4E1379E67300150707 /* StatusItemView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StatusItemView.m; sourceTree = ""; }; AA239F731379F17200150707 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; @@ -193,6 +201,28 @@ name = Models; sourceTree = ""; }; + 837DF12E139B2EC8009987F3 /* Nibs */ = { + isa = PBXGroup; + children = ( + AA4BBA411379E31C005CE351 /* MainMenu.xib */, + 837DF12C139B2E97009987F3 /* SettingsWindow.xib */, + ); + name = Nibs; + sourceTree = ""; + }; + 837DF132139B3B8A009987F3 /* Settings */ = { + isa = PBXGroup; + children = ( + AA6E6C811399F2EB003A4224 /* SharedSettings.h */, + AA6E6C821399F2EB003A4224 /* SharedSettings.m */, + 837DF12F139B319E009987F3 /* SettingsWindow.h */, + 837DF130139B319F009987F3 /* SettingsWindow.m */, + 837DF133139B8E18009987F3 /* SettingsWindowController.h */, + 837DF134139B8E18009987F3 /* SettingsWindowController.m */, + ); + name = Settings; + sourceTree = ""; + }; AA4BBA1D1379E31B005CE351 = { isa = PBXGroup; children = ( @@ -242,13 +272,12 @@ 83405B2B137F68870060CAD4 /* Additions */, 83405B20137F684E0060CAD4 /* Networking */, 83405B0D137F5D8A0060CAD4 /* JSON */, + 837DF132139B3B8A009987F3 /* Settings */, AA239F4D1379E67300150707 /* StatusItemView.h */, AA239F4E1379E67300150707 /* StatusItemView.m */, AA4BBA3E1379E31C005CE351 /* BitTickerAppDelegate.h */, AA4BBA3F1379E31C005CE351 /* BitTickerAppDelegate.m */, - AA4BBA411379E31C005CE351 /* MainMenu.xib */, - AA6E6C811399F2EB003A4224 /* SharedSettings.h */, - AA6E6C821399F2EB003A4224 /* SharedSettings.m */, + 837DF12E139B2EC8009987F3 /* Nibs */, AA4BBA331379E31B005CE351 /* Supporting Files */, ); path = BitTicker; @@ -321,6 +350,7 @@ AA4BBA431379E31C005CE351 /* MainMenu.xib in Resources */, AAD64420137B680E00589EAC /* Logo.icns in Resources */, AA6E6C4D1399DC3E003A4224 /* Credits.html in Resources */, + 837DF12D139B2E97009987F3 /* SettingsWindow.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -353,6 +383,8 @@ AAD172641399BA1E00B505B0 /* EMKeychainItem.m in Sources */, AA6E6C831399F2EB003A4224 /* SharedSettings.m in Sources */, AAB399B3139B0D8500B9438F /* Wallet.m in Sources */, + 837DF131139B319F009987F3 /* SettingsWindow.m in Sources */, + 837DF135139B8E19009987F3 /* SettingsWindowController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/BitTicker/BitTickerAppDelegate.h b/BitTicker/BitTickerAppDelegate.h index 62d18bc..99a8c61 100644 --- a/BitTicker/BitTickerAppDelegate.h +++ b/BitTicker/BitTickerAppDelegate.h @@ -22,6 +22,7 @@ @class Ticker; @class StatusItemView; @class RequestHandler; +@class SettingsWindow; @class MtGoxMarket; @@ -69,11 +70,7 @@ NSNumberFormatter *currencyFormatter; - //User interface stuff - IBOutlet NSWindow *settings_window; - IBOutlet NSTextField *username_field; - IBOutlet NSSecureTextField *password_field; - IBOutlet NSComboBox *market_selector; + NSWindowController *settingsWindowController; } - (void) quitProgram:(id)sender; diff --git a/BitTicker/BitTickerAppDelegate.m b/BitTicker/BitTickerAppDelegate.m index a89c6bf..dfd641f 100644 --- a/BitTicker/BitTickerAppDelegate.m +++ b/BitTicker/BitTickerAppDelegate.m @@ -24,6 +24,9 @@ #import "MtGoxMarket.h" #import "SharedSettings.h" +#import "SettingsWindowController.h" +#import "SettingsWindow.h" + @implementation BitTickerAppDelegate @synthesize stats; @@ -44,6 +47,8 @@ - (void)awakeFromNib { sharedSettingManager = [SharedSettings sharedSettingManager]; [sharedSettingManager checkDefaults]; + settingsWindowController = [[SettingsWindowController alloc] init]; + // menu stuff trayMenu = [[NSMenu alloc] initWithTitle:@"Ticker"]; //graphItem = [[NSMenuItem alloc] init]; @@ -207,7 +212,8 @@ - (void)awakeFromNib { [walletSectionLabel setBordered:NO]; [walletSectionLabel setAlignment:NSLeftTextAlignment]; [walletSectionLabel setBackgroundColor:[NSColor clearColor]]; - [walletSectionLabel setStringValue:[NSString stringWithFormat:@"%@ Wallet",sharedSettingManager.selectedMarket]]; + // TODO: Allow multiple wallets + [walletSectionLabel setStringValue:@"MtGox Wallet"]; [walletSectionLabel setTextColor:[NSColor blueColor]]; [walletSectionLabel setFont:[NSFont fontWithName:headerFont size:headerFontSize]]; [walletView addSubview:walletSectionLabel]; @@ -370,6 +376,8 @@ - (void)applicationWillTerminate:(NSNotification *)notification { [statsItem release]; [currencyFormatter release]; + [settingsWindowController release]; + //ticker stuff [highValue release]; [lowValue release]; @@ -391,11 +399,14 @@ - (IBAction)showAbout:(id)sender { [NSApp activateIgnoringOtherApps:YES]; } -- (IBAction)showSettings:(id)sender { - [settings_window makeKeyAndOrderFront:nil]; +- (IBAction)showSettings:(id)sender { + [settingsWindowController.window makeKeyAndOrderFront:nil]; [NSApp activateIgnoringOtherApps:YES]; } +-(void)settingsWindowClosed { + +} #pragma mark Actions - (void)quitProgram:(id)sender { [NSApp terminate:self]; diff --git a/BitTicker/BitcoinMarket.h b/BitTicker/BitcoinMarket.h index 3abe921..27ab6ed 100644 --- a/BitTicker/BitcoinMarket.h +++ b/BitTicker/BitcoinMarket.h @@ -11,6 +11,13 @@ #import "BitcoinMarketDelegate.h" #import "RequestHandlerDelegate.h" #import "SharedSettings.h" + +enum kBitcoinMarkets { + eMarketMtGox = 0, + // Used for market enumeration. Keep this as the last value. + eNumberOfMarkets = 1 +}; + @class RequestHandler; @interface BitcoinMarket : NSObject { diff --git a/BitTicker/MtGoxMarket.m b/BitTicker/MtGoxMarket.m index 448c19e..80f28cf 100644 --- a/BitTicker/MtGoxMarket.m +++ b/BitTicker/MtGoxMarket.m @@ -50,8 +50,8 @@ -(void)fetchMarketDepth { -(void)fetchWallet { MSLog(@"Fetching wallet..."); - NSString *username = sharedSettingManager.username; - NSString *password = sharedSettingManager.password; + NSString *username = [sharedSettingManager usernameForMarket:eMarketMtGox]; + NSString *password = [sharedSettingManager passwordForMarket:eMarketMtGox]; if ([username isEqualToString:@"ChangeMe"] || username == nil) { return; } diff --git a/BitTicker/SettingsWindow.h b/BitTicker/SettingsWindow.h new file mode 100644 index 0000000..3f0db48 --- /dev/null +++ b/BitTicker/SettingsWindow.h @@ -0,0 +1,31 @@ +// +// SettingsWindowController.h +// BitTicker +// +// Created by Matt Stith on 6/4/11. +// Copyright 2011 none. All rights reserved. +// + +#import + +@class SharedSettings; + +@interface SettingsWindow : NSWindow { + IBOutlet NSTableView *marketListTable; + IBOutlet NSButton *enabledCheckbox; + IBOutlet NSTextField *marketLabel; + IBOutlet NSTextField *usernameField; + IBOutlet NSSecureTextField *passwordField; + +@private + +} + + +@property (nonatomic,retain) NSTableView *marketListTable; +@property (nonatomic,retain) NSButton *enabledCheckbox; +@property (nonatomic,retain) NSTextField *marketLabel; +@property (nonatomic,retain) NSTextField *usernameField; +@property (nonatomic,retain) NSSecureTextField *passwordField; + +@end diff --git a/BitTicker/SettingsWindow.m b/BitTicker/SettingsWindow.m new file mode 100644 index 0000000..f2e3262 --- /dev/null +++ b/BitTicker/SettingsWindow.m @@ -0,0 +1,21 @@ +// +// SettingsWindowController.m +// BitTicker +// +// Created by Matt Stith on 6/4/11. +// Copyright 2011 none. All rights reserved. +// + +#import "SettingsWindow.h" + +#import "SharedSettings.h" +#import "BitcoinMarket.h" + +@implementation SettingsWindow +@synthesize marketListTable; +@synthesize enabledCheckbox; +@synthesize marketLabel; +@synthesize usernameField; +@synthesize passwordField; + +@end diff --git a/BitTicker/SettingsWindow.xib b/BitTicker/SettingsWindow.xib new file mode 100644 index 0000000..354218e --- /dev/null +++ b/BitTicker/SettingsWindow.xib @@ -0,0 +1,856 @@ + + + + 1060 + 10J869 + 1305 + 1038.35 + 461.00 + + com.apple.InterfaceBuilder.CocoaPlugin + 1305 + + + YES + NSScroller + NSTableHeaderView + NSButton + NSScrollView + NSTextFieldCell + NSButtonCell + NSSecureTextFieldCell + NSTableView + NSCustomObject + NSSecureTextField + NSView + NSWindowTemplate + NSTableColumn + NSTextField + + + YES + com.apple.InterfaceBuilder.CocoaPlugin + + + YES + + YES + + + + + YES + + SettingsWindowController + + + FirstResponder + + + NSApplication + + + 15 + 2 + {{196, 240}, {480, 134}} + 544735232 + Settings + SettingsWindow + + + + 256 + + YES + + + 272 + + YES + + + 2304 + + YES + + + 256 + {103, 76} + + + + YES + + + 256 + {103, 17} + + + + + + + + -2147483392 + {{224, 0}, {16, 17}} + + + + + + YES + + 100 + 40 + 1000 + + 75628096 + 2048 + Market + + LucidaGrande + 11 + 3100 + + + 3 + MC4zMzMzMzI5ODU2AA + + + 6 + System + headerTextColor + + 3 + MAA + + + + + 337772096 + 2048 + Text Cell + + LucidaGrande + 13 + 1044 + + + + 6 + System + controlBackgroundColor + + 3 + MC42NjY2NjY2NjY3AA + + + + 6 + System + controlTextColor + + + + 3 + YES + YES + + + + 3 + 2 + + 3 + MQA + + + 6 + System + gridColor + + 3 + MC41AA + + + 17 + -700448768 + + + 4 + 15 + 0 + YES + 0 + + + {{1, 17}, {103, 76}} + + + + + + 4 + + + + -2147483392 + {{224, 17}, {15, 102}} + + + + + _doScroller: + 37 + 0.1947367936372757 + + + + -2147483392 + {{1, 78}, {103, 15}} + + + + 1 + + _doScroller: + 1 + 0.99038461538461542 + + + + 2304 + + YES + + + {{1, 0}, {103, 17}} + + + + + + 4 + + + + {{20, 20}, {105, 94}} + + + + 562 + + + + + + QSAAAEEgAABBmAAAQZgAAA + + + + 268 + {{131, 90}, {72, 18}} + + + + YES + + -2080244224 + 0 + Enabled + + + 1211912703 + 2 + + NSImage + NSSwitch + + + NSSwitch + + + + 200 + 25 + + + + + 268 + {{206, 85}, {257, 29}} + + + + YES + + 68288064 + 272630784 + MtGox + + LucidaGrande + 24 + 16 + + + + 6 + System + controlColor + + + + + + + + 268 + {{130, 58}, {71, 17}} + + + + YES + + 68288064 + 272630784 + Username: + + + + + + + + + 268 + {{133, 26}, {68, 17}} + + + + YES + + 68288064 + 272630784 + Password: + + + + + + + + + 268 + {{206, 55}, {254, 22}} + + + + YES + + -1804468671 + 272630784 + + + + YES + + 6 + System + textBackgroundColor + + + + 6 + System + textColor + + + + + + + 268 + {{206, 23}, {254, 22}} + + + + YES + + 343014976 + 272630848 + + + + YES + + + + YES + NSAllRomanInputSourcesLocaleIdentifier + + + + + {{7, 11}, {480, 134}} + + + + + {{0, 0}, {1680, 1028}} + {1e+13, 1e+13} + + + + + YES + + + enabledCheckbox + + + + 43 + + + + marketLabel + + + + 44 + + + + marketListTable + + + + 45 + + + + passwordField + + + + 46 + + + + usernameField + + + + 47 + + + + window + + + + 50 + + + + delegate + + + + 51 + + + + dataSource + + + + 52 + + + + delegate + + + + 53 + + + + delegate + + + + 54 + + + + enabledDidChange: + + + + 56 + + + + + YES + + 0 + + + + + + -2 + + + File's Owner + + + -1 + + + First Responder + + + -3 + + + Application + + + 1 + + + YES + + + + Window - Settings + + + 2 + + + YES + + + + + + + + + + + + 3 + + + YES + + + + + + + + + 4 + + + + + 5 + + + + + 6 + + + + + 7 + + + YES + + + + + + 8 + + + YES + + + + + + 11 + + + + + 12 + + + YES + + + + + + 13 + + + + + 14 + + + YES + + + + + + 15 + + + + + 16 + + + YES + + + + + + 17 + + + + + 18 + + + YES + + + + + + 19 + + + + + 20 + + + YES + + + + + + 21 + + + + + 36 + + + YES + + + + + + 37 + + + + + + + YES + + YES + -1.IBPluginDependency + -2.IBPluginDependency + -3.IBPluginDependency + 1.IBPluginDependency + 1.IBWindowTemplateEditedContentRect + 1.NSWindowTemplate.visibleAtLaunch + 1.WindowOrigin + 1.editorWindowContentRectSynchronizationRect + 12.IBPluginDependency + 13.IBPluginDependency + 14.IBPluginDependency + 15.IBPluginDependency + 16.IBPluginDependency + 17.IBPluginDependency + 18.IBPluginDependency + 19.IBPluginDependency + 2.IBPluginDependency + 20.IBPluginDependency + 21.IBPluginDependency + 3.IBPluginDependency + 36.IBPluginDependency + 37.IBPluginDependency + 4.IBPluginDependency + 5.IBPluginDependency + 6.IBPluginDependency + 7.IBPluginDependency + + + YES + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + {{357, 418}, {480, 270}} + + {196, 240} + {{357, 418}, {480, 270}} + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + + + + YES + + + + + + YES + + + + + 56 + + + + YES + + SettingsWindow + NSWindow + + YES + + YES + enabledCheckbox + marketLabel + marketListTable + passwordField + usernameField + + + YES + NSButton + NSTextField + NSTableView + NSSecureTextField + NSTextField + + + + YES + + YES + enabledCheckbox + marketLabel + marketListTable + passwordField + usernameField + + + YES + + enabledCheckbox + NSButton + + + marketLabel + NSTextField + + + marketListTable + NSTableView + + + passwordField + NSSecureTextField + + + usernameField + NSTextField + + + + + IBProjectSource + ./Classes/SettingsWindow.h + + + + SettingsWindowController + NSWindowController + + enabledDidChange: + id + + + enabledDidChange: + + enabledDidChange: + id + + + + IBProjectSource + ./Classes/SettingsWindowController.h + + + + + 0 + IBCocoaFramework + + com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3 + + + YES + 3 + + NSSwitch + {15, 15} + + + diff --git a/BitTicker/SettingsWindowController.h b/BitTicker/SettingsWindowController.h new file mode 100644 index 0000000..86f5057 --- /dev/null +++ b/BitTicker/SettingsWindowController.h @@ -0,0 +1,27 @@ +// +// SettingsWindowController.h +// BitTicker +// +// Created by Matt Stith on 6/5/11. +// Copyright 2011 none. All rights reserved. +// + +#import + +@class SettingsWindow; +@class SharedSettings; + +@interface SettingsWindowController : NSWindowController { +@private + SharedSettings *sharedSettings; + + // Same as self.window, but typecasted + SettingsWindow *settingsWindow; + + NSInteger selectedMarket; + +} + +-(IBAction)enabledDidChange:(id)sender; + +@end diff --git a/BitTicker/SettingsWindowController.m b/BitTicker/SettingsWindowController.m new file mode 100644 index 0000000..80ef6b6 --- /dev/null +++ b/BitTicker/SettingsWindowController.m @@ -0,0 +1,93 @@ +// +// SettingsWindowController.m +// BitTicker +// +// Created by Matt Stith on 6/5/11. +// Copyright 2011 none. All rights reserved. +// + +#import "SettingsWindowController.h" + +#import "SettingsWindow.h" +#import "SharedSettings.h" + +#import "BitcoinMarket.h" + +@implementation SettingsWindowController + + +- (id)init { + if (!(self=[super initWithWindowNibName:@"SettingsWindow"])) return self; + sharedSettings = [SharedSettings sharedSettingManager]; + return self; +} + +- (void)dealloc +{ + [super dealloc]; +} + +- (void)windowDidLoad +{ + [super windowDidLoad]; + settingsWindow = (SettingsWindow*)self.window; + + NSTableView *table = settingsWindow.marketListTable; + + table.allowsMultipleSelection = NO; + table.allowsColumnReordering = NO; + table.allowsColumnResizing = NO; + table.allowsEmptySelection = NO; + table.allowsColumnSelection = NO; + + [table reloadData]; + + NSIndexSet *firstMarket = [NSIndexSet indexSetWithIndex:0]; + [table selectRowIndexes:firstMarket byExtendingSelection:NO]; +} +#pragma mark - Table view +- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView { + MSLog(@"Number of rows: %i",eNumberOfMarkets); + return eNumberOfMarkets; +} +- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex { + return [sharedSettings stringForMarket:rowIndex]; +} +- (void)tableViewSelectionDidChange:(NSNotification *)aNotification { + selectedMarket = [settingsWindow.marketListTable selectedRow]; + BOOL enabled = [sharedSettings isMarketEnabled:selectedMarket]; + NSString *username = [sharedSettings usernameForMarket:selectedMarket]; + NSString *password = [sharedSettings passwordForMarket:selectedMarket]; + + settingsWindow.enabledCheckbox.state = enabled? NSOnState : NSOffState; + + if (username == nil) username = @""; + if (password == nil) password = @""; + settingsWindow.usernameField.stringValue = username; + settingsWindow.passwordField.stringValue = password; + + settingsWindow.marketLabel.stringValue = [sharedSettings stringForMarket:selectedMarket]; +} +// Customize table, it's pretty static. User doesn't need to interact. +- (BOOL)tableView:(NSTableView *)aTableView shouldEditTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex { + return NO; +} +- (BOOL)tableView:(NSTableView *)tableView shouldReorderColumn:(NSInteger)columnIndex toColumn:(NSInteger)newColumnIndex { + return NO; +} +#pragma mark - Actions +-(IBAction)enabledDidChange:(id)sender { + BOOL enabled = [sender state] == NSOnState; + [sharedSettings setIsEnabled:enabled forMarket:selectedMarket]; +} +- (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)fieldEditor { + if ([control isKindOfClass:[NSSecureTextField class]]) { + [sharedSettings setPassword:[fieldEditor string] forMarket:selectedMarket]; + MSLog(@"Password changed"); + } else { + [sharedSettings setUsername:[fieldEditor string] forMarket:selectedMarket]; + MSLog(@"Username changed"); + } + return YES; +} +@end diff --git a/BitTicker/SharedSettings.h b/BitTicker/SharedSettings.h index 394cc26..f5009c3 100644 --- a/BitTicker/SharedSettings.h +++ b/BitTicker/SharedSettings.h @@ -7,15 +7,21 @@ #import @interface SharedSettings : NSObject { - - //Settings - NSString *username; - NSString *password; NSString *selectedMarket; } - (void) checkDefaults; + (id)sharedSettingManager; -@property (retain) NSString *username; -@property (retain) NSString *password; + +-(BOOL)isMarketEnabled:(NSInteger)market; +-(void)setIsEnabled:(BOOL)enabled forMarket:(NSInteger)market; + +-(NSString*)usernameForMarket:(NSInteger)market; +-(void)setUsername:(NSString*)username forMarket:(NSInteger)market; + +-(NSString*)passwordForMarket:(NSInteger)market; +-(void)setPassword:(NSString*)password forMarket:(NSInteger)market; + +-(NSString*)stringForMarket:(NSInteger)market; + @property (retain) NSString *selectedMarket; @end diff --git a/BitTicker/SharedSettings.m b/BitTicker/SharedSettings.m index ebb169c..9b878fb 100644 --- a/BitTicker/SharedSettings.m +++ b/BitTicker/SharedSettings.m @@ -7,12 +7,12 @@ #import "SharedSettings.h" #import "EMKeychainItem.h" +#import "BitcoinMarket.h" + static SharedSettings *sharedSettingManager = nil; @implementation SharedSettings -@dynamic username; -@dynamic password; @dynamic selectedMarket; - (id)init { @@ -32,35 +32,46 @@ - (void) checkDefaults { [[NSUserDefaults standardUserDefaults] setObject:@"ChangeMe" forKey:@"username"]; [[NSUserDefaults standardUserDefaults] setObject:@"MtGox" forKey:@"selectedMarket"]; [[NSUserDefaults standardUserDefaults] synchronize]; - if (!self.password) { - self.password = @"ChangeMe"; - } } } -- (NSString *)username { - return [[NSUserDefaults standardUserDefaults] objectForKey:@"username"]; + +-(BOOL)isMarketEnabled:(NSInteger)market { + NSString *enabledKey = [NSString stringWithFormat:@"%@-enabled",[self stringForMarket:market]]; + return [[NSUserDefaults standardUserDefaults] boolForKey:enabledKey]; +} +-(void)setIsEnabled:(BOOL)enabled forMarket:(NSInteger)market { + NSString *enabledKey = [NSString stringWithFormat:@"%@-enabled",[self stringForMarket:market]]; + [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:enabledKey]; } -- (void)setUsername:(NSString *)newusername { - [[NSUserDefaults standardUserDefaults] setObject:newusername forKey:@"username"]; +-(NSString*)usernameForMarket:(NSInteger)market { + NSString *usernameKey = [NSString stringWithFormat:@"%@-username",[self stringForMarket:market]]; + return [[NSUserDefaults standardUserDefaults] stringForKey:usernameKey]; +} +-(void)setUsername:(NSString*)username forMarket:(NSInteger)market { + NSString *usernameKey = [NSString stringWithFormat:@"%@-username",[self stringForMarket:market]]; + [[NSUserDefaults standardUserDefaults] setObject:username forKey:usernameKey]; [[NSUserDefaults standardUserDefaults] synchronize]; - NSLog(@"Set Username: %@",newusername); } -- (NSString *)password { - EMGenericKeychainItem *keychainItem = [EMGenericKeychainItem genericKeychainItemForService:@"BitTicker-MtGox" withUsername:self.username]; +-(NSString*)passwordForMarket:(NSInteger)market { + NSString *username = [self usernameForMarket:market]; + NSString *passwordKey = [NSString stringWithFormat:@"BitTicker-%@",[self stringForMarket:market]]; + EMGenericKeychainItem *keychainItem = [EMGenericKeychainItem genericKeychainItemForService:passwordKey withUsername:username]; + MSLog(@"Returning password from key %@: %@",passwordKey,keychainItem.password); return keychainItem.password; } - -- (void)setPassword:(NSString *)newpassword { - EMGenericKeychainItem *keychainItem = [EMGenericKeychainItem genericKeychainItemForService:@"BitTicker-MtGox" withUsername:self.username]; - keychainItem.password = newpassword; - NSLog(@"Set password: %@",newpassword); +-(void)setPassword:(NSString*)password forMarket:(NSInteger)market { + NSString *username = [self usernameForMarket:market]; + NSString *passwordKey = [NSString stringWithFormat:@"BitTicker-%@",[self stringForMarket:market]]; + MSLog(@"Setting password %@ with key %@",password,passwordKey); + EMGenericKeychainItem *keychainItem = [EMGenericKeychainItem genericKeychainItemForService:passwordKey withUsername:username]; + keychainItem.password = password; } - (NSString *)selectedMarket { - return [[NSUserDefaults standardUserDefaults] objectForKey:@"selectedMarket"]; + return [[NSUserDefaults standardUserDefaults] stringForKey:@"selectedMarket"]; } - (void)setSelectedMarket:(NSString *)newmarket { @@ -69,6 +80,19 @@ - (void)setSelectedMarket:(NSString *)newmarket { NSLog(@"Set Market: %@",newmarket); } +-(NSString*)stringForMarket:(NSInteger)market { + switch (market) { + case eMarketMtGox: + return @"MtGox"; + break; + default: + return @"Unknown"; + break; + } +} + +#pragma mark - Singleton jazz + + (id)sharedSettingManager { @synchronized(self) { if(sharedSettingManager == nil) diff --git a/BitTicker/en.lproj/MainMenu.xib b/BitTicker/en.lproj/MainMenu.xib index 2cc501e..dc9f22e 100644 --- a/BitTicker/en.lproj/MainMenu.xib +++ b/BitTicker/en.lproj/MainMenu.xib @@ -12,18 +12,10 @@ YES - NSComboBoxCell - NSMenuItem + NSUserDefaultsController NSMenu - NSTextFieldCell - NSSecureTextFieldCell - NSComboBox + NSMenuItem NSCustomObject - NSSecureTextField - NSView - NSWindowTemplate - NSTextField - NSUserDefaultsController YES @@ -111,254 +103,6 @@ _NSMainMenu - - 4099 - 2 - {{196, 240}, {383, 166}} - 1618478080 - Settings - NSWindow - - - - 256 - - YES - - - 268 - {{60, 129}, {52, 17}} - - - - YES - - 68288064 - 272630784 - Market: - - LucidaGrande - 13 - 1044 - - - - 6 - System - controlColor - - 3 - MC42NjY2NjY2NjY3AA - - - - 6 - System - controlTextColor - - 3 - MAA - - - - - - - 268 - {{117, 123}, {249, 26}} - - - - YES - - 343014976 - 272630784 - - - - YES - - 6 - System - textBackgroundColor - - 3 - MQA - - - - 1 - YES - - YES - MtGox - - - - - 274 - {13, 21} - - - YES - - YES - - 10 - 10 - 1000 - - 75628032 - 0 - - - LucidaGrande - 12 - 16 - - - 3 - MC4zMzMzMzI5ODU2AA - - - - - 338820672 - 1024 - - - YES - - 6 - System - controlBackgroundColor - - - - - 3 - YES - - - - 3 - 2 - - - 6 - System - gridColor - - 3 - MC41AA - - - 19 - tableViewAction: - -765427712 - - - - 1 - 15 - 0 - YES - 0 - - - - - - 268 - {{41, 77}, {71, 17}} - - - - YES - - 68288064 - 272630784 - Username: - - - - - - - - - 268 - {{117, 74}, {246, 22}} - - - - YES - - -1804468671 - 272630784 - - - - YES - - - 6 - System - textColor - - - - - - - 268 - {{44, 23}, {68, 17}} - - - - YES - - 68288064 - 272630784 - Password: - - - - - - - - - 268 - {{117, 20}, {246, 22}} - - - - YES - - 343014976 - 272630848 - - - - YES - - - - YES - NSAllRomanInputSourcesLocaleIdentifier - - - - - {{7, 11}, {383, 166}} - - - - - {{0, 0}, {1280, 778}} - {1e+13, 1e+13} - YES @@ -393,98 +137,6 @@ 686 - - - settings_window - - - - 701 - - - - market_selector - - - - 702 - - - - username_field - - - - 703 - - - - password_field - - - - 704 - - - - value: self.username - - - - - - value: self.username - value - self.username - - NSNullPlaceholder - ChangeMe - - 2 - - - 726 - - - - value: self.password - - - - - - value: self.password - value - self.password - - NSNullPlaceholder - ChangeMe - - 2 - - - 727 - - - - value: self.selectedMarket - - - - - - value: self.selectedMarket - value - self.selectedMarket - - NSNullPlaceholder - MtGox - - 2 - - - 728 - @@ -569,120 +221,6 @@ Menu Item - About BitTicker - - 687 - - - YES - - - - Settings Window - - - 688 - - - YES - - - - - - - - - Settings View - - - 689 - - - YES - - - - Markets - - - 690 - - - YES - - - - Password Label - - - 691 - - - YES - - - - Username Label - - - 692 - - - YES - - - - - - 693 - - - YES - - - - username_field - - - 694 - - - YES - - - - password_field - - - 695 - - - - - 696 - - - - - 697 - - - - - 698 - - - - - 699 - - - - - 700 - - - 705 @@ -721,25 +259,6 @@ 561.ImportedFromIB2 564.IBPluginDependency 564.ImportedFromIB2 - 687.IBPluginDependency - 687.IBWindowTemplateEditedContentRect - 687.NSWindowTemplate.visibleAtLaunch - 687.WindowOrigin - 687.editorWindowContentRectSynchronizationRect - 688.IBPluginDependency - 689.IBPluginDependency - 690.IBPluginDependency - 691.IBPluginDependency - 692.IBPluginDependency - 693.IBPluginDependency - 694.IBPluginDependency - 695.IBPluginDependency - 696.IBPluginDependency - 697.IBPluginDependency - 698.IBPluginDependency - 699.IBPluginDependency - 700.IBComboBoxObjectValuesKey.objectValues - 700.IBPluginDependency 705.IBPluginDependency 719.IBPluginDependency @@ -768,28 +287,6 @@ com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin - {{357, 418}, {480, 270}} - - {196, 240} - {{357, 418}, {480, 270}} - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - - YES - MtGox - - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin @@ -805,7 +302,7 @@ - 728 + 739 @@ -845,52 +342,6 @@ - - YES - - YES - market_selector - password_field - settings_window - username_field - - - YES - NSComboBox - NSSecureTextField - NSWindow - NSTextField - - - - YES - - YES - market_selector - password_field - settings_window - username_field - - - YES - - market_selector - NSComboBox - - - password_field - NSSecureTextField - - - settings_window - NSWindow - - - username_field - NSTextField - - - IBProjectSource ./Classes/BitTickerAppDelegate.h From 40bf2525a4bf1fb71439938f542d3889d6c045a8 Mon Sep 17 00:00:00 2001 From: Matt Stith Date: Sun, 5 Jun 2011 06:53:19 -0400 Subject: [PATCH 16/46] Not using these --- BitTicker/BitTickerAppDelegate.m | 1 - BitTicker/SharedSettings.h | 4 +--- BitTicker/SharedSettings.m | 27 ++------------------------- 3 files changed, 3 insertions(+), 29 deletions(-) diff --git a/BitTicker/BitTickerAppDelegate.m b/BitTicker/BitTickerAppDelegate.m index dfd641f..15d275e 100644 --- a/BitTicker/BitTickerAppDelegate.m +++ b/BitTicker/BitTickerAppDelegate.m @@ -45,7 +45,6 @@ - (void)awakeFromNib { [_statusItem setView:statusItemView]; sharedSettingManager = [SharedSettings sharedSettingManager]; - [sharedSettingManager checkDefaults]; settingsWindowController = [[SettingsWindowController alloc] init]; diff --git a/BitTicker/SharedSettings.h b/BitTicker/SharedSettings.h index f5009c3..022c3d6 100644 --- a/BitTicker/SharedSettings.h +++ b/BitTicker/SharedSettings.h @@ -7,9 +7,8 @@ #import @interface SharedSettings : NSObject { - NSString *selectedMarket; + } -- (void) checkDefaults; + (id)sharedSettingManager; -(BOOL)isMarketEnabled:(NSInteger)market; @@ -23,5 +22,4 @@ -(NSString*)stringForMarket:(NSInteger)market; -@property (retain) NSString *selectedMarket; @end diff --git a/BitTicker/SharedSettings.m b/BitTicker/SharedSettings.m index 9b878fb..3b4b783 100644 --- a/BitTicker/SharedSettings.m +++ b/BitTicker/SharedSettings.m @@ -13,8 +13,6 @@ @implementation SharedSettings -@dynamic selectedMarket; - - (id)init { NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init]; if ((self = [super init])) { @@ -24,17 +22,6 @@ - (id)init { return self; } -- (void) checkDefaults { - if ([[NSUserDefaults standardUserDefaults] boolForKey:@"AlreadyRan"]) { - - } else { - [[NSUserDefaults standardUserDefaults] setBool:TRUE forKey:@"AlreadyRan"]; - [[NSUserDefaults standardUserDefaults] setObject:@"ChangeMe" forKey:@"username"]; - [[NSUserDefaults standardUserDefaults] setObject:@"MtGox" forKey:@"selectedMarket"]; - [[NSUserDefaults standardUserDefaults] synchronize]; - } -} - -(BOOL)isMarketEnabled:(NSInteger)market { NSString *enabledKey = [NSString stringWithFormat:@"%@-enabled",[self stringForMarket:market]]; @@ -59,27 +46,17 @@ -(NSString*)passwordForMarket:(NSInteger)market { NSString *username = [self usernameForMarket:market]; NSString *passwordKey = [NSString stringWithFormat:@"BitTicker-%@",[self stringForMarket:market]]; EMGenericKeychainItem *keychainItem = [EMGenericKeychainItem genericKeychainItemForService:passwordKey withUsername:username]; - MSLog(@"Returning password from key %@: %@",passwordKey,keychainItem.password); + MSLog(@"Returning password for username: %@",username); return keychainItem.password; } -(void)setPassword:(NSString*)password forMarket:(NSInteger)market { NSString *username = [self usernameForMarket:market]; NSString *passwordKey = [NSString stringWithFormat:@"BitTicker-%@",[self stringForMarket:market]]; - MSLog(@"Setting password %@ with key %@",password,passwordKey); + MSLog(@"Setting password for username %@",username); EMGenericKeychainItem *keychainItem = [EMGenericKeychainItem genericKeychainItemForService:passwordKey withUsername:username]; keychainItem.password = password; } -- (NSString *)selectedMarket { - return [[NSUserDefaults standardUserDefaults] stringForKey:@"selectedMarket"]; -} - -- (void)setSelectedMarket:(NSString *)newmarket { - [[NSUserDefaults standardUserDefaults] setObject:newmarket forKey:@"selectedMarket"]; - [[NSUserDefaults standardUserDefaults] synchronize]; - NSLog(@"Set Market: %@",newmarket); -} - -(NSString*)stringForMarket:(NSInteger)market { switch (market) { case eMarketMtGox: From 688cbc66e8c7738bd0a2c0933fe1c55d9ccfff65 Mon Sep 17 00:00:00 2001 From: Matt Stith Date: Sun, 5 Jun 2011 07:23:39 -0400 Subject: [PATCH 17/46] Better keychain management --- BitTicker/SharedSettings.h | 7 ++++- BitTicker/SharedSettings.m | 61 +++++++++++++++++++++++++++----------- 2 files changed, 50 insertions(+), 18 deletions(-) diff --git a/BitTicker/SharedSettings.h b/BitTicker/SharedSettings.h index 022c3d6..1aaa514 100644 --- a/BitTicker/SharedSettings.h +++ b/BitTicker/SharedSettings.h @@ -6,10 +6,13 @@ #import -@interface SharedSettings : NSObject { +@class EMGenericKeychainItem; +@interface SharedSettings : NSObject { + NSMutableDictionary *keychainItems; } + (id)sharedSettingManager; +-(EMGenericKeychainItem*)keychainItemForService:(NSInteger)service; -(BOOL)isMarketEnabled:(NSInteger)market; -(void)setIsEnabled:(BOOL)enabled forMarket:(NSInteger)market; @@ -22,4 +25,6 @@ -(NSString*)stringForMarket:(NSInteger)market; + + @end diff --git a/BitTicker/SharedSettings.m b/BitTicker/SharedSettings.m index 3b4b783..bae9ea3 100644 --- a/BitTicker/SharedSettings.m +++ b/BitTicker/SharedSettings.m @@ -14,15 +14,38 @@ @implementation SharedSettings - (id)init { - NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init]; - if ((self = [super init])) { - - } - [autoreleasepool release]; - return self; + if (!(self = [super init])) return self; + + [EMGenericKeychainItem setLogsErrors:YES]; + keychainItems = [[NSMutableDictionary alloc] init]; + + return self; +} + +-(EMGenericKeychainItem*)keychainItemForService:(NSInteger)service { + NSString *serviceString = [@"BitTicker-" stringByAppendingString:[self stringForMarket:service]]; + + EMGenericKeychainItem *item = [keychainItems objectForKey:serviceString]; + + if (!item) { + // We don't have one cached, check if one exists for the username + NSString *username = [self usernameForMarket:service]; + if (!username) { + // Username isn't in defaults, obviously password isn't either. + item = [EMGenericKeychainItem addGenericKeychainItemForService:serviceString withUsername:@"" password:@""]; + } else { + item = [EMGenericKeychainItem genericKeychainItemForService:serviceString withUsername:username]; + + if (!item) { + // We still don't have it. Make a new one. + item = [EMGenericKeychainItem addGenericKeychainItemForService:serviceString withUsername:username password:@""]; + } + } + } + [keychainItems setObject:item forKey:serviceString]; + return item; } - -(BOOL)isMarketEnabled:(NSInteger)market { NSString *enabledKey = [NSString stringWithFormat:@"%@-enabled",[self stringForMarket:market]]; return [[NSUserDefaults standardUserDefaults] boolForKey:enabledKey]; @@ -38,23 +61,26 @@ -(NSString*)usernameForMarket:(NSInteger)market { } -(void)setUsername:(NSString*)username forMarket:(NSInteger)market { NSString *usernameKey = [NSString stringWithFormat:@"%@-username",[self stringForMarket:market]]; + + // Check for old username/password in keychain + EMGenericKeychainItem *keychainItem = [self keychainItemForService:market]; + + // If there is one, we need to update it. + if (keychainItem) { + keychainItem.username = username; + } + [[NSUserDefaults standardUserDefaults] setObject:username forKey:usernameKey]; [[NSUserDefaults standardUserDefaults] synchronize]; } -(NSString*)passwordForMarket:(NSInteger)market { - NSString *username = [self usernameForMarket:market]; - NSString *passwordKey = [NSString stringWithFormat:@"BitTicker-%@",[self stringForMarket:market]]; - EMGenericKeychainItem *keychainItem = [EMGenericKeychainItem genericKeychainItemForService:passwordKey withUsername:username]; - MSLog(@"Returning password for username: %@",username); - return keychainItem.password; + EMGenericKeychainItem *keychainItem = [self keychainItemForService:market]; + return keychainItem.password; } -(void)setPassword:(NSString*)password forMarket:(NSInteger)market { - NSString *username = [self usernameForMarket:market]; - NSString *passwordKey = [NSString stringWithFormat:@"BitTicker-%@",[self stringForMarket:market]]; - MSLog(@"Setting password for username %@",username); - EMGenericKeychainItem *keychainItem = [EMGenericKeychainItem genericKeychainItemForService:passwordKey withUsername:username]; - keychainItem.password = password; + EMGenericKeychainItem *keychainItem = [self keychainItemForService:market]; + keychainItem.password = password; } -(NSString*)stringForMarket:(NSInteger)market { @@ -100,6 +126,7 @@ - (id)autorelease { -(void)dealloc { + [keychainItems release]; [super dealloc]; } From 168c5bfb3790bedc688b8415b79928589280325e Mon Sep 17 00:00:00 2001 From: mrsteveman1 Date: Sun, 5 Jun 2011 10:19:25 -0400 Subject: [PATCH 18/46] section header for ticker data --- BitTicker/BitTickerAppDelegate.m | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/BitTicker/BitTickerAppDelegate.m b/BitTicker/BitTickerAppDelegate.m index a89c6bf..4db33ea 100644 --- a/BitTicker/BitTickerAppDelegate.m +++ b/BitTicker/BitTickerAppDelegate.m @@ -48,7 +48,7 @@ - (void)awakeFromNib { trayMenu = [[NSMenu alloc] initWithTitle:@"Ticker"]; //graphItem = [[NSMenuItem alloc] init]; statsItem = [[NSMenuItem alloc] init]; - statsView = [[NSView alloc] initWithFrame:CGRectMake(0,70,180,90)]; + statsView = [[NSView alloc] initWithFrame:CGRectMake(0,70,180,105)]; [statsItem setView:statsView]; [trayMenu addItem:statsItem]; @@ -64,6 +64,19 @@ - (void)awakeFromNib { NSInteger labelOffset = 20; NSInteger valueOffset = 110; + + //section header + NSTextField *tickerSectionLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,93,headerWidth,menuHeight)]; + [tickerSectionLabel setEditable:FALSE]; + [tickerSectionLabel setBordered:NO]; + [tickerSectionLabel setAlignment:NSLeftTextAlignment]; + [tickerSectionLabel setBackgroundColor:[NSColor clearColor]]; + [tickerSectionLabel setStringValue:[NSString stringWithFormat:@"%@ Ticker",sharedSettingManager.selectedMarket]]; + [tickerSectionLabel setTextColor:[NSColor blueColor]]; + [tickerSectionLabel setFont:[NSFont fontWithName:headerFont size:headerFontSize]]; + [statsView addSubview:tickerSectionLabel]; + [tickerSectionLabel release]; + highValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,75,valueWidth,menuHeight)]; [highValue setEditable:FALSE]; [highValue setBordered:NO]; From 045a57ce0c16b53a908c8b69040b94e9c931fd99 Mon Sep 17 00:00:00 2001 From: mrsteveman1 Date: Sun, 5 Jun 2011 14:01:38 -0400 Subject: [PATCH 19/46] fix compile error in menu, check for keychainitem with blank user before making a new one, only set wallet value fields if we have a last ticker price available --- BitTicker/BitTickerAppDelegate.m | 23 +++++++++++++++-------- BitTicker/SharedSettings.m | 6 +++++- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/BitTicker/BitTickerAppDelegate.m b/BitTicker/BitTickerAppDelegate.m index 7f58d0f..c14a57a 100644 --- a/BitTicker/BitTickerAppDelegate.m +++ b/BitTicker/BitTickerAppDelegate.m @@ -75,7 +75,7 @@ - (void)awakeFromNib { [tickerSectionLabel setBordered:NO]; [tickerSectionLabel setAlignment:NSLeftTextAlignment]; [tickerSectionLabel setBackgroundColor:[NSColor clearColor]]; - [tickerSectionLabel setStringValue:[NSString stringWithFormat:@"%@ Ticker",sharedSettingManager.selectedMarket]]; + [tickerSectionLabel setStringValue:@"Ticker"]; [tickerSectionLabel setTextColor:[NSColor blueColor]]; [tickerSectionLabel setFont:[NSFont fontWithName:headerFont size:headerFontSize]]; [statsView addSubview:tickerSectionLabel]; @@ -464,16 +464,23 @@ -(void)bitcoinMarket:(BitcoinMarket*)market didReceiveRecentTradesData:(NSArray* -(void)bitcoinMarket:(BitcoinMarket*)market didReceiveWallet:(Wallet*)wallet { double btc = [wallet.btc doubleValue]; double usd = [wallet.usd doubleValue]; - double last = [self.tickerValue doubleValue]; - [BTCValue setStringValue:[NSString stringWithFormat:@"%f.04",[wallet.btc floatValue]]]; + [BTCValue setStringValue:[NSString stringWithFormat:@"%f.04",[wallet.btc floatValue]]]; [USDValue setStringValue:[currencyFormatter stringFromNumber:wallet.usd]]; + double last = [self.tickerValue doubleValue]; + if (last == 0) { + //no last yet so cant multiply anyway + } + else { + NSNumber *BTCxRate = [NSNumber numberWithDouble:btc*last]; + [BTCxUSDValue setStringValue:[currencyFormatter stringFromNumber:BTCxRate]]; + + NSNumber *walletUSD = [NSNumber numberWithDouble:[BTCxRate doubleValue] + usd]; + [walletUSDValue setStringValue: [currencyFormatter stringFromNumber:walletUSD]]; + + } - NSNumber *BTCxRate = [NSNumber numberWithDouble:btc*last]; - [BTCxUSDValue setStringValue:[currencyFormatter stringFromNumber:BTCxRate]]; - - NSNumber *walletUSD = [NSNumber numberWithDouble:[BTCxRate doubleValue] + usd]; - [walletUSDValue setStringValue: [currencyFormatter stringFromNumber:walletUSD]]; + } @end diff --git a/BitTicker/SharedSettings.m b/BitTicker/SharedSettings.m index bae9ea3..564ce9e 100644 --- a/BitTicker/SharedSettings.m +++ b/BitTicker/SharedSettings.m @@ -32,7 +32,11 @@ -(EMGenericKeychainItem*)keychainItemForService:(NSInteger)service { NSString *username = [self usernameForMarket:service]; if (!username) { // Username isn't in defaults, obviously password isn't either. - item = [EMGenericKeychainItem addGenericKeychainItemForService:serviceString withUsername:@"" password:@""]; + item = [EMGenericKeychainItem genericKeychainItemForService:serviceString withUsername:@""]; + if (!item) { + // getting a generic/blank item didnt work either so it must not exist + item = [EMGenericKeychainItem addGenericKeychainItemForService:serviceString withUsername:@"" password:@""]; + } } else { item = [EMGenericKeychainItem genericKeychainItemForService:serviceString withUsername:username]; From 5da6c9d9ffa6f0e541ea138b24a8896ec9809f21 Mon Sep 17 00:00:00 2001 From: mrsteveman1 Date: Wed, 8 Jun 2011 17:11:38 -0400 Subject: [PATCH 20/46] support for showing bitcoin.cz mining stats, added API key field to settings --- BitTicker.xcodeproj/project.pbxproj | 12 +++ BitTicker/BitTickerAppDelegate.h | 17 ++- BitTicker/BitTickerAppDelegate.m | 122 +++++++++++++++++++++- BitTicker/BitcoinCZ.h | 20 ++++ BitTicker/BitcoinCZ.m | 76 ++++++++++++++ BitTicker/BitcoinMarket.h | 6 +- BitTicker/BitcoinMarket.m | 4 + BitTicker/BitcoinMarketDelegate.h | 2 + BitTicker/Miner.h | 30 ++++++ BitTicker/Miner.m | 22 ++++ BitTicker/SettingsWindow.h | 2 + BitTicker/SettingsWindow.m | 1 + BitTicker/SettingsWindow.xib | 151 ++++++++++++++++++++++++--- BitTicker/SettingsWindowController.m | 21 ++-- BitTicker/SharedSettings.h | 5 +- BitTicker/SharedSettings.m | 16 ++- 16 files changed, 477 insertions(+), 30 deletions(-) create mode 100644 BitTicker/BitcoinCZ.h create mode 100644 BitTicker/BitcoinCZ.m create mode 100644 BitTicker/Miner.h create mode 100644 BitTicker/Miner.m diff --git a/BitTicker.xcodeproj/project.pbxproj b/BitTicker.xcodeproj/project.pbxproj index 1d69c7e..4d4076e 100644 --- a/BitTicker.xcodeproj/project.pbxproj +++ b/BitTicker.xcodeproj/project.pbxproj @@ -17,6 +17,8 @@ 837DF12D139B2E97009987F3 /* SettingsWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 837DF12C139B2E97009987F3 /* SettingsWindow.xib */; }; 837DF131139B319F009987F3 /* SettingsWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 837DF130139B319F009987F3 /* SettingsWindow.m */; }; 837DF135139B8E19009987F3 /* SettingsWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 837DF134139B8E18009987F3 /* SettingsWindowController.m */; }; + AA0140D2139FF9050084A3A6 /* BitcoinCZ.m in Sources */ = {isa = PBXBuildFile; fileRef = AA0140D1139FF9050084A3A6 /* BitcoinCZ.m */; }; + AA0140D6139FFA7D0084A3A6 /* Miner.m in Sources */ = {isa = PBXBuildFile; fileRef = AA0140D5139FFA7D0084A3A6 /* Miner.m */; }; AA239F511379E67300150707 /* StatusItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = AA239F4E1379E67300150707 /* StatusItemView.m */; }; AA239F761379F17200150707 /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA239F731379F17200150707 /* CoreServices.framework */; }; AA239F771379F17200150707 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = AA239F741379F17200150707 /* libz.dylib */; }; @@ -67,6 +69,10 @@ 837DF130139B319F009987F3 /* SettingsWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SettingsWindow.m; sourceTree = ""; }; 837DF133139B8E18009987F3 /* SettingsWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SettingsWindowController.h; sourceTree = ""; }; 837DF134139B8E18009987F3 /* SettingsWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SettingsWindowController.m; sourceTree = ""; }; + AA0140D0139FF9050084A3A6 /* BitcoinCZ.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BitcoinCZ.h; path = "../../../SourceCache/BitTicker-Mac/BitTicker/BitcoinCZ.h"; sourceTree = ""; }; + AA0140D1139FF9050084A3A6 /* BitcoinCZ.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BitcoinCZ.m; path = "../../../SourceCache/BitTicker-Mac/BitTicker/BitcoinCZ.m"; sourceTree = ""; }; + AA0140D4139FFA7C0084A3A6 /* Miner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Miner.h; path = "../../../SourceCache/BitTicker-Mac/BitTicker/Miner.h"; sourceTree = ""; }; + AA0140D5139FFA7D0084A3A6 /* Miner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Miner.m; path = "../../../SourceCache/BitTicker-Mac/BitTicker/Miner.m"; sourceTree = ""; }; AA239F4D1379E67300150707 /* StatusItemView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StatusItemView.h; sourceTree = ""; }; AA239F4E1379E67300150707 /* StatusItemView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StatusItemView.m; sourceTree = ""; }; AA239F731379F17200150707 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; @@ -193,6 +199,10 @@ 83405B36137F6DDF0060CAD4 /* BitcoinMarket.m */, 83405B37137F6DDF0060CAD4 /* MtGoxMarket.h */, 83405B38137F6DDF0060CAD4 /* MtGoxMarket.m */, + AA0140D0139FF9050084A3A6 /* BitcoinCZ.h */, + AA0140D1139FF9050084A3A6 /* BitcoinCZ.m */, + AA0140D4139FFA7C0084A3A6 /* Miner.h */, + AA0140D5139FFA7D0084A3A6 /* Miner.m */, AAB399B1139B0D8500B9438F /* Wallet.h */, AAB399B2139B0D8500B9438F /* Wallet.m */, AAD172621399BA1D00B505B0 /* EMKeychainItem.h */, @@ -385,6 +395,8 @@ AAB399B3139B0D8500B9438F /* Wallet.m in Sources */, 837DF131139B319F009987F3 /* SettingsWindow.m in Sources */, 837DF135139B8E19009987F3 /* SettingsWindowController.m in Sources */, + AA0140D2139FF9050084A3A6 /* BitcoinCZ.m in Sources */, + AA0140D6139FFA7D0084A3A6 /* Miner.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/BitTicker/BitTickerAppDelegate.h b/BitTicker/BitTickerAppDelegate.h index 99a8c61..5a69963 100644 --- a/BitTicker/BitTickerAppDelegate.h +++ b/BitTicker/BitTickerAppDelegate.h @@ -25,16 +25,20 @@ @class SettingsWindow; @class MtGoxMarket; +@class BitcoinCZ; +@class Miner; #import "BitcoinMarketDelegate.h" @interface BitTickerAppDelegate : NSObject { SharedSettings *sharedSettingManager; MtGoxMarket *market; + BitcoinCZ *miner; NSTimer *tickerTimer; NSTimer *walletTimer; - + NSTimer *minerTimer; + NSMutableArray *stats; StatusItemView *statusItemView; NSStatusItem *_statusItem; @@ -53,11 +57,22 @@ NSTextField *USDValue; NSTextField *walletUSDValue; + //miner stuff + NSTextField *confirmedReward; + NSTextField *unconfirmedReward; + NSTextField *estimatedReward; + NSTextField *username; + NSTextField *workers; + NSTextField *hashOutput; + NSView *statsView; NSMenuItem *statsItem; NSView *walletView; NSMenuItem *walletItem; + + NSView *minerView; + NSMenuItem *minerItem; // below the line NSMenuItem *quitItem; diff --git a/BitTicker/BitTickerAppDelegate.m b/BitTicker/BitTickerAppDelegate.m index c14a57a..ccda619 100644 --- a/BitTicker/BitTickerAppDelegate.m +++ b/BitTicker/BitTickerAppDelegate.m @@ -20,8 +20,10 @@ #import "BitTickerAppDelegate.h" #import "Ticker.h" #import "Wallet.h" +#import "Miner.h" #import "StatusItemView.h" #import "MtGoxMarket.h" +#import "BitcoinCZ.h" #import "SharedSettings.h" #import "SettingsWindowController.h" @@ -328,14 +330,113 @@ - (void)awakeFromNib { [trayMenu addItem:[NSMenuItem separatorItem]]; + minerItem = [[NSMenuItem alloc] init]; + minerView = [[NSView alloc] initWithFrame:CGRectMake(0,0,180,75)]; + [minerItem setView:minerView]; + [trayMenu addItem:minerItem]; + //section header + NSTextField *minerSectionLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,64,headerWidth,menuHeight)]; + [minerSectionLabel setEditable:FALSE]; + [minerSectionLabel setBordered:NO]; + [minerSectionLabel setAlignment:NSLeftTextAlignment]; + [minerSectionLabel setBackgroundColor:[NSColor clearColor]]; + [minerSectionLabel setStringValue:@"Miner Data"]; + [minerSectionLabel setTextColor:[NSColor blueColor]]; + [minerSectionLabel setFont:[NSFont fontWithName:headerFont size:headerFontSize]]; + [minerView addSubview:minerSectionLabel]; + [minerSectionLabel release]; + + confirmedReward = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 45, valueWidth, menuHeight)]; + [confirmedReward setEditable:FALSE]; + [confirmedReward setBordered:NO]; + [confirmedReward setAlignment:NSRightTextAlignment]; + [confirmedReward setBackgroundColor:[NSColor clearColor]]; + [confirmedReward setTextColor:[NSColor blackColor]]; + [confirmedReward setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [minerView addSubview:confirmedReward]; + + NSTextField *confirmedRewardLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,45,labelWidth,menuHeight)]; + [confirmedRewardLabel setEditable:FALSE]; + [confirmedRewardLabel setBordered:NO]; + [confirmedRewardLabel setAlignment:NSLeftTextAlignment]; + [confirmedRewardLabel setBackgroundColor:[NSColor clearColor]]; + [confirmedRewardLabel setStringValue:@"Confirmed:"]; + [confirmedRewardLabel setTextColor:[NSColor blackColor]]; + [confirmedRewardLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [minerView addSubview:confirmedRewardLabel]; + [confirmedRewardLabel release]; + + unconfirmedReward = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 30, valueWidth, menuHeight)]; + [unconfirmedReward setEditable:FALSE]; + [unconfirmedReward setBordered:NO]; + [unconfirmedReward setAlignment:NSRightTextAlignment]; + [unconfirmedReward setBackgroundColor:[NSColor clearColor]]; + [unconfirmedReward setTextColor:[NSColor blackColor]]; + [unconfirmedReward setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [minerView addSubview:unconfirmedReward]; + + NSTextField *unconfirmedRewardLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,30,labelWidth,menuHeight)]; + [unconfirmedRewardLabel setEditable:FALSE]; + [unconfirmedRewardLabel setBordered:NO]; + [unconfirmedRewardLabel setAlignment:NSLeftTextAlignment]; + [unconfirmedRewardLabel setBackgroundColor:[NSColor clearColor]]; + [unconfirmedRewardLabel setStringValue:@"Unconfirmed:"]; + [unconfirmedRewardLabel setTextColor:[NSColor blackColor]]; + [unconfirmedRewardLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [minerView addSubview:unconfirmedRewardLabel]; + [unconfirmedRewardLabel release]; + + + + estimatedReward = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 15, valueWidth, menuHeight)]; + [estimatedReward setEditable:FALSE]; + [estimatedReward setBordered:NO]; + [estimatedReward setAlignment:NSRightTextAlignment]; + [estimatedReward setBackgroundColor:[NSColor clearColor]]; + [estimatedReward setTextColor:[NSColor blackColor]]; + [estimatedReward setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [minerView addSubview:estimatedReward]; + + NSTextField *estimatedRewardLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,15,labelWidth,menuHeight)]; + [estimatedRewardLabel setEditable:FALSE]; + [estimatedRewardLabel setBordered:NO]; + [estimatedRewardLabel setAlignment:NSLeftTextAlignment]; + [estimatedRewardLabel setBackgroundColor:[NSColor clearColor]]; + [estimatedRewardLabel setStringValue:@"Estimated:"]; + [estimatedRewardLabel setTextColor:[NSColor blackColor]]; + [estimatedRewardLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [minerView addSubview:estimatedRewardLabel]; + [estimatedRewardLabel release]; + + + + hashOutput = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 0, valueWidth, menuHeight)]; + [hashOutput setEditable:FALSE]; + [hashOutput setBordered:NO]; + [hashOutput setAlignment:NSRightTextAlignment]; + [hashOutput setBackgroundColor:[NSColor clearColor]]; + [hashOutput setTextColor:[NSColor blackColor]]; + [hashOutput setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [minerView addSubview:hashOutput]; + + NSTextField *hashOutputLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,0,labelWidth,menuHeight)]; + [hashOutputLabel setEditable:FALSE]; + [hashOutputLabel setBordered:NO]; + [hashOutputLabel setAlignment:NSLeftTextAlignment]; + [hashOutputLabel setBackgroundColor:[NSColor clearColor]]; + [hashOutputLabel setStringValue:@"MHash/s:"]; + [hashOutputLabel setTextColor:[NSColor blackColor]]; + [hashOutputLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [minerView addSubview:hashOutputLabel]; + [hashOutputLabel release]; - + [trayMenu addItem:[NSMenuItem separatorItem]]; refreshItem = [trayMenu addItemWithTitle:@"Refresh" action:@selector(refreshTicker:) @@ -368,12 +469,15 @@ - (void)awakeFromNib { MSLog(@"Starting"); market = [[MtGoxMarket alloc] initWithDelegate:self]; + miner = [[BitcoinCZ alloc] initWithDelegate:self]; tickerTimer = [[NSTimer scheduledTimerWithTimeInterval:30 target:market selector:@selector(fetchTicker) userInfo:nil repeats:YES] retain]; walletTimer = [[NSTimer scheduledTimerWithTimeInterval:60 target:market selector:@selector(fetchWallet) userInfo:nil repeats:YES] retain]; + minerTimer = [[NSTimer scheduledTimerWithTimeInterval:60 target:miner selector:@selector(fetchMiner) userInfo:nil repeats:YES] retain]; [market fetchTicker]; [market fetchWallet]; + [miner fetchMiner]; } #pragma mark Application delegate @@ -479,8 +583,22 @@ -(void)bitcoinMarket:(BitcoinMarket*)market didReceiveWallet:(Wallet*)wallet { [walletUSDValue setStringValue: [currencyFormatter stringFromNumber:walletUSD]]; } - +} +-(void)bitcoinMarket:(BitcoinMarket*)market didReceiveMiner:(Miner*)minerdata { + [unconfirmedReward setStringValue:[minerdata.unconfirmed_reward stringValue]]; + [confirmedReward setStringValue:[minerdata.confirmed_reward stringValue]]; + [estimatedReward setStringValue:[minerdata.estimated_reward stringValue]]; + double hashcount; + for (NSDictionary *worker in minerdata.workers) { + hashcount = hashcount + [[worker objectForKey:@"hashrate"] doubleValue]; + } + NSNumberFormatter *hashFormatter = [[NSNumberFormatter alloc] init]; + hashFormatter.numberStyle = NSNumberFormatterDecimalStyle; + hashFormatter.hasThousandSeparators = YES; + [hashOutput setStringValue:[hashFormatter stringFromNumber:[NSNumber numberWithDouble:hashcount]]]; + + [hashFormatter release]; } @end diff --git a/BitTicker/BitcoinCZ.h b/BitTicker/BitcoinCZ.h new file mode 100644 index 0000000..4802b57 --- /dev/null +++ b/BitTicker/BitcoinCZ.h @@ -0,0 +1,20 @@ +// +// BitcoinCZ.h +// BitTicker +// +// Created by steve on 6/8/11. +// Copyright 2011 none. All rights reserved. +// + +#import + +#import "BitcoinMarket.h" +#import "Miner.h" + +@interface BitcoinCZ : BitcoinMarket { + +} + +-(id)initWithDelegate:(id)delegate; + +@end diff --git a/BitTicker/BitcoinCZ.m b/BitTicker/BitcoinCZ.m new file mode 100644 index 0000000..33f3d46 --- /dev/null +++ b/BitTicker/BitcoinCZ.m @@ -0,0 +1,76 @@ +// +// BitcoinCZ.m +// BitTicker +// +// Created by steve on 6/8/11. +// Copyright 2011 none. All rights reserved. +// + +#import "BitcoinCZ.h" +#import "Miner.h" +#define BITCOINCZ_MINING_URL @"http://mining.bitcoin.cz/accounts/profile/json" + +@implementation BitcoinCZ + +-(id)initWithDelegate:(id)delegate { + if (!(self = [super initWithDelegate:delegate])) return self; + _tickerURL = @""; + _tradeURL = @""; + _depthURL = @""; + _walletURL = @""; + _minerURL = BITCOINCZ_MINING_URL; + return self; +} + +-(void)fetchMiner { + MSLog(@"Fetching miner..."); + NSString *apiKey = [sharedSettingManager apiKeyForMarket:eMarketBitcoinCZ]; + if ([apiKey isEqualToString:@""] || apiKey == nil) { + return; + } + NSString *url = [NSString stringWithFormat:@"%@/%@",self.minerURL,apiKey]; + [self downloadJsonDataFromURL:[NSURL URLWithString:url] callback:@selector(didFetchMiner:)]; +} + +-(void)didFetchMiner:(NSDictionary *)minerdata { + Miner *newminer = [[Miner alloc] init]; + NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; + [formatter setNumberStyle:NSNumberFormatterDecimalStyle]; + newminer.unconfirmed_reward = [formatter numberFromString:[minerdata objectForKey:@"unconfirmed_reward"]]; + newminer.confirmed_reward = [formatter numberFromString:[minerdata objectForKey:@"confirmed_reward"]]; + newminer.estimated_reward = [formatter numberFromString:[minerdata objectForKey:@"estimated_reward"]]; + newminer.send_threshold = [formatter numberFromString:[minerdata objectForKey:@"send_threshold"]]; + newminer.wallet = [minerdata objectForKey:@"wallet"]; + newminer.username = [minerdata objectForKey:@"username"]; + NSMutableArray *workerArray = [NSMutableArray arrayWithCapacity:10]; + NSDictionary *dict = [minerdata objectForKey:@"workers"]; + for(NSString *key in dict) { + NSDictionary *value = [dict objectForKey:key]; + NSMutableDictionary *tempDict = [NSMutableDictionary dictionaryWithDictionary:value]; + [tempDict setObject:key forKey:@"worker_name"]; + [workerArray addObject:tempDict]; + } + newminer.workers = workerArray; + [formatter release]; + [_delegate bitcoinMarket:self didReceiveMiner:newminer]; +} +/*{ + "username": "mrsteveman1", + "unconfirmed_reward": "0.00346862", + "send_threshold": "1.00000000", + "confirmed_reward": "0.00000000", + "workers": { + "mrsteveman1.office": { + "last_share": 1307558336, + "score": "13.8764", + "hashrate": 169, + "shares": 9, + "alive": true + } + }, + "wallet": "1Hob3YewSMPjx6B9ULLhdBVEmFRmdsrEF6", + "estimated_reward": "0.00713821" + +}*/ + +@end diff --git a/BitTicker/BitcoinMarket.h b/BitTicker/BitcoinMarket.h index 27ab6ed..49a8ea3 100644 --- a/BitTicker/BitcoinMarket.h +++ b/BitTicker/BitcoinMarket.h @@ -14,8 +14,9 @@ enum kBitcoinMarkets { eMarketMtGox = 0, + eMarketBitcoinCZ = 1, // Used for market enumeration. Keep this as the last value. - eNumberOfMarkets = 1 + eNumberOfMarkets = 2 }; @class RequestHandler; @@ -31,6 +32,7 @@ enum kBitcoinMarkets { NSString *_tickerURL; NSString *_depthURL; NSString *_walletURL; + NSString *_minerURL; } -(id)initWithDelegate:(id)delegate; @@ -45,6 +47,7 @@ enum kBitcoinMarkets { -(void)fetchTicker; -(void)fetchMarketDepth; -(void)fetchWallet; +-(void)fetchMiner; @property (nonatomic, assign) id delegate; @@ -52,5 +55,6 @@ enum kBitcoinMarkets { @property (readonly,nonatomic,retain) NSString *tickerURL; @property (readonly,nonatomic,retain) NSString *depthURL; @property (readonly,nonatomic,retain) NSString *walletURL; +@property (readonly,nonatomic,retain) NSString *minerURL; @end diff --git a/BitTicker/BitcoinMarket.m b/BitTicker/BitcoinMarket.m index e45b37b..886c864 100644 --- a/BitTicker/BitcoinMarket.m +++ b/BitTicker/BitcoinMarket.m @@ -19,6 +19,7 @@ @implementation BitcoinMarket @synthesize tradeURL = _tradeURL; @synthesize walletURL = _walletURL; @synthesize depthURL = _depthURL; +@synthesize minerURL = _minerURL; -(id)initWithDelegate:(id)delegate { if (!(self = [super init])) return self; @@ -132,4 +133,7 @@ -(void)fetchMarketDepth { -(void)fetchWallet { [NSException raise:@"MethodNotOverwrittenException" format:@"%s must be overwritten by BitcoinMarket subclasses",__func__]; } +-(void)fetchMiner { + [NSException raise:@"MethodNotOverwrittenException" format:@"%s must be overwritten by BitcoinMarket subclasses",__func__]; +} @end diff --git a/BitTicker/BitcoinMarketDelegate.h b/BitTicker/BitcoinMarketDelegate.h index d01e922..73a1fa1 100644 --- a/BitTicker/BitcoinMarketDelegate.h +++ b/BitTicker/BitcoinMarketDelegate.h @@ -11,6 +11,7 @@ @class BitcoinMarket; @class Ticker; @class Wallet; +@class Miner; @protocol BitcoinMarketDelegate @@ -27,4 +28,5 @@ -(void)bitcoinMarket:(BitcoinMarket*)market didReceiveTicker:(Ticker*)ticker; -(void)bitcoinMarket:(BitcoinMarket*)market didReceiveRecentTradesData:(NSArray*)trades; -(void)bitcoinMarket:(BitcoinMarket*)market didReceiveWallet:(Wallet*)wallet; +-(void)bitcoinMarket:(BitcoinMarket*)market didReceiveMiner:(Miner*)minerdata; @end diff --git a/BitTicker/Miner.h b/BitTicker/Miner.h new file mode 100644 index 0000000..fead5a3 --- /dev/null +++ b/BitTicker/Miner.h @@ -0,0 +1,30 @@ +// +// Miner.h +// BitTicker +// +// Created by steve on 6/8/11. +// Copyright 2011 none. All rights reserved. +// + +#import + + +@interface Miner : NSObject { + NSString *_username; + NSNumber *_unconfirmed_reward; + NSNumber *_send_threshold; + NSNumber *_confirmed_reward; + NSNumber *_estimated_reward; + NSString *_wallet; + NSArray *_workers; +} + +@property (retain) NSString *username; +@property (retain) NSNumber *unconfirmed_reward; +@property (retain) NSNumber *confirmed_reward; +@property (retain) NSNumber *estimated_reward; +@property (retain) NSNumber *send_threshold; +@property (retain) NSString *wallet; +@property (retain) NSArray *workers; + +@end \ No newline at end of file diff --git a/BitTicker/Miner.m b/BitTicker/Miner.m new file mode 100644 index 0000000..9e54691 --- /dev/null +++ b/BitTicker/Miner.m @@ -0,0 +1,22 @@ +// +// Miner.m +// BitTicker +// +// Created by steve on 6/8/11. +// Copyright 2011 none. All rights reserved. +// + +#import "Miner.h" + + +@implementation Miner + +@synthesize unconfirmed_reward = _unconfirmed_reward; +@synthesize confirmed_reward = _confirmed_reward; +@synthesize estimated_reward = _estimated_reward; +@synthesize send_threshold = _send_threshold; +@synthesize wallet = _wallet; +@synthesize workers = _workers; +@synthesize username = _username; + +@end \ No newline at end of file diff --git a/BitTicker/SettingsWindow.h b/BitTicker/SettingsWindow.h index 3f0db48..0d4e5b5 100644 --- a/BitTicker/SettingsWindow.h +++ b/BitTicker/SettingsWindow.h @@ -16,6 +16,7 @@ IBOutlet NSTextField *marketLabel; IBOutlet NSTextField *usernameField; IBOutlet NSSecureTextField *passwordField; + IBOutlet NSTextField *apiKeyField; @private @@ -27,5 +28,6 @@ @property (nonatomic,retain) NSTextField *marketLabel; @property (nonatomic,retain) NSTextField *usernameField; @property (nonatomic,retain) NSSecureTextField *passwordField; +@property (nonatomic,retain) NSTextField *apiKeyField; @end diff --git a/BitTicker/SettingsWindow.m b/BitTicker/SettingsWindow.m index f2e3262..79fded0 100644 --- a/BitTicker/SettingsWindow.m +++ b/BitTicker/SettingsWindow.m @@ -17,5 +17,6 @@ @implementation SettingsWindow @synthesize marketLabel; @synthesize usernameField; @synthesize passwordField; +@synthesize apiKeyField; @end diff --git a/BitTicker/SettingsWindow.xib b/BitTicker/SettingsWindow.xib index 354218e..6bb9052 100644 --- a/BitTicker/SettingsWindow.xib +++ b/BitTicker/SettingsWindow.xib @@ -3,12 +3,12 @@ 1060 10J869 - 1305 + 1306 1038.35 461.00 com.apple.InterfaceBuilder.CocoaPlugin - 1305 + 1306 YES @@ -52,7 +52,7 @@ 15 2 - {{196, 240}, {480, 134}} + {{196, 240}, {540, 165}} 544735232 Settings SettingsWindow @@ -75,7 +75,7 @@ 256 - {103, 76} + {103, 107} @@ -184,7 +184,7 @@ 0 - {{1, 17}, {103, 76}} + {{1, 17}, {103, 107}} @@ -234,7 +234,7 @@ - {{20, 20}, {105, 94}} + {{20, 20}, {105, 125}} @@ -249,7 +249,7 @@ 268 - {{131, 90}, {72, 18}} + {{131, 121}, {72, 18}} @@ -278,7 +278,7 @@ 268 - {{206, 85}, {257, 29}} + {{206, 116}, {257, 29}} @@ -305,7 +305,7 @@ 268 - {{130, 58}, {71, 17}} + {{130, 89}, {71, 17}} @@ -323,7 +323,7 @@ 268 - {{133, 26}, {68, 17}} + {{133, 57}, {68, 17}} @@ -338,10 +338,28 @@ + + + 268 + {{145, 29}, {68, 17}} + + + + YES + + 68288064 + 272630784 + API Key: + + + + + + 268 - {{206, 55}, {254, 22}} + {{206, 86}, {314, 22}} @@ -370,10 +388,11 @@ 268 - {{206, 23}, {254, 22}} + {{206, 54}, {314, 22}} - + + 1 YES 343014976 @@ -390,13 +409,33 @@ + + + 268 + {{206, 24}, {314, 22}} + + + + 2 + YES + + -1804468671 + 272630784 + + + + YES + + + + - {{7, 11}, {480, 134}} + {{7, 11}, {540, 165}} - {{0, 0}, {1680, 1028}} + {{0, 0}, {1280, 778}} {1e+13, 1e+13} @@ -491,6 +530,22 @@ 56 + + + delegate + + + + 61 + + + + apiKeyField + + + + 62 + @@ -541,6 +596,8 @@ + + @@ -678,6 +735,35 @@ + + 57 + + + YES + + + + StaticText - API Key: + + + 58 + + + + + 59 + + + YES + + + + + + 60 + + + @@ -701,14 +787,21 @@ 18.IBPluginDependency 19.IBPluginDependency 2.IBPluginDependency + 20.IBAttributePlaceholdersKey 20.IBPluginDependency 21.IBPluginDependency 3.IBPluginDependency + 36.IBAttributePlaceholdersKey 36.IBPluginDependency 37.IBPluginDependency 4.IBPluginDependency 5.IBPluginDependency + 57.IBPluginDependency + 58.IBPluginDependency + 59.IBAttributePlaceholdersKey + 59.IBPluginDependency 6.IBPluginDependency + 60.IBPluginDependency 7.IBPluginDependency @@ -730,15 +823,34 @@ com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin + + YES + + + com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin + + YES + + + com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin + + YES + + + + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin @@ -753,7 +865,7 @@ - 56 + 62 @@ -765,6 +877,7 @@ YES YES + apiKeyField enabledCheckbox marketLabel marketListTable @@ -773,6 +886,7 @@ YES + NSTextField NSButton NSTextField NSTableView @@ -784,6 +898,7 @@ YES YES + apiKeyField enabledCheckbox marketLabel marketListTable @@ -792,6 +907,10 @@ YES + + apiKeyField + NSTextField + enabledCheckbox NSButton diff --git a/BitTicker/SettingsWindowController.m b/BitTicker/SettingsWindowController.m index 80ef6b6..572e0c5 100644 --- a/BitTicker/SettingsWindowController.m +++ b/BitTicker/SettingsWindowController.m @@ -58,14 +58,18 @@ - (void)tableViewSelectionDidChange:(NSNotification *)aNotification { BOOL enabled = [sharedSettings isMarketEnabled:selectedMarket]; NSString *username = [sharedSettings usernameForMarket:selectedMarket]; NSString *password = [sharedSettings passwordForMarket:selectedMarket]; - + NSString *apiKey = [sharedSettings apiKeyForMarket:selectedMarket]; + settingsWindow.enabledCheckbox.state = enabled? NSOnState : NSOffState; if (username == nil) username = @""; if (password == nil) password = @""; + if (apiKey == nil) apiKey = @""; + settingsWindow.usernameField.stringValue = username; settingsWindow.passwordField.stringValue = password; - + settingsWindow.apiKeyField.stringValue = apiKey; + settingsWindow.marketLabel.stringValue = [sharedSettings stringForMarket:selectedMarket]; } // Customize table, it's pretty static. User doesn't need to interact. @@ -81,12 +85,15 @@ -(IBAction)enabledDidChange:(id)sender { [sharedSettings setIsEnabled:enabled forMarket:selectedMarket]; } - (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)fieldEditor { - if ([control isKindOfClass:[NSSecureTextField class]]) { - [sharedSettings setPassword:[fieldEditor string] forMarket:selectedMarket]; - MSLog(@"Password changed"); - } else { - [sharedSettings setUsername:[fieldEditor string] forMarket:selectedMarket]; + if ([control tag] == 0) { + [sharedSettings setUsername:[fieldEditor string] forMarket:selectedMarket]; MSLog(@"Username changed"); + } else if ([control tag] == 1) { + [sharedSettings setPassword:[fieldEditor string] forMarket:selectedMarket]; + MSLog(@"Password changed"); + } else if ([control tag] == 2) { + [sharedSettings setAPIKey:[fieldEditor string] forMarket:selectedMarket]; + MSLog(@"API key changed"); } return YES; } diff --git a/BitTicker/SharedSettings.h b/BitTicker/SharedSettings.h index 1aaa514..5fc3cf8 100644 --- a/BitTicker/SharedSettings.h +++ b/BitTicker/SharedSettings.h @@ -23,8 +23,11 @@ -(NSString*)passwordForMarket:(NSInteger)market; -(void)setPassword:(NSString*)password forMarket:(NSInteger)market; +-(NSString*)apiKeyForMarket:(NSInteger)market; +-(void)setAPIKey:(NSString*)newAPIKey forMarket:(NSInteger)market; + -(NSString*)stringForMarket:(NSInteger)market; -@end +@end \ No newline at end of file diff --git a/BitTicker/SharedSettings.m b/BitTicker/SharedSettings.m index 564ce9e..faf4752 100644 --- a/BitTicker/SharedSettings.m +++ b/BitTicker/SharedSettings.m @@ -78,6 +78,16 @@ -(void)setUsername:(NSString*)username forMarket:(NSInteger)market { [[NSUserDefaults standardUserDefaults] synchronize]; } +-(NSString*)apiKeyForMarket:(NSInteger)market { + NSString *apiKey = [NSString stringWithFormat:@"%@-apiKey",[self stringForMarket:market]]; + return [[NSUserDefaults standardUserDefaults] stringForKey:apiKey]; +} +-(void)setAPIKey:(NSString*)newAPIKey forMarket:(NSInteger)market { + NSString *apiKey = [NSString stringWithFormat:@"%@-apiKey",[self stringForMarket:market]]; + [[NSUserDefaults standardUserDefaults] setObject:newAPIKey forKey:apiKey]; + [[NSUserDefaults standardUserDefaults] synchronize]; +} + -(NSString*)passwordForMarket:(NSInteger)market { EMGenericKeychainItem *keychainItem = [self keychainItemForService:market]; return keychainItem.password; @@ -92,6 +102,9 @@ -(NSString*)stringForMarket:(NSInteger)market { case eMarketMtGox: return @"MtGox"; break; + case eMarketBitcoinCZ: + return @"BitcoinCZ"; + break; default: return @"Unknown"; break; @@ -134,5 +147,4 @@ -(void)dealloc { [super dealloc]; } -@end - +@end \ No newline at end of file From 5141280e2b832e753205ffe9f44f41924487a1c4 Mon Sep 17 00:00:00 2001 From: mrsteveman1 Date: Wed, 8 Jun 2011 17:31:05 -0400 Subject: [PATCH 21/46] fix paths and remove debug data --- BitTicker.xcodeproj/project.pbxproj | 8 ++++---- BitTicker/BitcoinCZ.m | 18 ------------------ 2 files changed, 4 insertions(+), 22 deletions(-) diff --git a/BitTicker.xcodeproj/project.pbxproj b/BitTicker.xcodeproj/project.pbxproj index 4d4076e..fb9300a 100644 --- a/BitTicker.xcodeproj/project.pbxproj +++ b/BitTicker.xcodeproj/project.pbxproj @@ -69,10 +69,10 @@ 837DF130139B319F009987F3 /* SettingsWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SettingsWindow.m; sourceTree = ""; }; 837DF133139B8E18009987F3 /* SettingsWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SettingsWindowController.h; sourceTree = ""; }; 837DF134139B8E18009987F3 /* SettingsWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SettingsWindowController.m; sourceTree = ""; }; - AA0140D0139FF9050084A3A6 /* BitcoinCZ.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BitcoinCZ.h; path = "../../../SourceCache/BitTicker-Mac/BitTicker/BitcoinCZ.h"; sourceTree = ""; }; - AA0140D1139FF9050084A3A6 /* BitcoinCZ.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BitcoinCZ.m; path = "../../../SourceCache/BitTicker-Mac/BitTicker/BitcoinCZ.m"; sourceTree = ""; }; - AA0140D4139FFA7C0084A3A6 /* Miner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Miner.h; path = "../../../SourceCache/BitTicker-Mac/BitTicker/Miner.h"; sourceTree = ""; }; - AA0140D5139FFA7D0084A3A6 /* Miner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Miner.m; path = "../../../SourceCache/BitTicker-Mac/BitTicker/Miner.m"; sourceTree = ""; }; + AA0140D0139FF9050084A3A6 /* BitcoinCZ.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BitcoinCZ.h; path = "BitcoinCZ.h"; sourceTree = ""; }; + AA0140D1139FF9050084A3A6 /* BitcoinCZ.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BitcoinCZ.m; path = "BitcoinCZ.m"; sourceTree = ""; }; + AA0140D4139FFA7C0084A3A6 /* Miner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Miner.h; path = "Miner.h"; sourceTree = ""; }; + AA0140D5139FFA7D0084A3A6 /* Miner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Miner.m; path = "Miner.m"; sourceTree = ""; }; AA239F4D1379E67300150707 /* StatusItemView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StatusItemView.h; sourceTree = ""; }; AA239F4E1379E67300150707 /* StatusItemView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StatusItemView.m; sourceTree = ""; }; AA239F731379F17200150707 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; diff --git a/BitTicker/BitcoinCZ.m b/BitTicker/BitcoinCZ.m index 33f3d46..3f15ed8 100644 --- a/BitTicker/BitcoinCZ.m +++ b/BitTicker/BitcoinCZ.m @@ -54,23 +54,5 @@ -(void)didFetchMiner:(NSDictionary *)minerdata { [formatter release]; [_delegate bitcoinMarket:self didReceiveMiner:newminer]; } -/*{ - "username": "mrsteveman1", - "unconfirmed_reward": "0.00346862", - "send_threshold": "1.00000000", - "confirmed_reward": "0.00000000", - "workers": { - "mrsteveman1.office": { - "last_share": 1307558336, - "score": "13.8764", - "hashrate": 169, - "shares": 9, - "alive": true - } - }, - "wallet": "1Hob3YewSMPjx6B9ULLhdBVEmFRmdsrEF6", - "estimated_reward": "0.00713821" - -}*/ @end From f564ac4bf7361a0788320c6fe87fd037263e5721 Mon Sep 17 00:00:00 2001 From: Martin Ceperley Date: Thu, 9 Jun 2011 00:25:54 -0400 Subject: [PATCH 22/46] Ticker update flashes red and green depending on direction --- BitTicker/StatusItemView.h | 8 +++++ BitTicker/StatusItemView.m | 72 +++++++++++++++++++++++++++++++++++--- 2 files changed, 76 insertions(+), 4 deletions(-) diff --git a/BitTicker/StatusItemView.h b/BitTicker/StatusItemView.h index b84b62e..6b3b97d 100644 --- a/BitTicker/StatusItemView.h +++ b/BitTicker/StatusItemView.h @@ -22,11 +22,19 @@ @interface StatusItemView : NSView { NSStatusItem *statusItem; NSNumber *tickerValue; + NSNumber *previousTickerValue; + NSDate *lastUpdated; + NSTimer *colorTimer; + NSColor *flashColor; BOOL isMenuVisible; + BOOL isAnimating; + BOOL firstTick; } @property (retain, nonatomic) NSStatusItem *statusItem; @property (retain, nonatomic) NSNumber *tickerValue; +@property (retain, nonatomic) NSNumber *previousTickerValue; +@property (retain, nonatomic) NSTimer *colorTimer; - (void)setTickerValue:(NSNumber *)value; diff --git a/BitTicker/StatusItemView.m b/BitTicker/StatusItemView.m index 16092a0..e83ea28 100644 --- a/BitTicker/StatusItemView.m +++ b/BitTicker/StatusItemView.m @@ -23,10 +23,12 @@ #define StatusItemViewPaddingWidth 6 #define StatusItemViewPaddingHeight 3 #define StatusItemWidth 50 +#define ColorFadeFramerate 30.0 +#define ColorFadeDuration 1.0 @implementation StatusItemView -@synthesize statusItem; +@synthesize statusItem, previousTickerValue, colorTimer; - (void)drawRect:(NSRect)rect { // Draw status bar background, highlighted if menu is showing @@ -37,7 +39,7 @@ - (void)drawRect:(NSRect)rect { NSMutableDictionary *fontAttributes = [[NSMutableDictionary alloc] init]; NSFont *font = [NSFont fontWithName:@"LucidaGrande" size:16]; - NSColor *black = [NSColor blackColor]; + NSColor *white = [NSColor whiteColor]; [fontAttributes setObject:font forKey:NSFontAttributeName]; @@ -54,7 +56,24 @@ - (void)drawRect:(NSRect)rect { [tickerPretty drawAtPoint:point withAttributes:fontAttributes]; } else { - [fontAttributes setObject:black forKey:NSForegroundColorAttributeName]; + NSColor *foreground_color; + if(isAnimating){ + NSTimeInterval duration = -1.0 * [lastUpdated timeIntervalSinceNow]; + double colorAlpha; + + if(duration >= ColorFadeDuration){ + [self.colorTimer invalidate]; + isAnimating = NO; + colorAlpha = 0.0; + } else { + colorAlpha = 1.0 - (duration / ColorFadeDuration); + } + + foreground_color = [[NSColor blackColor] blendedColorWithFraction:colorAlpha ofColor:flashColor]; + } + else foreground_color = [NSColor blackColor]; + + [fontAttributes setObject:foreground_color forKey:NSForegroundColorAttributeName]; [tickerPretty drawAtPoint:point withAttributes:fontAttributes]; } @@ -66,9 +85,14 @@ - (id)initWithFrame:(NSRect)frame { CGRect newFrame = CGRectMake(0,0,StatusItemWidth,[[NSStatusBar systemStatusBar] thickness]); self = [super initWithFrame:newFrame]; if (self) { + firstTick = YES; + self.previousTickerValue = [NSNumber numberWithInt:0]; self.tickerValue = [NSNumber numberWithInt:0]; + flashColor = [[NSColor blackColor] retain]; + lastUpdated = [[NSDate date] retain]; statusItem = nil; isMenuVisible = NO; + isAnimating = NO; [statusItem setLength:StatusItemWidth]; } return self; @@ -111,11 +135,51 @@ - (NSColor *)ForegroundColor { } } +- (void)updateFade { + [self setNeedsDisplay:YES]; +} + - (void)setTickerValue:(NSNumber *)value { + + [previousTickerValue release]; + previousTickerValue = tickerValue; + [value retain]; - [tickerValue release]; tickerValue = value; + + double current = [tickerValue doubleValue]; + double previous = [previousTickerValue doubleValue]; + BOOL animate_color = YES; + if(firstTick){ + firstTick = NO; + animate_color = NO; + } else if(current > previous){ + [flashColor release]; + flashColor = [[NSColor greenColor] retain]; + } else if(current < previous){ + [flashColor release]; + flashColor = [[NSColor redColor] retain]; + } else { + animate_color = NO; + } + + if(animate_color){ + + self.colorTimer = [[NSTimer scheduledTimerWithTimeInterval:(1.0/ColorFadeFramerate) + target:self + selector:@selector(updateFade) + userInfo:nil + repeats:YES] retain]; + + [lastUpdated release]; + lastUpdated = [[NSDate date] retain]; + isAnimating = YES; + } + + [self setNeedsDisplay:YES]; + + } - (NSNumber *)tickerValue { From 037006976e09969a372403f36cb419e6f091cbae Mon Sep 17 00:00:00 2001 From: mrsteveman1 Date: Thu, 9 Jun 2011 10:19:39 -0400 Subject: [PATCH 23/46] bold text in the menu bar, keep the text slightly green or red if the price is going up or down --- BitTicker/StatusItemView.h | 1 + BitTicker/StatusItemView.m | 14 +++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/BitTicker/StatusItemView.h b/BitTicker/StatusItemView.h index 6b3b97d..e754bdc 100644 --- a/BitTicker/StatusItemView.h +++ b/BitTicker/StatusItemView.h @@ -26,6 +26,7 @@ NSDate *lastUpdated; NSTimer *colorTimer; NSColor *flashColor; + NSColor *currentColor; BOOL isMenuVisible; BOOL isAnimating; BOOL firstTick; diff --git a/BitTicker/StatusItemView.m b/BitTicker/StatusItemView.m index e83ea28..bc8e026 100644 --- a/BitTicker/StatusItemView.m +++ b/BitTicker/StatusItemView.m @@ -37,7 +37,7 @@ - (void)drawRect:(NSRect)rect { NSPoint point = NSMakePoint(1, 1); NSMutableDictionary *fontAttributes = [[NSMutableDictionary alloc] init]; - NSFont *font = [NSFont fontWithName:@"LucidaGrande" size:16]; + NSFont *font = [NSFont fontWithName:@"LucidaGrande-Bold" size:16]; NSColor *white = [NSColor whiteColor]; @@ -69,9 +69,9 @@ - (void)drawRect:(NSRect)rect { colorAlpha = 1.0 - (duration / ColorFadeDuration); } - foreground_color = [[NSColor blackColor] blendedColorWithFraction:colorAlpha ofColor:flashColor]; + foreground_color = [currentColor blendedColorWithFraction:colorAlpha ofColor:flashColor]; } - else foreground_color = [NSColor blackColor]; + else foreground_color = currentColor; [fontAttributes setObject:foreground_color forKey:NSForegroundColorAttributeName]; [tickerPretty drawAtPoint:point withAttributes:fontAttributes]; @@ -89,6 +89,7 @@ - (id)initWithFrame:(NSRect)frame { self.previousTickerValue = [NSNumber numberWithInt:0]; self.tickerValue = [NSNumber numberWithInt:0]; flashColor = [[NSColor blackColor] retain]; + currentColor = [[NSColor blackColor] retain]; lastUpdated = [[NSDate date] retain]; statusItem = nil; isMenuVisible = NO; @@ -156,9 +157,16 @@ - (void)setTickerValue:(NSNumber *)value { } else if(current > previous){ [flashColor release]; flashColor = [[NSColor greenColor] retain]; + [currentColor release]; + currentColor = [[NSColor colorWithDeviceRed:0 green:0.55 blue:0 alpha:1.0] retain]; + NSLog(@"Going green..."); } else if(current < previous){ [flashColor release]; flashColor = [[NSColor redColor] retain]; + [currentColor release]; + currentColor = [[NSColor colorWithDeviceRed:0.55 green:0 blue:0 alpha:1.0] retain]; + NSLog(@"Going red..."); + } else { animate_color = NO; } From 49e6f3b048e6d5a9755f64b9d606557370675da2 Mon Sep 17 00:00:00 2001 From: mrsteveman1 Date: Fri, 10 Jun 2011 17:26:45 -0400 Subject: [PATCH 24/46] add menu options to the main menu (fixes github issue #5, keyboard shortcuts work now) --- BitTicker/BitTickerAppDelegate.h | 2 +- BitTicker/en.lproj/MainMenu.xib | 68 ++++++++++++++++++++++++++++++-- 2 files changed, 65 insertions(+), 5 deletions(-) diff --git a/BitTicker/BitTickerAppDelegate.h b/BitTicker/BitTickerAppDelegate.h index 5a69963..51486ea 100644 --- a/BitTicker/BitTickerAppDelegate.h +++ b/BitTicker/BitTickerAppDelegate.h @@ -89,7 +89,7 @@ } - (void) quitProgram:(id)sender; -- (void)refreshTicker:(id)sender; +- (IBAction)refreshTicker:(id)sender; - (IBAction)showSettings:(id)sender; - (IBAction)showAbout:(id)sender; @property (retain, nonatomic) NSNumber *tickerValue; diff --git a/BitTicker/en.lproj/MainMenu.xib b/BitTicker/en.lproj/MainMenu.xib index dc9f22e..804ec40 100644 --- a/BitTicker/en.lproj/MainMenu.xib +++ b/BitTicker/en.lproj/MainMenu.xib @@ -3,12 +3,12 @@ 1060 10J869 - 1305 + 1306 1038.35 461.00 com.apple.InterfaceBuilder.CocoaPlugin - 1305 + 1306 YES @@ -68,10 +68,29 @@ BitTicker YES + + + Refresh + r + 1048576 + 2147483647 + + + About BitTicker - + a + 1048576 + 2147483647 + + + + + + Settings + s + 1048576 2147483647 @@ -137,6 +156,22 @@ 686 + + + refreshTicker: + + + + 743 + + + + showSettings: + + + + 744 + @@ -201,6 +236,8 @@ + + @@ -231,6 +268,18 @@ + + 740 + + + Menu Item - Settings + + + 742 + + + Menu Item - Refresh + @@ -261,6 +310,8 @@ 564.ImportedFromIB2 705.IBPluginDependency 719.IBPluginDependency + 740.IBPluginDependency + 742.IBPluginDependency YES @@ -288,6 +339,8 @@ com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin @@ -302,7 +355,7 @@ - 739 + 744 @@ -314,6 +367,7 @@ YES YES + refreshTicker: showAbout: showSettings: @@ -321,17 +375,23 @@ YES id id + id YES YES + refreshTicker: showAbout: showSettings: YES + + refreshTicker: + id + showAbout: id From c582b82ddbbbdeeda7d49d98c19b1f5542323e63 Mon Sep 17 00:00:00 2001 From: mrsteveman1 Date: Fri, 10 Jun 2011 17:36:20 -0400 Subject: [PATCH 25/46] remove miner stuff (its still in another tree, going to fork the app) --- BitTicker.xcodeproj/project.pbxproj | 12 --- BitTicker/BitTickerAppDelegate.h | 15 --- BitTicker/BitTickerAppDelegate.m | 137 +--------------------------- BitTicker/BitcoinCZ.h | 20 ---- BitTicker/BitcoinCZ.m | 58 ------------ BitTicker/BitcoinMarket.h | 7 +- BitTicker/BitcoinMarket.m | 5 +- BitTicker/Miner.h | 30 ------ BitTicker/Miner.m | 22 ----- BitTicker/SharedSettings.m | 3 - 10 files changed, 4 insertions(+), 305 deletions(-) delete mode 100644 BitTicker/BitcoinCZ.h delete mode 100644 BitTicker/BitcoinCZ.m delete mode 100644 BitTicker/Miner.h delete mode 100644 BitTicker/Miner.m diff --git a/BitTicker.xcodeproj/project.pbxproj b/BitTicker.xcodeproj/project.pbxproj index fb9300a..1d69c7e 100644 --- a/BitTicker.xcodeproj/project.pbxproj +++ b/BitTicker.xcodeproj/project.pbxproj @@ -17,8 +17,6 @@ 837DF12D139B2E97009987F3 /* SettingsWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 837DF12C139B2E97009987F3 /* SettingsWindow.xib */; }; 837DF131139B319F009987F3 /* SettingsWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 837DF130139B319F009987F3 /* SettingsWindow.m */; }; 837DF135139B8E19009987F3 /* SettingsWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 837DF134139B8E18009987F3 /* SettingsWindowController.m */; }; - AA0140D2139FF9050084A3A6 /* BitcoinCZ.m in Sources */ = {isa = PBXBuildFile; fileRef = AA0140D1139FF9050084A3A6 /* BitcoinCZ.m */; }; - AA0140D6139FFA7D0084A3A6 /* Miner.m in Sources */ = {isa = PBXBuildFile; fileRef = AA0140D5139FFA7D0084A3A6 /* Miner.m */; }; AA239F511379E67300150707 /* StatusItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = AA239F4E1379E67300150707 /* StatusItemView.m */; }; AA239F761379F17200150707 /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA239F731379F17200150707 /* CoreServices.framework */; }; AA239F771379F17200150707 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = AA239F741379F17200150707 /* libz.dylib */; }; @@ -69,10 +67,6 @@ 837DF130139B319F009987F3 /* SettingsWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SettingsWindow.m; sourceTree = ""; }; 837DF133139B8E18009987F3 /* SettingsWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SettingsWindowController.h; sourceTree = ""; }; 837DF134139B8E18009987F3 /* SettingsWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SettingsWindowController.m; sourceTree = ""; }; - AA0140D0139FF9050084A3A6 /* BitcoinCZ.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BitcoinCZ.h; path = "BitcoinCZ.h"; sourceTree = ""; }; - AA0140D1139FF9050084A3A6 /* BitcoinCZ.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BitcoinCZ.m; path = "BitcoinCZ.m"; sourceTree = ""; }; - AA0140D4139FFA7C0084A3A6 /* Miner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Miner.h; path = "Miner.h"; sourceTree = ""; }; - AA0140D5139FFA7D0084A3A6 /* Miner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Miner.m; path = "Miner.m"; sourceTree = ""; }; AA239F4D1379E67300150707 /* StatusItemView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StatusItemView.h; sourceTree = ""; }; AA239F4E1379E67300150707 /* StatusItemView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StatusItemView.m; sourceTree = ""; }; AA239F731379F17200150707 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; @@ -199,10 +193,6 @@ 83405B36137F6DDF0060CAD4 /* BitcoinMarket.m */, 83405B37137F6DDF0060CAD4 /* MtGoxMarket.h */, 83405B38137F6DDF0060CAD4 /* MtGoxMarket.m */, - AA0140D0139FF9050084A3A6 /* BitcoinCZ.h */, - AA0140D1139FF9050084A3A6 /* BitcoinCZ.m */, - AA0140D4139FFA7C0084A3A6 /* Miner.h */, - AA0140D5139FFA7D0084A3A6 /* Miner.m */, AAB399B1139B0D8500B9438F /* Wallet.h */, AAB399B2139B0D8500B9438F /* Wallet.m */, AAD172621399BA1D00B505B0 /* EMKeychainItem.h */, @@ -395,8 +385,6 @@ AAB399B3139B0D8500B9438F /* Wallet.m in Sources */, 837DF131139B319F009987F3 /* SettingsWindow.m in Sources */, 837DF135139B8E19009987F3 /* SettingsWindowController.m in Sources */, - AA0140D2139FF9050084A3A6 /* BitcoinCZ.m in Sources */, - AA0140D6139FFA7D0084A3A6 /* Miner.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/BitTicker/BitTickerAppDelegate.h b/BitTicker/BitTickerAppDelegate.h index 51486ea..aa2176c 100644 --- a/BitTicker/BitTickerAppDelegate.h +++ b/BitTicker/BitTickerAppDelegate.h @@ -25,19 +25,15 @@ @class SettingsWindow; @class MtGoxMarket; -@class BitcoinCZ; -@class Miner; #import "BitcoinMarketDelegate.h" @interface BitTickerAppDelegate : NSObject { SharedSettings *sharedSettingManager; MtGoxMarket *market; - BitcoinCZ *miner; NSTimer *tickerTimer; NSTimer *walletTimer; - NSTimer *minerTimer; NSMutableArray *stats; StatusItemView *statusItemView; @@ -56,23 +52,12 @@ NSTextField *BTCxUSDValue; NSTextField *USDValue; NSTextField *walletUSDValue; - - //miner stuff - NSTextField *confirmedReward; - NSTextField *unconfirmedReward; - NSTextField *estimatedReward; - NSTextField *username; - NSTextField *workers; - NSTextField *hashOutput; NSView *statsView; NSMenuItem *statsItem; NSView *walletView; NSMenuItem *walletItem; - - NSView *minerView; - NSMenuItem *minerItem; // below the line NSMenuItem *quitItem; diff --git a/BitTicker/BitTickerAppDelegate.m b/BitTicker/BitTickerAppDelegate.m index ccda619..79539ba 100644 --- a/BitTicker/BitTickerAppDelegate.m +++ b/BitTicker/BitTickerAppDelegate.m @@ -20,10 +20,8 @@ #import "BitTickerAppDelegate.h" #import "Ticker.h" #import "Wallet.h" -#import "Miner.h" #import "StatusItemView.h" #import "MtGoxMarket.h" -#import "BitcoinCZ.h" #import "SharedSettings.h" #import "SettingsWindowController.h" @@ -321,121 +319,7 @@ - (void)awakeFromNib { [walletLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; [walletView addSubview:walletLabel]; [walletLabel release]; - - - - - - - [trayMenu addItem:[NSMenuItem separatorItem]]; - - - minerItem = [[NSMenuItem alloc] init]; - minerView = [[NSView alloc] initWithFrame:CGRectMake(0,0,180,75)]; - [minerItem setView:minerView]; - [trayMenu addItem:minerItem]; - - //section header - NSTextField *minerSectionLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,64,headerWidth,menuHeight)]; - [minerSectionLabel setEditable:FALSE]; - [minerSectionLabel setBordered:NO]; - [minerSectionLabel setAlignment:NSLeftTextAlignment]; - [minerSectionLabel setBackgroundColor:[NSColor clearColor]]; - [minerSectionLabel setStringValue:@"Miner Data"]; - [minerSectionLabel setTextColor:[NSColor blueColor]]; - [minerSectionLabel setFont:[NSFont fontWithName:headerFont size:headerFontSize]]; - [minerView addSubview:minerSectionLabel]; - [minerSectionLabel release]; - - confirmedReward = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 45, valueWidth, menuHeight)]; - [confirmedReward setEditable:FALSE]; - [confirmedReward setBordered:NO]; - [confirmedReward setAlignment:NSRightTextAlignment]; - [confirmedReward setBackgroundColor:[NSColor clearColor]]; - [confirmedReward setTextColor:[NSColor blackColor]]; - [confirmedReward setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [minerView addSubview:confirmedReward]; - - NSTextField *confirmedRewardLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,45,labelWidth,menuHeight)]; - [confirmedRewardLabel setEditable:FALSE]; - [confirmedRewardLabel setBordered:NO]; - [confirmedRewardLabel setAlignment:NSLeftTextAlignment]; - [confirmedRewardLabel setBackgroundColor:[NSColor clearColor]]; - [confirmedRewardLabel setStringValue:@"Confirmed:"]; - [confirmedRewardLabel setTextColor:[NSColor blackColor]]; - [confirmedRewardLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [minerView addSubview:confirmedRewardLabel]; - [confirmedRewardLabel release]; - - unconfirmedReward = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 30, valueWidth, menuHeight)]; - [unconfirmedReward setEditable:FALSE]; - [unconfirmedReward setBordered:NO]; - [unconfirmedReward setAlignment:NSRightTextAlignment]; - [unconfirmedReward setBackgroundColor:[NSColor clearColor]]; - [unconfirmedReward setTextColor:[NSColor blackColor]]; - [unconfirmedReward setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [minerView addSubview:unconfirmedReward]; - - NSTextField *unconfirmedRewardLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,30,labelWidth,menuHeight)]; - [unconfirmedRewardLabel setEditable:FALSE]; - [unconfirmedRewardLabel setBordered:NO]; - [unconfirmedRewardLabel setAlignment:NSLeftTextAlignment]; - [unconfirmedRewardLabel setBackgroundColor:[NSColor clearColor]]; - [unconfirmedRewardLabel setStringValue:@"Unconfirmed:"]; - [unconfirmedRewardLabel setTextColor:[NSColor blackColor]]; - [unconfirmedRewardLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [minerView addSubview:unconfirmedRewardLabel]; - [unconfirmedRewardLabel release]; - - - - estimatedReward = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 15, valueWidth, menuHeight)]; - [estimatedReward setEditable:FALSE]; - [estimatedReward setBordered:NO]; - [estimatedReward setAlignment:NSRightTextAlignment]; - [estimatedReward setBackgroundColor:[NSColor clearColor]]; - [estimatedReward setTextColor:[NSColor blackColor]]; - [estimatedReward setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [minerView addSubview:estimatedReward]; - - NSTextField *estimatedRewardLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,15,labelWidth,menuHeight)]; - [estimatedRewardLabel setEditable:FALSE]; - [estimatedRewardLabel setBordered:NO]; - [estimatedRewardLabel setAlignment:NSLeftTextAlignment]; - [estimatedRewardLabel setBackgroundColor:[NSColor clearColor]]; - [estimatedRewardLabel setStringValue:@"Estimated:"]; - [estimatedRewardLabel setTextColor:[NSColor blackColor]]; - [estimatedRewardLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [minerView addSubview:estimatedRewardLabel]; - [estimatedRewardLabel release]; - - - - hashOutput = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 0, valueWidth, menuHeight)]; - [hashOutput setEditable:FALSE]; - [hashOutput setBordered:NO]; - [hashOutput setAlignment:NSRightTextAlignment]; - [hashOutput setBackgroundColor:[NSColor clearColor]]; - [hashOutput setTextColor:[NSColor blackColor]]; - [hashOutput setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [minerView addSubview:hashOutput]; - - NSTextField *hashOutputLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,0,labelWidth,menuHeight)]; - [hashOutputLabel setEditable:FALSE]; - [hashOutputLabel setBordered:NO]; - [hashOutputLabel setAlignment:NSLeftTextAlignment]; - [hashOutputLabel setBackgroundColor:[NSColor clearColor]]; - [hashOutputLabel setStringValue:@"MHash/s:"]; - [hashOutputLabel setTextColor:[NSColor blackColor]]; - [hashOutputLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [minerView addSubview:hashOutputLabel]; - [hashOutputLabel release]; - - - - - - + [trayMenu addItem:[NSMenuItem separatorItem]]; refreshItem = [trayMenu addItemWithTitle:@"Refresh" @@ -469,15 +353,12 @@ - (void)awakeFromNib { MSLog(@"Starting"); market = [[MtGoxMarket alloc] initWithDelegate:self]; - miner = [[BitcoinCZ alloc] initWithDelegate:self]; tickerTimer = [[NSTimer scheduledTimerWithTimeInterval:30 target:market selector:@selector(fetchTicker) userInfo:nil repeats:YES] retain]; walletTimer = [[NSTimer scheduledTimerWithTimeInterval:60 target:market selector:@selector(fetchWallet) userInfo:nil repeats:YES] retain]; - minerTimer = [[NSTimer scheduledTimerWithTimeInterval:60 target:miner selector:@selector(fetchMiner) userInfo:nil repeats:YES] retain]; [market fetchTicker]; [market fetchWallet]; - [miner fetchMiner]; } #pragma mark Application delegate @@ -585,20 +466,4 @@ -(void)bitcoinMarket:(BitcoinMarket*)market didReceiveWallet:(Wallet*)wallet { } } --(void)bitcoinMarket:(BitcoinMarket*)market didReceiveMiner:(Miner*)minerdata { - [unconfirmedReward setStringValue:[minerdata.unconfirmed_reward stringValue]]; - [confirmedReward setStringValue:[minerdata.confirmed_reward stringValue]]; - [estimatedReward setStringValue:[minerdata.estimated_reward stringValue]]; - double hashcount; - for (NSDictionary *worker in minerdata.workers) { - hashcount = hashcount + [[worker objectForKey:@"hashrate"] doubleValue]; - } - NSNumberFormatter *hashFormatter = [[NSNumberFormatter alloc] init]; - hashFormatter.numberStyle = NSNumberFormatterDecimalStyle; - hashFormatter.hasThousandSeparators = YES; - [hashOutput setStringValue:[hashFormatter stringFromNumber:[NSNumber numberWithDouble:hashcount]]]; - - [hashFormatter release]; -} - @end diff --git a/BitTicker/BitcoinCZ.h b/BitTicker/BitcoinCZ.h deleted file mode 100644 index 4802b57..0000000 --- a/BitTicker/BitcoinCZ.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// BitcoinCZ.h -// BitTicker -// -// Created by steve on 6/8/11. -// Copyright 2011 none. All rights reserved. -// - -#import - -#import "BitcoinMarket.h" -#import "Miner.h" - -@interface BitcoinCZ : BitcoinMarket { - -} - --(id)initWithDelegate:(id)delegate; - -@end diff --git a/BitTicker/BitcoinCZ.m b/BitTicker/BitcoinCZ.m deleted file mode 100644 index 3f15ed8..0000000 --- a/BitTicker/BitcoinCZ.m +++ /dev/null @@ -1,58 +0,0 @@ -// -// BitcoinCZ.m -// BitTicker -// -// Created by steve on 6/8/11. -// Copyright 2011 none. All rights reserved. -// - -#import "BitcoinCZ.h" -#import "Miner.h" -#define BITCOINCZ_MINING_URL @"http://mining.bitcoin.cz/accounts/profile/json" - -@implementation BitcoinCZ - --(id)initWithDelegate:(id)delegate { - if (!(self = [super initWithDelegate:delegate])) return self; - _tickerURL = @""; - _tradeURL = @""; - _depthURL = @""; - _walletURL = @""; - _minerURL = BITCOINCZ_MINING_URL; - return self; -} - --(void)fetchMiner { - MSLog(@"Fetching miner..."); - NSString *apiKey = [sharedSettingManager apiKeyForMarket:eMarketBitcoinCZ]; - if ([apiKey isEqualToString:@""] || apiKey == nil) { - return; - } - NSString *url = [NSString stringWithFormat:@"%@/%@",self.minerURL,apiKey]; - [self downloadJsonDataFromURL:[NSURL URLWithString:url] callback:@selector(didFetchMiner:)]; -} - --(void)didFetchMiner:(NSDictionary *)minerdata { - Miner *newminer = [[Miner alloc] init]; - NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; - [formatter setNumberStyle:NSNumberFormatterDecimalStyle]; - newminer.unconfirmed_reward = [formatter numberFromString:[minerdata objectForKey:@"unconfirmed_reward"]]; - newminer.confirmed_reward = [formatter numberFromString:[minerdata objectForKey:@"confirmed_reward"]]; - newminer.estimated_reward = [formatter numberFromString:[minerdata objectForKey:@"estimated_reward"]]; - newminer.send_threshold = [formatter numberFromString:[minerdata objectForKey:@"send_threshold"]]; - newminer.wallet = [minerdata objectForKey:@"wallet"]; - newminer.username = [minerdata objectForKey:@"username"]; - NSMutableArray *workerArray = [NSMutableArray arrayWithCapacity:10]; - NSDictionary *dict = [minerdata objectForKey:@"workers"]; - for(NSString *key in dict) { - NSDictionary *value = [dict objectForKey:key]; - NSMutableDictionary *tempDict = [NSMutableDictionary dictionaryWithDictionary:value]; - [tempDict setObject:key forKey:@"worker_name"]; - [workerArray addObject:tempDict]; - } - newminer.workers = workerArray; - [formatter release]; - [_delegate bitcoinMarket:self didReceiveMiner:newminer]; -} - -@end diff --git a/BitTicker/BitcoinMarket.h b/BitTicker/BitcoinMarket.h index 49a8ea3..bb151d4 100644 --- a/BitTicker/BitcoinMarket.h +++ b/BitTicker/BitcoinMarket.h @@ -14,9 +14,8 @@ enum kBitcoinMarkets { eMarketMtGox = 0, - eMarketBitcoinCZ = 1, // Used for market enumeration. Keep this as the last value. - eNumberOfMarkets = 2 + eNumberOfMarkets = 1 }; @class RequestHandler; @@ -32,7 +31,7 @@ enum kBitcoinMarkets { NSString *_tickerURL; NSString *_depthURL; NSString *_walletURL; - NSString *_minerURL; + } -(id)initWithDelegate:(id)delegate; @@ -47,7 +46,6 @@ enum kBitcoinMarkets { -(void)fetchTicker; -(void)fetchMarketDepth; -(void)fetchWallet; --(void)fetchMiner; @property (nonatomic, assign) id delegate; @@ -55,6 +53,5 @@ enum kBitcoinMarkets { @property (readonly,nonatomic,retain) NSString *tickerURL; @property (readonly,nonatomic,retain) NSString *depthURL; @property (readonly,nonatomic,retain) NSString *walletURL; -@property (readonly,nonatomic,retain) NSString *minerURL; @end diff --git a/BitTicker/BitcoinMarket.m b/BitTicker/BitcoinMarket.m index 886c864..ac91067 100644 --- a/BitTicker/BitcoinMarket.m +++ b/BitTicker/BitcoinMarket.m @@ -19,7 +19,6 @@ @implementation BitcoinMarket @synthesize tradeURL = _tradeURL; @synthesize walletURL = _walletURL; @synthesize depthURL = _depthURL; -@synthesize minerURL = _minerURL; -(id)initWithDelegate:(id)delegate { if (!(self = [super init])) return self; @@ -133,7 +132,5 @@ -(void)fetchMarketDepth { -(void)fetchWallet { [NSException raise:@"MethodNotOverwrittenException" format:@"%s must be overwritten by BitcoinMarket subclasses",__func__]; } --(void)fetchMiner { - [NSException raise:@"MethodNotOverwrittenException" format:@"%s must be overwritten by BitcoinMarket subclasses",__func__]; -} + @end diff --git a/BitTicker/Miner.h b/BitTicker/Miner.h deleted file mode 100644 index fead5a3..0000000 --- a/BitTicker/Miner.h +++ /dev/null @@ -1,30 +0,0 @@ -// -// Miner.h -// BitTicker -// -// Created by steve on 6/8/11. -// Copyright 2011 none. All rights reserved. -// - -#import - - -@interface Miner : NSObject { - NSString *_username; - NSNumber *_unconfirmed_reward; - NSNumber *_send_threshold; - NSNumber *_confirmed_reward; - NSNumber *_estimated_reward; - NSString *_wallet; - NSArray *_workers; -} - -@property (retain) NSString *username; -@property (retain) NSNumber *unconfirmed_reward; -@property (retain) NSNumber *confirmed_reward; -@property (retain) NSNumber *estimated_reward; -@property (retain) NSNumber *send_threshold; -@property (retain) NSString *wallet; -@property (retain) NSArray *workers; - -@end \ No newline at end of file diff --git a/BitTicker/Miner.m b/BitTicker/Miner.m deleted file mode 100644 index 9e54691..0000000 --- a/BitTicker/Miner.m +++ /dev/null @@ -1,22 +0,0 @@ -// -// Miner.m -// BitTicker -// -// Created by steve on 6/8/11. -// Copyright 2011 none. All rights reserved. -// - -#import "Miner.h" - - -@implementation Miner - -@synthesize unconfirmed_reward = _unconfirmed_reward; -@synthesize confirmed_reward = _confirmed_reward; -@synthesize estimated_reward = _estimated_reward; -@synthesize send_threshold = _send_threshold; -@synthesize wallet = _wallet; -@synthesize workers = _workers; -@synthesize username = _username; - -@end \ No newline at end of file diff --git a/BitTicker/SharedSettings.m b/BitTicker/SharedSettings.m index faf4752..c6941b3 100644 --- a/BitTicker/SharedSettings.m +++ b/BitTicker/SharedSettings.m @@ -102,9 +102,6 @@ -(NSString*)stringForMarket:(NSInteger)market { case eMarketMtGox: return @"MtGox"; break; - case eMarketBitcoinCZ: - return @"BitcoinCZ"; - break; default: return @"Unknown"; break; From b9edebf924af5794a2061e8cad7319e397dfa819 Mon Sep 17 00:00:00 2001 From: mrsteveman1 Date: Fri, 10 Jun 2011 17:38:26 -0400 Subject: [PATCH 26/46] made 2 methods return (IBAction) and moved 2 others so theyre all in the same area --- BitTicker/BitTickerAppDelegate.m | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/BitTicker/BitTickerAppDelegate.m b/BitTicker/BitTickerAppDelegate.m index 79539ba..f88c8cf 100644 --- a/BitTicker/BitTickerAppDelegate.m +++ b/BitTicker/BitTickerAppDelegate.m @@ -391,6 +391,19 @@ - (void)applicationWillTerminate:(NSNotification *)notification { } +-(void)settingsWindowClosed { + +} + +#pragma mark Actions + +- (IBAction)quitProgram:(id)sender { + [NSApp terminate:self]; +} +- (IBAction)refreshTicker:(id)sender { + [market fetchTicker]; +} + - (IBAction)showAbout:(id)sender { [NSApp orderFrontStandardAboutPanel:nil]; [NSApp activateIgnoringOtherApps:YES]; @@ -401,17 +414,6 @@ - (IBAction)showSettings:(id)sender { [NSApp activateIgnoringOtherApps:YES]; } --(void)settingsWindowClosed { - -} -#pragma mark Actions -- (void)quitProgram:(id)sender { - [NSApp terminate:self]; -} -- (void)refreshTicker:(id)sender { - [market fetchTicker]; -} - #pragma mark Bitcoin market delegate // A request failed for some reason, for example the API being down -(void)bitcoinMarket:(BitcoinMarket*)market requestFailedWithError:(NSError*)error { From dfea8c109a7b2ccb331a1cabde67c6a3a0f400d9 Mon Sep 17 00:00:00 2001 From: mrsteveman1 Date: Fri, 10 Jun 2011 18:23:18 -0400 Subject: [PATCH 27/46] move all menu code to a separate controller and make it the delegate for market events --- BitTicker.xcodeproj/project.pbxproj | 12 + BitTicker/BitTickerAppDelegate.h | 44 +-- BitTicker/BitTickerAppDelegate.m | 399 +------------------------- BitTicker/CustomMenuView.h | 17 ++ BitTicker/CustomMenuView.m | 34 +++ BitTicker/MenuController.h | 56 ++++ BitTicker/MenuController.m | 416 ++++++++++++++++++++++++++++ 7 files changed, 544 insertions(+), 434 deletions(-) create mode 100644 BitTicker/CustomMenuView.h create mode 100644 BitTicker/CustomMenuView.m create mode 100644 BitTicker/MenuController.h create mode 100644 BitTicker/MenuController.m diff --git a/BitTicker.xcodeproj/project.pbxproj b/BitTicker.xcodeproj/project.pbxproj index 1d69c7e..c2a7b32 100644 --- a/BitTicker.xcodeproj/project.pbxproj +++ b/BitTicker.xcodeproj/project.pbxproj @@ -23,6 +23,8 @@ AA239F781379F17200150707 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA239F751379F17200150707 /* SystemConfiguration.framework */; }; AA239F7B1379F1B200150707 /* Quartz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA239F791379F1B200150707 /* Quartz.framework */; }; AA239F7C1379F1B200150707 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA239F7A1379F1B200150707 /* QuartzCore.framework */; }; + AA435C0E13A2CB550050F307 /* MenuController.m in Sources */ = {isa = PBXBuildFile; fileRef = AA435C0D13A2CB550050F307 /* MenuController.m */; }; + AA435C1213A2CCAA0050F307 /* CustomMenuView.m in Sources */ = {isa = PBXBuildFile; fileRef = AA435C1113A2CCA90050F307 /* CustomMenuView.m */; }; AA4BBA2D1379E31B005CE351 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA4BBA2C1379E31B005CE351 /* Cocoa.framework */; }; AA4BBA371379E31C005CE351 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = AA4BBA351379E31C005CE351 /* InfoPlist.strings */; }; AA4BBA3A1379E31C005CE351 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = AA4BBA391379E31C005CE351 /* main.m */; }; @@ -74,6 +76,10 @@ AA239F751379F17200150707 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; AA239F791379F1B200150707 /* Quartz.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quartz.framework; path = System/Library/Frameworks/Quartz.framework; sourceTree = SDKROOT; }; AA239F7A1379F1B200150707 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; + AA435C0C13A2CB550050F307 /* MenuController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MenuController.h; path = "../../../SourceCache/BitTicker-Mac/BitTicker/MenuController.h"; sourceTree = ""; }; + AA435C0D13A2CB550050F307 /* MenuController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MenuController.m; path = "../../../SourceCache/BitTicker-Mac/BitTicker/MenuController.m"; sourceTree = ""; }; + AA435C1013A2CCA90050F307 /* CustomMenuView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CustomMenuView.h; path = "../../../SourceCache/BitTicker-Mac/BitTicker/CustomMenuView.h"; sourceTree = ""; }; + AA435C1113A2CCA90050F307 /* CustomMenuView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CustomMenuView.m; path = "../../../SourceCache/BitTicker-Mac/BitTicker/CustomMenuView.m"; sourceTree = ""; }; AA4BBA281379E31B005CE351 /* BitTicker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BitTicker.app; sourceTree = BUILT_PRODUCTS_DIR; }; AA4BBA2C1379E31B005CE351 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; AA4BBA2F1379E31B005CE351 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; @@ -275,6 +281,10 @@ 837DF132139B3B8A009987F3 /* Settings */, AA239F4D1379E67300150707 /* StatusItemView.h */, AA239F4E1379E67300150707 /* StatusItemView.m */, + AA435C0C13A2CB550050F307 /* MenuController.h */, + AA435C0D13A2CB550050F307 /* MenuController.m */, + AA435C1013A2CCA90050F307 /* CustomMenuView.h */, + AA435C1113A2CCA90050F307 /* CustomMenuView.m */, AA4BBA3E1379E31C005CE351 /* BitTickerAppDelegate.h */, AA4BBA3F1379E31C005CE351 /* BitTickerAppDelegate.m */, 837DF12E139B2EC8009987F3 /* Nibs */, @@ -385,6 +395,8 @@ AAB399B3139B0D8500B9438F /* Wallet.m in Sources */, 837DF131139B319F009987F3 /* SettingsWindow.m in Sources */, 837DF135139B8E19009987F3 /* SettingsWindowController.m in Sources */, + AA435C0E13A2CB550050F307 /* MenuController.m in Sources */, + AA435C1213A2CCAA0050F307 /* CustomMenuView.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/BitTicker/BitTickerAppDelegate.h b/BitTicker/BitTickerAppDelegate.h index aa2176c..39043e2 100644 --- a/BitTicker/BitTickerAppDelegate.h +++ b/BitTicker/BitTickerAppDelegate.h @@ -19,16 +19,13 @@ #import #import "SharedSettings.h" -@class Ticker; -@class StatusItemView; +#import "MenuController.h" @class RequestHandler; @class SettingsWindow; - @class MtGoxMarket; -#import "BitcoinMarketDelegate.h" - -@interface BitTickerAppDelegate : NSObject { +@interface BitTickerAppDelegate : NSObject { + MenuController *menuController; SharedSettings *sharedSettingManager; MtGoxMarket *market; @@ -36,39 +33,6 @@ NSTimer *walletTimer; NSMutableArray *stats; - StatusItemView *statusItemView; - NSStatusItem *_statusItem; - - //fields for each stat - NSTextField *highValue; - NSTextField *lowValue; - NSTextField *volValue; - NSTextField *buyValue; - NSTextField *sellValue; - NSTextField *lastValue; - - //wallet stuff - NSTextField *BTCValue; - NSTextField *BTCxUSDValue; - NSTextField *USDValue; - NSTextField *walletUSDValue; - - NSView *statsView; - NSMenuItem *statsItem; - - NSView *walletView; - NSMenuItem *walletItem; - - // below the line - NSMenuItem *quitItem; - NSMenuItem *aboutItem; - NSMenuItem *settingsItem; - NSMenuItem *refreshItem; - NSMenuItem *preferenceItem; - - NSMenu *trayMenu; - - NSNumberFormatter *currencyFormatter; NSWindowController *settingsWindowController; } @@ -77,7 +41,7 @@ - (IBAction)refreshTicker:(id)sender; - (IBAction)showSettings:(id)sender; - (IBAction)showAbout:(id)sender; -@property (retain, nonatomic) NSNumber *tickerValue; + @property (retain) NSMutableArray *stats; @property (nonatomic) NSInteger cancelThread; diff --git a/BitTicker/BitTickerAppDelegate.m b/BitTicker/BitTickerAppDelegate.m index f88c8cf..24093c2 100644 --- a/BitTicker/BitTickerAppDelegate.m +++ b/BitTicker/BitTickerAppDelegate.m @@ -18,9 +18,6 @@ */ #import "BitTickerAppDelegate.h" -#import "Ticker.h" -#import "Wallet.h" -#import "StatusItemView.h" #import "MtGoxMarket.h" #import "SharedSettings.h" @@ -31,328 +28,16 @@ @implementation BitTickerAppDelegate @synthesize stats; @synthesize cancelThread; -@synthesize tickerValue; - - (void)awakeFromNib { - _statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength]; - [_statusItem retain]; - - statusItemView = [[StatusItemView alloc] init]; - statusItemView.statusItem = _statusItem; - [statusItemView setToolTip:NSLocalizedString(@"BitTicker", - @"Status Item Tooltip")]; - [_statusItem setView:statusItemView]; - sharedSettingManager = [SharedSettings sharedSettingManager]; settingsWindowController = [[SettingsWindowController alloc] init]; - - // menu stuff - trayMenu = [[NSMenu alloc] initWithTitle:@"Ticker"]; - //graphItem = [[NSMenuItem alloc] init]; - statsItem = [[NSMenuItem alloc] init]; - statsView = [[NSView alloc] initWithFrame:CGRectMake(0,70,180,105)]; - [statsItem setView:statsView]; - [trayMenu addItem:statsItem]; - - NSString *menuFont = @"LucidaGrande"; - NSInteger menuFontSize = 12; - NSString *headerFont = @"LucidaGrande-Bold"; - NSInteger headerFontSize = 13; - - NSInteger menuHeight = 15; - NSInteger labelWidth = 70; - NSInteger headerWidth = 120; - NSInteger valueWidth = 60; - - NSInteger labelOffset = 20; - NSInteger valueOffset = 110; - - //section header - NSTextField *tickerSectionLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,93,headerWidth,menuHeight)]; - [tickerSectionLabel setEditable:FALSE]; - [tickerSectionLabel setBordered:NO]; - [tickerSectionLabel setAlignment:NSLeftTextAlignment]; - [tickerSectionLabel setBackgroundColor:[NSColor clearColor]]; - [tickerSectionLabel setStringValue:@"Ticker"]; - [tickerSectionLabel setTextColor:[NSColor blueColor]]; - [tickerSectionLabel setFont:[NSFont fontWithName:headerFont size:headerFontSize]]; - [statsView addSubview:tickerSectionLabel]; - [tickerSectionLabel release]; - - highValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,75,valueWidth,menuHeight)]; - [highValue setEditable:FALSE]; - [highValue setBordered:NO]; - [highValue setAlignment:NSRightTextAlignment]; - [highValue setBackgroundColor:[NSColor clearColor]]; - [highValue setTextColor:[NSColor blackColor]]; - [highValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [statsView addSubview:highValue]; - - NSTextField *highLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,75,labelWidth,menuHeight)]; - [highLabel setEditable:FALSE]; - [highLabel setBordered:NO]; - [highLabel setAlignment:NSLeftTextAlignment]; - [highLabel setBackgroundColor:[NSColor clearColor]]; - [highLabel setStringValue:@"High:"]; - [highLabel setTextColor:[NSColor blackColor]]; - [highLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [statsView addSubview:highLabel]; - [highLabel release]; - - // - lowValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,60,valueWidth,menuHeight)]; - [lowValue setEditable:FALSE]; - [lowValue setBordered:NO]; - [lowValue setAlignment:NSRightTextAlignment]; - [lowValue setBackgroundColor:[NSColor clearColor]]; - [lowValue setTextColor:[NSColor blackColor]]; - [lowValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [statsView addSubview:lowValue]; - - NSTextField *lowLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,60,labelWidth,menuHeight)]; - [lowLabel setEditable:FALSE]; - [lowLabel setBordered:NO]; - [lowLabel setAlignment:NSLeftTextAlignment]; - [lowLabel setBackgroundColor:[NSColor clearColor]]; - [lowLabel setStringValue:@"Low:"]; - [lowLabel setTextColor:[NSColor blackColor]]; - [lowLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [statsView addSubview:lowLabel]; - [lowLabel release]; - - // - buyValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,45,valueWidth,menuHeight)]; - [buyValue setEditable:FALSE]; - [buyValue setBordered:NO]; - [buyValue setAlignment:NSRightTextAlignment]; - [buyValue setBackgroundColor:[NSColor clearColor]]; - [buyValue setTextColor:[NSColor blackColor]]; - [buyValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [statsView addSubview:buyValue]; - - NSTextField *buyLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,45,labelWidth,menuHeight)]; - [buyLabel setEditable:FALSE]; - [buyLabel setBordered:NO]; - [buyLabel setAlignment:NSLeftTextAlignment]; - [buyLabel setBackgroundColor:[NSColor clearColor]]; - [buyLabel setStringValue:@"Buy:"]; - [buyLabel setTextColor:[NSColor blackColor]]; - [buyLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [statsView addSubview:buyLabel]; - [buyLabel release]; - - // - sellValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,30,valueWidth,menuHeight)]; - [sellValue setEditable:FALSE]; - [sellValue setBordered:NO]; - [sellValue setAlignment:NSRightTextAlignment]; - [sellValue setBackgroundColor:[NSColor clearColor]]; - [sellValue setTextColor:[NSColor blackColor]]; - [sellValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [statsView addSubview:sellValue]; - - NSTextField *sellLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,30,labelWidth,menuHeight)]; - [sellLabel setEditable:FALSE]; - [sellLabel setBordered:NO]; - [sellLabel setAlignment:NSLeftTextAlignment]; - [sellLabel setBackgroundColor:[NSColor clearColor]]; - [sellLabel setStringValue:@"Sell:"]; - [sellLabel setTextColor:[NSColor blackColor]]; - [sellLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [statsView addSubview:sellLabel]; - [sellLabel release]; - - // - lastValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,15,valueWidth,menuHeight)]; - [lastValue setEditable:FALSE]; - [lastValue setBordered:NO]; - [lastValue setAlignment:NSRightTextAlignment]; - [lastValue setBackgroundColor:[NSColor clearColor]]; - [lastValue setTextColor:[NSColor blackColor]]; - [lastValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [statsView addSubview:lastValue]; - - NSTextField *lastLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,15,labelWidth,menuHeight)]; - [lastLabel setEditable:FALSE]; - [lastLabel setBordered:NO]; - [lastLabel setAlignment:NSLeftTextAlignment]; - [lastLabel setBackgroundColor:[NSColor clearColor]]; - [lastLabel setStringValue:@"Last:"]; - [lastLabel setTextColor:[NSColor blackColor]]; - [lastLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [statsView addSubview:lastLabel]; - [lastLabel release]; - - // - volValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,0,valueWidth,menuHeight)]; - [volValue setEditable:FALSE]; - [volValue setBordered:NO]; - [volValue setAlignment:NSRightTextAlignment]; - [volValue setBackgroundColor:[NSColor clearColor]]; - [volValue setTextColor:[NSColor blackColor]]; - [volValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [statsView addSubview:volValue]; - - - NSTextField *volLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,0,labelWidth,menuHeight)]; - [volLabel setEditable:FALSE]; - [volLabel setBordered:NO]; - [volLabel setAlignment:NSLeftTextAlignment]; - [volLabel setBackgroundColor:[NSColor clearColor]]; - [volLabel setStringValue:@"Volume:"]; - [volLabel setTextColor:[NSColor blackColor]]; - [volLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [statsView addSubview:volLabel]; - [volLabel release]; - - - [trayMenu addItem:[NSMenuItem separatorItem]]; - - - walletItem = [[NSMenuItem alloc] init]; - walletView = [[NSView alloc] initWithFrame:CGRectMake(0,0,180,75)]; - [walletItem setView:walletView]; - [trayMenu addItem:walletItem]; - - - - //section header - NSTextField *walletSectionLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,64,headerWidth,menuHeight)]; - [walletSectionLabel setEditable:FALSE]; - [walletSectionLabel setBordered:NO]; - [walletSectionLabel setAlignment:NSLeftTextAlignment]; - [walletSectionLabel setBackgroundColor:[NSColor clearColor]]; - // TODO: Allow multiple wallets - [walletSectionLabel setStringValue:@"MtGox Wallet"]; - [walletSectionLabel setTextColor:[NSColor blueColor]]; - [walletSectionLabel setFont:[NSFont fontWithName:headerFont size:headerFontSize]]; - [walletView addSubview:walletSectionLabel]; - [walletSectionLabel release]; - - - - - - - BTCValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 45, valueWidth, menuHeight)]; - [BTCValue setEditable:FALSE]; - [BTCValue setBordered:NO]; - [BTCValue setAlignment:NSRightTextAlignment]; - [BTCValue setBackgroundColor:[NSColor clearColor]]; - [BTCValue setTextColor:[NSColor blackColor]]; - [BTCValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [walletView addSubview:BTCValue]; - - NSTextField *BTCLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,45,labelWidth,menuHeight)]; - [BTCLabel setEditable:FALSE]; - [BTCLabel setBordered:NO]; - [BTCLabel setAlignment:NSLeftTextAlignment]; - [BTCLabel setBackgroundColor:[NSColor clearColor]]; - [BTCLabel setStringValue:@"BTC:"]; - [BTCLabel setTextColor:[NSColor blackColor]]; - [BTCLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [walletView addSubview:BTCLabel]; - [BTCLabel release]; - - BTCxUSDValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 30, valueWidth, menuHeight)]; - [BTCxUSDValue setEditable:FALSE]; - [BTCxUSDValue setBordered:NO]; - [BTCxUSDValue setAlignment:NSRightTextAlignment]; - [BTCxUSDValue setBackgroundColor:[NSColor clearColor]]; - [BTCxUSDValue setTextColor:[NSColor blackColor]]; - [BTCxUSDValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [walletView addSubview:BTCxUSDValue]; - - NSTextField *BTCxUSDLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,30,labelWidth,menuHeight)]; - [BTCxUSDLabel setEditable:FALSE]; - [BTCxUSDLabel setBordered:NO]; - [BTCxUSDLabel setAlignment:NSLeftTextAlignment]; - [BTCxUSDLabel setBackgroundColor:[NSColor clearColor]]; - [BTCxUSDLabel setStringValue:@"BTC * Last:"]; - [BTCxUSDLabel setTextColor:[NSColor blackColor]]; - [BTCxUSDLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [walletView addSubview:BTCxUSDLabel]; - [BTCxUSDLabel release]; - - - - USDValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 15, valueWidth, menuHeight)]; - [USDValue setEditable:FALSE]; - [USDValue setBordered:NO]; - [USDValue setAlignment:NSRightTextAlignment]; - [USDValue setBackgroundColor:[NSColor clearColor]]; - [USDValue setTextColor:[NSColor blackColor]]; - [USDValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [walletView addSubview:USDValue]; - - NSTextField *USDLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,15,labelWidth,menuHeight)]; - [USDLabel setEditable:FALSE]; - [USDLabel setBordered:NO]; - [USDLabel setAlignment:NSLeftTextAlignment]; - [USDLabel setBackgroundColor:[NSColor clearColor]]; - [USDLabel setStringValue:@"USD:"]; - [USDLabel setTextColor:[NSColor blackColor]]; - [USDLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [walletView addSubview:USDLabel]; - [USDLabel release]; - - - - walletUSDValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 0, valueWidth, menuHeight)]; - [walletUSDValue setEditable:FALSE]; - [walletUSDValue setBordered:NO]; - [walletUSDValue setAlignment:NSRightTextAlignment]; - [walletUSDValue setBackgroundColor:[NSColor clearColor]]; - [walletUSDValue setTextColor:[NSColor blackColor]]; - [walletUSDValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [walletView addSubview:walletUSDValue]; - - NSTextField *walletLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,0,labelWidth,menuHeight)]; - [walletLabel setEditable:FALSE]; - [walletLabel setBordered:NO]; - [walletLabel setAlignment:NSLeftTextAlignment]; - [walletLabel setBackgroundColor:[NSColor clearColor]]; - [walletLabel setStringValue:@"Total:"]; - [walletLabel setTextColor:[NSColor blackColor]]; - [walletLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [walletView addSubview:walletLabel]; - [walletLabel release]; - - [trayMenu addItem:[NSMenuItem separatorItem]]; - - refreshItem = [trayMenu addItemWithTitle:@"Refresh" - action:@selector(refreshTicker:) - keyEquivalent:@"r"]; - aboutItem = [trayMenu addItemWithTitle: @"About" - action: @selector (showAbout:) - keyEquivalent: @"a"]; - settingsItem = [trayMenu addItemWithTitle: @"Settings" - action: @selector (showSettings:) - keyEquivalent: @"s"]; - - - quitItem = [trayMenu addItemWithTitle: @"Quit" - action: @selector (quitProgram:) - keyEquivalent: @"q"]; - - - [statusItemView setMenu:trayMenu]; - - - currencyFormatter = [[NSNumberFormatter alloc] init]; - currencyFormatter.numberStyle = NSNumberFormatterCurrencyStyle; - currencyFormatter.currencyCode = @"USD"; // TODO: Base on market currency - currencyFormatter.thousandSeparator = @","; // TODO: Base on local seperator for currency - currencyFormatter.alwaysShowsDecimalSeparator = YES; - currencyFormatter.hasThousandSeparators = YES; - currencyFormatter.minimumFractionDigits = 4; // TODO: Configurable - - + menuController = [[MenuController alloc] init]; + [menuController createMenus]; MSLog(@"Starting"); - market = [[MtGoxMarket alloc] initWithDelegate:self]; + market = [[MtGoxMarket alloc] initWithDelegate:menuController]; tickerTimer = [[NSTimer scheduledTimerWithTimeInterval:30 target:market selector:@selector(fetchTicker) userInfo:nil repeats:YES] retain]; walletTimer = [[NSTimer scheduledTimerWithTimeInterval:60 target:market selector:@selector(fetchWallet) userInfo:nil repeats:YES] retain]; @@ -367,28 +52,8 @@ - (void)applicationWillTerminate:(NSNotification *)notification { [market release]; [tickerTimer invalidate]; [tickerTimer release]; - [_statusItem release]; - [statusItemView release]; - [trayMenu release]; - [statsItem release]; - [currencyFormatter release]; - - [settingsWindowController release]; - - //ticker stuff - [highValue release]; - [lowValue release]; - [volValue release]; - [buyValue release]; - [sellValue release]; - [lastValue release]; - - //wallet stuff - [BTCValue release]; - [BTCxUSDValue release]; - [USDValue release]; - [walletUSDValue release]; - + [settingsWindowController release]; + [menuController release]; } -(void)settingsWindowClosed { @@ -414,58 +79,4 @@ - (IBAction)showSettings:(id)sender { [NSApp activateIgnoringOtherApps:YES]; } -#pragma mark Bitcoin market delegate -// A request failed for some reason, for example the API being down --(void)bitcoinMarket:(BitcoinMarket*)market requestFailedWithError:(NSError*)error { - MSLog(@"Error: %@",error); -} - -// Request wasn't formatted as expected --(void)bitcoinMarket:(BitcoinMarket*)market didReceiveInvalidResponse:(NSData*)data { - MSLog(@"Invalid response: %@",data); -} - --(void)bitcoinMarket:(BitcoinMarket*)market didReceiveTicker:(Ticker*)ticker { - [statusItemView setTickerValue:ticker.last]; - self.tickerValue = ticker.last; - MSLog(@"Got mah ticker: %@",ticker); - - [highValue setStringValue:[currencyFormatter stringFromNumber:ticker.high]]; - [lowValue setStringValue:[currencyFormatter stringFromNumber:ticker.low]]; - [buyValue setStringValue:[currencyFormatter stringFromNumber:ticker.buy]]; - [sellValue setStringValue: [currencyFormatter stringFromNumber:ticker.sell]]; - [lastValue setStringValue: [currencyFormatter stringFromNumber:ticker.last]]; - - NSNumberFormatter *volumeFormatter = [[NSNumberFormatter alloc] init]; - volumeFormatter.numberStyle = NSNumberFormatterDecimalStyle; - volumeFormatter.hasThousandSeparators = YES; - [volValue setStringValue:[volumeFormatter stringFromNumber:ticker.volume]]; - - [volumeFormatter release]; -} - --(void)bitcoinMarket:(BitcoinMarket*)market didReceiveRecentTradesData:(NSArray*)trades { - -} - --(void)bitcoinMarket:(BitcoinMarket*)market didReceiveWallet:(Wallet*)wallet { - double btc = [wallet.btc doubleValue]; - double usd = [wallet.usd doubleValue]; - [BTCValue setStringValue:[NSString stringWithFormat:@"%f.04",[wallet.btc floatValue]]]; - [USDValue setStringValue:[currencyFormatter stringFromNumber:wallet.usd]]; - - double last = [self.tickerValue doubleValue]; - if (last == 0) { - //no last yet so cant multiply anyway - } - else { - NSNumber *BTCxRate = [NSNumber numberWithDouble:btc*last]; - [BTCxUSDValue setStringValue:[currencyFormatter stringFromNumber:BTCxRate]]; - - NSNumber *walletUSD = [NSNumber numberWithDouble:[BTCxRate doubleValue] + usd]; - [walletUSDValue setStringValue: [currencyFormatter stringFromNumber:walletUSD]]; - - } -} - @end diff --git a/BitTicker/CustomMenuView.h b/BitTicker/CustomMenuView.h new file mode 100644 index 0000000..18af3c7 --- /dev/null +++ b/BitTicker/CustomMenuView.h @@ -0,0 +1,17 @@ +// +// CustomMenuView.h +// BitTicker +// +// Created by steve on 6/10/11. +// Copyright 2011 none. All rights reserved. +// + +#import + + +@interface CustomMenuView : NSView { +@private + +} + +@end diff --git a/BitTicker/CustomMenuView.m b/BitTicker/CustomMenuView.m new file mode 100644 index 0000000..f1796e5 --- /dev/null +++ b/BitTicker/CustomMenuView.m @@ -0,0 +1,34 @@ +// +// CustomMenuView.m +// BitTicker +// +// Created by steve on 6/10/11. +// Copyright 2011 none. All rights reserved. +// + +#import "CustomMenuView.h" + + +@implementation CustomMenuView + +- (id)initWithFrame:(NSRect)frame +{ + self = [super initWithFrame:frame]; + if (self) { + // Initialization code here. + } + + return self; +} + +- (void)dealloc +{ + [super dealloc]; +} + +- (void)drawRect:(NSRect)dirtyRect +{ + // Drawing code here. +} + +@end diff --git a/BitTicker/MenuController.h b/BitTicker/MenuController.h new file mode 100644 index 0000000..894423b --- /dev/null +++ b/BitTicker/MenuController.h @@ -0,0 +1,56 @@ +// +// MenuController.h +// BitTicker +// +// Created by steve on 6/10/11. +// Copyright 2011 none. All rights reserved. +// + +#import +#import "BitcoinMarketDelegate.h" +@class StatusItemView; +@class Ticker; +@class BitcoinMarket; + +@interface MenuController : NSObject { + + + NSTextField *highValue; + NSTextField *lowValue; + NSTextField *volValue; + NSTextField *buyValue; + NSTextField *sellValue; + NSTextField *lastValue; + + NSTextField *BTCValue; + NSTextField *BTCxUSDValue; + NSTextField *USDValue; + NSTextField *walletUSDValue; + + NSView *statsView; + NSMenuItem *statsItem; + + NSView *walletView; + NSMenuItem *walletItem; + + NSMenuItem *quitItem; + NSMenuItem *aboutItem; + NSMenuItem *settingsItem; + NSMenuItem *refreshItem; + NSMenuItem *preferenceItem; + + NSMenu *trayMenu; + + StatusItemView *statusItemView; + NSStatusItem *_statusItem; + NSNumberFormatter *currencyFormatter; + + NSNumber *_tickerValue; + +} + +-(void)createMenus; + +@property (retain, nonatomic) NSNumber *tickerValue; + +@end diff --git a/BitTicker/MenuController.m b/BitTicker/MenuController.m new file mode 100644 index 0000000..4c2009a --- /dev/null +++ b/BitTicker/MenuController.m @@ -0,0 +1,416 @@ +// +// MenuController.m +// BitTicker +// +// Created by steve on 6/10/11. +// Copyright 2011 none. All rights reserved. +// + +#import "MenuController.h" +#import "Ticker.h" +#import "Wallet.h" +#import "StatusItemView.h" + +@implementation MenuController + +@synthesize tickerValue = _tickerValue; + +- (id)init +{ + self = [super init]; + if (self) { + currencyFormatter = [[NSNumberFormatter alloc] init]; + currencyFormatter.numberStyle = NSNumberFormatterCurrencyStyle; + currencyFormatter.currencyCode = @"USD"; // TODO: Base on market currency + currencyFormatter.thousandSeparator = @","; // TODO: Base on local seperator for currency + currencyFormatter.alwaysShowsDecimalSeparator = YES; + currencyFormatter.hasThousandSeparators = YES; + currencyFormatter.minimumFractionDigits = 4; // TODO: Configurable + } + + return self; +} + +-(void)createMenus { + + _statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength]; + [_statusItem retain]; + + statusItemView = [[StatusItemView alloc] init]; + statusItemView.statusItem = _statusItem; + [statusItemView setToolTip:NSLocalizedString(@"BitTicker", + @"Status Item Tooltip")]; + [_statusItem setView:statusItemView]; + + + + // menu stuff + trayMenu = [[NSMenu alloc] initWithTitle:@"Ticker"]; + //graphItem = [[NSMenuItem alloc] init]; + statsItem = [[NSMenuItem alloc] init]; + statsView = [[NSView alloc] initWithFrame:CGRectMake(0,70,180,105)]; + [statsItem setView:statsView]; + [trayMenu addItem:statsItem]; + + NSString *menuFont = @"LucidaGrande"; + NSInteger menuFontSize = 12; + NSString *headerFont = @"LucidaGrande-Bold"; + NSInteger headerFontSize = 13; + + NSInteger menuHeight = 15; + NSInteger labelWidth = 70; + NSInteger headerWidth = 120; + NSInteger valueWidth = 60; + + NSInteger labelOffset = 20; + NSInteger valueOffset = 110; + + //section header + NSTextField *tickerSectionLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,93,headerWidth,menuHeight)]; + [tickerSectionLabel setEditable:FALSE]; + [tickerSectionLabel setBordered:NO]; + [tickerSectionLabel setAlignment:NSLeftTextAlignment]; + [tickerSectionLabel setBackgroundColor:[NSColor clearColor]]; + [tickerSectionLabel setStringValue:@"Ticker"]; + [tickerSectionLabel setTextColor:[NSColor blueColor]]; + [tickerSectionLabel setFont:[NSFont fontWithName:headerFont size:headerFontSize]]; + [statsView addSubview:tickerSectionLabel]; + [tickerSectionLabel release]; + + highValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,75,valueWidth,menuHeight)]; + [highValue setEditable:FALSE]; + [highValue setBordered:NO]; + [highValue setAlignment:NSRightTextAlignment]; + [highValue setBackgroundColor:[NSColor clearColor]]; + [highValue setTextColor:[NSColor blackColor]]; + [highValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [statsView addSubview:highValue]; + + NSTextField *highLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,75,labelWidth,menuHeight)]; + [highLabel setEditable:FALSE]; + [highLabel setBordered:NO]; + [highLabel setAlignment:NSLeftTextAlignment]; + [highLabel setBackgroundColor:[NSColor clearColor]]; + [highLabel setStringValue:@"High:"]; + [highLabel setTextColor:[NSColor blackColor]]; + [highLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [statsView addSubview:highLabel]; + [highLabel release]; + + // + lowValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,60,valueWidth,menuHeight)]; + [lowValue setEditable:FALSE]; + [lowValue setBordered:NO]; + [lowValue setAlignment:NSRightTextAlignment]; + [lowValue setBackgroundColor:[NSColor clearColor]]; + [lowValue setTextColor:[NSColor blackColor]]; + [lowValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [statsView addSubview:lowValue]; + + NSTextField *lowLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,60,labelWidth,menuHeight)]; + [lowLabel setEditable:FALSE]; + [lowLabel setBordered:NO]; + [lowLabel setAlignment:NSLeftTextAlignment]; + [lowLabel setBackgroundColor:[NSColor clearColor]]; + [lowLabel setStringValue:@"Low:"]; + [lowLabel setTextColor:[NSColor blackColor]]; + [lowLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [statsView addSubview:lowLabel]; + [lowLabel release]; + + // + buyValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,45,valueWidth,menuHeight)]; + [buyValue setEditable:FALSE]; + [buyValue setBordered:NO]; + [buyValue setAlignment:NSRightTextAlignment]; + [buyValue setBackgroundColor:[NSColor clearColor]]; + [buyValue setTextColor:[NSColor blackColor]]; + [buyValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [statsView addSubview:buyValue]; + + NSTextField *buyLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,45,labelWidth,menuHeight)]; + [buyLabel setEditable:FALSE]; + [buyLabel setBordered:NO]; + [buyLabel setAlignment:NSLeftTextAlignment]; + [buyLabel setBackgroundColor:[NSColor clearColor]]; + [buyLabel setStringValue:@"Buy:"]; + [buyLabel setTextColor:[NSColor blackColor]]; + [buyLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [statsView addSubview:buyLabel]; + [buyLabel release]; + + // + sellValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,30,valueWidth,menuHeight)]; + [sellValue setEditable:FALSE]; + [sellValue setBordered:NO]; + [sellValue setAlignment:NSRightTextAlignment]; + [sellValue setBackgroundColor:[NSColor clearColor]]; + [sellValue setTextColor:[NSColor blackColor]]; + [sellValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [statsView addSubview:sellValue]; + + NSTextField *sellLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,30,labelWidth,menuHeight)]; + [sellLabel setEditable:FALSE]; + [sellLabel setBordered:NO]; + [sellLabel setAlignment:NSLeftTextAlignment]; + [sellLabel setBackgroundColor:[NSColor clearColor]]; + [sellLabel setStringValue:@"Sell:"]; + [sellLabel setTextColor:[NSColor blackColor]]; + [sellLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [statsView addSubview:sellLabel]; + [sellLabel release]; + + // + lastValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,15,valueWidth,menuHeight)]; + [lastValue setEditable:FALSE]; + [lastValue setBordered:NO]; + [lastValue setAlignment:NSRightTextAlignment]; + [lastValue setBackgroundColor:[NSColor clearColor]]; + [lastValue setTextColor:[NSColor blackColor]]; + [lastValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [statsView addSubview:lastValue]; + + NSTextField *lastLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,15,labelWidth,menuHeight)]; + [lastLabel setEditable:FALSE]; + [lastLabel setBordered:NO]; + [lastLabel setAlignment:NSLeftTextAlignment]; + [lastLabel setBackgroundColor:[NSColor clearColor]]; + [lastLabel setStringValue:@"Last:"]; + [lastLabel setTextColor:[NSColor blackColor]]; + [lastLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [statsView addSubview:lastLabel]; + [lastLabel release]; + + // + volValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,0,valueWidth,menuHeight)]; + [volValue setEditable:FALSE]; + [volValue setBordered:NO]; + [volValue setAlignment:NSRightTextAlignment]; + [volValue setBackgroundColor:[NSColor clearColor]]; + [volValue setTextColor:[NSColor blackColor]]; + [volValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [statsView addSubview:volValue]; + + + NSTextField *volLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,0,labelWidth,menuHeight)]; + [volLabel setEditable:FALSE]; + [volLabel setBordered:NO]; + [volLabel setAlignment:NSLeftTextAlignment]; + [volLabel setBackgroundColor:[NSColor clearColor]]; + [volLabel setStringValue:@"Volume:"]; + [volLabel setTextColor:[NSColor blackColor]]; + [volLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [statsView addSubview:volLabel]; + [volLabel release]; + + + [trayMenu addItem:[NSMenuItem separatorItem]]; + + + walletItem = [[NSMenuItem alloc] init]; + walletView = [[NSView alloc] initWithFrame:CGRectMake(0,0,180,75)]; + [walletItem setView:walletView]; + [trayMenu addItem:walletItem]; + + + + //section header + NSTextField *walletSectionLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,64,headerWidth,menuHeight)]; + [walletSectionLabel setEditable:FALSE]; + [walletSectionLabel setBordered:NO]; + [walletSectionLabel setAlignment:NSLeftTextAlignment]; + [walletSectionLabel setBackgroundColor:[NSColor clearColor]]; + // TODO: Allow multiple wallets + [walletSectionLabel setStringValue:@"MtGox Wallet"]; + [walletSectionLabel setTextColor:[NSColor blueColor]]; + [walletSectionLabel setFont:[NSFont fontWithName:headerFont size:headerFontSize]]; + [walletView addSubview:walletSectionLabel]; + [walletSectionLabel release]; + + + + + + + BTCValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 45, valueWidth, menuHeight)]; + [BTCValue setEditable:FALSE]; + [BTCValue setBordered:NO]; + [BTCValue setAlignment:NSRightTextAlignment]; + [BTCValue setBackgroundColor:[NSColor clearColor]]; + [BTCValue setTextColor:[NSColor blackColor]]; + [BTCValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [walletView addSubview:BTCValue]; + + NSTextField *BTCLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,45,labelWidth,menuHeight)]; + [BTCLabel setEditable:FALSE]; + [BTCLabel setBordered:NO]; + [BTCLabel setAlignment:NSLeftTextAlignment]; + [BTCLabel setBackgroundColor:[NSColor clearColor]]; + [BTCLabel setStringValue:@"BTC:"]; + [BTCLabel setTextColor:[NSColor blackColor]]; + [BTCLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [walletView addSubview:BTCLabel]; + [BTCLabel release]; + + BTCxUSDValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 30, valueWidth, menuHeight)]; + [BTCxUSDValue setEditable:FALSE]; + [BTCxUSDValue setBordered:NO]; + [BTCxUSDValue setAlignment:NSRightTextAlignment]; + [BTCxUSDValue setBackgroundColor:[NSColor clearColor]]; + [BTCxUSDValue setTextColor:[NSColor blackColor]]; + [BTCxUSDValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [walletView addSubview:BTCxUSDValue]; + + NSTextField *BTCxUSDLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,30,labelWidth,menuHeight)]; + [BTCxUSDLabel setEditable:FALSE]; + [BTCxUSDLabel setBordered:NO]; + [BTCxUSDLabel setAlignment:NSLeftTextAlignment]; + [BTCxUSDLabel setBackgroundColor:[NSColor clearColor]]; + [BTCxUSDLabel setStringValue:@"BTC * Last:"]; + [BTCxUSDLabel setTextColor:[NSColor blackColor]]; + [BTCxUSDLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [walletView addSubview:BTCxUSDLabel]; + [BTCxUSDLabel release]; + + + + USDValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 15, valueWidth, menuHeight)]; + [USDValue setEditable:FALSE]; + [USDValue setBordered:NO]; + [USDValue setAlignment:NSRightTextAlignment]; + [USDValue setBackgroundColor:[NSColor clearColor]]; + [USDValue setTextColor:[NSColor blackColor]]; + [USDValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [walletView addSubview:USDValue]; + + NSTextField *USDLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,15,labelWidth,menuHeight)]; + [USDLabel setEditable:FALSE]; + [USDLabel setBordered:NO]; + [USDLabel setAlignment:NSLeftTextAlignment]; + [USDLabel setBackgroundColor:[NSColor clearColor]]; + [USDLabel setStringValue:@"USD:"]; + [USDLabel setTextColor:[NSColor blackColor]]; + [USDLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [walletView addSubview:USDLabel]; + [USDLabel release]; + + + + walletUSDValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 0, valueWidth, menuHeight)]; + [walletUSDValue setEditable:FALSE]; + [walletUSDValue setBordered:NO]; + [walletUSDValue setAlignment:NSRightTextAlignment]; + [walletUSDValue setBackgroundColor:[NSColor clearColor]]; + [walletUSDValue setTextColor:[NSColor blackColor]]; + [walletUSDValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [walletView addSubview:walletUSDValue]; + + NSTextField *walletLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,0,labelWidth,menuHeight)]; + [walletLabel setEditable:FALSE]; + [walletLabel setBordered:NO]; + [walletLabel setAlignment:NSLeftTextAlignment]; + [walletLabel setBackgroundColor:[NSColor clearColor]]; + [walletLabel setStringValue:@"Total:"]; + [walletLabel setTextColor:[NSColor blackColor]]; + [walletLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [walletView addSubview:walletLabel]; + [walletLabel release]; + + [trayMenu addItem:[NSMenuItem separatorItem]]; + + refreshItem = [trayMenu addItemWithTitle:@"Refresh" + action:@selector(refreshTicker:) + keyEquivalent:@"r"]; + aboutItem = [trayMenu addItemWithTitle: @"About" + action: @selector (showAbout:) + keyEquivalent: @"a"]; + settingsItem = [trayMenu addItemWithTitle: @"Settings" + action: @selector (showSettings:) + keyEquivalent: @"s"]; + + + quitItem = [trayMenu addItemWithTitle: @"Quit" + action: @selector (quitProgram:) + keyEquivalent: @"q"]; + + + [statusItemView setMenu:trayMenu]; +} + +- (void)dealloc { + [currencyFormatter release]; + [_statusItem release]; + [statusItemView release]; + [trayMenu release]; + [statsItem release]; + //ticker stuff + [highValue release]; + [lowValue release]; + [volValue release]; + [buyValue release]; + [sellValue release]; + [lastValue release]; + + //wallet stuff + [BTCValue release]; + [BTCxUSDValue release]; + [USDValue release]; + [walletUSDValue release]; + [super dealloc]; +} + +#pragma mark Bitcoin market delegate +// A request failed for some reason, for example the API being down +-(void)bitcoinMarket:(BitcoinMarket*)market requestFailedWithError:(NSError*)error { + MSLog(@"Error: %@",error); +} + +// Request wasn't formatted as expected +-(void)bitcoinMarket:(BitcoinMarket*)market didReceiveInvalidResponse:(NSData*)data { + MSLog(@"Invalid response: %@",data); +} + +-(void)bitcoinMarket:(BitcoinMarket*)market didReceiveTicker:(Ticker*)ticker { + [statusItemView setTickerValue:ticker.last]; + self.tickerValue = ticker.last; + MSLog(@"Got mah ticker: %@",ticker); + + [highValue setStringValue:[currencyFormatter stringFromNumber:ticker.high]]; + [lowValue setStringValue:[currencyFormatter stringFromNumber:ticker.low]]; + [buyValue setStringValue:[currencyFormatter stringFromNumber:ticker.buy]]; + [sellValue setStringValue: [currencyFormatter stringFromNumber:ticker.sell]]; + [lastValue setStringValue: [currencyFormatter stringFromNumber:ticker.last]]; + + NSNumberFormatter *volumeFormatter = [[NSNumberFormatter alloc] init]; + volumeFormatter.numberStyle = NSNumberFormatterDecimalStyle; + volumeFormatter.hasThousandSeparators = YES; + [volValue setStringValue:[volumeFormatter stringFromNumber:ticker.volume]]; + + [volumeFormatter release]; +} + +-(void)bitcoinMarket:(BitcoinMarket*)market didReceiveRecentTradesData:(NSArray*)trades { + +} + +-(void)bitcoinMarket:(BitcoinMarket*)market didReceiveWallet:(Wallet*)wallet { + double btc = [wallet.btc doubleValue]; + double usd = [wallet.usd doubleValue]; + [BTCValue setStringValue:[NSString stringWithFormat:@"%f.04",[wallet.btc floatValue]]]; + [USDValue setStringValue:[currencyFormatter stringFromNumber:wallet.usd]]; + + double last = [self.tickerValue doubleValue]; + if (last == 0) { + //no last yet so cant multiply anyway + } + else { + NSNumber *BTCxRate = [NSNumber numberWithDouble:btc*last]; + [BTCxUSDValue setStringValue:[currencyFormatter stringFromNumber:BTCxRate]]; + + NSNumber *walletUSD = [NSNumber numberWithDouble:[BTCxRate doubleValue] + usd]; + [walletUSDValue setStringValue: [currencyFormatter stringFromNumber:walletUSD]]; + + } +} + +@end From 31bae42f242db646c1cfe40b1f2d06ed02d364e7 Mon Sep 17 00:00:00 2001 From: mrsteveman1 Date: Fri, 10 Jun 2011 18:25:14 -0400 Subject: [PATCH 28/46] fix paths in xcode project --- BitTicker.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/BitTicker.xcodeproj/project.pbxproj b/BitTicker.xcodeproj/project.pbxproj index c2a7b32..f83a65a 100644 --- a/BitTicker.xcodeproj/project.pbxproj +++ b/BitTicker.xcodeproj/project.pbxproj @@ -76,10 +76,10 @@ AA239F751379F17200150707 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; AA239F791379F1B200150707 /* Quartz.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quartz.framework; path = System/Library/Frameworks/Quartz.framework; sourceTree = SDKROOT; }; AA239F7A1379F1B200150707 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; - AA435C0C13A2CB550050F307 /* MenuController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MenuController.h; path = "../../../SourceCache/BitTicker-Mac/BitTicker/MenuController.h"; sourceTree = ""; }; - AA435C0D13A2CB550050F307 /* MenuController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MenuController.m; path = "../../../SourceCache/BitTicker-Mac/BitTicker/MenuController.m"; sourceTree = ""; }; - AA435C1013A2CCA90050F307 /* CustomMenuView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CustomMenuView.h; path = "../../../SourceCache/BitTicker-Mac/BitTicker/CustomMenuView.h"; sourceTree = ""; }; - AA435C1113A2CCA90050F307 /* CustomMenuView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CustomMenuView.m; path = "../../../SourceCache/BitTicker-Mac/BitTicker/CustomMenuView.m"; sourceTree = ""; }; + AA435C0C13A2CB550050F307 /* MenuController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MenuController.h; path = "MenuController.h"; sourceTree = ""; }; + AA435C0D13A2CB550050F307 /* MenuController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MenuController.m; path = "MenuController.m"; sourceTree = ""; }; + AA435C1013A2CCA90050F307 /* CustomMenuView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CustomMenuView.h; path = "CustomMenuView.h"; sourceTree = ""; }; + AA435C1113A2CCA90050F307 /* CustomMenuView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CustomMenuView.m; path = "CustomMenuView.m"; sourceTree = ""; }; AA4BBA281379E31B005CE351 /* BitTicker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BitTicker.app; sourceTree = BUILT_PRODUCTS_DIR; }; AA4BBA2C1379E31B005CE351 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; AA4BBA2F1379E31B005CE351 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; From 5872cd2ca8b7615f733dea9826d04ccfed7c14d3 Mon Sep 17 00:00:00 2001 From: mrsteveman1 Date: Fri, 10 Jun 2011 21:04:05 -0400 Subject: [PATCH 29/46] custom view class for each market view, method for adding a menu for a market --- BitTicker.xcodeproj/project.pbxproj | 22 +- BitTicker/BitTickerAppDelegate.m | 4 +- BitTicker/CustomMenuView.h | 4 +- BitTicker/CustomMenuView.m | 5 +- BitTicker/MenuController.h | 42 ++- BitTicker/MenuController.m | 379 +++++----------------------- BitTicker/MtGoxMenuView.h | 40 +++ BitTicker/MtGoxMenuView.m | 314 +++++++++++++++++++++++ 8 files changed, 451 insertions(+), 359 deletions(-) create mode 100644 BitTicker/MtGoxMenuView.h create mode 100644 BitTicker/MtGoxMenuView.m diff --git a/BitTicker.xcodeproj/project.pbxproj b/BitTicker.xcodeproj/project.pbxproj index f83a65a..b884a45 100644 --- a/BitTicker.xcodeproj/project.pbxproj +++ b/BitTicker.xcodeproj/project.pbxproj @@ -25,6 +25,7 @@ AA239F7C1379F1B200150707 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA239F7A1379F1B200150707 /* QuartzCore.framework */; }; AA435C0E13A2CB550050F307 /* MenuController.m in Sources */ = {isa = PBXBuildFile; fileRef = AA435C0D13A2CB550050F307 /* MenuController.m */; }; AA435C1213A2CCAA0050F307 /* CustomMenuView.m in Sources */ = {isa = PBXBuildFile; fileRef = AA435C1113A2CCA90050F307 /* CustomMenuView.m */; }; + AA435C2E13A2E0860050F307 /* MtGoxMenuView.m in Sources */ = {isa = PBXBuildFile; fileRef = AA435C2D13A2E0860050F307 /* MtGoxMenuView.m */; }; AA4BBA2D1379E31B005CE351 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA4BBA2C1379E31B005CE351 /* Cocoa.framework */; }; AA4BBA371379E31C005CE351 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = AA4BBA351379E31C005CE351 /* InfoPlist.strings */; }; AA4BBA3A1379E31C005CE351 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = AA4BBA391379E31C005CE351 /* main.m */; }; @@ -76,10 +77,12 @@ AA239F751379F17200150707 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; AA239F791379F1B200150707 /* Quartz.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quartz.framework; path = System/Library/Frameworks/Quartz.framework; sourceTree = SDKROOT; }; AA239F7A1379F1B200150707 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; - AA435C0C13A2CB550050F307 /* MenuController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MenuController.h; path = "MenuController.h"; sourceTree = ""; }; - AA435C0D13A2CB550050F307 /* MenuController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MenuController.m; path = "MenuController.m"; sourceTree = ""; }; - AA435C1013A2CCA90050F307 /* CustomMenuView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CustomMenuView.h; path = "CustomMenuView.h"; sourceTree = ""; }; - AA435C1113A2CCA90050F307 /* CustomMenuView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CustomMenuView.m; path = "CustomMenuView.m"; sourceTree = ""; }; + AA435C0C13A2CB550050F307 /* MenuController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MenuController.h; sourceTree = ""; }; + AA435C0D13A2CB550050F307 /* MenuController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MenuController.m; sourceTree = ""; }; + AA435C1013A2CCA90050F307 /* CustomMenuView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CustomMenuView.h; sourceTree = ""; }; + AA435C1113A2CCA90050F307 /* CustomMenuView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CustomMenuView.m; sourceTree = ""; }; + AA435C2C13A2E0850050F307 /* MtGoxMenuView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MtGoxMenuView.h; path = "MtGoxMenuView.h"; sourceTree = ""; }; + AA435C2D13A2E0860050F307 /* MtGoxMenuView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MtGoxMenuView.m; path = "MtGoxMenuView.m"; sourceTree = ""; }; AA4BBA281379E31B005CE351 /* BitTicker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BitTicker.app; sourceTree = BUILT_PRODUCTS_DIR; }; AA4BBA2C1379E31B005CE351 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; AA4BBA2F1379E31B005CE351 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; @@ -203,6 +206,12 @@ AAB399B2139B0D8500B9438F /* Wallet.m */, AAD172621399BA1D00B505B0 /* EMKeychainItem.h */, AAD172631399BA1D00B505B0 /* EMKeychainItem.m */, + AA239F4D1379E67300150707 /* StatusItemView.h */, + AA239F4E1379E67300150707 /* StatusItemView.m */, + AA435C1013A2CCA90050F307 /* CustomMenuView.h */, + AA435C1113A2CCA90050F307 /* CustomMenuView.m */, + AA435C2C13A2E0850050F307 /* MtGoxMenuView.h */, + AA435C2D13A2E0860050F307 /* MtGoxMenuView.m */, ); name = Models; sourceTree = ""; @@ -279,12 +288,8 @@ 83405B20137F684E0060CAD4 /* Networking */, 83405B0D137F5D8A0060CAD4 /* JSON */, 837DF132139B3B8A009987F3 /* Settings */, - AA239F4D1379E67300150707 /* StatusItemView.h */, - AA239F4E1379E67300150707 /* StatusItemView.m */, AA435C0C13A2CB550050F307 /* MenuController.h */, AA435C0D13A2CB550050F307 /* MenuController.m */, - AA435C1013A2CCA90050F307 /* CustomMenuView.h */, - AA435C1113A2CCA90050F307 /* CustomMenuView.m */, AA4BBA3E1379E31C005CE351 /* BitTickerAppDelegate.h */, AA4BBA3F1379E31C005CE351 /* BitTickerAppDelegate.m */, 837DF12E139B2EC8009987F3 /* Nibs */, @@ -397,6 +402,7 @@ 837DF135139B8E19009987F3 /* SettingsWindowController.m in Sources */, AA435C0E13A2CB550050F307 /* MenuController.m in Sources */, AA435C1213A2CCAA0050F307 /* CustomMenuView.m in Sources */, + AA435C2E13A2E0860050F307 /* MtGoxMenuView.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/BitTicker/BitTickerAppDelegate.m b/BitTicker/BitTickerAppDelegate.m index 24093c2..18bf8ea 100644 --- a/BitTicker/BitTickerAppDelegate.m +++ b/BitTicker/BitTickerAppDelegate.m @@ -35,7 +35,7 @@ - (void)awakeFromNib { settingsWindowController = [[SettingsWindowController alloc] init]; menuController = [[MenuController alloc] init]; - [menuController createMenus]; + MSLog(@"Starting"); market = [[MtGoxMarket alloc] initWithDelegate:menuController]; @@ -44,6 +44,8 @@ - (void)awakeFromNib { [market fetchTicker]; [market fetchWallet]; + [menuController createMenuForMarket:market]; + [menuController addSelectorItems]; } #pragma mark Application delegate diff --git a/BitTicker/CustomMenuView.h b/BitTicker/CustomMenuView.h index 18af3c7..a92f0dd 100644 --- a/BitTicker/CustomMenuView.h +++ b/BitTicker/CustomMenuView.h @@ -10,8 +10,10 @@ @interface CustomMenuView : NSView { -@private + } +- (id)initWithFrame:(NSRect)frame; + @end diff --git a/BitTicker/CustomMenuView.m b/BitTicker/CustomMenuView.m index f1796e5..7e7e948 100644 --- a/BitTicker/CustomMenuView.m +++ b/BitTicker/CustomMenuView.m @@ -11,13 +11,12 @@ @implementation CustomMenuView -- (id)initWithFrame:(NSRect)frame -{ +- (id)initWithFrame:(NSRect)frame { self = [super initWithFrame:frame]; if (self) { // Initialization code here. } - + return self; } diff --git a/BitTicker/MenuController.h b/BitTicker/MenuController.h index 894423b..9d5158d 100644 --- a/BitTicker/MenuController.h +++ b/BitTicker/MenuController.h @@ -8,49 +8,37 @@ #import #import "BitcoinMarketDelegate.h" +#import "SharedSettings.h" @class StatusItemView; @class Ticker; @class BitcoinMarket; @interface MenuController : NSObject { - - - NSTextField *highValue; - NSTextField *lowValue; - NSTextField *volValue; - NSTextField *buyValue; - NSTextField *sellValue; - NSTextField *lastValue; + SharedSettings *sharedSettingManager; + NSMenu *trayMenu; + NSMutableDictionary *_viewDict; + StatusItemView *statusItemView; + NSStatusItem *_statusItem; - NSTextField *BTCValue; - NSTextField *BTCxUSDValue; - NSTextField *USDValue; - NSTextField *walletUSDValue; + NSNumberFormatter *currencyFormatter; + + NSNumber *_tickerValue; - NSView *statsView; - NSMenuItem *statsItem; - - NSView *walletView; - NSMenuItem *walletItem; - NSMenuItem *quitItem; NSMenuItem *aboutItem; NSMenuItem *settingsItem; NSMenuItem *refreshItem; NSMenuItem *preferenceItem; - NSMenu *trayMenu; - - StatusItemView *statusItemView; - NSStatusItem *_statusItem; - NSNumberFormatter *currencyFormatter; - - NSNumber *_tickerValue; + NSInteger _currentMenuStop; } --(void)createMenus; +-(void)createMenuForMarket:(BitcoinMarket*)market; -@property (retain, nonatomic) NSNumber *tickerValue; +-(void)addSelectorItems; +@property (retain, nonatomic) NSNumber *tickerValue; +@property () NSInteger currentMenuStop; +@property (retain) NSMutableDictionary *viewDict; @end diff --git a/BitTicker/MenuController.m b/BitTicker/MenuController.m index 4c2009a..0265eee 100644 --- a/BitTicker/MenuController.m +++ b/BitTicker/MenuController.m @@ -10,15 +10,26 @@ #import "Ticker.h" #import "Wallet.h" #import "StatusItemView.h" +#import "CustomMenuView.h" +#import "SharedSettings.h" + +#import "MtGoxMenuView.h" + +#define MENU_VIEW_HEIGHT 105 +#define MENU_VIEW_WIDTH 180 @implementation MenuController @synthesize tickerValue = _tickerValue; +@synthesize currentMenuStop = _currentMenuStop; +@synthesize viewDict = _viewDict; - (id)init { self = [super init]; - if (self) { + if (self) { + self.viewDict = [NSMutableDictionary dictionaryWithCapacity:10]; + sharedSettingManager = [SharedSettings sharedSettingManager]; currencyFormatter = [[NSNumberFormatter alloc] init]; currencyFormatter.numberStyle = NSNumberFormatterCurrencyStyle; currencyFormatter.currencyCode = @"USD"; // TODO: Base on market currency @@ -26,299 +37,44 @@ - (id)init currencyFormatter.alwaysShowsDecimalSeparator = YES; currencyFormatter.hasThousandSeparators = YES; currencyFormatter.minimumFractionDigits = 4; // TODO: Configurable + + + _statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength]; + [_statusItem retain]; + + statusItemView = [[StatusItemView alloc] init]; + statusItemView.statusItem = _statusItem; + [statusItemView setToolTip:NSLocalizedString(@"BitTicker", @"Status Item Tooltip")]; + + [_statusItem setView:statusItemView]; + + trayMenu = [[NSMenu alloc] initWithTitle:@"Ticker"]; + [statusItemView setMenu:trayMenu]; + self.currentMenuStop = 0; } return self; } --(void)createMenus { - - _statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength]; - [_statusItem retain]; - - statusItemView = [[StatusItemView alloc] init]; - statusItemView.statusItem = _statusItem; - [statusItemView setToolTip:NSLocalizedString(@"BitTicker", - @"Status Item Tooltip")]; - [_statusItem setView:statusItemView]; - - - - // menu stuff - trayMenu = [[NSMenu alloc] initWithTitle:@"Ticker"]; - //graphItem = [[NSMenuItem alloc] init]; - statsItem = [[NSMenuItem alloc] init]; - statsView = [[NSView alloc] initWithFrame:CGRectMake(0,70,180,105)]; - [statsItem setView:statsView]; - [trayMenu addItem:statsItem]; - - NSString *menuFont = @"LucidaGrande"; - NSInteger menuFontSize = 12; - NSString *headerFont = @"LucidaGrande-Bold"; - NSInteger headerFontSize = 13; - - NSInteger menuHeight = 15; - NSInteger labelWidth = 70; - NSInteger headerWidth = 120; - NSInteger valueWidth = 60; - - NSInteger labelOffset = 20; - NSInteger valueOffset = 110; - - //section header - NSTextField *tickerSectionLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,93,headerWidth,menuHeight)]; - [tickerSectionLabel setEditable:FALSE]; - [tickerSectionLabel setBordered:NO]; - [tickerSectionLabel setAlignment:NSLeftTextAlignment]; - [tickerSectionLabel setBackgroundColor:[NSColor clearColor]]; - [tickerSectionLabel setStringValue:@"Ticker"]; - [tickerSectionLabel setTextColor:[NSColor blueColor]]; - [tickerSectionLabel setFont:[NSFont fontWithName:headerFont size:headerFontSize]]; - [statsView addSubview:tickerSectionLabel]; - [tickerSectionLabel release]; - - highValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,75,valueWidth,menuHeight)]; - [highValue setEditable:FALSE]; - [highValue setBordered:NO]; - [highValue setAlignment:NSRightTextAlignment]; - [highValue setBackgroundColor:[NSColor clearColor]]; - [highValue setTextColor:[NSColor blackColor]]; - [highValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [statsView addSubview:highValue]; - - NSTextField *highLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,75,labelWidth,menuHeight)]; - [highLabel setEditable:FALSE]; - [highLabel setBordered:NO]; - [highLabel setAlignment:NSLeftTextAlignment]; - [highLabel setBackgroundColor:[NSColor clearColor]]; - [highLabel setStringValue:@"High:"]; - [highLabel setTextColor:[NSColor blackColor]]; - [highLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [statsView addSubview:highLabel]; - [highLabel release]; - - // - lowValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,60,valueWidth,menuHeight)]; - [lowValue setEditable:FALSE]; - [lowValue setBordered:NO]; - [lowValue setAlignment:NSRightTextAlignment]; - [lowValue setBackgroundColor:[NSColor clearColor]]; - [lowValue setTextColor:[NSColor blackColor]]; - [lowValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [statsView addSubview:lowValue]; - - NSTextField *lowLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,60,labelWidth,menuHeight)]; - [lowLabel setEditable:FALSE]; - [lowLabel setBordered:NO]; - [lowLabel setAlignment:NSLeftTextAlignment]; - [lowLabel setBackgroundColor:[NSColor clearColor]]; - [lowLabel setStringValue:@"Low:"]; - [lowLabel setTextColor:[NSColor blackColor]]; - [lowLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [statsView addSubview:lowLabel]; - [lowLabel release]; - - // - buyValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,45,valueWidth,menuHeight)]; - [buyValue setEditable:FALSE]; - [buyValue setBordered:NO]; - [buyValue setAlignment:NSRightTextAlignment]; - [buyValue setBackgroundColor:[NSColor clearColor]]; - [buyValue setTextColor:[NSColor blackColor]]; - [buyValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [statsView addSubview:buyValue]; - - NSTextField *buyLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,45,labelWidth,menuHeight)]; - [buyLabel setEditable:FALSE]; - [buyLabel setBordered:NO]; - [buyLabel setAlignment:NSLeftTextAlignment]; - [buyLabel setBackgroundColor:[NSColor clearColor]]; - [buyLabel setStringValue:@"Buy:"]; - [buyLabel setTextColor:[NSColor blackColor]]; - [buyLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [statsView addSubview:buyLabel]; - [buyLabel release]; - - // - sellValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,30,valueWidth,menuHeight)]; - [sellValue setEditable:FALSE]; - [sellValue setBordered:NO]; - [sellValue setAlignment:NSRightTextAlignment]; - [sellValue setBackgroundColor:[NSColor clearColor]]; - [sellValue setTextColor:[NSColor blackColor]]; - [sellValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [statsView addSubview:sellValue]; - - NSTextField *sellLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,30,labelWidth,menuHeight)]; - [sellLabel setEditable:FALSE]; - [sellLabel setBordered:NO]; - [sellLabel setAlignment:NSLeftTextAlignment]; - [sellLabel setBackgroundColor:[NSColor clearColor]]; - [sellLabel setStringValue:@"Sell:"]; - [sellLabel setTextColor:[NSColor blackColor]]; - [sellLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [statsView addSubview:sellLabel]; - [sellLabel release]; - - // - lastValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,15,valueWidth,menuHeight)]; - [lastValue setEditable:FALSE]; - [lastValue setBordered:NO]; - [lastValue setAlignment:NSRightTextAlignment]; - [lastValue setBackgroundColor:[NSColor clearColor]]; - [lastValue setTextColor:[NSColor blackColor]]; - [lastValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [statsView addSubview:lastValue]; - - NSTextField *lastLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,15,labelWidth,menuHeight)]; - [lastLabel setEditable:FALSE]; - [lastLabel setBordered:NO]; - [lastLabel setAlignment:NSLeftTextAlignment]; - [lastLabel setBackgroundColor:[NSColor clearColor]]; - [lastLabel setStringValue:@"Last:"]; - [lastLabel setTextColor:[NSColor blackColor]]; - [lastLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [statsView addSubview:lastLabel]; - [lastLabel release]; - - // - volValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,0,valueWidth,menuHeight)]; - [volValue setEditable:FALSE]; - [volValue setBordered:NO]; - [volValue setAlignment:NSRightTextAlignment]; - [volValue setBackgroundColor:[NSColor clearColor]]; - [volValue setTextColor:[NSColor blackColor]]; - [volValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [statsView addSubview:volValue]; - - - NSTextField *volLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,0,labelWidth,menuHeight)]; - [volLabel setEditable:FALSE]; - [volLabel setBordered:NO]; - [volLabel setAlignment:NSLeftTextAlignment]; - [volLabel setBackgroundColor:[NSColor clearColor]]; - [volLabel setStringValue:@"Volume:"]; - [volLabel setTextColor:[NSColor blackColor]]; - [volLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [statsView addSubview:volLabel]; - [volLabel release]; - - +-(void)createMenuForMarket:(BitcoinMarket*)market { + NSMenuItem *menuItem = [[NSMenuItem alloc] init]; + NSString *marketClass = NSStringFromClass([market class]); + CustomMenuView *menuView; + if ([marketClass isEqualToString:@"MtGoxMarket"]) { + menuView = [[MtGoxMenuView alloc] initWithFrame:CGRectMake(0,self.currentMenuStop,MENU_VIEW_WIDTH,MENU_VIEW_HEIGHT)]; + } + else { + menuView = [[CustomMenuView alloc] initWithFrame:CGRectMake(0,self.currentMenuStop,MENU_VIEW_WIDTH,MENU_VIEW_HEIGHT)]; + } + [menuItem setView:menuView]; + [trayMenu addItem:menuItem]; [trayMenu addItem:[NSMenuItem separatorItem]]; + [self.viewDict setObject:menuView forKey:NSStringFromClass([market class])]; + [menuItem release]; +} - - walletItem = [[NSMenuItem alloc] init]; - walletView = [[NSView alloc] initWithFrame:CGRectMake(0,0,180,75)]; - [walletItem setView:walletView]; - [trayMenu addItem:walletItem]; - - - - //section header - NSTextField *walletSectionLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,64,headerWidth,menuHeight)]; - [walletSectionLabel setEditable:FALSE]; - [walletSectionLabel setBordered:NO]; - [walletSectionLabel setAlignment:NSLeftTextAlignment]; - [walletSectionLabel setBackgroundColor:[NSColor clearColor]]; - // TODO: Allow multiple wallets - [walletSectionLabel setStringValue:@"MtGox Wallet"]; - [walletSectionLabel setTextColor:[NSColor blueColor]]; - [walletSectionLabel setFont:[NSFont fontWithName:headerFont size:headerFontSize]]; - [walletView addSubview:walletSectionLabel]; - [walletSectionLabel release]; - - - - - - - BTCValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 45, valueWidth, menuHeight)]; - [BTCValue setEditable:FALSE]; - [BTCValue setBordered:NO]; - [BTCValue setAlignment:NSRightTextAlignment]; - [BTCValue setBackgroundColor:[NSColor clearColor]]; - [BTCValue setTextColor:[NSColor blackColor]]; - [BTCValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [walletView addSubview:BTCValue]; - - NSTextField *BTCLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,45,labelWidth,menuHeight)]; - [BTCLabel setEditable:FALSE]; - [BTCLabel setBordered:NO]; - [BTCLabel setAlignment:NSLeftTextAlignment]; - [BTCLabel setBackgroundColor:[NSColor clearColor]]; - [BTCLabel setStringValue:@"BTC:"]; - [BTCLabel setTextColor:[NSColor blackColor]]; - [BTCLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [walletView addSubview:BTCLabel]; - [BTCLabel release]; - - BTCxUSDValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 30, valueWidth, menuHeight)]; - [BTCxUSDValue setEditable:FALSE]; - [BTCxUSDValue setBordered:NO]; - [BTCxUSDValue setAlignment:NSRightTextAlignment]; - [BTCxUSDValue setBackgroundColor:[NSColor clearColor]]; - [BTCxUSDValue setTextColor:[NSColor blackColor]]; - [BTCxUSDValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [walletView addSubview:BTCxUSDValue]; - - NSTextField *BTCxUSDLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,30,labelWidth,menuHeight)]; - [BTCxUSDLabel setEditable:FALSE]; - [BTCxUSDLabel setBordered:NO]; - [BTCxUSDLabel setAlignment:NSLeftTextAlignment]; - [BTCxUSDLabel setBackgroundColor:[NSColor clearColor]]; - [BTCxUSDLabel setStringValue:@"BTC * Last:"]; - [BTCxUSDLabel setTextColor:[NSColor blackColor]]; - [BTCxUSDLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [walletView addSubview:BTCxUSDLabel]; - [BTCxUSDLabel release]; - - - - USDValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 15, valueWidth, menuHeight)]; - [USDValue setEditable:FALSE]; - [USDValue setBordered:NO]; - [USDValue setAlignment:NSRightTextAlignment]; - [USDValue setBackgroundColor:[NSColor clearColor]]; - [USDValue setTextColor:[NSColor blackColor]]; - [USDValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [walletView addSubview:USDValue]; - - NSTextField *USDLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,15,labelWidth,menuHeight)]; - [USDLabel setEditable:FALSE]; - [USDLabel setBordered:NO]; - [USDLabel setAlignment:NSLeftTextAlignment]; - [USDLabel setBackgroundColor:[NSColor clearColor]]; - [USDLabel setStringValue:@"USD:"]; - [USDLabel setTextColor:[NSColor blackColor]]; - [USDLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [walletView addSubview:USDLabel]; - [USDLabel release]; - - - - walletUSDValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 0, valueWidth, menuHeight)]; - [walletUSDValue setEditable:FALSE]; - [walletUSDValue setBordered:NO]; - [walletUSDValue setAlignment:NSRightTextAlignment]; - [walletUSDValue setBackgroundColor:[NSColor clearColor]]; - [walletUSDValue setTextColor:[NSColor blackColor]]; - [walletUSDValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [walletView addSubview:walletUSDValue]; - - NSTextField *walletLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,0,labelWidth,menuHeight)]; - [walletLabel setEditable:FALSE]; - [walletLabel setBordered:NO]; - [walletLabel setAlignment:NSLeftTextAlignment]; - [walletLabel setBackgroundColor:[NSColor clearColor]]; - [walletLabel setStringValue:@"Total:"]; - [walletLabel setTextColor:[NSColor blackColor]]; - [walletLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [walletView addSubview:walletLabel]; - [walletLabel release]; - - [trayMenu addItem:[NSMenuItem separatorItem]]; - - refreshItem = [trayMenu addItemWithTitle:@"Refresh" +-(void)addSelectorItems { + refreshItem = [trayMenu addItemWithTitle:@"Refresh" action:@selector(refreshTicker:) keyEquivalent:@"r"]; aboutItem = [trayMenu addItemWithTitle: @"About" @@ -327,14 +83,11 @@ -(void)createMenus { settingsItem = [trayMenu addItemWithTitle: @"Settings" action: @selector (showSettings:) keyEquivalent: @"s"]; - - + + quitItem = [trayMenu addItemWithTitle: @"Quit" action: @selector (quitProgram:) keyEquivalent: @"q"]; - - - [statusItemView setMenu:trayMenu]; } - (void)dealloc { @@ -342,20 +95,6 @@ - (void)dealloc { [_statusItem release]; [statusItemView release]; [trayMenu release]; - [statsItem release]; - //ticker stuff - [highValue release]; - [lowValue release]; - [volValue release]; - [buyValue release]; - [sellValue release]; - [lastValue release]; - - //wallet stuff - [BTCValue release]; - [BTCxUSDValue release]; - [USDValue release]; - [walletUSDValue release]; [super dealloc]; } @@ -375,16 +114,18 @@ -(void)bitcoinMarket:(BitcoinMarket*)market didReceiveTicker:(Ticker*)ticker { self.tickerValue = ticker.last; MSLog(@"Got mah ticker: %@",ticker); - [highValue setStringValue:[currencyFormatter stringFromNumber:ticker.high]]; - [lowValue setStringValue:[currencyFormatter stringFromNumber:ticker.low]]; - [buyValue setStringValue:[currencyFormatter stringFromNumber:ticker.buy]]; - [sellValue setStringValue: [currencyFormatter stringFromNumber:ticker.sell]]; - [lastValue setStringValue: [currencyFormatter stringFromNumber:ticker.last]]; - NSNumberFormatter *volumeFormatter = [[NSNumberFormatter alloc] init]; volumeFormatter.numberStyle = NSNumberFormatterDecimalStyle; volumeFormatter.hasThousandSeparators = YES; - [volValue setStringValue:[volumeFormatter stringFromNumber:ticker.volume]]; + CustomMenuView *view = [self.viewDict objectForKey:NSStringFromClass( [market class] ) ] ; + + + [view setHigh:[currencyFormatter stringFromNumber:ticker.high]]; + [view setLow:[currencyFormatter stringFromNumber:ticker.low]]; + [view setBuy:[currencyFormatter stringFromNumber:ticker.buy]]; + [view setSell:[currencyFormatter stringFromNumber:ticker.sell]]; + [view setLast:[currencyFormatter stringFromNumber:ticker.last]]; + [view setVol:[volumeFormatter stringFromNumber:ticker.volume]]; [volumeFormatter release]; } @@ -394,23 +135,23 @@ -(void)bitcoinMarket:(BitcoinMarket*)market didReceiveRecentTradesData:(NSArray* } -(void)bitcoinMarket:(BitcoinMarket*)market didReceiveWallet:(Wallet*)wallet { + id view = [self.viewDict objectForKey:NSStringFromClass( [market class] ) ] ; double btc = [wallet.btc doubleValue]; double usd = [wallet.usd doubleValue]; - [BTCValue setStringValue:[NSString stringWithFormat:@"%f.04",[wallet.btc floatValue]]]; - [USDValue setStringValue:[currencyFormatter stringFromNumber:wallet.usd]]; - double last = [self.tickerValue doubleValue]; + if (last == 0) { //no last yet so cant multiply anyway } else { NSNumber *BTCxRate = [NSNumber numberWithDouble:btc*last]; - [BTCxUSDValue setStringValue:[currencyFormatter stringFromNumber:BTCxRate]]; + [view setBtcusd:[currencyFormatter stringFromNumber:BTCxRate]]; NSNumber *walletUSD = [NSNumber numberWithDouble:[BTCxRate doubleValue] + usd]; - [walletUSDValue setStringValue: [currencyFormatter stringFromNumber:walletUSD]]; - + [view setWallet:[currencyFormatter stringFromNumber:walletUSD]]; } + [view setBtc:[NSString stringWithFormat:@"%f.04",[wallet.btc floatValue]]]; + [view setUsd:[currencyFormatter stringFromNumber:wallet.usd]]; } @end diff --git a/BitTicker/MtGoxMenuView.h b/BitTicker/MtGoxMenuView.h new file mode 100644 index 0000000..3ebd3cc --- /dev/null +++ b/BitTicker/MtGoxMenuView.h @@ -0,0 +1,40 @@ +// +// MtGoxMenuView.h +// BitTicker +// +// Created by steve on 6/10/11. +// Copyright 2011 none. All rights reserved. +// + +#import +#import "CustomMenuView.h" + +@interface MtGoxMenuView : CustomMenuView { + + NSTextField *highValue; + NSTextField *lowValue; + NSTextField *volValue; + NSTextField *buyValue; + NSTextField *sellValue; + NSTextField *lastValue; + + NSTextField *BTCValue; + NSTextField *BTCxUSDValue; + NSTextField *USDValue; + NSTextField *walletUSDValue; + +} + +@property (retain) NSString *high; +@property (retain) NSString *low; +@property (retain) NSString *vol; +@property (retain) NSString *buy; +@property (retain) NSString *sell; +@property (retain) NSString *last; + +@property (retain) NSString *btc; +@property (retain) NSString *btcusd; +@property (retain) NSString *usd; +@property (retain) NSString *wallet; + +@end diff --git a/BitTicker/MtGoxMenuView.m b/BitTicker/MtGoxMenuView.m new file mode 100644 index 0000000..3f88f09 --- /dev/null +++ b/BitTicker/MtGoxMenuView.m @@ -0,0 +1,314 @@ +// +// MtGoxMenuView.m +// BitTicker +// +// Created by steve on 6/10/11. +// Copyright 2011 none. All rights reserved. +// + +#import "MtGoxMenuView.h" +#define menuFont @"LucidaGrande" +#define menuFontSize 12 +#define headerFont @"LucidaGrande-Bold" +#define headerFontSize 13 + +#define menuHeight 15 +#define labelWidth 70 +#define headerWidth 120 +#define valueWidth 60 + +#define labelOffset 20 +#define valueOffset 110 + +@implementation MtGoxMenuView + +@dynamic high; +@dynamic low; +@dynamic vol; +@dynamic buy; +@dynamic sell; +@dynamic last; + +@dynamic btc; +@dynamic btcusd; +@dynamic usd; +@dynamic wallet; + +- (id)initWithFrame:(NSRect)frame { + CGRect newFrame = frame; + newFrame.size.height = frame.size.height + 60; + self = [super initWithFrame:newFrame]; + if (self) { + NSTextField *sectionLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,153,headerWidth,menuHeight)]; + [sectionLabel setEditable:FALSE]; + [sectionLabel setBordered:NO]; + [sectionLabel setAlignment:NSLeftTextAlignment]; + [sectionLabel setBackgroundColor:[NSColor clearColor]]; + [sectionLabel setStringValue:@"Mt Gox"]; + [sectionLabel setTextColor:[NSColor blueColor]]; + [sectionLabel setFont:[NSFont fontWithName:headerFont size:headerFontSize]]; + [self addSubview:sectionLabel]; + [sectionLabel release]; + + highValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,135,valueWidth,menuHeight)]; + [highValue setEditable:FALSE]; + [highValue setBordered:NO]; + [highValue setAlignment:NSRightTextAlignment]; + [highValue setBackgroundColor:[NSColor clearColor]]; + [highValue setTextColor:[NSColor blackColor]]; + [highValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:highValue]; + + NSTextField *highLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,135,labelWidth,menuHeight)]; + [highLabel setEditable:FALSE]; + [highLabel setBordered:NO]; + [highLabel setAlignment:NSLeftTextAlignment]; + [highLabel setBackgroundColor:[NSColor clearColor]]; + [highLabel setStringValue:@"High:"]; + [highLabel setTextColor:[NSColor blackColor]]; + [highLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:highLabel]; + [highLabel release]; + + lowValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,120,valueWidth,menuHeight)]; + [lowValue setEditable:FALSE]; + [lowValue setBordered:NO]; + [lowValue setAlignment:NSRightTextAlignment]; + [lowValue setBackgroundColor:[NSColor clearColor]]; + [lowValue setTextColor:[NSColor blackColor]]; + [lowValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:lowValue]; + + NSTextField *lowLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,120,labelWidth,menuHeight)]; + [lowLabel setEditable:FALSE]; + [lowLabel setBordered:NO]; + [lowLabel setAlignment:NSLeftTextAlignment]; + [lowLabel setBackgroundColor:[NSColor clearColor]]; + [lowLabel setStringValue:@"Low:"]; + [lowLabel setTextColor:[NSColor blackColor]]; + [lowLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:lowLabel]; + [lowLabel release]; + + buyValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,105,valueWidth,menuHeight)]; + [buyValue setEditable:FALSE]; + [buyValue setBordered:NO]; + [buyValue setAlignment:NSRightTextAlignment]; + [buyValue setBackgroundColor:[NSColor clearColor]]; + [buyValue setTextColor:[NSColor blackColor]]; + [buyValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:buyValue]; + + NSTextField *buyLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,105,labelWidth,menuHeight)]; + [buyLabel setEditable:FALSE]; + [buyLabel setBordered:NO]; + [buyLabel setAlignment:NSLeftTextAlignment]; + [buyLabel setBackgroundColor:[NSColor clearColor]]; + [buyLabel setStringValue:@"Buy:"]; + [buyLabel setTextColor:[NSColor blackColor]]; + [buyLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:buyLabel]; + [buyLabel release]; + + sellValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,90,valueWidth,menuHeight)]; + [sellValue setEditable:FALSE]; + [sellValue setBordered:NO]; + [sellValue setAlignment:NSRightTextAlignment]; + [sellValue setBackgroundColor:[NSColor clearColor]]; + [sellValue setTextColor:[NSColor blackColor]]; + [sellValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:sellValue]; + + NSTextField *sellLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,90,labelWidth,menuHeight)]; + [sellLabel setEditable:FALSE]; + [sellLabel setBordered:NO]; + [sellLabel setAlignment:NSLeftTextAlignment]; + [sellLabel setBackgroundColor:[NSColor clearColor]]; + [sellLabel setStringValue:@"Sell:"]; + [sellLabel setTextColor:[NSColor blackColor]]; + [sellLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:sellLabel]; + [sellLabel release]; + + lastValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,75,valueWidth,menuHeight)]; + [lastValue setEditable:FALSE]; + [lastValue setBordered:NO]; + [lastValue setAlignment:NSRightTextAlignment]; + [lastValue setBackgroundColor:[NSColor clearColor]]; + [lastValue setTextColor:[NSColor blackColor]]; + [lastValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:lastValue]; + + NSTextField *lastLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,75,labelWidth,menuHeight)]; + [lastLabel setEditable:FALSE]; + [lastLabel setBordered:NO]; + [lastLabel setAlignment:NSLeftTextAlignment]; + [lastLabel setBackgroundColor:[NSColor clearColor]]; + [lastLabel setStringValue:@"Last:"]; + [lastLabel setTextColor:[NSColor blackColor]]; + [lastLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:lastLabel]; + [lastLabel release]; + + volValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,60,valueWidth,menuHeight)]; + [volValue setEditable:FALSE]; + [volValue setBordered:NO]; + [volValue setAlignment:NSRightTextAlignment]; + [volValue setBackgroundColor:[NSColor clearColor]]; + [volValue setTextColor:[NSColor blackColor]]; + [volValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:volValue]; + + NSTextField *volLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,60,labelWidth,menuHeight)]; + [volLabel setEditable:FALSE]; + [volLabel setBordered:NO]; + [volLabel setAlignment:NSLeftTextAlignment]; + [volLabel setBackgroundColor:[NSColor clearColor]]; + [volLabel setStringValue:@"Volume:"]; + [volLabel setTextColor:[NSColor blackColor]]; + [volLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:volLabel]; + [volLabel release]; + + BTCValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 45, valueWidth, menuHeight)]; + [BTCValue setEditable:FALSE]; + [BTCValue setBordered:NO]; + [BTCValue setAlignment:NSRightTextAlignment]; + [BTCValue setBackgroundColor:[NSColor clearColor]]; + [BTCValue setTextColor:[NSColor blackColor]]; + [BTCValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:BTCValue]; + + NSTextField *BTCLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,45,labelWidth,menuHeight)]; + [BTCLabel setEditable:FALSE]; + [BTCLabel setBordered:NO]; + [BTCLabel setAlignment:NSLeftTextAlignment]; + [BTCLabel setBackgroundColor:[NSColor clearColor]]; + [BTCLabel setStringValue:@"BTC:"]; + [BTCLabel setTextColor:[NSColor blackColor]]; + [BTCLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:BTCLabel]; + [BTCLabel release]; + + BTCxUSDValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 30, valueWidth, menuHeight)]; + [BTCxUSDValue setEditable:FALSE]; + [BTCxUSDValue setBordered:NO]; + [BTCxUSDValue setAlignment:NSRightTextAlignment]; + [BTCxUSDValue setBackgroundColor:[NSColor clearColor]]; + [BTCxUSDValue setTextColor:[NSColor blackColor]]; + [BTCxUSDValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:BTCxUSDValue]; + + NSTextField *BTCxUSDLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,30,labelWidth,menuHeight)]; + [BTCxUSDLabel setEditable:FALSE]; + [BTCxUSDLabel setBordered:NO]; + [BTCxUSDLabel setAlignment:NSLeftTextAlignment]; + [BTCxUSDLabel setBackgroundColor:[NSColor clearColor]]; + [BTCxUSDLabel setStringValue:@"BTC * Last:"]; + [BTCxUSDLabel setTextColor:[NSColor blackColor]]; + [BTCxUSDLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:BTCxUSDLabel]; + [BTCxUSDLabel release]; + + USDValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 15, valueWidth, menuHeight)]; + [USDValue setEditable:FALSE]; + [USDValue setBordered:NO]; + [USDValue setAlignment:NSRightTextAlignment]; + [USDValue setBackgroundColor:[NSColor clearColor]]; + [USDValue setTextColor:[NSColor blackColor]]; + [USDValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:USDValue]; + + NSTextField *USDLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,15,labelWidth,menuHeight)]; + [USDLabel setEditable:FALSE]; + [USDLabel setBordered:NO]; + [USDLabel setAlignment:NSLeftTextAlignment]; + [USDLabel setBackgroundColor:[NSColor clearColor]]; + [USDLabel setStringValue:@"USD:"]; + [USDLabel setTextColor:[NSColor blackColor]]; + [USDLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:USDLabel]; + [USDLabel release]; + + walletUSDValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 0, valueWidth, menuHeight)]; + [walletUSDValue setEditable:FALSE]; + [walletUSDValue setBordered:NO]; + [walletUSDValue setAlignment:NSRightTextAlignment]; + [walletUSDValue setBackgroundColor:[NSColor clearColor]]; + [walletUSDValue setTextColor:[NSColor blackColor]]; + [walletUSDValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:walletUSDValue]; + + NSTextField *walletLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,0,labelWidth,menuHeight)]; + [walletLabel setEditable:FALSE]; + [walletLabel setBordered:NO]; + [walletLabel setAlignment:NSLeftTextAlignment]; + [walletLabel setBackgroundColor:[NSColor clearColor]]; + [walletLabel setStringValue:@"Total:"]; + [walletLabel setTextColor:[NSColor blackColor]]; + [walletLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:walletLabel]; + [walletLabel release]; + } + + return self; +} + +- (id)init { + self = [super init]; + if (self) { + + } + + return self; +} + +#pragma mark - +#pragma mark Properties + +-(void)setHigh:(NSString *)string { + [highValue setStringValue:string]; +} + +-(void)setLow:(NSString *)string { + [lowValue setStringValue:string]; +} + +-(void)setVol:(NSString *)string { + [volValue setStringValue:string]; +} + +-(void)setBuy:(NSString *)string { + [buyValue setStringValue:string]; +} + +-(void)setSell:(NSString *)string { + [sellValue setStringValue:string]; +} + +-(void)setLast:(NSString *)string { + [lastValue setStringValue:string]; +} + +-(void)setBtc:(NSString *)string { + [BTCValue setStringValue:string]; +} + +-(void)setBtcusd:(NSString *)string { + [BTCxUSDValue setStringValue:string]; +} + +-(void)setUsd:(NSString *)string { + [USDValue setStringValue:string]; +} + +-(void)setWallet:(NSString *)string { + [walletUSDValue setStringValue:string]; +} + +- (void)dealloc +{ + [super dealloc]; +} + +@end From 2e9b13f46725dc8993ff2907e04df61b42aa160f Mon Sep 17 00:00:00 2001 From: mrsteveman1 Date: Fri, 10 Jun 2011 21:16:31 -0400 Subject: [PATCH 30/46] make these properties part of the parent class --- BitTicker/CustomMenuView.h | 12 +++++++ BitTicker/CustomMenuView.m | 67 ++++++++++++++++++++++++++++++++++++++ BitTicker/MtGoxMenuView.h | 12 ------- BitTicker/MtGoxMenuView.m | 12 ------- 4 files changed, 79 insertions(+), 24 deletions(-) diff --git a/BitTicker/CustomMenuView.h b/BitTicker/CustomMenuView.h index a92f0dd..0378914 100644 --- a/BitTicker/CustomMenuView.h +++ b/BitTicker/CustomMenuView.h @@ -16,4 +16,16 @@ - (id)initWithFrame:(NSRect)frame; +@property (retain) NSString *high; +@property (retain) NSString *low; +@property (retain) NSString *vol; +@property (retain) NSString *buy; +@property (retain) NSString *sell; +@property (retain) NSString *last; + +@property (retain) NSString *btc; +@property (retain) NSString *btcusd; +@property (retain) NSString *usd; +@property (retain) NSString *wallet; + @end diff --git a/BitTicker/CustomMenuView.m b/BitTicker/CustomMenuView.m index 7e7e948..91ba5e1 100644 --- a/BitTicker/CustomMenuView.m +++ b/BitTicker/CustomMenuView.m @@ -11,6 +11,18 @@ @implementation CustomMenuView +@dynamic high; +@dynamic low; +@dynamic vol; +@dynamic buy; +@dynamic sell; +@dynamic last; + +@dynamic btc; +@dynamic btcusd; +@dynamic usd; +@dynamic wallet; + - (id)initWithFrame:(NSRect)frame { self = [super initWithFrame:frame]; if (self) { @@ -30,4 +42,59 @@ - (void)drawRect:(NSRect)dirtyRect // Drawing code here. } +#pragma mark - +#pragma mark Properties + +// Override these + +-(void)setHigh:(NSString *)string { + [NSException raise:@"MethodNotOverwrittenException" format:@"%s must be overwritten by CustomMenuView subclasses",__func__]; + +} + +-(void)setLow:(NSString *)string { + [NSException raise:@"MethodNotOverwrittenException" format:@"%s must be overwritten by CustomMenuView subclasses",__func__]; + +} + +-(void)setVol:(NSString *)string { + [NSException raise:@"MethodNotOverwrittenException" format:@"%s must be overwritten by CustomMenuView subclasses",__func__]; + +} + +-(void)setBuy:(NSString *)string { + [NSException raise:@"MethodNotOverwrittenException" format:@"%s must be overwritten by CustomMenuView subclasses",__func__]; + +} + +-(void)setSell:(NSString *)string { + [NSException raise:@"MethodNotOverwrittenException" format:@"%s must be overwritten by CustomMenuView subclasses",__func__]; + +} + +-(void)setLast:(NSString *)string { + [NSException raise:@"MethodNotOverwrittenException" format:@"%s must be overwritten by CustomMenuView subclasses",__func__]; + +} + +-(void)setBtc:(NSString *)string { + [NSException raise:@"MethodNotOverwrittenException" format:@"%s must be overwritten by CustomMenuView subclasses",__func__]; + +} + +-(void)setBtcusd:(NSString *)string { + [NSException raise:@"MethodNotOverwrittenException" format:@"%s must be overwritten by CustomMenuView subclasses",__func__]; + +} + +-(void)setUsd:(NSString *)string { + [NSException raise:@"MethodNotOverwrittenException" format:@"%s must be overwritten by CustomMenuView subclasses",__func__]; + +} + +-(void)setWallet:(NSString *)string { + [NSException raise:@"MethodNotOverwrittenException" format:@"%s must be overwritten by CustomMenuView subclasses",__func__]; + +} + @end diff --git a/BitTicker/MtGoxMenuView.h b/BitTicker/MtGoxMenuView.h index 3ebd3cc..f33069d 100644 --- a/BitTicker/MtGoxMenuView.h +++ b/BitTicker/MtGoxMenuView.h @@ -25,16 +25,4 @@ } -@property (retain) NSString *high; -@property (retain) NSString *low; -@property (retain) NSString *vol; -@property (retain) NSString *buy; -@property (retain) NSString *sell; -@property (retain) NSString *last; - -@property (retain) NSString *btc; -@property (retain) NSString *btcusd; -@property (retain) NSString *usd; -@property (retain) NSString *wallet; - @end diff --git a/BitTicker/MtGoxMenuView.m b/BitTicker/MtGoxMenuView.m index 3f88f09..5e81c96 100644 --- a/BitTicker/MtGoxMenuView.m +++ b/BitTicker/MtGoxMenuView.m @@ -22,18 +22,6 @@ @implementation MtGoxMenuView -@dynamic high; -@dynamic low; -@dynamic vol; -@dynamic buy; -@dynamic sell; -@dynamic last; - -@dynamic btc; -@dynamic btcusd; -@dynamic usd; -@dynamic wallet; - - (id)initWithFrame:(NSRect)frame { CGRect newFrame = frame; newFrame.size.height = frame.size.height + 60; From 51cc751b3f53a23db29056c186cdb4e4363cdd64 Mon Sep 17 00:00:00 2001 From: mrsteveman1 Date: Fri, 10 Jun 2011 21:33:42 -0400 Subject: [PATCH 31/46] automate selection of a market view subclass so it doesnt have to be hard coded --- BitTicker.xcodeproj/project.pbxproj | 12 ++++++------ BitTicker/MenuController.m | 12 +++++------- BitTicker/{MtGoxMenuView.h => MtGoxMarketMenuView.h} | 4 ++-- BitTicker/{MtGoxMenuView.m => MtGoxMarketMenuView.m} | 4 ++-- 4 files changed, 15 insertions(+), 17 deletions(-) rename BitTicker/{MtGoxMenuView.h => MtGoxMarketMenuView.h} (85%) rename BitTicker/{MtGoxMenuView.m => MtGoxMarketMenuView.m} (99%) diff --git a/BitTicker.xcodeproj/project.pbxproj b/BitTicker.xcodeproj/project.pbxproj index b884a45..c819f84 100644 --- a/BitTicker.xcodeproj/project.pbxproj +++ b/BitTicker.xcodeproj/project.pbxproj @@ -25,7 +25,7 @@ AA239F7C1379F1B200150707 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA239F7A1379F1B200150707 /* QuartzCore.framework */; }; AA435C0E13A2CB550050F307 /* MenuController.m in Sources */ = {isa = PBXBuildFile; fileRef = AA435C0D13A2CB550050F307 /* MenuController.m */; }; AA435C1213A2CCAA0050F307 /* CustomMenuView.m in Sources */ = {isa = PBXBuildFile; fileRef = AA435C1113A2CCA90050F307 /* CustomMenuView.m */; }; - AA435C2E13A2E0860050F307 /* MtGoxMenuView.m in Sources */ = {isa = PBXBuildFile; fileRef = AA435C2D13A2E0860050F307 /* MtGoxMenuView.m */; }; + AA435C2E13A2E0860050F307 /* MtGoxMarketMenuView.m in Sources */ = {isa = PBXBuildFile; fileRef = AA435C2D13A2E0860050F307 /* MtGoxMarketMenuView.m */; }; AA4BBA2D1379E31B005CE351 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA4BBA2C1379E31B005CE351 /* Cocoa.framework */; }; AA4BBA371379E31C005CE351 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = AA4BBA351379E31C005CE351 /* InfoPlist.strings */; }; AA4BBA3A1379E31C005CE351 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = AA4BBA391379E31C005CE351 /* main.m */; }; @@ -81,8 +81,8 @@ AA435C0D13A2CB550050F307 /* MenuController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MenuController.m; sourceTree = ""; }; AA435C1013A2CCA90050F307 /* CustomMenuView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CustomMenuView.h; sourceTree = ""; }; AA435C1113A2CCA90050F307 /* CustomMenuView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CustomMenuView.m; sourceTree = ""; }; - AA435C2C13A2E0850050F307 /* MtGoxMenuView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MtGoxMenuView.h; path = "MtGoxMenuView.h"; sourceTree = ""; }; - AA435C2D13A2E0860050F307 /* MtGoxMenuView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MtGoxMenuView.m; path = "MtGoxMenuView.m"; sourceTree = ""; }; + AA435C2C13A2E0850050F307 /* MtGoxMarketMenuView.h */ = {isa = PBXFileReference; fileEncoding = 4; path = MtGoxMarketMenuView.h; sourceTree = ""; }; + AA435C2D13A2E0860050F307 /* MtGoxMarketMenuView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MtGoxMarketMenuView.m; sourceTree = ""; }; AA4BBA281379E31B005CE351 /* BitTicker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BitTicker.app; sourceTree = BUILT_PRODUCTS_DIR; }; AA4BBA2C1379E31B005CE351 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; AA4BBA2F1379E31B005CE351 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; @@ -210,8 +210,8 @@ AA239F4E1379E67300150707 /* StatusItemView.m */, AA435C1013A2CCA90050F307 /* CustomMenuView.h */, AA435C1113A2CCA90050F307 /* CustomMenuView.m */, - AA435C2C13A2E0850050F307 /* MtGoxMenuView.h */, - AA435C2D13A2E0860050F307 /* MtGoxMenuView.m */, + AA435C2C13A2E0850050F307 /* MtGoxMarketMenuView.h */, + AA435C2D13A2E0860050F307 /* MtGoxMarketMenuView.m */, ); name = Models; sourceTree = ""; @@ -402,7 +402,7 @@ 837DF135139B8E19009987F3 /* SettingsWindowController.m in Sources */, AA435C0E13A2CB550050F307 /* MenuController.m in Sources */, AA435C1213A2CCAA0050F307 /* CustomMenuView.m in Sources */, - AA435C2E13A2E0860050F307 /* MtGoxMenuView.m in Sources */, + AA435C2E13A2E0860050F307 /* MtGoxMarketMenuView.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/BitTicker/MenuController.m b/BitTicker/MenuController.m index 0265eee..c727805 100644 --- a/BitTicker/MenuController.m +++ b/BitTicker/MenuController.m @@ -59,13 +59,11 @@ - (id)init -(void)createMenuForMarket:(BitcoinMarket*)market { NSMenuItem *menuItem = [[NSMenuItem alloc] init]; NSString *marketClass = NSStringFromClass([market class]); - CustomMenuView *menuView; - if ([marketClass isEqualToString:@"MtGoxMarket"]) { - menuView = [[MtGoxMenuView alloc] initWithFrame:CGRectMake(0,self.currentMenuStop,MENU_VIEW_WIDTH,MENU_VIEW_HEIGHT)]; - } - else { - menuView = [[CustomMenuView alloc] initWithFrame:CGRectMake(0,self.currentMenuStop,MENU_VIEW_WIDTH,MENU_VIEW_HEIGHT)]; - } + + // NOTE: The next line creates an instance of a CustomMenuView + // subclass by looking at the name of the market passed in to + // this method. + CustomMenuView *menuView = [[NSClassFromString([NSString stringWithFormat:@"%@MenuView",marketClass]) alloc] initWithFrame:CGRectMake(0,self.currentMenuStop,MENU_VIEW_WIDTH,MENU_VIEW_HEIGHT)]; [menuItem setView:menuView]; [trayMenu addItem:menuItem]; [trayMenu addItem:[NSMenuItem separatorItem]]; diff --git a/BitTicker/MtGoxMenuView.h b/BitTicker/MtGoxMarketMenuView.h similarity index 85% rename from BitTicker/MtGoxMenuView.h rename to BitTicker/MtGoxMarketMenuView.h index f33069d..e7e14d4 100644 --- a/BitTicker/MtGoxMenuView.h +++ b/BitTicker/MtGoxMarketMenuView.h @@ -1,5 +1,5 @@ // -// MtGoxMenuView.h +// MtGoxMarketMenuView.h // BitTicker // // Created by steve on 6/10/11. @@ -9,7 +9,7 @@ #import #import "CustomMenuView.h" -@interface MtGoxMenuView : CustomMenuView { +@interface MtGoxMarketMenuView : CustomMenuView { NSTextField *highValue; NSTextField *lowValue; diff --git a/BitTicker/MtGoxMenuView.m b/BitTicker/MtGoxMarketMenuView.m similarity index 99% rename from BitTicker/MtGoxMenuView.m rename to BitTicker/MtGoxMarketMenuView.m index 5e81c96..c397b35 100644 --- a/BitTicker/MtGoxMenuView.m +++ b/BitTicker/MtGoxMarketMenuView.m @@ -6,7 +6,7 @@ // Copyright 2011 none. All rights reserved. // -#import "MtGoxMenuView.h" +#import "MtGoxMarketMenuView.h" #define menuFont @"LucidaGrande" #define menuFontSize 12 #define headerFont @"LucidaGrande-Bold" @@ -20,7 +20,7 @@ #define labelOffset 20 #define valueOffset 110 -@implementation MtGoxMenuView +@implementation MtGoxMarketMenuView - (id)initWithFrame:(NSRect)frame { CGRect newFrame = frame; From d53787adf996ff7e824875a6d873bccc08f01980 Mon Sep 17 00:00:00 2001 From: mrsteveman1 Date: Fri, 10 Jun 2011 21:57:51 -0400 Subject: [PATCH 32/46] class for tradehill and support for it in the market enum and shared settings --- BitTicker.xcodeproj/project.pbxproj | 8 +- BitTicker/BitcoinMarket.h | 3 +- BitTicker/MenuController.m | 2 +- BitTicker/SharedSettings.m | 3 + BitTicker/TradeHillMarket.h | 19 +++++ BitTicker/TradeHillMarket.m | 124 ++++++++++++++++++++++++++++ 6 files changed, 156 insertions(+), 3 deletions(-) create mode 100644 BitTicker/TradeHillMarket.h create mode 100644 BitTicker/TradeHillMarket.m diff --git a/BitTicker.xcodeproj/project.pbxproj b/BitTicker.xcodeproj/project.pbxproj index c819f84..69d1ae4 100644 --- a/BitTicker.xcodeproj/project.pbxproj +++ b/BitTicker.xcodeproj/project.pbxproj @@ -42,6 +42,7 @@ AA6C46D7137E10B2000A1DCB /* SBJsonWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = AA6C46CE137E10B2000A1DCB /* SBJsonWriter.m */; }; AA6E6C4D1399DC3E003A4224 /* Credits.html in Resources */ = {isa = PBXBuildFile; fileRef = AA6E6C4C1399DC3E003A4224 /* Credits.html */; }; AA6E6C831399F2EB003A4224 /* SharedSettings.m in Sources */ = {isa = PBXBuildFile; fileRef = AA6E6C821399F2EB003A4224 /* SharedSettings.m */; }; + AA83B79C13A2FF1C000F71A6 /* TradeHillMarket.m in Sources */ = {isa = PBXBuildFile; fileRef = AA83B79B13A2FF1C000F71A6 /* TradeHillMarket.m */; }; AAB399B3139B0D8500B9438F /* Wallet.m in Sources */ = {isa = PBXBuildFile; fileRef = AAB399B2139B0D8500B9438F /* Wallet.m */; }; AAD172641399BA1E00B505B0 /* EMKeychainItem.m in Sources */ = {isa = PBXBuildFile; fileRef = AAD172631399BA1D00B505B0 /* EMKeychainItem.m */; }; AAD1726D1399BD3500B505B0 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AAD1726C1399BD3500B505B0 /* Security.framework */; }; @@ -81,7 +82,7 @@ AA435C0D13A2CB550050F307 /* MenuController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MenuController.m; sourceTree = ""; }; AA435C1013A2CCA90050F307 /* CustomMenuView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CustomMenuView.h; sourceTree = ""; }; AA435C1113A2CCA90050F307 /* CustomMenuView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CustomMenuView.m; sourceTree = ""; }; - AA435C2C13A2E0850050F307 /* MtGoxMarketMenuView.h */ = {isa = PBXFileReference; fileEncoding = 4; path = MtGoxMarketMenuView.h; sourceTree = ""; }; + AA435C2C13A2E0850050F307 /* MtGoxMarketMenuView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MtGoxMarketMenuView.h; sourceTree = ""; }; AA435C2D13A2E0860050F307 /* MtGoxMarketMenuView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MtGoxMarketMenuView.m; sourceTree = ""; }; AA4BBA281379E31B005CE351 /* BitTicker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BitTicker.app; sourceTree = BUILT_PRODUCTS_DIR; }; AA4BBA2C1379E31B005CE351 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; @@ -117,6 +118,8 @@ AA6E6C4C1399DC3E003A4224 /* Credits.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = Credits.html; sourceTree = ""; }; AA6E6C811399F2EB003A4224 /* SharedSettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SharedSettings.h; sourceTree = ""; }; AA6E6C821399F2EB003A4224 /* SharedSettings.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SharedSettings.m; sourceTree = ""; }; + AA83B79A13A2FF1C000F71A6 /* TradeHillMarket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TradeHillMarket.h; path = "TradeHillMarket.h"; sourceTree = ""; }; + AA83B79B13A2FF1C000F71A6 /* TradeHillMarket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TradeHillMarket.m; path = "TradeHillMarket.m"; sourceTree = ""; }; AAB399B1139B0D8500B9438F /* Wallet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Wallet.h; sourceTree = ""; }; AAB399B2139B0D8500B9438F /* Wallet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Wallet.m; sourceTree = ""; }; AAD172621399BA1D00B505B0 /* EMKeychainItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EMKeychainItem.h; sourceTree = ""; }; @@ -202,6 +205,8 @@ 83405B36137F6DDF0060CAD4 /* BitcoinMarket.m */, 83405B37137F6DDF0060CAD4 /* MtGoxMarket.h */, 83405B38137F6DDF0060CAD4 /* MtGoxMarket.m */, + AA83B79A13A2FF1C000F71A6 /* TradeHillMarket.h */, + AA83B79B13A2FF1C000F71A6 /* TradeHillMarket.m */, AAB399B1139B0D8500B9438F /* Wallet.h */, AAB399B2139B0D8500B9438F /* Wallet.m */, AAD172621399BA1D00B505B0 /* EMKeychainItem.h */, @@ -403,6 +408,7 @@ AA435C0E13A2CB550050F307 /* MenuController.m in Sources */, AA435C1213A2CCAA0050F307 /* CustomMenuView.m in Sources */, AA435C2E13A2E0860050F307 /* MtGoxMarketMenuView.m in Sources */, + AA83B79C13A2FF1C000F71A6 /* TradeHillMarket.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/BitTicker/BitcoinMarket.h b/BitTicker/BitcoinMarket.h index bb151d4..c0f9ce7 100644 --- a/BitTicker/BitcoinMarket.h +++ b/BitTicker/BitcoinMarket.h @@ -14,8 +14,9 @@ enum kBitcoinMarkets { eMarketMtGox = 0, + eMarketTradeHill = 1, // Used for market enumeration. Keep this as the last value. - eNumberOfMarkets = 1 + eNumberOfMarkets = 2 }; @class RequestHandler; diff --git a/BitTicker/MenuController.m b/BitTicker/MenuController.m index c727805..a9aa90c 100644 --- a/BitTicker/MenuController.m +++ b/BitTicker/MenuController.m @@ -13,7 +13,7 @@ #import "CustomMenuView.h" #import "SharedSettings.h" -#import "MtGoxMenuView.h" +#import "MtGoxMarketMenuView.h" #define MENU_VIEW_HEIGHT 105 #define MENU_VIEW_WIDTH 180 diff --git a/BitTicker/SharedSettings.m b/BitTicker/SharedSettings.m index c6941b3..1b05ff2 100644 --- a/BitTicker/SharedSettings.m +++ b/BitTicker/SharedSettings.m @@ -102,6 +102,9 @@ -(NSString*)stringForMarket:(NSInteger)market { case eMarketMtGox: return @"MtGox"; break; + case eMarketTradeHill: + return @"TradeHill"; + break; default: return @"Unknown"; break; diff --git a/BitTicker/TradeHillMarket.h b/BitTicker/TradeHillMarket.h new file mode 100644 index 0000000..6d8e5b1 --- /dev/null +++ b/BitTicker/TradeHillMarket.h @@ -0,0 +1,19 @@ +// +// TradeHillMarket.h +// BitTicker +// +// Created by steve on 6/10/11. +// Copyright 2011 none. All rights reserved. +// + +#import + +#import "BitcoinMarket.h" + +@interface TradeHillMarket : BitcoinMarket { + +} + +-(id)initWithDelegate:(id)delegate; + +@end diff --git a/BitTicker/TradeHillMarket.m b/BitTicker/TradeHillMarket.m new file mode 100644 index 0000000..f72229d --- /dev/null +++ b/BitTicker/TradeHillMarket.m @@ -0,0 +1,124 @@ +// +// TradeHillMarket.m +// BitTicker +// +// Created by steve on 6/10/11. +// Copyright 2011 none. All rights reserved. +// + +#import "TradeHillMarket.h" + +#import "Trade.h" +#import "Ticker.h" +#import "Wallet.h" + +// TODO: they have URLs for multiple currencies... +#define TRADEHILL_TICKER_URL @"" +#define TRADEHILL_TRADES_URL @"https://www.tradehill.com/API/USD/Trades" +#define TRADEHILL_MARKETDEPTH_URL @"https://www.tradehill.com/API/USD/Orderbook" +#define TRADEHILL_WALLET_URL @"" + +@implementation TradeHillMarket + +-(id)initWithDelegate:(id)delegate { + if (!(self = [super initWithDelegate:delegate])) return self; + _tickerURL = TRADEHILL_TICKER_URL; + _tradeURL = TRADEHILL_TRADES_URL; + _depthURL = TRADEHILL_MARKETDEPTH_URL; + _walletURL = TRADEHILL_WALLET_URL; + return self; +} + +-(void)fetchRecentTrades { + MSLog(@"Fetching recent trades..."); + [self downloadJsonDataFromURL:[NSURL URLWithString:self.tradeURL] callback:@selector(didFetchRecentTrades:)]; +} + +-(void)fetchTicker { + MSLog(@"Fetching ticker..."); + [self downloadJsonDataFromURL:[NSURL URLWithString:self.tickerURL] callback:@selector(didFetchTickerData:)]; +} + +-(void)fetchMarketDepth { + MSLog(@"Fetching market depth..."); + [self downloadJsonDataFromURL:[NSURL URLWithString:self.depthURL] callback:@selector(didFetchMarketDepth:)]; +} + +-(void)fetchWallet { + MSLog(@"Fetching wallet..."); + NSString *username = [sharedSettingManager usernameForMarket:eMarketTradeHill]; + NSString *password = [sharedSettingManager passwordForMarket:eMarketTradeHill]; + if ([username isEqualToString:@"ChangeMe"] || username == nil) { + return; + } + if ([password isEqualToString:@"ChangeMe"] || password == nil) { + return; + } + NSDictionary *post = [NSDictionary dictionaryWithObjectsAndKeys:username,@"name",password,@"pass",nil]; + [self downloadJsonDataFromURL:[NSURL URLWithString:self.walletURL] withPostData:post callback:@selector(didFetchWallet:)]; +} + +-(void)didFetchRecentTrades:(NSArray*)tradeData { + NSMutableArray *trades = [NSMutableArray array]; + + for (NSDictionary *tradeDict in tradeData) { + Trade *newTrade = [[Trade alloc] init]; + newTrade.amount = [tradeDict objectForKey:@"amount"]; + newTrade.price = [tradeDict objectForKey:@"price"]; + newTrade.tid = [[tradeDict objectForKey:@"tid"] intValue]; + + newTrade.date = [NSDate dateWithTimeIntervalSince1970:[[tradeDict objectForKey:@"amount"] doubleValue]]; + + [trades addObject:newTrade]; + [newTrade release]; + } + MSLog(@"Got %i trades",trades.count); + + // Reverse the trades so 0 is the most recent + NSMutableArray *orderedTrades = [NSMutableArray array]; + NSEnumerator *reverseEnumerator = [trades reverseObjectEnumerator]; + id object; + + while ((object = [reverseEnumerator nextObject])) { + [orderedTrades addObject:object]; + } + + [_delegate bitcoinMarket:self didReceiveRecentTradesData:orderedTrades]; +} + +-(void)didFetchTickerData:(NSDictionary*)tickerData { + NSDictionary *tickerDict = [tickerData objectForKey:@"ticker"]; + + Ticker *ticker = [[Ticker alloc] init]; + // Dont know if these are correct yet + ticker.buy = [tickerDict objectForKey:@"buy"]; + ticker.sell = [tickerDict objectForKey:@"sell"]; + ticker.high = [tickerDict objectForKey:@"high"]; + ticker.low = [tickerDict objectForKey:@"low"]; + ticker.last = [tickerDict objectForKey:@"last"]; + ticker.volume = [tickerDict objectForKey:@"vol"]; + + [_delegate bitcoinMarket:self didReceiveTicker:ticker]; + [ticker release]; +} + +-(void)didFetchMarketDepth:(NSDictionary*)marketDepth { + MSLog(@"Got %i asks and %i bids",[[marketDepth objectForKey:@"asks"] count],[[marketDepth objectForKey:@"bids"] count]); +} + +-(void)didFetchWallet:(NSDictionary *)dictwallet { + Wallet *newwallet = [[Wallet alloc] init]; + + // these might need to be changed once their API is available + newwallet.btc = [dictwallet objectForKey:@"btcs"]; + newwallet.usd = [dictwallet objectForKey:@"usds"]; + [_delegate bitcoinMarket:self didReceiveWallet:newwallet]; +} + + +- (void)dealloc +{ + [super dealloc]; +} + +@end From 17caa3ebca5df728003a18ebaf121c605a79db19 Mon Sep 17 00:00:00 2001 From: mrsteveman1 Date: Fri, 10 Jun 2011 21:58:48 -0400 Subject: [PATCH 33/46] check for blank instead of arbitrary string --- BitTicker/MtGoxMarket.m | 4 ++-- BitTicker/TradeHillMarket.m | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/BitTicker/MtGoxMarket.m b/BitTicker/MtGoxMarket.m index 80f28cf..ac43870 100644 --- a/BitTicker/MtGoxMarket.m +++ b/BitTicker/MtGoxMarket.m @@ -52,10 +52,10 @@ -(void)fetchWallet { MSLog(@"Fetching wallet..."); NSString *username = [sharedSettingManager usernameForMarket:eMarketMtGox]; NSString *password = [sharedSettingManager passwordForMarket:eMarketMtGox]; - if ([username isEqualToString:@"ChangeMe"] || username == nil) { + if ([username isEqualToString:@""] || username == nil) { return; } - if ([password isEqualToString:@"ChangeMe"] || password == nil) { + if ([password isEqualToString:@""] || password == nil) { return; } NSDictionary *post = [NSDictionary dictionaryWithObjectsAndKeys:username,@"name",password,@"pass",nil]; diff --git a/BitTicker/TradeHillMarket.m b/BitTicker/TradeHillMarket.m index f72229d..4f9288a 100644 --- a/BitTicker/TradeHillMarket.m +++ b/BitTicker/TradeHillMarket.m @@ -48,10 +48,10 @@ -(void)fetchWallet { MSLog(@"Fetching wallet..."); NSString *username = [sharedSettingManager usernameForMarket:eMarketTradeHill]; NSString *password = [sharedSettingManager passwordForMarket:eMarketTradeHill]; - if ([username isEqualToString:@"ChangeMe"] || username == nil) { + if ([username isEqualToString:@""] || username == nil) { return; } - if ([password isEqualToString:@"ChangeMe"] || password == nil) { + if ([password isEqualToString:@""] || password == nil) { return; } NSDictionary *post = [NSDictionary dictionaryWithObjectsAndKeys:username,@"name",password,@"pass",nil]; From 1353187b367bbe1b54a28ed8663eed2163c8b729 Mon Sep 17 00:00:00 2001 From: mrsteveman1 Date: Fri, 10 Jun 2011 22:13:20 -0400 Subject: [PATCH 34/46] tradehill view --- BitTicker.xcodeproj/project.pbxproj | 10 +- BitTicker/BitTickerAppDelegate.h | 2 + BitTicker/BitTickerAppDelegate.m | 7 +- BitTicker/TradeHillMarketMenuView.h | 27 +++ BitTicker/TradeHillMarketMenuView.m | 301 ++++++++++++++++++++++++++++ 5 files changed, 343 insertions(+), 4 deletions(-) create mode 100644 BitTicker/TradeHillMarketMenuView.h create mode 100644 BitTicker/TradeHillMarketMenuView.m diff --git a/BitTicker.xcodeproj/project.pbxproj b/BitTicker.xcodeproj/project.pbxproj index 69d1ae4..10244ec 100644 --- a/BitTicker.xcodeproj/project.pbxproj +++ b/BitTicker.xcodeproj/project.pbxproj @@ -43,6 +43,7 @@ AA6E6C4D1399DC3E003A4224 /* Credits.html in Resources */ = {isa = PBXBuildFile; fileRef = AA6E6C4C1399DC3E003A4224 /* Credits.html */; }; AA6E6C831399F2EB003A4224 /* SharedSettings.m in Sources */ = {isa = PBXBuildFile; fileRef = AA6E6C821399F2EB003A4224 /* SharedSettings.m */; }; AA83B79C13A2FF1C000F71A6 /* TradeHillMarket.m in Sources */ = {isa = PBXBuildFile; fileRef = AA83B79B13A2FF1C000F71A6 /* TradeHillMarket.m */; }; + AA83B7AC13A30583000F71A6 /* TradeHillMarketMenuView.m in Sources */ = {isa = PBXBuildFile; fileRef = AA83B7AB13A30582000F71A6 /* TradeHillMarketMenuView.m */; }; AAB399B3139B0D8500B9438F /* Wallet.m in Sources */ = {isa = PBXBuildFile; fileRef = AAB399B2139B0D8500B9438F /* Wallet.m */; }; AAD172641399BA1E00B505B0 /* EMKeychainItem.m in Sources */ = {isa = PBXBuildFile; fileRef = AAD172631399BA1D00B505B0 /* EMKeychainItem.m */; }; AAD1726D1399BD3500B505B0 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AAD1726C1399BD3500B505B0 /* Security.framework */; }; @@ -118,8 +119,10 @@ AA6E6C4C1399DC3E003A4224 /* Credits.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = Credits.html; sourceTree = ""; }; AA6E6C811399F2EB003A4224 /* SharedSettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SharedSettings.h; sourceTree = ""; }; AA6E6C821399F2EB003A4224 /* SharedSettings.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SharedSettings.m; sourceTree = ""; }; - AA83B79A13A2FF1C000F71A6 /* TradeHillMarket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TradeHillMarket.h; path = "TradeHillMarket.h"; sourceTree = ""; }; - AA83B79B13A2FF1C000F71A6 /* TradeHillMarket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TradeHillMarket.m; path = "TradeHillMarket.m"; sourceTree = ""; }; + AA83B79A13A2FF1C000F71A6 /* TradeHillMarket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TradeHillMarket.h; sourceTree = ""; }; + AA83B79B13A2FF1C000F71A6 /* TradeHillMarket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TradeHillMarket.m; sourceTree = ""; }; + AA83B7AA13A30582000F71A6 /* TradeHillMarketMenuView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TradeHillMarketMenuView.h; path = "TradeHillMarketMenuView.h"; sourceTree = ""; }; + AA83B7AB13A30582000F71A6 /* TradeHillMarketMenuView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TradeHillMarketMenuView.m; path = "TradeHillMarketMenuView.m"; sourceTree = ""; }; AAB399B1139B0D8500B9438F /* Wallet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Wallet.h; sourceTree = ""; }; AAB399B2139B0D8500B9438F /* Wallet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Wallet.m; sourceTree = ""; }; AAD172621399BA1D00B505B0 /* EMKeychainItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EMKeychainItem.h; sourceTree = ""; }; @@ -217,6 +220,8 @@ AA435C1113A2CCA90050F307 /* CustomMenuView.m */, AA435C2C13A2E0850050F307 /* MtGoxMarketMenuView.h */, AA435C2D13A2E0860050F307 /* MtGoxMarketMenuView.m */, + AA83B7AA13A30582000F71A6 /* TradeHillMarketMenuView.h */, + AA83B7AB13A30582000F71A6 /* TradeHillMarketMenuView.m */, ); name = Models; sourceTree = ""; @@ -409,6 +414,7 @@ AA435C1213A2CCAA0050F307 /* CustomMenuView.m in Sources */, AA435C2E13A2E0860050F307 /* MtGoxMarketMenuView.m in Sources */, AA83B79C13A2FF1C000F71A6 /* TradeHillMarket.m in Sources */, + AA83B7AC13A30583000F71A6 /* TradeHillMarketMenuView.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/BitTicker/BitTickerAppDelegate.h b/BitTicker/BitTickerAppDelegate.h index 39043e2..49f952a 100644 --- a/BitTicker/BitTickerAppDelegate.h +++ b/BitTicker/BitTickerAppDelegate.h @@ -23,11 +23,13 @@ @class RequestHandler; @class SettingsWindow; @class MtGoxMarket; +@class TradeHillMarket; @interface BitTickerAppDelegate : NSObject { MenuController *menuController; SharedSettings *sharedSettingManager; MtGoxMarket *market; + TradeHillMarket *tradehill; NSTimer *tickerTimer; NSTimer *walletTimer; diff --git a/BitTicker/BitTickerAppDelegate.m b/BitTicker/BitTickerAppDelegate.m index 18bf8ea..dbbf057 100644 --- a/BitTicker/BitTickerAppDelegate.m +++ b/BitTicker/BitTickerAppDelegate.m @@ -19,6 +19,7 @@ #import "BitTickerAppDelegate.h" #import "MtGoxMarket.h" +#import "TradeHillMarket.h" #import "SharedSettings.h" #import "SettingsWindowController.h" @@ -38,13 +39,15 @@ - (void)awakeFromNib { MSLog(@"Starting"); market = [[MtGoxMarket alloc] initWithDelegate:menuController]; - tickerTimer = [[NSTimer scheduledTimerWithTimeInterval:30 target:market selector:@selector(fetchTicker) userInfo:nil repeats:YES] retain]; walletTimer = [[NSTimer scheduledTimerWithTimeInterval:60 target:market selector:@selector(fetchWallet) userInfo:nil repeats:YES] retain]; - [market fetchTicker]; [market fetchWallet]; [menuController createMenuForMarket:market]; + + //tradehill = [[TradeHillMarket alloc] initWithDelegate:menuController]; + //[menuController createMenuForMarket:tradehill]; + [menuController addSelectorItems]; } diff --git a/BitTicker/TradeHillMarketMenuView.h b/BitTicker/TradeHillMarketMenuView.h new file mode 100644 index 0000000..9b3fc17 --- /dev/null +++ b/BitTicker/TradeHillMarketMenuView.h @@ -0,0 +1,27 @@ +// +// TradeHillMarketMenuView.h +// BitTicker +// +// Created by steve on 6/10/11. +// Copyright 2011 none. All rights reserved. +// + +#import +#import "CustomMenuView.h" + +@interface TradeHillMarketMenuView : CustomMenuView { + NSTextField *highValue; + NSTextField *lowValue; + NSTextField *volValue; + NSTextField *buyValue; + NSTextField *sellValue; + NSTextField *lastValue; + + NSTextField *BTCValue; + NSTextField *BTCxUSDValue; + NSTextField *USDValue; + NSTextField *walletUSDValue; + +} + +@end diff --git a/BitTicker/TradeHillMarketMenuView.m b/BitTicker/TradeHillMarketMenuView.m new file mode 100644 index 0000000..14196ab --- /dev/null +++ b/BitTicker/TradeHillMarketMenuView.m @@ -0,0 +1,301 @@ +// +// TradeHillMarketMenuView.m +// BitTicker +// +// Created by steve on 6/10/11. +// Copyright 2011 none. All rights reserved. +// + +#import "TradeHillMarketMenuView.h" +#define menuFont @"LucidaGrande" +#define menuFontSize 12 +#define headerFont @"LucidaGrande-Bold" +#define headerFontSize 13 + +#define menuHeight 15 +#define labelWidth 70 +#define headerWidth 120 +#define valueWidth 60 + +#define labelOffset 20 +#define valueOffset 110 + +@implementation TradeHillMarketMenuView + +- (id)initWithFrame:(NSRect)frame { + CGRect newFrame = frame; + newFrame.size.height = frame.size.height + 60; + self = [super initWithFrame:newFrame]; + if (self) { + NSTextField *sectionLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,153,headerWidth,menuHeight)]; + [sectionLabel setEditable:FALSE]; + [sectionLabel setBordered:NO]; + [sectionLabel setAlignment:NSLeftTextAlignment]; + [sectionLabel setBackgroundColor:[NSColor clearColor]]; + [sectionLabel setStringValue:@"Trade Hill"]; + [sectionLabel setTextColor:[NSColor blueColor]]; + [sectionLabel setFont:[NSFont fontWithName:headerFont size:headerFontSize]]; + [self addSubview:sectionLabel]; + [sectionLabel release]; + + highValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,135,valueWidth,menuHeight)]; + [highValue setEditable:FALSE]; + [highValue setBordered:NO]; + [highValue setAlignment:NSRightTextAlignment]; + [highValue setBackgroundColor:[NSColor clearColor]]; + [highValue setTextColor:[NSColor blackColor]]; + [highValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:highValue]; + + NSTextField *highLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,135,labelWidth,menuHeight)]; + [highLabel setEditable:FALSE]; + [highLabel setBordered:NO]; + [highLabel setAlignment:NSLeftTextAlignment]; + [highLabel setBackgroundColor:[NSColor clearColor]]; + [highLabel setStringValue:@"High:"]; + [highLabel setTextColor:[NSColor blackColor]]; + [highLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:highLabel]; + [highLabel release]; + + lowValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,120,valueWidth,menuHeight)]; + [lowValue setEditable:FALSE]; + [lowValue setBordered:NO]; + [lowValue setAlignment:NSRightTextAlignment]; + [lowValue setBackgroundColor:[NSColor clearColor]]; + [lowValue setTextColor:[NSColor blackColor]]; + [lowValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:lowValue]; + + NSTextField *lowLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,120,labelWidth,menuHeight)]; + [lowLabel setEditable:FALSE]; + [lowLabel setBordered:NO]; + [lowLabel setAlignment:NSLeftTextAlignment]; + [lowLabel setBackgroundColor:[NSColor clearColor]]; + [lowLabel setStringValue:@"Low:"]; + [lowLabel setTextColor:[NSColor blackColor]]; + [lowLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:lowLabel]; + [lowLabel release]; + + buyValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,105,valueWidth,menuHeight)]; + [buyValue setEditable:FALSE]; + [buyValue setBordered:NO]; + [buyValue setAlignment:NSRightTextAlignment]; + [buyValue setBackgroundColor:[NSColor clearColor]]; + [buyValue setTextColor:[NSColor blackColor]]; + [buyValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:buyValue]; + + NSTextField *buyLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,105,labelWidth,menuHeight)]; + [buyLabel setEditable:FALSE]; + [buyLabel setBordered:NO]; + [buyLabel setAlignment:NSLeftTextAlignment]; + [buyLabel setBackgroundColor:[NSColor clearColor]]; + [buyLabel setStringValue:@"Buy:"]; + [buyLabel setTextColor:[NSColor blackColor]]; + [buyLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:buyLabel]; + [buyLabel release]; + + sellValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,90,valueWidth,menuHeight)]; + [sellValue setEditable:FALSE]; + [sellValue setBordered:NO]; + [sellValue setAlignment:NSRightTextAlignment]; + [sellValue setBackgroundColor:[NSColor clearColor]]; + [sellValue setTextColor:[NSColor blackColor]]; + [sellValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:sellValue]; + + NSTextField *sellLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,90,labelWidth,menuHeight)]; + [sellLabel setEditable:FALSE]; + [sellLabel setBordered:NO]; + [sellLabel setAlignment:NSLeftTextAlignment]; + [sellLabel setBackgroundColor:[NSColor clearColor]]; + [sellLabel setStringValue:@"Sell:"]; + [sellLabel setTextColor:[NSColor blackColor]]; + [sellLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:sellLabel]; + [sellLabel release]; + + lastValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,75,valueWidth,menuHeight)]; + [lastValue setEditable:FALSE]; + [lastValue setBordered:NO]; + [lastValue setAlignment:NSRightTextAlignment]; + [lastValue setBackgroundColor:[NSColor clearColor]]; + [lastValue setTextColor:[NSColor blackColor]]; + [lastValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:lastValue]; + + NSTextField *lastLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,75,labelWidth,menuHeight)]; + [lastLabel setEditable:FALSE]; + [lastLabel setBordered:NO]; + [lastLabel setAlignment:NSLeftTextAlignment]; + [lastLabel setBackgroundColor:[NSColor clearColor]]; + [lastLabel setStringValue:@"Last:"]; + [lastLabel setTextColor:[NSColor blackColor]]; + [lastLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:lastLabel]; + [lastLabel release]; + + volValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,60,valueWidth,menuHeight)]; + [volValue setEditable:FALSE]; + [volValue setBordered:NO]; + [volValue setAlignment:NSRightTextAlignment]; + [volValue setBackgroundColor:[NSColor clearColor]]; + [volValue setTextColor:[NSColor blackColor]]; + [volValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:volValue]; + + NSTextField *volLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,60,labelWidth,menuHeight)]; + [volLabel setEditable:FALSE]; + [volLabel setBordered:NO]; + [volLabel setAlignment:NSLeftTextAlignment]; + [volLabel setBackgroundColor:[NSColor clearColor]]; + [volLabel setStringValue:@"Volume:"]; + [volLabel setTextColor:[NSColor blackColor]]; + [volLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:volLabel]; + [volLabel release]; + + BTCValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 45, valueWidth, menuHeight)]; + [BTCValue setEditable:FALSE]; + [BTCValue setBordered:NO]; + [BTCValue setAlignment:NSRightTextAlignment]; + [BTCValue setBackgroundColor:[NSColor clearColor]]; + [BTCValue setTextColor:[NSColor blackColor]]; + [BTCValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:BTCValue]; + + NSTextField *BTCLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,45,labelWidth,menuHeight)]; + [BTCLabel setEditable:FALSE]; + [BTCLabel setBordered:NO]; + [BTCLabel setAlignment:NSLeftTextAlignment]; + [BTCLabel setBackgroundColor:[NSColor clearColor]]; + [BTCLabel setStringValue:@"BTC:"]; + [BTCLabel setTextColor:[NSColor blackColor]]; + [BTCLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:BTCLabel]; + [BTCLabel release]; + + BTCxUSDValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 30, valueWidth, menuHeight)]; + [BTCxUSDValue setEditable:FALSE]; + [BTCxUSDValue setBordered:NO]; + [BTCxUSDValue setAlignment:NSRightTextAlignment]; + [BTCxUSDValue setBackgroundColor:[NSColor clearColor]]; + [BTCxUSDValue setTextColor:[NSColor blackColor]]; + [BTCxUSDValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:BTCxUSDValue]; + + NSTextField *BTCxUSDLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,30,labelWidth,menuHeight)]; + [BTCxUSDLabel setEditable:FALSE]; + [BTCxUSDLabel setBordered:NO]; + [BTCxUSDLabel setAlignment:NSLeftTextAlignment]; + [BTCxUSDLabel setBackgroundColor:[NSColor clearColor]]; + [BTCxUSDLabel setStringValue:@"BTC * Last:"]; + [BTCxUSDLabel setTextColor:[NSColor blackColor]]; + [BTCxUSDLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:BTCxUSDLabel]; + [BTCxUSDLabel release]; + + USDValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 15, valueWidth, menuHeight)]; + [USDValue setEditable:FALSE]; + [USDValue setBordered:NO]; + [USDValue setAlignment:NSRightTextAlignment]; + [USDValue setBackgroundColor:[NSColor clearColor]]; + [USDValue setTextColor:[NSColor blackColor]]; + [USDValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:USDValue]; + + NSTextField *USDLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,15,labelWidth,menuHeight)]; + [USDLabel setEditable:FALSE]; + [USDLabel setBordered:NO]; + [USDLabel setAlignment:NSLeftTextAlignment]; + [USDLabel setBackgroundColor:[NSColor clearColor]]; + [USDLabel setStringValue:@"USD:"]; + [USDLabel setTextColor:[NSColor blackColor]]; + [USDLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:USDLabel]; + [USDLabel release]; + + walletUSDValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 0, valueWidth, menuHeight)]; + [walletUSDValue setEditable:FALSE]; + [walletUSDValue setBordered:NO]; + [walletUSDValue setAlignment:NSRightTextAlignment]; + [walletUSDValue setBackgroundColor:[NSColor clearColor]]; + [walletUSDValue setTextColor:[NSColor blackColor]]; + [walletUSDValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:walletUSDValue]; + + NSTextField *walletLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,0,labelWidth,menuHeight)]; + [walletLabel setEditable:FALSE]; + [walletLabel setBordered:NO]; + [walletLabel setAlignment:NSLeftTextAlignment]; + [walletLabel setBackgroundColor:[NSColor clearColor]]; + [walletLabel setStringValue:@"Total:"]; + [walletLabel setTextColor:[NSColor blackColor]]; + [walletLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; + [self addSubview:walletLabel]; + [walletLabel release]; + } + + return self; +} + +- (id)init { + self = [super init]; + if (self) { + + } + + return self; +} + +#pragma mark - +#pragma mark Properties + +-(void)setHigh:(NSString *)string { + [highValue setStringValue:string]; +} + +-(void)setLow:(NSString *)string { + [lowValue setStringValue:string]; +} + +-(void)setVol:(NSString *)string { + [volValue setStringValue:string]; +} + +-(void)setBuy:(NSString *)string { + [buyValue setStringValue:string]; +} + +-(void)setSell:(NSString *)string { + [sellValue setStringValue:string]; +} + +-(void)setLast:(NSString *)string { + [lastValue setStringValue:string]; +} + +-(void)setBtc:(NSString *)string { + [BTCValue setStringValue:string]; +} + +-(void)setBtcusd:(NSString *)string { + [BTCxUSDValue setStringValue:string]; +} + +-(void)setUsd:(NSString *)string { + [USDValue setStringValue:string]; +} + +-(void)setWallet:(NSString *)string { + [walletUSDValue setStringValue:string]; +} + +- (void)dealloc +{ + [super dealloc]; +} +@end From 58c08e82b322357c252cf9229c008da866bc6f98 Mon Sep 17 00:00:00 2001 From: mrsteveman1 Date: Sun, 12 Jun 2011 17:56:25 -0400 Subject: [PATCH 35/46] support for the main window, which can select a market and load a specific view into the right side panel (works exactly like UISplitViewController on iPad) --- BitTicker.xcodeproj/project.pbxproj | 40 +- BitTicker/BitTickerAppDelegate.h | 2 + BitTicker/BitTickerAppDelegate.m | 9 + BitTicker/MainPanelView.xib | 213 ++++++++++ BitTicker/MainWindow.xib | 599 ++++++++++++++++++++++++++++ BitTicker/MainWindowController.h | 32 ++ BitTicker/MainWindowController.m | 91 +++++ BitTicker/MenuController.h | 1 + BitTicker/MenuController.m | 11 +- BitTicker/MtGoxPanel.xib | 279 +++++++++++++ BitTicker/ToolbarTicker.h | 17 + BitTicker/ToolbarTicker.m | 29 ++ BitTicker/TradeHillPanel.xib | 279 +++++++++++++ 13 files changed, 1595 insertions(+), 7 deletions(-) create mode 100644 BitTicker/MainPanelView.xib create mode 100644 BitTicker/MainWindow.xib create mode 100644 BitTicker/MainWindowController.h create mode 100644 BitTicker/MainWindowController.m create mode 100644 BitTicker/MtGoxPanel.xib create mode 100644 BitTicker/ToolbarTicker.h create mode 100644 BitTicker/ToolbarTicker.m create mode 100644 BitTicker/TradeHillPanel.xib diff --git a/BitTicker.xcodeproj/project.pbxproj b/BitTicker.xcodeproj/project.pbxproj index 10244ec..e9250d2 100644 --- a/BitTicker.xcodeproj/project.pbxproj +++ b/BitTicker.xcodeproj/project.pbxproj @@ -17,6 +17,8 @@ 837DF12D139B2E97009987F3 /* SettingsWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 837DF12C139B2E97009987F3 /* SettingsWindow.xib */; }; 837DF131139B319F009987F3 /* SettingsWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 837DF130139B319F009987F3 /* SettingsWindow.m */; }; 837DF135139B8E19009987F3 /* SettingsWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 837DF134139B8E18009987F3 /* SettingsWindowController.m */; }; + AA1F92A013A526D600D5643C /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = AA1F929F13A526D600D5643C /* MainWindow.xib */; }; + AA1F92A413A52D7D00D5643C /* ToolbarTicker.m in Sources */ = {isa = PBXBuildFile; fileRef = AA1F92A313A52D7D00D5643C /* ToolbarTicker.m */; }; AA239F511379E67300150707 /* StatusItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = AA239F4E1379E67300150707 /* StatusItemView.m */; }; AA239F761379F17200150707 /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA239F731379F17200150707 /* CoreServices.framework */; }; AA239F771379F17200150707 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = AA239F741379F17200150707 /* libz.dylib */; }; @@ -44,6 +46,10 @@ AA6E6C831399F2EB003A4224 /* SharedSettings.m in Sources */ = {isa = PBXBuildFile; fileRef = AA6E6C821399F2EB003A4224 /* SharedSettings.m */; }; AA83B79C13A2FF1C000F71A6 /* TradeHillMarket.m in Sources */ = {isa = PBXBuildFile; fileRef = AA83B79B13A2FF1C000F71A6 /* TradeHillMarket.m */; }; AA83B7AC13A30583000F71A6 /* TradeHillMarketMenuView.m in Sources */ = {isa = PBXBuildFile; fileRef = AA83B7AB13A30582000F71A6 /* TradeHillMarketMenuView.m */; }; + AA86BF0F13A536250089B39E /* MainWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = AA86BF0D13A536250089B39E /* MainWindowController.m */; }; + AA86BF2113A55C100089B39E /* MtGoxPanel.xib in Resources */ = {isa = PBXBuildFile; fileRef = AA86BF2013A55C0F0089B39E /* MtGoxPanel.xib */; }; + AA86BF2313A55C880089B39E /* TradeHillPanel.xib in Resources */ = {isa = PBXBuildFile; fileRef = AA86BF2213A55C870089B39E /* TradeHillPanel.xib */; }; + AA86BF2513A56A470089B39E /* MainPanelView.xib in Resources */ = {isa = PBXBuildFile; fileRef = AA86BF2413A56A470089B39E /* MainPanelView.xib */; }; AAB399B3139B0D8500B9438F /* Wallet.m in Sources */ = {isa = PBXBuildFile; fileRef = AAB399B2139B0D8500B9438F /* Wallet.m */; }; AAD172641399BA1E00B505B0 /* EMKeychainItem.m in Sources */ = {isa = PBXBuildFile; fileRef = AAD172631399BA1D00B505B0 /* EMKeychainItem.m */; }; AAD1726D1399BD3500B505B0 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AAD1726C1399BD3500B505B0 /* Security.framework */; }; @@ -72,6 +78,9 @@ 837DF130139B319F009987F3 /* SettingsWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SettingsWindow.m; sourceTree = ""; }; 837DF133139B8E18009987F3 /* SettingsWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SettingsWindowController.h; sourceTree = ""; }; 837DF134139B8E18009987F3 /* SettingsWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SettingsWindowController.m; sourceTree = ""; }; + AA1F929F13A526D600D5643C /* MainWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MainWindow.xib; sourceTree = ""; }; + AA1F92A213A52D7D00D5643C /* ToolbarTicker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ToolbarTicker.h; sourceTree = ""; }; + AA1F92A313A52D7D00D5643C /* ToolbarTicker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ToolbarTicker.m; sourceTree = ""; }; AA239F4D1379E67300150707 /* StatusItemView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StatusItemView.h; sourceTree = ""; }; AA239F4E1379E67300150707 /* StatusItemView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StatusItemView.m; sourceTree = ""; }; AA239F731379F17200150707 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; @@ -121,8 +130,13 @@ AA6E6C821399F2EB003A4224 /* SharedSettings.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SharedSettings.m; sourceTree = ""; }; AA83B79A13A2FF1C000F71A6 /* TradeHillMarket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TradeHillMarket.h; sourceTree = ""; }; AA83B79B13A2FF1C000F71A6 /* TradeHillMarket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TradeHillMarket.m; sourceTree = ""; }; - AA83B7AA13A30582000F71A6 /* TradeHillMarketMenuView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TradeHillMarketMenuView.h; path = "TradeHillMarketMenuView.h"; sourceTree = ""; }; - AA83B7AB13A30582000F71A6 /* TradeHillMarketMenuView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TradeHillMarketMenuView.m; path = "TradeHillMarketMenuView.m"; sourceTree = ""; }; + AA83B7AA13A30582000F71A6 /* TradeHillMarketMenuView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TradeHillMarketMenuView.h; sourceTree = ""; }; + AA83B7AB13A30582000F71A6 /* TradeHillMarketMenuView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TradeHillMarketMenuView.m; sourceTree = ""; }; + AA86BF0C13A536250089B39E /* MainWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MainWindowController.h; path = "MainWindowController.h"; sourceTree = ""; }; + AA86BF0D13A536250089B39E /* MainWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MainWindowController.m; path = "MainWindowController.m"; sourceTree = ""; }; + AA86BF2013A55C0F0089B39E /* MtGoxPanel.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = MtGoxPanel.xib; path = "MtGoxPanel.xib"; sourceTree = ""; }; + AA86BF2213A55C870089B39E /* TradeHillPanel.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = TradeHillPanel.xib; path = "TradeHillPanel.xib"; sourceTree = ""; }; + AA86BF2413A56A470089B39E /* MainPanelView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = MainPanelView.xib; path = "MainPanelView.xib"; sourceTree = ""; }; AAB399B1139B0D8500B9438F /* Wallet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Wallet.h; sourceTree = ""; }; AAB399B2139B0D8500B9438F /* Wallet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Wallet.m; sourceTree = ""; }; AAD172621399BA1D00B505B0 /* EMKeychainItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EMKeychainItem.h; sourceTree = ""; }; @@ -222,6 +236,8 @@ AA435C2D13A2E0860050F307 /* MtGoxMarketMenuView.m */, AA83B7AA13A30582000F71A6 /* TradeHillMarketMenuView.h */, AA83B7AB13A30582000F71A6 /* TradeHillMarketMenuView.m */, + AA1F92A213A52D7D00D5643C /* ToolbarTicker.h */, + AA1F92A313A52D7D00D5643C /* ToolbarTicker.m */, ); name = Models; sourceTree = ""; @@ -231,6 +247,10 @@ children = ( AA4BBA411379E31C005CE351 /* MainMenu.xib */, 837DF12C139B2E97009987F3 /* SettingsWindow.xib */, + AA1F929F13A526D600D5643C /* MainWindow.xib */, + AA86BF2013A55C0F0089B39E /* MtGoxPanel.xib */, + AA86BF2213A55C870089B39E /* TradeHillPanel.xib */, + AA86BF2413A56A470089B39E /* MainPanelView.xib */, ); name = Nibs; sourceTree = ""; @@ -248,6 +268,15 @@ name = Settings; sourceTree = ""; }; + AA1F92AB13A5331200D5643C /* MainWindow */ = { + isa = PBXGroup; + children = ( + AA86BF0C13A536250089B39E /* MainWindowController.h */, + AA86BF0D13A536250089B39E /* MainWindowController.m */, + ); + name = MainWindow; + sourceTree = ""; + }; AA4BBA1D1379E31B005CE351 = { isa = PBXGroup; children = ( @@ -298,6 +327,7 @@ 83405B20137F684E0060CAD4 /* Networking */, 83405B0D137F5D8A0060CAD4 /* JSON */, 837DF132139B3B8A009987F3 /* Settings */, + AA1F92AB13A5331200D5643C /* MainWindow */, AA435C0C13A2CB550050F307 /* MenuController.h */, AA435C0D13A2CB550050F307 /* MenuController.m */, AA4BBA3E1379E31C005CE351 /* BitTickerAppDelegate.h */, @@ -376,6 +406,10 @@ AAD64420137B680E00589EAC /* Logo.icns in Resources */, AA6E6C4D1399DC3E003A4224 /* Credits.html in Resources */, 837DF12D139B2E97009987F3 /* SettingsWindow.xib in Resources */, + AA1F92A013A526D600D5643C /* MainWindow.xib in Resources */, + AA86BF2113A55C100089B39E /* MtGoxPanel.xib in Resources */, + AA86BF2313A55C880089B39E /* TradeHillPanel.xib in Resources */, + AA86BF2513A56A470089B39E /* MainPanelView.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -415,6 +449,8 @@ AA435C2E13A2E0860050F307 /* MtGoxMarketMenuView.m in Sources */, AA83B79C13A2FF1C000F71A6 /* TradeHillMarket.m in Sources */, AA83B7AC13A30583000F71A6 /* TradeHillMarketMenuView.m in Sources */, + AA1F92A413A52D7D00D5643C /* ToolbarTicker.m in Sources */, + AA86BF0F13A536250089B39E /* MainWindowController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/BitTicker/BitTickerAppDelegate.h b/BitTicker/BitTickerAppDelegate.h index 49f952a..7de75be 100644 --- a/BitTicker/BitTickerAppDelegate.h +++ b/BitTicker/BitTickerAppDelegate.h @@ -37,10 +37,12 @@ NSMutableArray *stats; NSWindowController *settingsWindowController; + NSWindowController *mainWindowController; } - (void) quitProgram:(id)sender; - (IBAction)refreshTicker:(id)sender; +- (IBAction)showMainWindow:(id)sender; - (IBAction)showSettings:(id)sender; - (IBAction)showAbout:(id)sender; diff --git a/BitTicker/BitTickerAppDelegate.m b/BitTicker/BitTickerAppDelegate.m index dbbf057..7cf9f33 100644 --- a/BitTicker/BitTickerAppDelegate.m +++ b/BitTicker/BitTickerAppDelegate.m @@ -25,6 +25,8 @@ #import "SettingsWindowController.h" #import "SettingsWindow.h" +#import "MainWindowController.h" + @implementation BitTickerAppDelegate @synthesize stats; @@ -35,6 +37,8 @@ - (void)awakeFromNib { settingsWindowController = [[SettingsWindowController alloc] init]; + mainWindowController = [[MainWindowController alloc] init]; + menuController = [[MenuController alloc] init]; MSLog(@"Starting"); @@ -84,4 +88,9 @@ - (IBAction)showSettings:(id)sender { [NSApp activateIgnoringOtherApps:YES]; } +- (IBAction)showMainWindow:(id)sender { + [mainWindowController.window makeKeyAndOrderFront:nil]; + [NSApp activateIgnoringOtherApps:YES]; +} + @end diff --git a/BitTicker/MainPanelView.xib b/BitTicker/MainPanelView.xib new file mode 100644 index 0000000..d62424b --- /dev/null +++ b/BitTicker/MainPanelView.xib @@ -0,0 +1,213 @@ + + + + 1060 + 10J869 + 1306 + 1038.35 + 461.00 + + com.apple.InterfaceBuilder.CocoaPlugin + 1306 + + + YES + NSCustomView + NSCustomObject + + + YES + com.apple.InterfaceBuilder.CocoaPlugin + + + YES + + YES + + + + + YES + + MainWindowController + + + FirstResponder + + + NSApplication + + + + 268 + {650, 376} + + + + NSView + + + + + YES + + + _mainPanel + + + + 2 + + + + + YES + + 0 + + + + + + -2 + + + File's Owner + + + -1 + + + First Responder + + + -3 + + + Application + + + 1 + + + + + + + YES + + YES + -1.IBPluginDependency + -2.IBPluginDependency + -3.IBPluginDependency + 1.IBPluginDependency + 1.WindowOrigin + 1.editorWindowContentRectSynchronizationRect + + + YES + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + {628, 654} + {{357, 416}, {480, 272}} + + + + YES + + + + + + YES + + + + + 2 + + + + YES + + MainWindowController + NSWindowController + + YES + + YES + _mainPanel + _mtGoxPanel + _tradeHillPanel + mainView + marketListTable + panelController + + + YES + NSView + NSView + NSView + NSView + NSTableView + NSViewController + + + + YES + + YES + _mainPanel + _mtGoxPanel + _tradeHillPanel + mainView + marketListTable + panelController + + + YES + + _mainPanel + NSView + + + _mtGoxPanel + NSView + + + _tradeHillPanel + NSView + + + mainView + NSView + + + marketListTable + NSTableView + + + panelController + NSViewController + + + + + IBProjectSource + ./Classes/MainWindowController.h + + + + + 0 + IBCocoaFramework + + com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3 + + + YES + 3 + + diff --git a/BitTicker/MainWindow.xib b/BitTicker/MainWindow.xib new file mode 100644 index 0000000..e38d3a4 --- /dev/null +++ b/BitTicker/MainWindow.xib @@ -0,0 +1,599 @@ + + + + 1060 + 10J869 + 1306 + 1038.35 + 461.00 + + com.apple.InterfaceBuilder.CocoaPlugin + 1306 + + + YES + NSView + NSSplitView + NSWindowTemplate + NSScrollView + NSTableView + NSTextFieldCell + NSCustomView + NSToolbar + NSTableColumn + NSScroller + NSCustomObject + + + YES + com.apple.InterfaceBuilder.CocoaPlugin + + + YES + + YES + + + + + YES + + MainWindowController + + + FirstResponder + + + NSApplication + + + 4103 + 2 + {{491, 310}, {800, 400}} + 1618477056 + BitTicker + NSWindow + + + C12D5163-4A1D-43FC-B333-0D03CB3309F0 + + + YES + YES + YES + NO + 1 + 1 + + YES + + + + + YES + + + YES + + + YES + + + + + 256 + + YES + + + 274 + + YES + + + 256 + + YES + + + 274 + + YES + + + 2304 + + YES + + + 4396 + {149, 376} + + + + YES + + + -2147483392 + {{223.984, 0}, {16, 17}} + + + YES + + 146 + 40 + 1000 + + 75628096 + 2048 + + + LucidaGrande + 11 + 3100 + + + 3 + MC4zMzMzMzI5ODU2AA + + + 6 + System + headerTextColor + + 3 + MAA + + + + + 69336641 + 2112 + MtGox + + LucidaGrande + 13 + 1044 + + MtGox + + + 6 + System + controlBackgroundColor + + 3 + MC42NjY2NjY2NjY3AA + + + + 1 + MC4zMjI2NDYxMDM5IDAuNDA0Nzg0MjUxNSAwLjUzNTcxNDI4NTcAA + + + 3 + YES + YES + + + + 3 + 2 + + 1 + MC44ODkxOTc0ODgyIDAuODY2OTkyMDQ2NSAxAA + + + 6 + System + gridColor + + 3 + MC41AA + + + 17 + 314572800 + + + 0 + 15 + 0 + NO + 0 + + + {149, 376} + + + + + + 3 + MQA + + 4 + + + + -2147483392 + {{223.984, 17}, {15, 101.993}} + + + + + _doScroller: + 0.99734748010610075 + + + + -2147483392 + {{-100, -100}, {222.984, 15}} + + + + 1 + + _doScroller: + 0.61904761904761907 + + + {149, 376} + + + + 528 + + + + QSAAAEEgAABBmAAAQZgAAA + + + {149, 376} + + + + NSView + + + + 256 + {{150, 0}, {650, 376}} + + + + NSView + + + {{0, 24}, {800, 376}} + + + + YES + 2 + maindivider + + + {{7, 11}, {800, 400}} + + + + 2 + + {{0, 0}, {1280, 778}} + {1e+13, 1e+13} + NO + 24 + + + + + YES + + + window + + + + 54 + + + + mainView + + + + 77 + + + + delegate + + + + 87 + + + + dataSource + + + + 88 + + + + marketListTable + + + + 89 + + + + + YES + + 0 + + + + + + -2 + + + File's Owner + + + -1 + + + First Responder + + + -3 + + + Application + + + 1 + + + YES + + + + + Main Window - BitTicker + + + 2 + + + YES + + + + WindowView + + + 3 + + + YES + + + + + 64 + + + YES + + + + + + + 65 + + + YES + + + + + + 66 + + + YES + + + + + 81 + + + YES + + + + + + Scroll View - Table View + + + 82 + + + + + 83 + + + + + 84 + + + YES + + + + + + 85 + + + YES + + + + + + 86 + + + + + + + YES + + YES + -1.IBPluginDependency + -2.IBPluginDependency + -3.IBPluginDependency + 1.IBNSWindowAutoPositionCentersHorizontal + 1.IBNSWindowAutoPositionCentersVertical + 1.IBPluginDependency + 1.IBWindowTemplateEditedContentRect + 1.NSWindowTemplate.visibleAtLaunch + 1.WindowOrigin + 1.editorWindowContentRectSynchronizationRect + 2.IBPluginDependency + 3.IBPluginDependency + 64.IBPluginDependency + 65.IBPluginDependency + 66.IBPluginDependency + 81.IBPluginDependency + 82.IBPluginDependency + 83.IBPluginDependency + 84.IBPluginDependency + + + YES + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + + + com.apple.InterfaceBuilder.CocoaPlugin + {{357, 418}, {480, 270}} + + {196, 240} + {{357, 418}, {480, 270}} + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + + + + YES + + + + + + YES + + + + + 89 + + + + YES + + MainWindowController + NSWindowController + + YES + + YES + _mtGoxView + _tradeHillView + mainView + marketListTable + panelController + + + YES + NSView + NSView + NSView + NSTableView + NSViewController + + + + YES + + YES + _mtGoxView + _tradeHillView + mainView + marketListTable + panelController + + + YES + + _mtGoxView + NSView + + + _tradeHillView + NSView + + + mainView + NSView + + + marketListTable + NSTableView + + + panelController + NSViewController + + + + + IBProjectSource + ./Classes/MainWindowController.h + + + + + 0 + IBCocoaFramework + + com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3 + + + YES + 3 + + diff --git a/BitTicker/MainWindowController.h b/BitTicker/MainWindowController.h new file mode 100644 index 0000000..7ad1285 --- /dev/null +++ b/BitTicker/MainWindowController.h @@ -0,0 +1,32 @@ +// +// MainWindowController.h +// BitTicker +// +// Created by steve on 6/12/11. +// Copyright 2011 none. All rights reserved. +// + +#import + +@class SharedSettings; + +@interface MainWindowController : NSWindowController { +@private + SharedSettings *sharedSettings; + NSMutableDictionary *_viewDict; + IBOutlet NSTableView *marketListTable; + NSInteger selectedMarket; + NSView *_mainView; + IBOutlet NSView *_mtGoxPanel; + IBOutlet NSView *_tradeHillPanel; + IBOutlet NSView *_mainPanel; + NSView *_currentPanel; + IBOutlet NSViewController *panelController; +} + +@property (retain) NSMutableDictionary *viewDict; + +@property (retain) IBOutlet NSView *mainView; +@property (nonatomic,retain) NSTableView *marketListTable; +@property (retain) NSView *currentPanel; +@end diff --git a/BitTicker/MainWindowController.m b/BitTicker/MainWindowController.m new file mode 100644 index 0000000..070ab4b --- /dev/null +++ b/BitTicker/MainWindowController.m @@ -0,0 +1,91 @@ +// +// MainWindowController.m +// BitTicker +// +// Created by steve on 6/12/11. +// Copyright 2011 none. All rights reserved. +// + +#import "MainWindowController.h" + +#import "SharedSettings.h" + +#import "BitcoinMarket.h" +@implementation MainWindowController + +@synthesize mainView = _mainView; + +@synthesize currentPanel = _currentPanel; + +@synthesize marketListTable; + +@synthesize viewDict = _viewDict; + +- (id)init { + if (!(self=[super initWithWindowNibName:@"MainWindow"])) return self; + sharedSettings = [SharedSettings sharedSettingManager]; + panelController = [[NSViewController alloc] initWithNibName:@"PanelController" bundle:nil]; + [NSBundle loadNibNamed:@"MtGoxPanel" owner:self]; + [NSBundle loadNibNamed:@"TradeHillPanel" owner:self]; + [NSBundle loadNibNamed:@"MainPanelView" owner:self]; + self.viewDict = [NSMutableDictionary dictionaryWithCapacity:10]; + [self.viewDict setObject:_mtGoxPanel forKey:[sharedSettings stringForMarket:0]]; + [self.viewDict setObject:_tradeHillPanel forKey:[sharedSettings stringForMarket:1]]; + NSLog(@"%@",self.viewDict); + return self; +} + +- (void)windowDidLoad { + [super windowDidLoad]; + [self.marketListTable reloadData]; + NSIndexSet *firstMarket = [NSIndexSet indexSetWithIndex:0]; + [self.marketListTable selectRowIndexes:firstMarket byExtendingSelection:NO]; +} + +#pragma mark - Table view +- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView { + return eNumberOfMarkets; +} +- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex { + NSString *rowString = [sharedSettings stringForMarket:rowIndex]; + return rowString; + +} +- (void)tableViewSelectionDidChange:(NSNotification *)aNotification { + selectedMarket = [self.marketListTable selectedRow]; + NSString *string = [sharedSettings stringForMarket:selectedMarket]; + NSView *panel = [self.viewDict objectForKey:string]; + if (selectedMarket == -1) { + NSLog(@"Load Main Panel: %@",_mainPanel); + [self.mainView replaceSubview:self.currentPanel with:_mainPanel]; + self.currentPanel = _mainPanel; + } + else { + if (self.currentPanel) { + NSLog(@"Replace: %@",self.currentPanel); + [self.mainView replaceSubview:self.currentPanel with:panel]; + self.currentPanel = panel; + } + else { + NSLog(@"New: %@",panel); + [self.mainView addSubview:panel]; + self.currentPanel = panel; + } + } +} +// Customize table, it's pretty static. User doesn't need to interact. +- (BOOL)tableView:(NSTableView *)aTableView shouldEditTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex { + return NO; +} +- (BOOL)tableView:(NSTableView *)tableView shouldReorderColumn:(NSInteger)columnIndex toColumn:(NSInteger)newColumnIndex { + return NO; +} + +#pragma mark - Actions + +- (void)dealloc +{ + [super dealloc]; +} + +@end diff --git a/BitTicker/MenuController.h b/BitTicker/MenuController.h index 9d5158d..9f223e6 100644 --- a/BitTicker/MenuController.h +++ b/BitTicker/MenuController.h @@ -27,6 +27,7 @@ NSMenuItem *quitItem; NSMenuItem *aboutItem; NSMenuItem *settingsItem; + NSMenuItem *mainWindowItem; NSMenuItem *refreshItem; NSMenuItem *preferenceItem; diff --git a/BitTicker/MenuController.m b/BitTicker/MenuController.m index a9aa90c..4c0d30a 100644 --- a/BitTicker/MenuController.m +++ b/BitTicker/MenuController.m @@ -75,14 +75,15 @@ -(void)addSelectorItems { refreshItem = [trayMenu addItemWithTitle:@"Refresh" action:@selector(refreshTicker:) keyEquivalent:@"r"]; - aboutItem = [trayMenu addItemWithTitle: @"About" - action: @selector (showAbout:) - keyEquivalent: @"a"]; settingsItem = [trayMenu addItemWithTitle: @"Settings" action: @selector (showSettings:) keyEquivalent: @"s"]; - - + mainWindowItem = [trayMenu addItemWithTitle: @"Main Window" + action: @selector (showMainWindow:) + keyEquivalent: @"m"]; + aboutItem = [trayMenu addItemWithTitle: @"About" + action: @selector (showAbout:) + keyEquivalent: @"a"]; quitItem = [trayMenu addItemWithTitle: @"Quit" action: @selector (quitProgram:) keyEquivalent: @"q"]; diff --git a/BitTicker/MtGoxPanel.xib b/BitTicker/MtGoxPanel.xib new file mode 100644 index 0000000..ff030ea --- /dev/null +++ b/BitTicker/MtGoxPanel.xib @@ -0,0 +1,279 @@ + + + + 1060 + 10J869 + 1306 + 1038.35 + 461.00 + + com.apple.InterfaceBuilder.CocoaPlugin + 1306 + + + YES + NSCustomView + NSTextField + NSTextFieldCell + NSCustomObject + + + YES + com.apple.InterfaceBuilder.CocoaPlugin + + + YES + + YES + + + + + YES + + MainWindowController + + + FirstResponder + + + NSApplication + + + + 274 + + YES + + + 268 + {{17, 339}, {50, 17}} + + + + YES + + 68288064 + 272630784 + Mt Gox + + LucidaGrande + 13 + 1044 + + + + 6 + System + controlColor + + 3 + MC42NjY2NjY2NjY3AA + + + + 6 + System + controlTextColor + + 3 + MAA + + + + + + {650, 376} + + + + NSView + + + + + YES + + + _mtGoxPanel + + + + 5 + + + + + YES + + 0 + + + + + + -2 + + + File's Owner + + + -1 + + + First Responder + + + -3 + + + Application + + + 1 + + + YES + + + + Mt Gox Panel + + + 3 + + + YES + + + + + + 4 + + + + + + + YES + + YES + -1.IBPluginDependency + -2.IBPluginDependency + -3.IBPluginDependency + 1.IBPluginDependency + 1.WindowOrigin + 1.editorWindowContentRectSynchronizationRect + 3.IBPluginDependency + 4.IBPluginDependency + + + YES + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + {628, 654} + {{357, 416}, {480, 272}} + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + + + + YES + + + + + + YES + + + + + 5 + + + + YES + + MainWindowController + NSWindowController + + YES + + YES + _mainPanel + _mtGoxPanel + _tradeHillPanel + mainView + marketListTable + panelController + + + YES + NSView + NSView + NSView + NSView + NSTableView + NSViewController + + + + YES + + YES + _mainPanel + _mtGoxPanel + _tradeHillPanel + mainView + marketListTable + panelController + + + YES + + _mainPanel + NSView + + + _mtGoxPanel + NSView + + + _tradeHillPanel + NSView + + + mainView + NSView + + + marketListTable + NSTableView + + + panelController + NSViewController + + + + + IBProjectSource + ./Classes/MainWindowController.h + + + + + 0 + IBCocoaFramework + + com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3 + + + YES + 3 + + diff --git a/BitTicker/ToolbarTicker.h b/BitTicker/ToolbarTicker.h new file mode 100644 index 0000000..7b012a9 --- /dev/null +++ b/BitTicker/ToolbarTicker.h @@ -0,0 +1,17 @@ +// +// ToolbarTicker.h +// BitTicker +// +// Created by steve on 6/12/11. +// Copyright 2011 none. All rights reserved. +// + +#import + + +@interface ToolbarTicker : NSToolbarItem { +@private + +} + +@end diff --git a/BitTicker/ToolbarTicker.m b/BitTicker/ToolbarTicker.m new file mode 100644 index 0000000..0ed6693 --- /dev/null +++ b/BitTicker/ToolbarTicker.m @@ -0,0 +1,29 @@ +// +// ToolbarTicker.m +// BitTicker +// +// Created by steve on 6/12/11. +// Copyright 2011 none. All rights reserved. +// + +#import "ToolbarTicker.h" + + +@implementation ToolbarTicker + +- (id)init +{ + self = [super init]; + if (self) { + // Initialization code here. + } + + return self; +} + +- (void)dealloc +{ + [super dealloc]; +} + +@end diff --git a/BitTicker/TradeHillPanel.xib b/BitTicker/TradeHillPanel.xib new file mode 100644 index 0000000..b071d9d --- /dev/null +++ b/BitTicker/TradeHillPanel.xib @@ -0,0 +1,279 @@ + + + + 1060 + 10J869 + 1306 + 1038.35 + 461.00 + + com.apple.InterfaceBuilder.CocoaPlugin + 1306 + + + YES + NSCustomView + NSTextField + NSTextFieldCell + NSCustomObject + + + YES + com.apple.InterfaceBuilder.CocoaPlugin + + + YES + + YES + + + + + YES + + MainWindowController + + + FirstResponder + + + NSApplication + + + + 274 + + YES + + + 268 + {{17, 339}, {66, 17}} + + + + YES + + 68288064 + 272630784 + Trade Hill + + LucidaGrande + 13 + 1044 + + + + 6 + System + controlColor + + 3 + MC42NjY2NjY2NjY3AA + + + + 6 + System + controlTextColor + + 3 + MAA + + + + + + {650, 376} + + + + NSView + + + + + YES + + + _tradeHillPanel + + + + 5 + + + + + YES + + 0 + + + + + + -2 + + + File's Owner + + + -1 + + + First Responder + + + -3 + + + Application + + + 1 + + + YES + + + + Trade Hill Panel + + + 3 + + + YES + + + + + + 4 + + + + + + + YES + + YES + -1.IBPluginDependency + -2.IBPluginDependency + -3.IBPluginDependency + 1.IBPluginDependency + 1.WindowOrigin + 1.editorWindowContentRectSynchronizationRect + 3.IBPluginDependency + 4.IBPluginDependency + + + YES + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + {628, 654} + {{357, 416}, {480, 272}} + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + + + + YES + + + + + + YES + + + + + 5 + + + + YES + + MainWindowController + NSWindowController + + YES + + YES + _mainPanel + _mtGoxPanel + _tradeHillPanel + mainView + marketListTable + panelController + + + YES + NSView + NSView + NSView + NSView + NSTableView + NSViewController + + + + YES + + YES + _mainPanel + _mtGoxPanel + _tradeHillPanel + mainView + marketListTable + panelController + + + YES + + _mainPanel + NSView + + + _mtGoxPanel + NSView + + + _tradeHillPanel + NSView + + + mainView + NSView + + + marketListTable + NSTableView + + + panelController + NSViewController + + + + + IBProjectSource + ./Classes/MainWindowController.h + + + + + 0 + IBCocoaFramework + + com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3 + + + YES + 3 + + From d6bf438b03f663b4e6d2825ab69251e4c3b72775 Mon Sep 17 00:00:00 2001 From: mrsteveman1 Date: Sun, 12 Jun 2011 18:23:19 -0400 Subject: [PATCH 36/46] default panel for main window, normal text colors and method for correctly showing a highlighted table row --- BitTicker/MainWindow.xib | 21 ++++++++++++++------- BitTicker/MainWindowController.m | 19 +++++++++++++++++++ 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/BitTicker/MainWindow.xib b/BitTicker/MainWindow.xib index e38d3a4..394eb47 100644 --- a/BitTicker/MainWindow.xib +++ b/BitTicker/MainWindow.xib @@ -168,7 +168,7 @@ 1 - MC4zMjI2NDYxMDM5IDAuNDA0Nzg0MjUxNSAwLjUzNTcxNDI4NTcAA + MCAwIDAAA 3 @@ -531,8 +531,9 @@ YES YES - _mtGoxView - _tradeHillView + _mainPanel + _mtGoxPanel + _tradeHillPanel mainView marketListTable panelController @@ -542,6 +543,7 @@ NSView NSView NSView + NSView NSTableView NSViewController @@ -550,8 +552,9 @@ YES YES - _mtGoxView - _tradeHillView + _mainPanel + _mtGoxPanel + _tradeHillPanel mainView marketListTable panelController @@ -559,11 +562,15 @@ YES - _mtGoxView + _mainPanel + NSView + + + _mtGoxPanel NSView - _tradeHillView + _tradeHillPanel NSView diff --git a/BitTicker/MainWindowController.m b/BitTicker/MainWindowController.m index 070ab4b..49ff249 100644 --- a/BitTicker/MainWindowController.m +++ b/BitTicker/MainWindowController.m @@ -43,6 +43,8 @@ - (void)windowDidLoad { } #pragma mark - Table view + + - (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView { return eNumberOfMarkets; } @@ -51,6 +53,23 @@ - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColu return rowString; } + +- (void)tableView:(NSTableView *)aTableView willDisplayCell:(id)aCell forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex { + NSString *rowString = [sharedSettings stringForMarket:rowIndex]; + NSColor *txtColor; + if ([self.marketListTable selectedRow] == rowIndex) { + txtColor = [NSColor whiteColor]; + } + else { + txtColor = [NSColor blackColor]; + } + + NSFont *txtFont = [NSFont boldSystemFontOfSize:14]; + NSDictionary *txtDict = [NSDictionary dictionaryWithObjectsAndKeys: txtFont, NSFontAttributeName, txtColor, NSForegroundColorAttributeName, nil]; + NSAttributedString *attrStr = [[[NSAttributedString alloc] initWithString:rowString attributes:txtDict] autorelease]; + [aCell setAttributedStringValue:attrStr]; +} + - (void)tableViewSelectionDidChange:(NSNotification *)aNotification { selectedMarket = [self.marketListTable selectedRow]; NSString *string = [sharedSettings stringForMarket:selectedMarket]; From 2865a94502c2e4d629b11235c868a2727c7b4412 Mon Sep 17 00:00:00 2001 From: mrsteveman1 Date: Sun, 12 Jun 2011 22:34:23 -0400 Subject: [PATCH 37/46] notification system replaces the multi-level delegate, changes for the main window panels --- BitTicker.xcodeproj/project.pbxproj | 16 +- BitTicker/BitTickerAppDelegate.m | 4 +- BitTicker/BitcoinMarket.m | 1 - BitTicker/MainPanelView.xib | 10 +- BitTicker/MainWindowController.m | 6 +- BitTicker/MenuController.h | 3 +- BitTicker/MenuController.m | 21 +- BitTicker/MtGoxMarket.h | 6 +- BitTicker/MtGoxMarket.m | 14 +- BitTicker/MtGoxPanel.xib | 532 +++++++++++++++++++++++++++- BitTicker/Ticker.h | 3 + BitTicker/Ticker.m | 3 +- BitTicker/Trade.h | 5 +- BitTicker/Trade.m | 4 +- BitTicker/TradeHillMarket.h | 6 +- BitTicker/TradeHillMarket.m | 14 +- BitTicker/TradeHillPanel.xib | 78 +--- BitTicker/Wallet.h | 5 +- BitTicker/Wallet.m | 4 +- BitTicker/WindowPanel.h | 28 ++ BitTicker/WindowPanel.m | 71 ++++ 21 files changed, 701 insertions(+), 133 deletions(-) create mode 100644 BitTicker/WindowPanel.h create mode 100644 BitTicker/WindowPanel.m diff --git a/BitTicker.xcodeproj/project.pbxproj b/BitTicker.xcodeproj/project.pbxproj index e9250d2..66ecafd 100644 --- a/BitTicker.xcodeproj/project.pbxproj +++ b/BitTicker.xcodeproj/project.pbxproj @@ -19,6 +19,7 @@ 837DF135139B8E19009987F3 /* SettingsWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 837DF134139B8E18009987F3 /* SettingsWindowController.m */; }; AA1F92A013A526D600D5643C /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = AA1F929F13A526D600D5643C /* MainWindow.xib */; }; AA1F92A413A52D7D00D5643C /* ToolbarTicker.m in Sources */ = {isa = PBXBuildFile; fileRef = AA1F92A313A52D7D00D5643C /* ToolbarTicker.m */; }; + AA201E7B13A59C4D0063066E /* WindowPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = AA201E7A13A59C4D0063066E /* WindowPanel.m */; }; AA239F511379E67300150707 /* StatusItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = AA239F4E1379E67300150707 /* StatusItemView.m */; }; AA239F761379F17200150707 /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA239F731379F17200150707 /* CoreServices.framework */; }; AA239F771379F17200150707 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = AA239F741379F17200150707 /* libz.dylib */; }; @@ -81,6 +82,8 @@ AA1F929F13A526D600D5643C /* MainWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MainWindow.xib; sourceTree = ""; }; AA1F92A213A52D7D00D5643C /* ToolbarTicker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ToolbarTicker.h; sourceTree = ""; }; AA1F92A313A52D7D00D5643C /* ToolbarTicker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ToolbarTicker.m; sourceTree = ""; }; + AA201E7913A59C4D0063066E /* WindowPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = WindowPanel.h; path = "WindowPanel.h"; sourceTree = ""; }; + AA201E7A13A59C4D0063066E /* WindowPanel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = WindowPanel.m; path = "WindowPanel.m"; sourceTree = ""; }; AA239F4D1379E67300150707 /* StatusItemView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StatusItemView.h; sourceTree = ""; }; AA239F4E1379E67300150707 /* StatusItemView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StatusItemView.m; sourceTree = ""; }; AA239F731379F17200150707 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; @@ -132,11 +135,11 @@ AA83B79B13A2FF1C000F71A6 /* TradeHillMarket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TradeHillMarket.m; sourceTree = ""; }; AA83B7AA13A30582000F71A6 /* TradeHillMarketMenuView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TradeHillMarketMenuView.h; sourceTree = ""; }; AA83B7AB13A30582000F71A6 /* TradeHillMarketMenuView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TradeHillMarketMenuView.m; sourceTree = ""; }; - AA86BF0C13A536250089B39E /* MainWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MainWindowController.h; path = "MainWindowController.h"; sourceTree = ""; }; - AA86BF0D13A536250089B39E /* MainWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MainWindowController.m; path = "MainWindowController.m"; sourceTree = ""; }; - AA86BF2013A55C0F0089B39E /* MtGoxPanel.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = MtGoxPanel.xib; path = "MtGoxPanel.xib"; sourceTree = ""; }; - AA86BF2213A55C870089B39E /* TradeHillPanel.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = TradeHillPanel.xib; path = "TradeHillPanel.xib"; sourceTree = ""; }; - AA86BF2413A56A470089B39E /* MainPanelView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = MainPanelView.xib; path = "MainPanelView.xib"; sourceTree = ""; }; + AA86BF0C13A536250089B39E /* MainWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MainWindowController.h; sourceTree = ""; }; + AA86BF0D13A536250089B39E /* MainWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MainWindowController.m; sourceTree = ""; }; + AA86BF2013A55C0F0089B39E /* MtGoxPanel.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MtGoxPanel.xib; sourceTree = ""; }; + AA86BF2213A55C870089B39E /* TradeHillPanel.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TradeHillPanel.xib; sourceTree = ""; }; + AA86BF2413A56A470089B39E /* MainPanelView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MainPanelView.xib; sourceTree = ""; }; AAB399B1139B0D8500B9438F /* Wallet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Wallet.h; sourceTree = ""; }; AAB399B2139B0D8500B9438F /* Wallet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Wallet.m; sourceTree = ""; }; AAD172621399BA1D00B505B0 /* EMKeychainItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EMKeychainItem.h; sourceTree = ""; }; @@ -273,6 +276,8 @@ children = ( AA86BF0C13A536250089B39E /* MainWindowController.h */, AA86BF0D13A536250089B39E /* MainWindowController.m */, + AA201E7913A59C4D0063066E /* WindowPanel.h */, + AA201E7A13A59C4D0063066E /* WindowPanel.m */, ); name = MainWindow; sourceTree = ""; @@ -451,6 +456,7 @@ AA83B7AC13A30583000F71A6 /* TradeHillMarketMenuView.m in Sources */, AA1F92A413A52D7D00D5643C /* ToolbarTicker.m in Sources */, AA86BF0F13A536250089B39E /* MainWindowController.m in Sources */, + AA201E7B13A59C4D0063066E /* WindowPanel.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/BitTicker/BitTickerAppDelegate.m b/BitTicker/BitTickerAppDelegate.m index 7cf9f33..04673f9 100644 --- a/BitTicker/BitTickerAppDelegate.m +++ b/BitTicker/BitTickerAppDelegate.m @@ -42,14 +42,14 @@ - (void)awakeFromNib { menuController = [[MenuController alloc] init]; MSLog(@"Starting"); - market = [[MtGoxMarket alloc] initWithDelegate:menuController]; + market = [[MtGoxMarket alloc] init]; tickerTimer = [[NSTimer scheduledTimerWithTimeInterval:30 target:market selector:@selector(fetchTicker) userInfo:nil repeats:YES] retain]; walletTimer = [[NSTimer scheduledTimerWithTimeInterval:60 target:market selector:@selector(fetchWallet) userInfo:nil repeats:YES] retain]; [market fetchTicker]; [market fetchWallet]; [menuController createMenuForMarket:market]; - //tradehill = [[TradeHillMarket alloc] initWithDelegate:menuController]; + //tradehill = [[TradeHillMarket alloc] init]; //[menuController createMenuForMarket:tradehill]; [menuController addSelectorItems]; diff --git a/BitTicker/BitcoinMarket.m b/BitTicker/BitcoinMarket.m index ac91067..1f148a2 100644 --- a/BitTicker/BitcoinMarket.m +++ b/BitTicker/BitcoinMarket.m @@ -25,7 +25,6 @@ -(id)initWithDelegate:(id)delegate { requestHandler = [[RequestHandler alloc] initWithDelegate:self]; self.delegate = delegate; - _selectorMap = [[NSMutableDictionary alloc] init]; sharedSettingManager = [SharedSettings sharedSettingManager]; return self; diff --git a/BitTicker/MainPanelView.xib b/BitTicker/MainPanelView.xib index d62424b..f77fe7d 100644 --- a/BitTicker/MainPanelView.xib +++ b/BitTicker/MainPanelView.xib @@ -44,7 +44,7 @@ - NSView + WindowPanel @@ -199,6 +199,14 @@ ./Classes/MainWindowController.h + + WindowPanel + NSView + + IBProjectSource + ./Classes/WindowPanel.h + + 0 diff --git a/BitTicker/MainWindowController.m b/BitTicker/MainWindowController.m index 49ff249..57b6304 100644 --- a/BitTicker/MainWindowController.m +++ b/BitTicker/MainWindowController.m @@ -31,7 +31,11 @@ - (id)init { self.viewDict = [NSMutableDictionary dictionaryWithCapacity:10]; [self.viewDict setObject:_mtGoxPanel forKey:[sharedSettings stringForMarket:0]]; [self.viewDict setObject:_tradeHillPanel forKey:[sharedSettings stringForMarket:1]]; - NSLog(@"%@",self.viewDict); + + + [[NSNotificationCenter defaultCenter] addObserver:_mtGoxPanel selector:@selector(didReceiveTicker:) name:@"MtGox-Ticker" object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:_mtGoxPanel selector:@selector(didReceiveWallet:) name:@"MtGox-Wallet" object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:_tradeHillPanel selector:@selector(didReceiveTicker:) name:@"TradeHill-Ticker" object:nil]; return self; } diff --git a/BitTicker/MenuController.h b/BitTicker/MenuController.h index 9f223e6..f8aa1b4 100644 --- a/BitTicker/MenuController.h +++ b/BitTicker/MenuController.h @@ -7,13 +7,12 @@ // #import -#import "BitcoinMarketDelegate.h" #import "SharedSettings.h" @class StatusItemView; @class Ticker; @class BitcoinMarket; -@interface MenuController : NSObject { +@interface MenuController : NSObject { SharedSettings *sharedSettingManager; NSMenu *trayMenu; NSMutableDictionary *_viewDict; diff --git a/BitTicker/MenuController.m b/BitTicker/MenuController.m index 4c0d30a..0c364b1 100644 --- a/BitTicker/MenuController.m +++ b/BitTicker/MenuController.m @@ -51,6 +51,8 @@ - (id)init trayMenu = [[NSMenu alloc] initWithTitle:@"Ticker"]; [statusItemView setMenu:trayMenu]; self.currentMenuStop = 0; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveTicker:) name:@"MtGox-Ticker" object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveWallet:) name:@"MtGox-Wallet" object:nil]; } return self; @@ -99,16 +101,19 @@ - (void)dealloc { #pragma mark Bitcoin market delegate // A request failed for some reason, for example the API being down --(void)bitcoinMarket:(BitcoinMarket*)market requestFailedWithError:(NSError*)error { +-(void)requestFailedWithError:(NSNotification *)notification { + NSError *error = [notification object]; MSLog(@"Error: %@",error); } // Request wasn't formatted as expected --(void)bitcoinMarket:(BitcoinMarket*)market didReceiveInvalidResponse:(NSData*)data { +-(void)didReceiveInvalidResponse:(NSNotification *)notification { + NSData *data = [notification object]; MSLog(@"Invalid response: %@",data); } --(void)bitcoinMarket:(BitcoinMarket*)market didReceiveTicker:(Ticker*)ticker { +-(void)didReceiveTicker:(NSNotification *)notification { + Ticker *ticker = [notification object]; [statusItemView setTickerValue:ticker.last]; self.tickerValue = ticker.last; MSLog(@"Got mah ticker: %@",ticker); @@ -116,7 +121,7 @@ -(void)bitcoinMarket:(BitcoinMarket*)market didReceiveTicker:(Ticker*)ticker { NSNumberFormatter *volumeFormatter = [[NSNumberFormatter alloc] init]; volumeFormatter.numberStyle = NSNumberFormatterDecimalStyle; volumeFormatter.hasThousandSeparators = YES; - CustomMenuView *view = [self.viewDict objectForKey:NSStringFromClass( [market class] ) ] ; + CustomMenuView *view = [self.viewDict objectForKey:NSStringFromClass( [ticker.market class] ) ] ; [view setHigh:[currencyFormatter stringFromNumber:ticker.high]]; @@ -129,12 +134,14 @@ -(void)bitcoinMarket:(BitcoinMarket*)market didReceiveTicker:(Ticker*)ticker { [volumeFormatter release]; } --(void)bitcoinMarket:(BitcoinMarket*)market didReceiveRecentTradesData:(NSArray*)trades { +-(void)didReceiveRecentTradesData:(NSNotification *)notification { + NSArray *trades = [notification object]; } --(void)bitcoinMarket:(BitcoinMarket*)market didReceiveWallet:(Wallet*)wallet { - id view = [self.viewDict objectForKey:NSStringFromClass( [market class] ) ] ; +-(void)didReceiveWallet:(NSNotification *)notification { + Wallet *wallet = [notification object]; + id view = [self.viewDict objectForKey:NSStringFromClass( [wallet.market class] ) ] ; double btc = [wallet.btc doubleValue]; double usd = [wallet.usd doubleValue]; double last = [self.tickerValue doubleValue]; diff --git a/BitTicker/MtGoxMarket.h b/BitTicker/MtGoxMarket.h index 34a8a57..ac448ac 100644 --- a/BitTicker/MtGoxMarket.h +++ b/BitTicker/MtGoxMarket.h @@ -7,13 +7,13 @@ // #import - +#import "BitcoinMarketDelegate.h" #import "BitcoinMarket.h" -@interface MtGoxMarket : BitcoinMarket { +@interface MtGoxMarket : BitcoinMarket { } --(id)initWithDelegate:(id)delegate; +-(id)init; @end diff --git a/BitTicker/MtGoxMarket.m b/BitTicker/MtGoxMarket.m index ac43870..96968b9 100644 --- a/BitTicker/MtGoxMarket.m +++ b/BitTicker/MtGoxMarket.m @@ -24,8 +24,8 @@ -(NSString*)makeURLStringWithSuffix:(NSString*)suffix; @implementation MtGoxMarket --(id)initWithDelegate:(id)delegate { - if (!(self = [super initWithDelegate:delegate])) return self; +-(id)init { + if (!(self = [super initWithDelegate:self])) return self; _tickerURL = MTGOX_TICKER_URL; _tradeURL = MTGOX_TRADES_URL; _depthURL = MTGOX_MARKETDEPTH_URL; @@ -87,7 +87,7 @@ -(void)didFetchRecentTrades:(NSArray*)tradeData { [orderedTrades addObject:object]; } - [_delegate bitcoinMarket:self didReceiveRecentTradesData:orderedTrades]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"MtGox-Trades" object:orderedTrades]; } -(void)didFetchTickerData:(NSDictionary*)tickerData { @@ -100,20 +100,22 @@ -(void)didFetchTickerData:(NSDictionary*)tickerData { ticker.low = [tickerDict objectForKey:@"low"]; ticker.last = [tickerDict objectForKey:@"last"]; ticker.volume = [tickerDict objectForKey:@"vol"]; - - [_delegate bitcoinMarket:self didReceiveTicker:ticker]; + ticker.market = self; + [[NSNotificationCenter defaultCenter] postNotificationName:@"MtGox-Ticker" object:ticker]; [ticker release]; } -(void)didFetchMarketDepth:(NSDictionary*)marketDepth { MSLog(@"Got %i asks and %i bids",[[marketDepth objectForKey:@"asks"] count],[[marketDepth objectForKey:@"bids"] count]); + [[NSNotificationCenter defaultCenter] postNotificationName:@"MtGox-Depth" object:marketDepth]; } -(void)didFetchWallet:(NSDictionary *)dictwallet { Wallet *newwallet = [[Wallet alloc] init]; newwallet.btc = [dictwallet objectForKey:@"btcs"]; newwallet.usd = [dictwallet objectForKey:@"usds"]; - [_delegate bitcoinMarket:self didReceiveWallet:newwallet]; + newwallet.market = self; + [[NSNotificationCenter defaultCenter] postNotificationName:@"MtGox-Wallet" object:newwallet]; } @end diff --git a/BitTicker/MtGoxPanel.xib b/BitTicker/MtGoxPanel.xib index ff030ea..16e42ba 100644 --- a/BitTicker/MtGoxPanel.xib +++ b/BitTicker/MtGoxPanel.xib @@ -44,50 +44,252 @@ 274 YES - + 268 - {{17, 339}, {50, 17}} + {{60, 117}, {96, 22}} YES - - 68288064 + + -1804468671 272630784 - Mt Gox - + + LucidaGrande 13 1044 - - + + YES + 6 System - controlColor + textBackgroundColor 3 - MC42NjY2NjY2NjY3AA + MQA - + 6 System - controlTextColor - + textColor + 3 MAA + + + 268 + {{60, 149}, {96, 22}} + + + + YES + + -1804468671 + 272630784 + + + + YES + + + + + + + 268 + {{60, 181}, {96, 22}} + + + + YES + + -1804468671 + 272630784 + + + + YES + + + + + + + 268 + {{60, 213}, {96, 22}} + + + + YES + + -1804468671 + 272630784 + + + + YES + + + + + + + 268 + {{60, 245}, {96, 22}} + + + + YES + + -1804468671 + 272630784 + + + + YES + + + + + + + 268 + {{17, 120}, {38, 17}} + + + + YES + + 68288064 + 272630784 + Low: + + + + 6 + System + controlColor + + 3 + MC42NjY2NjY2NjY3AA + + + + 6 + System + controlTextColor + + + + + + + 268 + {{15, 152}, {42, 17}} + + + + YES + + 68288064 + 272630784 + High: + + + + + + + + + 268 + {{19, 184}, {38, 17}} + + + + YES + + 68288064 + 272630784 + Sell: + + + + + + + + + 268 + {{19, 216}, {38, 17}} + + + + YES + + 68288064 + 272630784 + Buy: + + + + + + + + + 268 + {{17, 248}, {38, 17}} + + + + YES + + 68288064 + 272630784 + Last: + + + + + + + + + 268 + {{17, 312}, {142, 44}} + + + + YES + + 68288064 + 272630784 + Mt Gox + + LucidaGrande + 36 + 16 + + + + + + {650, 376} - - NSView + + WindowPanel @@ -101,6 +303,46 @@ 5 + + + buyField + + + + 32 + + + + highField + + + + 33 + + + + lastField + + + + 34 + + + + lowField + + + + 35 + + + + sellField + + + + 36 + @@ -134,6 +376,16 @@ YES + + + + + + + + + + @@ -153,6 +405,146 @@ + + 7 + + + YES + + + + + + 8 + + + + + 9 + + + YES + + + + + + 10 + + + + + 11 + + + YES + + + + + + 12 + + + + + 13 + + + YES + + + + + + 14 + + + + + 15 + + + YES + + + + + + 16 + + + + + 17 + + + YES + + + + + + 18 + + + + + 19 + + + YES + + + + + + 20 + + + + + 21 + + + YES + + + + + + 22 + + + + + 23 + + + YES + + + + + + 24 + + + + + 25 + + + YES + + + + + + 26 + + + @@ -165,8 +557,28 @@ 1.IBPluginDependency 1.WindowOrigin 1.editorWindowContentRectSynchronizationRect + 10.IBPluginDependency + 11.IBPluginDependency + 12.IBPluginDependency + 13.IBPluginDependency + 14.IBPluginDependency + 15.IBPluginDependency + 16.IBPluginDependency + 17.IBPluginDependency + 18.IBPluginDependency + 19.IBPluginDependency + 20.IBPluginDependency + 21.IBPluginDependency + 22.IBPluginDependency + 23.IBPluginDependency + 24.IBPluginDependency + 25.IBPluginDependency + 26.IBPluginDependency 3.IBPluginDependency 4.IBPluginDependency + 7.IBPluginDependency + 8.IBPluginDependency + 9.IBPluginDependency YES @@ -178,6 +590,26 @@ {{357, 416}, {480, 272}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin @@ -192,7 +624,7 @@ - 5 + 36 @@ -265,6 +697,74 @@ ./Classes/MainWindowController.h + + WindowPanel + NSView + + YES + + YES + buyField + highField + label + lastField + lowField + sellField + + + YES + NSTextField + NSTextField + NSTextField + NSTextField + NSTextField + NSTextField + + + + YES + + YES + buyField + highField + label + lastField + lowField + sellField + + + YES + + buyField + NSTextField + + + highField + NSTextField + + + label + NSTextField + + + lastField + NSTextField + + + lowField + NSTextField + + + sellField + NSTextField + + + + + IBProjectSource + ./Classes/WindowPanel.h + + 0 diff --git a/BitTicker/Ticker.h b/BitTicker/Ticker.h index 6545a2d..d98785f 100644 --- a/BitTicker/Ticker.h +++ b/BitTicker/Ticker.h @@ -7,6 +7,7 @@ // #import +@class BitcoinMarket; @interface Ticker : NSObject { @@ -16,7 +17,9 @@ NSNumber *_buy; NSNumber *_sell; NSNumber *_last; + BitcoinMarket *_market; } +@property (retain) BitcoinMarket *market; @property (nonatomic, retain) NSNumber *high; @property (nonatomic, retain) NSNumber *low; @property (nonatomic, retain) NSNumber *volume; diff --git a/BitTicker/Ticker.m b/BitTicker/Ticker.m index b5244b6..4149235 100644 --- a/BitTicker/Ticker.m +++ b/BitTicker/Ticker.m @@ -7,7 +7,7 @@ // #import "Ticker.h" - +#import "BitcoinMarket.h" @implementation Ticker @synthesize high=_high; @@ -16,6 +16,7 @@ @implementation Ticker @synthesize buy=_buy; @synthesize sell=_sell; @synthesize last=_last; +@synthesize market=_market; -(NSString*)description { return [NSString stringWithFormat:@"",_last, _high, _low, _volume]; diff --git a/BitTicker/Trade.h b/BitTicker/Trade.h index c99a81d..616a9fb 100644 --- a/BitTicker/Trade.h +++ b/BitTicker/Trade.h @@ -7,15 +7,18 @@ // #import - +@class BitcoinMarket; @interface Trade : NSObject { NSInteger _tid; // Trade ID NSNumber *_price; // Price in USD NSNumber *_amount; // Amount bought/sold in BTC NSDate *_date; // Time/date that trade was fulfilled + BitcoinMarket *_market; } +@property (retain) BitcoinMarket *market; + @property (nonatomic) NSInteger tid; @property (nonatomic, retain) NSNumber *price; @property (nonatomic, retain) NSNumber *amount; diff --git a/BitTicker/Trade.m b/BitTicker/Trade.m index d7a1516..e240e36 100644 --- a/BitTicker/Trade.m +++ b/BitTicker/Trade.m @@ -7,7 +7,7 @@ // #import "Trade.h" - +#import "BitcoinMarket.h" @implementation Trade @@ -16,4 +16,6 @@ @implementation Trade @synthesize price=_price; @synthesize amount=_amount; +@synthesize market=_market; + @end diff --git a/BitTicker/TradeHillMarket.h b/BitTicker/TradeHillMarket.h index 6d8e5b1..9fcf126 100644 --- a/BitTicker/TradeHillMarket.h +++ b/BitTicker/TradeHillMarket.h @@ -10,10 +10,12 @@ #import "BitcoinMarket.h" -@interface TradeHillMarket : BitcoinMarket { +#import "BitcoinMarket.h" + +@interface TradeHillMarket : BitcoinMarket { } --(id)initWithDelegate:(id)delegate; +-(id)init; @end diff --git a/BitTicker/TradeHillMarket.m b/BitTicker/TradeHillMarket.m index 4f9288a..9726904 100644 --- a/BitTicker/TradeHillMarket.m +++ b/BitTicker/TradeHillMarket.m @@ -20,8 +20,8 @@ @implementation TradeHillMarket --(id)initWithDelegate:(id)delegate { - if (!(self = [super initWithDelegate:delegate])) return self; +-(id)init { + if (!(self = [super initWithDelegate:self])) return self; _tickerURL = TRADEHILL_TICKER_URL; _tradeURL = TRADEHILL_TRADES_URL; _depthURL = TRADEHILL_MARKETDEPTH_URL; @@ -82,8 +82,8 @@ -(void)didFetchRecentTrades:(NSArray*)tradeData { while ((object = [reverseEnumerator nextObject])) { [orderedTrades addObject:object]; } + [[NSNotificationCenter defaultCenter] postNotificationName:@"TradeHill-Trades" object:tradeData]; - [_delegate bitcoinMarket:self didReceiveRecentTradesData:orderedTrades]; } -(void)didFetchTickerData:(NSDictionary*)tickerData { @@ -97,8 +97,9 @@ -(void)didFetchTickerData:(NSDictionary*)tickerData { ticker.low = [tickerDict objectForKey:@"low"]; ticker.last = [tickerDict objectForKey:@"last"]; ticker.volume = [tickerDict objectForKey:@"vol"]; - - [_delegate bitcoinMarket:self didReceiveTicker:ticker]; + ticker.market = self; + [[NSNotificationCenter defaultCenter] postNotificationName:@"TradeHill-Ticker" object:ticker]; + [ticker release]; } @@ -112,7 +113,8 @@ -(void)didFetchWallet:(NSDictionary *)dictwallet { // these might need to be changed once their API is available newwallet.btc = [dictwallet objectForKey:@"btcs"]; newwallet.usd = [dictwallet objectForKey:@"usds"]; - [_delegate bitcoinMarket:self didReceiveWallet:newwallet]; + newwallet.market = self; + [[NSNotificationCenter defaultCenter] postNotificationName:@"TradeHill-Wallet" object:newwallet]; } diff --git a/BitTicker/TradeHillPanel.xib b/BitTicker/TradeHillPanel.xib index b071d9d..228577e 100644 --- a/BitTicker/TradeHillPanel.xib +++ b/BitTicker/TradeHillPanel.xib @@ -49,7 +49,6 @@ 268 {{17, 339}, {66, 17}} - YES @@ -85,9 +84,8 @@ {650, 376} - - NSView + WindowPanel @@ -194,79 +192,7 @@ 5 - - - YES - - MainWindowController - NSWindowController - - YES - - YES - _mainPanel - _mtGoxPanel - _tradeHillPanel - mainView - marketListTable - panelController - - - YES - NSView - NSView - NSView - NSView - NSTableView - NSViewController - - - - YES - - YES - _mainPanel - _mtGoxPanel - _tradeHillPanel - mainView - marketListTable - panelController - - - YES - - _mainPanel - NSView - - - _mtGoxPanel - NSView - - - _tradeHillPanel - NSView - - - mainView - NSView - - - marketListTable - NSTableView - - - panelController - NSViewController - - - - - IBProjectSource - ./Classes/MainWindowController.h - - - - + 0 IBCocoaFramework diff --git a/BitTicker/Wallet.h b/BitTicker/Wallet.h index 7b847dc..be41fa2 100644 --- a/BitTicker/Wallet.h +++ b/BitTicker/Wallet.h @@ -7,14 +7,17 @@ // #import - +@class BitcoinMarket; @interface Wallet : NSObject { NSInteger _mid; // Market ID, 0 = MtGox NSNumber *_btc; // Wallet BTC contents NSNumber *_usd; // Wallet USD contents + BitcoinMarket *_market; } +@property (retain) BitcoinMarket *market; + @property (nonatomic) NSInteger mid; @property (nonatomic, retain) NSNumber *btc; @property (nonatomic, retain) NSNumber *usd; diff --git a/BitTicker/Wallet.m b/BitTicker/Wallet.m index c98027d..f3bf79a 100644 --- a/BitTicker/Wallet.m +++ b/BitTicker/Wallet.m @@ -7,7 +7,7 @@ // #import "Wallet.h" - +#import "BitcoinMarket.h" @implementation Wallet @@ -15,4 +15,6 @@ @implementation Wallet @synthesize btc = _btc; @synthesize usd = _usd; +@synthesize market=_market; + @end diff --git a/BitTicker/WindowPanel.h b/BitTicker/WindowPanel.h new file mode 100644 index 0000000..bf0cd5e --- /dev/null +++ b/BitTicker/WindowPanel.h @@ -0,0 +1,28 @@ +// +// WindowPanel.h +// BitTicker +// +// Created by steve on 6/12/11. +// Copyright 2011 none. All rights reserved. +// + +#import +@class Ticker; +@class Wallet; + +@interface WindowPanel : NSView { +@private + IBOutlet NSTextField *label; + IBOutlet NSTextField *lastField; + IBOutlet NSTextField *buyField; + IBOutlet NSTextField *sellField; + IBOutlet NSTextField *highField; + IBOutlet NSTextField *lowField; + NSNumberFormatter *currencyFormatter; +} + +-(void)didReceiveTicker:(NSNotification *)notification; + +-(void)didReceiveWallet:(NSNotification *)notification; + +@end diff --git a/BitTicker/WindowPanel.m b/BitTicker/WindowPanel.m new file mode 100644 index 0000000..10cdf76 --- /dev/null +++ b/BitTicker/WindowPanel.m @@ -0,0 +1,71 @@ +// +// WindowPanel.m +// BitTicker +// +// Created by steve on 6/12/11. +// Copyright 2011 none. All rights reserved. +// + +#import "WindowPanel.h" +#import "Ticker.h" +#import "Wallet.h" + + +@implementation WindowPanel + +- (id)initWithFrame:(NSRect)frame +{ + self = [super initWithFrame:frame]; + if (self) { + currencyFormatter = [[NSNumberFormatter alloc] init]; + currencyFormatter.numberStyle = NSNumberFormatterCurrencyStyle; + currencyFormatter.currencyCode = @"USD"; // TODO: Base on market currency + currencyFormatter.thousandSeparator = @","; // TODO: Base on local seperator for currency + currencyFormatter.alwaysShowsDecimalSeparator = YES; + currencyFormatter.hasThousandSeparators = YES; + currencyFormatter.minimumFractionDigits = 4; // TODO: Configurable + } + + return self; +} + +-(void)didReceiveTicker:(NSNotification *)notification { + Ticker *ticker = [notification object]; + [lastField setStringValue:[currencyFormatter stringFromNumber:ticker.last]]; + [highField setStringValue:[currencyFormatter stringFromNumber:ticker.high]]; + [lowField setStringValue:[currencyFormatter stringFromNumber:ticker.low]]; + [buyField setStringValue:[currencyFormatter stringFromNumber:ticker.buy]]; + [sellField setStringValue:[currencyFormatter stringFromNumber:ticker.sell]]; + +} + +-(void)didReceiveWallet:(NSNotification *)notification { + Wallet *wallet = [notification object]; +} + + +// A request failed for some reason, for example the API being down +-(void)requestFailedWithError:(NSNotification *)notification { + NSError *error = [notification object]; +} + +// Request wasn't formatted as expected +-(void)didReceiveInvalidResponse:(NSNotification *)notification { + NSData *data = [notification object]; +} + +-(void)didReceiveRecentTradesData:(NSNotification *)notification { + NSArray *trades = [notification object]; +} + +- (void)dealloc +{ + [super dealloc]; +} + +- (void)drawRect:(NSRect)dirtyRect +{ + // Drawing code here. +} + +@end From 998b82538b2c562cb7844ea26e435590313044de Mon Sep 17 00:00:00 2001 From: mrsteveman1 Date: Mon, 13 Jun 2011 10:21:49 -0400 Subject: [PATCH 38/46] remove notification observers on dealloc --- BitTicker/MainWindowController.m | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/BitTicker/MainWindowController.m b/BitTicker/MainWindowController.m index 57b6304..3de0c45 100644 --- a/BitTicker/MainWindowController.m +++ b/BitTicker/MainWindowController.m @@ -106,8 +106,9 @@ - (BOOL)tableView:(NSTableView *)tableView shouldReorderColumn:(NSInteger)column #pragma mark - Actions -- (void)dealloc -{ +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:_mtGoxPanel]; + [[NSNotificationCenter defaultCenter] removeObserver:_tradeHillPanel]; [super dealloc]; } From 80616892f6b94987177387fdc7f928f641310d32 Mon Sep 17 00:00:00 2001 From: mrsteveman1 Date: Mon, 13 Jun 2011 13:30:13 -0400 Subject: [PATCH 39/46] make main window panel views obtain and release their own notification observers --- BitTicker.xcodeproj/project.pbxproj | 62 ++++++++--- BitTicker/MainWindowController.m | 8 +- BitTicker/MtGoxMarket.h | 4 +- BitTicker/MtGoxPanel.h | 17 +++ BitTicker/MtGoxPanel.m | 31 ++++++ BitTicker/MtGoxPanel.xib | 16 ++- BitTicker/TradeHillPanel.h | 17 +++ BitTicker/TradeHillPanel.m | 30 ++++++ BitTicker/TradeHillPanel.xib | 154 +++++++++++++++++++++++++++- BitTicker/WindowPanel.m | 4 +- 10 files changed, 309 insertions(+), 34 deletions(-) create mode 100644 BitTicker/MtGoxPanel.h create mode 100644 BitTicker/MtGoxPanel.m create mode 100644 BitTicker/TradeHillPanel.h create mode 100644 BitTicker/TradeHillPanel.m diff --git a/BitTicker.xcodeproj/project.pbxproj b/BitTicker.xcodeproj/project.pbxproj index 66ecafd..5962d06 100644 --- a/BitTicker.xcodeproj/project.pbxproj +++ b/BitTicker.xcodeproj/project.pbxproj @@ -51,6 +51,8 @@ AA86BF2113A55C100089B39E /* MtGoxPanel.xib in Resources */ = {isa = PBXBuildFile; fileRef = AA86BF2013A55C0F0089B39E /* MtGoxPanel.xib */; }; AA86BF2313A55C880089B39E /* TradeHillPanel.xib in Resources */ = {isa = PBXBuildFile; fileRef = AA86BF2213A55C870089B39E /* TradeHillPanel.xib */; }; AA86BF2513A56A470089B39E /* MainPanelView.xib in Resources */ = {isa = PBXBuildFile; fileRef = AA86BF2413A56A470089B39E /* MainPanelView.xib */; }; + AAAB154213A67E3D006C76FC /* MtGoxPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = AAAB154113A67E3D006C76FC /* MtGoxPanel.m */; }; + AAAB154513A67E4C006C76FC /* TradeHillPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = AAAB154413A67E4C006C76FC /* TradeHillPanel.m */; }; AAB399B3139B0D8500B9438F /* Wallet.m in Sources */ = {isa = PBXBuildFile; fileRef = AAB399B2139B0D8500B9438F /* Wallet.m */; }; AAD172641399BA1E00B505B0 /* EMKeychainItem.m in Sources */ = {isa = PBXBuildFile; fileRef = AAD172631399BA1D00B505B0 /* EMKeychainItem.m */; }; AAD1726D1399BD3500B505B0 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AAD1726C1399BD3500B505B0 /* Security.framework */; }; @@ -82,8 +84,8 @@ AA1F929F13A526D600D5643C /* MainWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MainWindow.xib; sourceTree = ""; }; AA1F92A213A52D7D00D5643C /* ToolbarTicker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ToolbarTicker.h; sourceTree = ""; }; AA1F92A313A52D7D00D5643C /* ToolbarTicker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ToolbarTicker.m; sourceTree = ""; }; - AA201E7913A59C4D0063066E /* WindowPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = WindowPanel.h; path = "WindowPanel.h"; sourceTree = ""; }; - AA201E7A13A59C4D0063066E /* WindowPanel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = WindowPanel.m; path = "WindowPanel.m"; sourceTree = ""; }; + AA201E7913A59C4D0063066E /* WindowPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WindowPanel.h; sourceTree = ""; }; + AA201E7A13A59C4D0063066E /* WindowPanel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WindowPanel.m; sourceTree = ""; }; AA239F4D1379E67300150707 /* StatusItemView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StatusItemView.h; sourceTree = ""; }; AA239F4E1379E67300150707 /* StatusItemView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StatusItemView.m; sourceTree = ""; }; AA239F731379F17200150707 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; @@ -140,6 +142,10 @@ AA86BF2013A55C0F0089B39E /* MtGoxPanel.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MtGoxPanel.xib; sourceTree = ""; }; AA86BF2213A55C870089B39E /* TradeHillPanel.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TradeHillPanel.xib; sourceTree = ""; }; AA86BF2413A56A470089B39E /* MainPanelView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MainPanelView.xib; sourceTree = ""; }; + AAAB154013A67E3D006C76FC /* MtGoxPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MtGoxPanel.h; path = "MtGoxPanel.h"; sourceTree = ""; }; + AAAB154113A67E3D006C76FC /* MtGoxPanel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MtGoxPanel.m; path = "MtGoxPanel.m"; sourceTree = ""; }; + AAAB154313A67E4C006C76FC /* TradeHillPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TradeHillPanel.h; path = "TradeHillPanel.h"; sourceTree = ""; }; + AAAB154413A67E4C006C76FC /* TradeHillPanel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TradeHillPanel.m; path = "TradeHillPanel.m"; sourceTree = ""; }; AAB399B1139B0D8500B9438F /* Wallet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Wallet.h; sourceTree = ""; }; AAB399B2139B0D8500B9438F /* Wallet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Wallet.m; sourceTree = ""; }; AAD172621399BA1D00B505B0 /* EMKeychainItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EMKeychainItem.h; sourceTree = ""; }; @@ -220,25 +226,10 @@ 83405B31137F6DDF0060CAD4 /* Trade.m */, 83405B32137F6DDF0060CAD4 /* Ticker.h */, 83405B33137F6DDF0060CAD4 /* Ticker.m */, - 83405B34137F6DDF0060CAD4 /* BitcoinMarketDelegate.h */, - 83405B35137F6DDF0060CAD4 /* BitcoinMarket.h */, - 83405B36137F6DDF0060CAD4 /* BitcoinMarket.m */, - 83405B37137F6DDF0060CAD4 /* MtGoxMarket.h */, - 83405B38137F6DDF0060CAD4 /* MtGoxMarket.m */, - AA83B79A13A2FF1C000F71A6 /* TradeHillMarket.h */, - AA83B79B13A2FF1C000F71A6 /* TradeHillMarket.m */, AAB399B1139B0D8500B9438F /* Wallet.h */, AAB399B2139B0D8500B9438F /* Wallet.m */, AAD172621399BA1D00B505B0 /* EMKeychainItem.h */, AAD172631399BA1D00B505B0 /* EMKeychainItem.m */, - AA239F4D1379E67300150707 /* StatusItemView.h */, - AA239F4E1379E67300150707 /* StatusItemView.m */, - AA435C1013A2CCA90050F307 /* CustomMenuView.h */, - AA435C1113A2CCA90050F307 /* CustomMenuView.m */, - AA435C2C13A2E0850050F307 /* MtGoxMarketMenuView.h */, - AA435C2D13A2E0860050F307 /* MtGoxMarketMenuView.m */, - AA83B7AA13A30582000F71A6 /* TradeHillMarketMenuView.h */, - AA83B7AB13A30582000F71A6 /* TradeHillMarketMenuView.m */, AA1F92A213A52D7D00D5643C /* ToolbarTicker.h */, AA1F92A313A52D7D00D5643C /* ToolbarTicker.m */, ); @@ -278,6 +269,10 @@ AA86BF0D13A536250089B39E /* MainWindowController.m */, AA201E7913A59C4D0063066E /* WindowPanel.h */, AA201E7A13A59C4D0063066E /* WindowPanel.m */, + AAAB154013A67E3D006C76FC /* MtGoxPanel.h */, + AAAB154113A67E3D006C76FC /* MtGoxPanel.m */, + AAAB154313A67E4C006C76FC /* TradeHillPanel.h */, + AAAB154413A67E4C006C76FC /* TradeHillPanel.m */, ); name = MainWindow; sourceTree = ""; @@ -327,12 +322,16 @@ AA4BBA321379E31B005CE351 /* BitTicker */ = { isa = PBXGroup; children = ( + AAAB153F13A67DDB006C76FC /* MenuViews */, + AAAB153C13A67DA0006C76FC /* Markets */, 83405B2F137F6CFE0060CAD4 /* Models */, 83405B2B137F68870060CAD4 /* Additions */, 83405B20137F684E0060CAD4 /* Networking */, 83405B0D137F5D8A0060CAD4 /* JSON */, 837DF132139B3B8A009987F3 /* Settings */, AA1F92AB13A5331200D5643C /* MainWindow */, + AA239F4D1379E67300150707 /* StatusItemView.h */, + AA239F4E1379E67300150707 /* StatusItemView.m */, AA435C0C13A2CB550050F307 /* MenuController.h */, AA435C0D13A2CB550050F307 /* MenuController.m */, AA4BBA3E1379E31C005CE351 /* BitTickerAppDelegate.h */, @@ -356,6 +355,33 @@ name = "Supporting Files"; sourceTree = ""; }; + AAAB153C13A67DA0006C76FC /* Markets */ = { + isa = PBXGroup; + children = ( + 83405B34137F6DDF0060CAD4 /* BitcoinMarketDelegate.h */, + 83405B35137F6DDF0060CAD4 /* BitcoinMarket.h */, + 83405B36137F6DDF0060CAD4 /* BitcoinMarket.m */, + 83405B37137F6DDF0060CAD4 /* MtGoxMarket.h */, + 83405B38137F6DDF0060CAD4 /* MtGoxMarket.m */, + AA83B79A13A2FF1C000F71A6 /* TradeHillMarket.h */, + AA83B79B13A2FF1C000F71A6 /* TradeHillMarket.m */, + ); + name = Markets; + sourceTree = ""; + }; + AAAB153F13A67DDB006C76FC /* MenuViews */ = { + isa = PBXGroup; + children = ( + AA435C1013A2CCA90050F307 /* CustomMenuView.h */, + AA435C1113A2CCA90050F307 /* CustomMenuView.m */, + AA435C2C13A2E0850050F307 /* MtGoxMarketMenuView.h */, + AA435C2D13A2E0860050F307 /* MtGoxMarketMenuView.m */, + AA83B7AA13A30582000F71A6 /* TradeHillMarketMenuView.h */, + AA83B7AB13A30582000F71A6 /* TradeHillMarketMenuView.m */, + ); + name = MenuViews; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -457,6 +483,8 @@ AA1F92A413A52D7D00D5643C /* ToolbarTicker.m in Sources */, AA86BF0F13A536250089B39E /* MainWindowController.m in Sources */, AA201E7B13A59C4D0063066E /* WindowPanel.m in Sources */, + AAAB154213A67E3D006C76FC /* MtGoxPanel.m in Sources */, + AAAB154513A67E4C006C76FC /* TradeHillPanel.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/BitTicker/MainWindowController.m b/BitTicker/MainWindowController.m index 3de0c45..e501ed4 100644 --- a/BitTicker/MainWindowController.m +++ b/BitTicker/MainWindowController.m @@ -32,11 +32,7 @@ - (id)init { [self.viewDict setObject:_mtGoxPanel forKey:[sharedSettings stringForMarket:0]]; [self.viewDict setObject:_tradeHillPanel forKey:[sharedSettings stringForMarket:1]]; - - [[NSNotificationCenter defaultCenter] addObserver:_mtGoxPanel selector:@selector(didReceiveTicker:) name:@"MtGox-Ticker" object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:_mtGoxPanel selector:@selector(didReceiveWallet:) name:@"MtGox-Wallet" object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:_tradeHillPanel selector:@selector(didReceiveTicker:) name:@"TradeHill-Ticker" object:nil]; - return self; + return self; } - (void)windowDidLoad { @@ -107,8 +103,6 @@ - (BOOL)tableView:(NSTableView *)tableView shouldReorderColumn:(NSInteger)column #pragma mark - Actions - (void)dealloc { - [[NSNotificationCenter defaultCenter] removeObserver:_mtGoxPanel]; - [[NSNotificationCenter defaultCenter] removeObserver:_tradeHillPanel]; [super dealloc]; } diff --git a/BitTicker/MtGoxMarket.h b/BitTicker/MtGoxMarket.h index ac448ac..d42f558 100644 --- a/BitTicker/MtGoxMarket.h +++ b/BitTicker/MtGoxMarket.h @@ -9,8 +9,8 @@ #import #import "BitcoinMarketDelegate.h" #import "BitcoinMarket.h" - -@interface MtGoxMarket : BitcoinMarket { + +@interface MtGoxMarket : BitcoinMarket { } diff --git a/BitTicker/MtGoxPanel.h b/BitTicker/MtGoxPanel.h new file mode 100644 index 0000000..c3dfa91 --- /dev/null +++ b/BitTicker/MtGoxPanel.h @@ -0,0 +1,17 @@ +// +// MtGoxPanel.h +// BitTicker +// +// Created by steve on 6/13/11. +// Copyright 2011 none. All rights reserved. +// + +#import +#import "WindowPanel.h" + +@interface MtGoxPanel : WindowPanel { +@private + +} + +@end diff --git a/BitTicker/MtGoxPanel.m b/BitTicker/MtGoxPanel.m new file mode 100644 index 0000000..a162e59 --- /dev/null +++ b/BitTicker/MtGoxPanel.m @@ -0,0 +1,31 @@ +// +// MtGoxPanel.m +// BitTicker +// +// Created by steve on 6/13/11. +// Copyright 2011 none. All rights reserved. +// + +#import "MtGoxPanel.h" + + +@implementation MtGoxPanel + +- (id)initWithFrame:(NSRect)frame +{ + self = [super initWithFrame:frame]; + if (self) { + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveTicker:) name:@"MtGox-Ticker" object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveWallet:) name:@"MtGox-Wallet" object:nil]; + + } + + return self; +} + +- (void)dealloc +{ + [super dealloc]; +} + +@end diff --git a/BitTicker/MtGoxPanel.xib b/BitTicker/MtGoxPanel.xib index 16e42ba..c01775a 100644 --- a/BitTicker/MtGoxPanel.xib +++ b/BitTicker/MtGoxPanel.xib @@ -250,7 +250,7 @@ {{17, 248}, {38, 17}} - + YES 68288064 @@ -268,7 +268,7 @@ {{17, 312}, {142, 44}} - + YES 68288064 @@ -288,8 +288,8 @@ {650, 376} - - WindowPanel + + MtGoxPanel @@ -697,6 +697,14 @@ ./Classes/MainWindowController.h + + MtGoxPanel + WindowPanel + + IBProjectSource + ./Classes/MtGoxPanel.h + + WindowPanel NSView diff --git a/BitTicker/TradeHillPanel.h b/BitTicker/TradeHillPanel.h new file mode 100644 index 0000000..75d1672 --- /dev/null +++ b/BitTicker/TradeHillPanel.h @@ -0,0 +1,17 @@ +// +// TradeHillPanel.h +// BitTicker +// +// Created by steve on 6/13/11. +// Copyright 2011 none. All rights reserved. +// + +#import +#import "WindowPanel.h" + +@interface TradeHillPanel : WindowPanel { +@private + +} + +@end diff --git a/BitTicker/TradeHillPanel.m b/BitTicker/TradeHillPanel.m new file mode 100644 index 0000000..1c194a8 --- /dev/null +++ b/BitTicker/TradeHillPanel.m @@ -0,0 +1,30 @@ +// +// TradeHillPanel.m +// BitTicker +// +// Created by steve on 6/13/11. +// Copyright 2011 none. All rights reserved. +// + +#import "TradeHillPanel.h" + + +@implementation TradeHillPanel + +- (id)initWithFrame:(NSRect)frame +{ + self = [super initWithFrame:frame]; + if (self) { + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveTicker:) name:@"TradeHill-Ticker" object:nil]; + + } + + return self; +} + +- (void)dealloc +{ + [super dealloc]; +} + +@end diff --git a/BitTicker/TradeHillPanel.xib b/BitTicker/TradeHillPanel.xib index 228577e..84fb343 100644 --- a/BitTicker/TradeHillPanel.xib +++ b/BitTicker/TradeHillPanel.xib @@ -49,6 +49,7 @@ 268 {{17, 339}, {66, 17}} + YES @@ -84,8 +85,9 @@ {650, 376} + - WindowPanel + TradeHillPanel @@ -192,7 +194,155 @@ 5 - + + + YES + + MainWindowController + NSWindowController + + YES + + YES + _mainPanel + _mtGoxPanel + _tradeHillPanel + mainView + marketListTable + panelController + + + YES + NSView + NSView + NSView + NSView + NSTableView + NSViewController + + + + YES + + YES + _mainPanel + _mtGoxPanel + _tradeHillPanel + mainView + marketListTable + panelController + + + YES + + _mainPanel + NSView + + + _mtGoxPanel + NSView + + + _tradeHillPanel + NSView + + + mainView + NSView + + + marketListTable + NSTableView + + + panelController + NSViewController + + + + + IBProjectSource + ./Classes/MainWindowController.h + + + + TradeHillPanel + WindowPanel + + IBProjectSource + ./Classes/TradeHillPanel.h + + + + WindowPanel + NSView + + YES + + YES + buyField + highField + label + lastField + lowField + sellField + + + YES + NSTextField + NSTextField + NSTextField + NSTextField + NSTextField + NSTextField + + + + YES + + YES + buyField + highField + label + lastField + lowField + sellField + + + YES + + buyField + NSTextField + + + highField + NSTextField + + + label + NSTextField + + + lastField + NSTextField + + + lowField + NSTextField + + + sellField + NSTextField + + + + + IBProjectSource + ./Classes/WindowPanel.h + + + + 0 IBCocoaFramework diff --git a/BitTicker/WindowPanel.m b/BitTicker/WindowPanel.m index 10cdf76..5b8cf0a 100644 --- a/BitTicker/WindowPanel.m +++ b/BitTicker/WindowPanel.m @@ -58,8 +58,8 @@ -(void)didReceiveRecentTradesData:(NSNotification *)notification { NSArray *trades = [notification object]; } -- (void)dealloc -{ +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; [super dealloc]; } From b783606b31b397666d1c79f0eaf844caae54c602 Mon Sep 17 00:00:00 2001 From: Mikko Sivulainen Date: Wed, 29 Jun 2011 01:37:21 +0300 Subject: [PATCH 40/46] fixed a typo in mtgox ticker handling --- BitTicker/MtGoxMarket.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BitTicker/MtGoxMarket.m b/BitTicker/MtGoxMarket.m index 96968b9..81afa9e 100644 --- a/BitTicker/MtGoxMarket.m +++ b/BitTicker/MtGoxMarket.m @@ -71,7 +71,7 @@ -(void)didFetchRecentTrades:(NSArray*)tradeData { newTrade.price = [tradeDict objectForKey:@"price"]; newTrade.tid = [[tradeDict objectForKey:@"tid"] intValue]; - newTrade.date = [NSDate dateWithTimeIntervalSince1970:[[tradeDict objectForKey:@"amount"] doubleValue]]; + newTrade.date = [NSDate dateWithTimeIntervalSince1970:[[tradeDict objectForKey:@"date"] doubleValue]]; [trades addObject:newTrade]; [newTrade release]; From a40758f962bbff8fe47ccf39442cb7c0f99301dd Mon Sep 17 00:00:00 2001 From: mrsteveman1 Date: Thu, 31 May 2012 22:18:11 -0400 Subject: [PATCH 41/46] nuclear bomb, dont use this code yet. License change to BSD --- .gitignore | 0 BitTicker.xcodeproj/project.pbxproj | 393 ++----- BitTicker/BitTicker-Info.plist | 12 +- BitTicker/BitTicker-Prefix.pch | 37 +- BitTicker/BitTicker.entitlements | 8 + BitTicker/BitTickerAppDelegate.h | 51 +- BitTicker/BitTickerAppDelegate.m | 91 +- BitTicker/BitcoinMarket.h | 58 - BitTicker/BitcoinMarket.m | 135 --- BitTicker/BitcoinMarketDelegate.h | 32 - BitTicker/Credits.html | 2 +- BitTicker/CustomMenuView.h | 31 - BitTicker/CustomMenuView.m | 100 -- BitTicker/Dropdown.h | 39 + BitTicker/Dropdown.m | 85 ++ BitTicker/EMKeychainItem.h | 175 --- BitTicker/EMKeychainItem.m | 540 --------- BitTicker/GraphView.h | 38 + BitTicker/GraphView.m | 273 +++++ BitTicker/JSON.h | 69 -- BitTicker/Logo.icns | Bin BitTicker/MainPanelView.xib | 221 ---- BitTicker/MainWindow.xib | 606 ---------- BitTicker/MainWindowController.h | 32 - BitTicker/MainWindowController.m | 109 -- BitTicker/MenuController.h | 44 - BitTicker/MenuController.m | 163 --- BitTicker/MtGox.h | 18 + BitTicker/MtGox.m | 69 ++ BitTicker/MtGoxMarket.h | 19 - BitTicker/MtGoxMarket.m | 121 -- BitTicker/MtGoxMarketMenuView.h | 28 - BitTicker/MtGoxMarketMenuView.m | 302 ----- BitTicker/MtGoxPanel.h | 17 - BitTicker/MtGoxPanel.m | 31 - BitTicker/MtGoxPanel.xib | 787 ------------- BitTicker/NSMutableArray+Shift.h | 15 + BitTicker/NSMutableArray+Shift.m | 19 + BitTicker/NSMutableDictionary+IntegerKeys.h | 16 - BitTicker/NSMutableDictionary+IntegerKeys.m | 24 - BitTicker/NSObject+JSON.h | 61 - BitTicker/NSObject+JSON.m | 60 - BitTicker/RequestHandler.h | 34 - BitTicker/RequestHandler.m | 103 -- BitTicker/RequestHandlerDelegate.h | 15 - BitTicker/SBJsonParser.h | 113 -- BitTicker/SBJsonParser.m | 120 -- BitTicker/SBJsonStreamParser.h | 135 --- BitTicker/SBJsonStreamParser.m | 273 ----- BitTicker/SBJsonStreamParserAdapter.h | 88 -- BitTicker/SBJsonStreamParserAdapter.m | 171 --- BitTicker/SBJsonStreamParserState.h | 89 -- BitTicker/SBJsonStreamParserState.m | 370 ------ BitTicker/SBJsonStreamWriter.h | 163 --- BitTicker/SBJsonStreamWriter.m | 372 ------ BitTicker/SBJsonStreamWriterState.h | 75 -- BitTicker/SBJsonStreamWriterState.m | 132 --- BitTicker/SBJsonTokeniser.h | 69 -- BitTicker/SBJsonTokeniser.m | 476 -------- BitTicker/SBJsonWriter.h | 132 --- BitTicker/SBJsonWriter.m | 102 -- BitTicker/SettingsWindow.h | 33 - BitTicker/SettingsWindow.m | 22 - BitTicker/SettingsWindow.xib | 975 ---------------- BitTicker/SettingsWindowController.h | 27 - BitTicker/SettingsWindowController.m | 100 -- BitTicker/SharedSettings.h | 33 - BitTicker/SharedSettings.m | 150 --- BitTicker/StatusItemView.h | 28 +- BitTicker/StatusItemView.m | 107 +- BitTicker/TaggedNSURLConnection.h | 17 - BitTicker/TaggedNSURLConnection.m | 14 - BitTicker/Ticker.h | 29 - BitTicker/Ticker.m | 25 - BitTicker/ToolbarTicker.h | 17 - BitTicker/ToolbarTicker.m | 29 - BitTicker/Trade.h | 27 - BitTicker/Trade.m | 21 - BitTicker/TradeHillMarket.h | 21 - BitTicker/TradeHillMarket.m | 126 -- BitTicker/TradeHillMarketMenuView.h | 27 - BitTicker/TradeHillMarketMenuView.m | 301 ----- BitTicker/TradeHillPanel.h | 17 - BitTicker/TradeHillPanel.m | 30 - BitTicker/TradeHillPanel.xib | 355 ------ BitTicker/Wallet.h | 25 - BitTicker/Wallet.m | 20 - BitTicker/WindowPanel.h | 28 - BitTicker/WindowPanel.m | 71 -- BitTicker/en.lproj/InfoPlist.strings | 22 +- BitTicker/en.lproj/MainMenu.xib | 1153 +++++++++++++++---- BitTicker/main.m | 20 +- LICENSE | 30 + README | 16 - README.Markdown | 1 + 95 files changed, 1713 insertions(+), 10167 deletions(-) mode change 100644 => 100755 .gitignore mode change 100644 => 100755 BitTicker.xcodeproj/project.pbxproj mode change 100644 => 100755 BitTicker/BitTicker-Info.plist mode change 100644 => 100755 BitTicker/BitTicker-Prefix.pch create mode 100644 BitTicker/BitTicker.entitlements mode change 100644 => 100755 BitTicker/BitTickerAppDelegate.h mode change 100644 => 100755 BitTicker/BitTickerAppDelegate.m delete mode 100644 BitTicker/BitcoinMarket.h delete mode 100644 BitTicker/BitcoinMarket.m delete mode 100644 BitTicker/BitcoinMarketDelegate.h mode change 100644 => 100755 BitTicker/Credits.html delete mode 100644 BitTicker/CustomMenuView.h delete mode 100644 BitTicker/CustomMenuView.m create mode 100755 BitTicker/Dropdown.h create mode 100755 BitTicker/Dropdown.m delete mode 100644 BitTicker/EMKeychainItem.h delete mode 100644 BitTicker/EMKeychainItem.m create mode 100755 BitTicker/GraphView.h create mode 100755 BitTicker/GraphView.m delete mode 100755 BitTicker/JSON.h mode change 100644 => 100755 BitTicker/Logo.icns delete mode 100644 BitTicker/MainPanelView.xib delete mode 100644 BitTicker/MainWindow.xib delete mode 100644 BitTicker/MainWindowController.h delete mode 100644 BitTicker/MainWindowController.m delete mode 100644 BitTicker/MenuController.h delete mode 100644 BitTicker/MenuController.m create mode 100755 BitTicker/MtGox.h create mode 100755 BitTicker/MtGox.m delete mode 100644 BitTicker/MtGoxMarket.h delete mode 100644 BitTicker/MtGoxMarket.m delete mode 100644 BitTicker/MtGoxMarketMenuView.h delete mode 100644 BitTicker/MtGoxMarketMenuView.m delete mode 100644 BitTicker/MtGoxPanel.h delete mode 100644 BitTicker/MtGoxPanel.m delete mode 100644 BitTicker/MtGoxPanel.xib create mode 100755 BitTicker/NSMutableArray+Shift.h create mode 100755 BitTicker/NSMutableArray+Shift.m delete mode 100644 BitTicker/NSMutableDictionary+IntegerKeys.h delete mode 100644 BitTicker/NSMutableDictionary+IntegerKeys.m delete mode 100755 BitTicker/NSObject+JSON.h delete mode 100755 BitTicker/NSObject+JSON.m delete mode 100644 BitTicker/RequestHandler.h delete mode 100644 BitTicker/RequestHandler.m delete mode 100644 BitTicker/RequestHandlerDelegate.h delete mode 100755 BitTicker/SBJsonParser.h delete mode 100755 BitTicker/SBJsonParser.m delete mode 100755 BitTicker/SBJsonStreamParser.h delete mode 100755 BitTicker/SBJsonStreamParser.m delete mode 100755 BitTicker/SBJsonStreamParserAdapter.h delete mode 100755 BitTicker/SBJsonStreamParserAdapter.m delete mode 100755 BitTicker/SBJsonStreamParserState.h delete mode 100755 BitTicker/SBJsonStreamParserState.m delete mode 100755 BitTicker/SBJsonStreamWriter.h delete mode 100755 BitTicker/SBJsonStreamWriter.m delete mode 100755 BitTicker/SBJsonStreamWriterState.h delete mode 100755 BitTicker/SBJsonStreamWriterState.m delete mode 100755 BitTicker/SBJsonTokeniser.h delete mode 100755 BitTicker/SBJsonTokeniser.m delete mode 100755 BitTicker/SBJsonWriter.h delete mode 100755 BitTicker/SBJsonWriter.m delete mode 100644 BitTicker/SettingsWindow.h delete mode 100644 BitTicker/SettingsWindow.m delete mode 100644 BitTicker/SettingsWindow.xib delete mode 100644 BitTicker/SettingsWindowController.h delete mode 100644 BitTicker/SettingsWindowController.m delete mode 100644 BitTicker/SharedSettings.h delete mode 100644 BitTicker/SharedSettings.m mode change 100644 => 100755 BitTicker/StatusItemView.h mode change 100644 => 100755 BitTicker/StatusItemView.m delete mode 100644 BitTicker/TaggedNSURLConnection.h delete mode 100644 BitTicker/TaggedNSURLConnection.m delete mode 100644 BitTicker/Ticker.h delete mode 100644 BitTicker/Ticker.m delete mode 100644 BitTicker/ToolbarTicker.h delete mode 100644 BitTicker/ToolbarTicker.m delete mode 100644 BitTicker/Trade.h delete mode 100644 BitTicker/Trade.m delete mode 100644 BitTicker/TradeHillMarket.h delete mode 100644 BitTicker/TradeHillMarket.m delete mode 100644 BitTicker/TradeHillMarketMenuView.h delete mode 100644 BitTicker/TradeHillMarketMenuView.m delete mode 100644 BitTicker/TradeHillPanel.h delete mode 100644 BitTicker/TradeHillPanel.m delete mode 100644 BitTicker/TradeHillPanel.xib delete mode 100644 BitTicker/Wallet.h delete mode 100644 BitTicker/Wallet.m delete mode 100644 BitTicker/WindowPanel.h delete mode 100644 BitTicker/WindowPanel.m mode change 100644 => 100755 BitTicker/en.lproj/InfoPlist.strings mode change 100644 => 100755 BitTicker/en.lproj/MainMenu.xib mode change 100644 => 100755 BitTicker/main.m create mode 100644 LICENSE delete mode 100644 README create mode 100755 README.Markdown diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/BitTicker.xcodeproj/project.pbxproj b/BitTicker.xcodeproj/project.pbxproj old mode 100644 new mode 100755 index 5962d06..4c4f136 --- a/BitTicker.xcodeproj/project.pbxproj +++ b/BitTicker.xcodeproj/project.pbxproj @@ -7,85 +7,52 @@ objects = { /* Begin PBXBuildFile section */ - 83405B28137F68610060CAD4 /* TaggedNSURLConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 83405B22137F68610060CAD4 /* TaggedNSURLConnection.m */; }; - 83405B29137F68610060CAD4 /* NSMutableDictionary+IntegerKeys.m in Sources */ = {isa = PBXBuildFile; fileRef = 83405B24137F68610060CAD4 /* NSMutableDictionary+IntegerKeys.m */; }; - 83405B2A137F68610060CAD4 /* RequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 83405B27137F68610060CAD4 /* RequestHandler.m */; }; - 83405B39137F6DDF0060CAD4 /* Trade.m in Sources */ = {isa = PBXBuildFile; fileRef = 83405B31137F6DDF0060CAD4 /* Trade.m */; }; - 83405B3A137F6DDF0060CAD4 /* Ticker.m in Sources */ = {isa = PBXBuildFile; fileRef = 83405B33137F6DDF0060CAD4 /* Ticker.m */; }; - 83405B3B137F6DDF0060CAD4 /* BitcoinMarket.m in Sources */ = {isa = PBXBuildFile; fileRef = 83405B36137F6DDF0060CAD4 /* BitcoinMarket.m */; }; - 83405B3C137F6DDF0060CAD4 /* MtGoxMarket.m in Sources */ = {isa = PBXBuildFile; fileRef = 83405B38137F6DDF0060CAD4 /* MtGoxMarket.m */; }; - 837DF12D139B2E97009987F3 /* SettingsWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 837DF12C139B2E97009987F3 /* SettingsWindow.xib */; }; - 837DF131139B319F009987F3 /* SettingsWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 837DF130139B319F009987F3 /* SettingsWindow.m */; }; - 837DF135139B8E19009987F3 /* SettingsWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 837DF134139B8E18009987F3 /* SettingsWindowController.m */; }; - AA1F92A013A526D600D5643C /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = AA1F929F13A526D600D5643C /* MainWindow.xib */; }; - AA1F92A413A52D7D00D5643C /* ToolbarTicker.m in Sources */ = {isa = PBXBuildFile; fileRef = AA1F92A313A52D7D00D5643C /* ToolbarTicker.m */; }; - AA201E7B13A59C4D0063066E /* WindowPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = AA201E7A13A59C4D0063066E /* WindowPanel.m */; }; + 83405B3C137F6DDF0060CAD4 /* MtGox.m in Sources */ = {isa = PBXBuildFile; fileRef = 83405B38137F6DDF0060CAD4 /* MtGox.m */; }; AA239F511379E67300150707 /* StatusItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = AA239F4E1379E67300150707 /* StatusItemView.m */; }; AA239F761379F17200150707 /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA239F731379F17200150707 /* CoreServices.framework */; }; AA239F771379F17200150707 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = AA239F741379F17200150707 /* libz.dylib */; }; AA239F781379F17200150707 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA239F751379F17200150707 /* SystemConfiguration.framework */; }; AA239F7B1379F1B200150707 /* Quartz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA239F791379F1B200150707 /* Quartz.framework */; }; AA239F7C1379F1B200150707 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA239F7A1379F1B200150707 /* QuartzCore.framework */; }; - AA435C0E13A2CB550050F307 /* MenuController.m in Sources */ = {isa = PBXBuildFile; fileRef = AA435C0D13A2CB550050F307 /* MenuController.m */; }; - AA435C1213A2CCAA0050F307 /* CustomMenuView.m in Sources */ = {isa = PBXBuildFile; fileRef = AA435C1113A2CCA90050F307 /* CustomMenuView.m */; }; - AA435C2E13A2E0860050F307 /* MtGoxMarketMenuView.m in Sources */ = {isa = PBXBuildFile; fileRef = AA435C2D13A2E0860050F307 /* MtGoxMarketMenuView.m */; }; + AA435C0E13A2CB550050F307 /* Dropdown.m in Sources */ = {isa = PBXBuildFile; fileRef = AA435C0D13A2CB550050F307 /* Dropdown.m */; }; AA4BBA2D1379E31B005CE351 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA4BBA2C1379E31B005CE351 /* Cocoa.framework */; }; AA4BBA371379E31C005CE351 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = AA4BBA351379E31C005CE351 /* InfoPlist.strings */; }; AA4BBA3A1379E31C005CE351 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = AA4BBA391379E31C005CE351 /* main.m */; }; AA4BBA401379E31C005CE351 /* BitTickerAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = AA4BBA3F1379E31C005CE351 /* BitTickerAppDelegate.m */; }; AA4BBA431379E31C005CE351 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = AA4BBA411379E31C005CE351 /* MainMenu.xib */; }; - AA6C46CF137E10B2000A1DCB /* NSObject+JSON.m in Sources */ = {isa = PBXBuildFile; fileRef = AA6C46BE137E10B2000A1DCB /* NSObject+JSON.m */; }; - AA6C46D0137E10B2000A1DCB /* SBJsonParser.m in Sources */ = {isa = PBXBuildFile; fileRef = AA6C46C0137E10B2000A1DCB /* SBJsonParser.m */; }; - AA6C46D1137E10B2000A1DCB /* SBJsonStreamParser.m in Sources */ = {isa = PBXBuildFile; fileRef = AA6C46C2137E10B2000A1DCB /* SBJsonStreamParser.m */; }; - AA6C46D2137E10B2000A1DCB /* SBJsonStreamParserAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = AA6C46C4137E10B2000A1DCB /* SBJsonStreamParserAdapter.m */; }; - AA6C46D3137E10B2000A1DCB /* SBJsonStreamParserState.m in Sources */ = {isa = PBXBuildFile; fileRef = AA6C46C6137E10B2000A1DCB /* SBJsonStreamParserState.m */; }; - AA6C46D4137E10B2000A1DCB /* SBJsonStreamWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = AA6C46C8137E10B2000A1DCB /* SBJsonStreamWriter.m */; }; - AA6C46D5137E10B2000A1DCB /* SBJsonStreamWriterState.m in Sources */ = {isa = PBXBuildFile; fileRef = AA6C46CA137E10B2000A1DCB /* SBJsonStreamWriterState.m */; }; - AA6C46D6137E10B2000A1DCB /* SBJsonTokeniser.m in Sources */ = {isa = PBXBuildFile; fileRef = AA6C46CC137E10B2000A1DCB /* SBJsonTokeniser.m */; }; - AA6C46D7137E10B2000A1DCB /* SBJsonWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = AA6C46CE137E10B2000A1DCB /* SBJsonWriter.m */; }; AA6E6C4D1399DC3E003A4224 /* Credits.html in Resources */ = {isa = PBXBuildFile; fileRef = AA6E6C4C1399DC3E003A4224 /* Credits.html */; }; - AA6E6C831399F2EB003A4224 /* SharedSettings.m in Sources */ = {isa = PBXBuildFile; fileRef = AA6E6C821399F2EB003A4224 /* SharedSettings.m */; }; - AA83B79C13A2FF1C000F71A6 /* TradeHillMarket.m in Sources */ = {isa = PBXBuildFile; fileRef = AA83B79B13A2FF1C000F71A6 /* TradeHillMarket.m */; }; - AA83B7AC13A30583000F71A6 /* TradeHillMarketMenuView.m in Sources */ = {isa = PBXBuildFile; fileRef = AA83B7AB13A30582000F71A6 /* TradeHillMarketMenuView.m */; }; - AA86BF0F13A536250089B39E /* MainWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = AA86BF0D13A536250089B39E /* MainWindowController.m */; }; - AA86BF2113A55C100089B39E /* MtGoxPanel.xib in Resources */ = {isa = PBXBuildFile; fileRef = AA86BF2013A55C0F0089B39E /* MtGoxPanel.xib */; }; - AA86BF2313A55C880089B39E /* TradeHillPanel.xib in Resources */ = {isa = PBXBuildFile; fileRef = AA86BF2213A55C870089B39E /* TradeHillPanel.xib */; }; - AA86BF2513A56A470089B39E /* MainPanelView.xib in Resources */ = {isa = PBXBuildFile; fileRef = AA86BF2413A56A470089B39E /* MainPanelView.xib */; }; - AAAB154213A67E3D006C76FC /* MtGoxPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = AAAB154113A67E3D006C76FC /* MtGoxPanel.m */; }; - AAAB154513A67E4C006C76FC /* TradeHillPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = AAAB154413A67E4C006C76FC /* TradeHillPanel.m */; }; - AAB399B3139B0D8500B9438F /* Wallet.m in Sources */ = {isa = PBXBuildFile; fileRef = AAB399B2139B0D8500B9438F /* Wallet.m */; }; - AAD172641399BA1E00B505B0 /* EMKeychainItem.m in Sources */ = {isa = PBXBuildFile; fileRef = AAD172631399BA1D00B505B0 /* EMKeychainItem.m */; }; + AA79F0A9152ADB0C002C3EEB /* GraphView.m in Sources */ = {isa = PBXBuildFile; fileRef = AA79F0A7152AD215002C3EEB /* GraphView.m */; }; + AAA1992715295E9500025EA2 /* CorePlot.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AAA1992615295E9500025EA2 /* CorePlot.framework */; }; + AAA1992C1529608E00025EA2 /* CorePlot.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = AAA1992615295E9500025EA2 /* CorePlot.framework */; }; + AAA19944152969E000025EA2 /* AFHTTPClient.m in Sources */ = {isa = PBXBuildFile; fileRef = AAA1992F152969E000025EA2 /* AFHTTPClient.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + AAA19945152969E000025EA2 /* AFHTTPRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = AAA19931152969E000025EA2 /* AFHTTPRequestOperation.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + AAA19946152969E000025EA2 /* AFImageCache.m in Sources */ = {isa = PBXBuildFile; fileRef = AAA19933152969E000025EA2 /* AFImageCache.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + AAA19947152969E000025EA2 /* AFImageRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = AAA19935152969E000025EA2 /* AFImageRequestOperation.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + AAA19948152969E000025EA2 /* AFJSONRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = AAA19937152969E000025EA2 /* AFJSONRequestOperation.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + AAA19949152969E000025EA2 /* AFNetworkActivityIndicatorManager.m in Sources */ = {isa = PBXBuildFile; fileRef = AAA1993A152969E000025EA2 /* AFNetworkActivityIndicatorManager.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + AAA1994A152969E000025EA2 /* AFPropertyListRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = AAA1993D152969E000025EA2 /* AFPropertyListRequestOperation.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + AAA1994B152969E000025EA2 /* AFURLConnectionOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = AAA1993F152969E000025EA2 /* AFURLConnectionOperation.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + AAA1994C152969E000025EA2 /* AFXMLRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = AAA19941152969E000025EA2 /* AFXMLRequestOperation.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; AAD1726D1399BD3500B505B0 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AAD1726C1399BD3500B505B0 /* Security.framework */; }; AAD64420137B680E00589EAC /* Logo.icns in Resources */ = {isa = PBXBuildFile; fileRef = AAD6441F137B680E00589EAC /* Logo.icns */; }; /* End PBXBuildFile section */ +/* Begin PBXCopyFilesBuildPhase section */ + AAAB15C113A96DF1006C76FC /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + AAA1992C1529608E00025EA2 /* CorePlot.framework in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ - 83405B21137F68610060CAD4 /* TaggedNSURLConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TaggedNSURLConnection.h; sourceTree = ""; }; - 83405B22137F68610060CAD4 /* TaggedNSURLConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TaggedNSURLConnection.m; sourceTree = ""; }; - 83405B23137F68610060CAD4 /* NSMutableDictionary+IntegerKeys.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSMutableDictionary+IntegerKeys.h"; sourceTree = ""; }; - 83405B24137F68610060CAD4 /* NSMutableDictionary+IntegerKeys.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSMutableDictionary+IntegerKeys.m"; sourceTree = ""; }; - 83405B25137F68610060CAD4 /* RequestHandlerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RequestHandlerDelegate.h; sourceTree = ""; }; - 83405B26137F68610060CAD4 /* RequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RequestHandler.h; sourceTree = ""; }; - 83405B27137F68610060CAD4 /* RequestHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RequestHandler.m; sourceTree = ""; }; - 83405B30137F6DDF0060CAD4 /* Trade.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Trade.h; sourceTree = ""; }; - 83405B31137F6DDF0060CAD4 /* Trade.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Trade.m; sourceTree = ""; }; - 83405B32137F6DDF0060CAD4 /* Ticker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Ticker.h; sourceTree = ""; }; - 83405B33137F6DDF0060CAD4 /* Ticker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Ticker.m; sourceTree = ""; }; - 83405B34137F6DDF0060CAD4 /* BitcoinMarketDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BitcoinMarketDelegate.h; sourceTree = ""; }; - 83405B35137F6DDF0060CAD4 /* BitcoinMarket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BitcoinMarket.h; sourceTree = ""; }; - 83405B36137F6DDF0060CAD4 /* BitcoinMarket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BitcoinMarket.m; sourceTree = ""; }; - 83405B37137F6DDF0060CAD4 /* MtGoxMarket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MtGoxMarket.h; sourceTree = ""; }; - 83405B38137F6DDF0060CAD4 /* MtGoxMarket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MtGoxMarket.m; sourceTree = ""; }; - 837DF12C139B2E97009987F3 /* SettingsWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SettingsWindow.xib; sourceTree = ""; }; - 837DF12F139B319E009987F3 /* SettingsWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SettingsWindow.h; sourceTree = ""; }; - 837DF130139B319F009987F3 /* SettingsWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SettingsWindow.m; sourceTree = ""; }; - 837DF133139B8E18009987F3 /* SettingsWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SettingsWindowController.h; sourceTree = ""; }; - 837DF134139B8E18009987F3 /* SettingsWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SettingsWindowController.m; sourceTree = ""; }; - AA1F929F13A526D600D5643C /* MainWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MainWindow.xib; sourceTree = ""; }; - AA1F92A213A52D7D00D5643C /* ToolbarTicker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ToolbarTicker.h; sourceTree = ""; }; - AA1F92A313A52D7D00D5643C /* ToolbarTicker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ToolbarTicker.m; sourceTree = ""; }; - AA201E7913A59C4D0063066E /* WindowPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WindowPanel.h; sourceTree = ""; }; - AA201E7A13A59C4D0063066E /* WindowPanel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WindowPanel.m; sourceTree = ""; }; + 83405B37137F6DDF0060CAD4 /* MtGox.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MtGox.h; sourceTree = ""; }; + 83405B38137F6DDF0060CAD4 /* MtGox.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MtGox.m; sourceTree = ""; }; AA239F4D1379E67300150707 /* StatusItemView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StatusItemView.h; sourceTree = ""; }; AA239F4E1379E67300150707 /* StatusItemView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StatusItemView.m; sourceTree = ""; }; AA239F731379F17200150707 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; @@ -93,12 +60,8 @@ AA239F751379F17200150707 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; AA239F791379F1B200150707 /* Quartz.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quartz.framework; path = System/Library/Frameworks/Quartz.framework; sourceTree = SDKROOT; }; AA239F7A1379F1B200150707 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; - AA435C0C13A2CB550050F307 /* MenuController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MenuController.h; sourceTree = ""; }; - AA435C0D13A2CB550050F307 /* MenuController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MenuController.m; sourceTree = ""; }; - AA435C1013A2CCA90050F307 /* CustomMenuView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CustomMenuView.h; sourceTree = ""; }; - AA435C1113A2CCA90050F307 /* CustomMenuView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CustomMenuView.m; sourceTree = ""; }; - AA435C2C13A2E0850050F307 /* MtGoxMarketMenuView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MtGoxMarketMenuView.h; sourceTree = ""; }; - AA435C2D13A2E0860050F307 /* MtGoxMarketMenuView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MtGoxMarketMenuView.m; sourceTree = ""; }; + AA435C0C13A2CB550050F307 /* Dropdown.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Dropdown.h; sourceTree = ""; }; + AA435C0D13A2CB550050F307 /* Dropdown.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Dropdown.m; sourceTree = ""; }; AA4BBA281379E31B005CE351 /* BitTicker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BitTicker.app; sourceTree = BUILT_PRODUCTS_DIR; }; AA4BBA2C1379E31B005CE351 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; AA4BBA2F1379E31B005CE351 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; @@ -111,45 +74,31 @@ AA4BBA3E1379E31C005CE351 /* BitTickerAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BitTickerAppDelegate.h; sourceTree = ""; }; AA4BBA3F1379E31C005CE351 /* BitTickerAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BitTickerAppDelegate.m; sourceTree = ""; }; AA4BBA421379E31C005CE351 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/MainMenu.xib; sourceTree = ""; }; - AA6C46BC137E10B2000A1DCB /* JSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSON.h; sourceTree = ""; }; - AA6C46BD137E10B2000A1DCB /* NSObject+JSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+JSON.h"; sourceTree = ""; }; - AA6C46BE137E10B2000A1DCB /* NSObject+JSON.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+JSON.m"; sourceTree = ""; }; - AA6C46BF137E10B2000A1DCB /* SBJsonParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonParser.h; sourceTree = ""; }; - AA6C46C0137E10B2000A1DCB /* SBJsonParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonParser.m; sourceTree = ""; }; - AA6C46C1137E10B2000A1DCB /* SBJsonStreamParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonStreamParser.h; sourceTree = ""; }; - AA6C46C2137E10B2000A1DCB /* SBJsonStreamParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonStreamParser.m; sourceTree = ""; }; - AA6C46C3137E10B2000A1DCB /* SBJsonStreamParserAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonStreamParserAdapter.h; sourceTree = ""; }; - AA6C46C4137E10B2000A1DCB /* SBJsonStreamParserAdapter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonStreamParserAdapter.m; sourceTree = ""; }; - AA6C46C5137E10B2000A1DCB /* SBJsonStreamParserState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonStreamParserState.h; sourceTree = ""; }; - AA6C46C6137E10B2000A1DCB /* SBJsonStreamParserState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonStreamParserState.m; sourceTree = ""; }; - AA6C46C7137E10B2000A1DCB /* SBJsonStreamWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonStreamWriter.h; sourceTree = ""; }; - AA6C46C8137E10B2000A1DCB /* SBJsonStreamWriter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonStreamWriter.m; sourceTree = ""; }; - AA6C46C9137E10B2000A1DCB /* SBJsonStreamWriterState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonStreamWriterState.h; sourceTree = ""; }; - AA6C46CA137E10B2000A1DCB /* SBJsonStreamWriterState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonStreamWriterState.m; sourceTree = ""; }; - AA6C46CB137E10B2000A1DCB /* SBJsonTokeniser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonTokeniser.h; sourceTree = ""; }; - AA6C46CC137E10B2000A1DCB /* SBJsonTokeniser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonTokeniser.m; sourceTree = ""; }; - AA6C46CD137E10B2000A1DCB /* SBJsonWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonWriter.h; sourceTree = ""; }; - AA6C46CE137E10B2000A1DCB /* SBJsonWriter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonWriter.m; sourceTree = ""; }; AA6E6C4C1399DC3E003A4224 /* Credits.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = Credits.html; sourceTree = ""; }; - AA6E6C811399F2EB003A4224 /* SharedSettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SharedSettings.h; sourceTree = ""; }; - AA6E6C821399F2EB003A4224 /* SharedSettings.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SharedSettings.m; sourceTree = ""; }; - AA83B79A13A2FF1C000F71A6 /* TradeHillMarket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TradeHillMarket.h; sourceTree = ""; }; - AA83B79B13A2FF1C000F71A6 /* TradeHillMarket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TradeHillMarket.m; sourceTree = ""; }; - AA83B7AA13A30582000F71A6 /* TradeHillMarketMenuView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TradeHillMarketMenuView.h; sourceTree = ""; }; - AA83B7AB13A30582000F71A6 /* TradeHillMarketMenuView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TradeHillMarketMenuView.m; sourceTree = ""; }; - AA86BF0C13A536250089B39E /* MainWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MainWindowController.h; sourceTree = ""; }; - AA86BF0D13A536250089B39E /* MainWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MainWindowController.m; sourceTree = ""; }; - AA86BF2013A55C0F0089B39E /* MtGoxPanel.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MtGoxPanel.xib; sourceTree = ""; }; - AA86BF2213A55C870089B39E /* TradeHillPanel.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TradeHillPanel.xib; sourceTree = ""; }; - AA86BF2413A56A470089B39E /* MainPanelView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MainPanelView.xib; sourceTree = ""; }; - AAAB154013A67E3D006C76FC /* MtGoxPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MtGoxPanel.h; path = "MtGoxPanel.h"; sourceTree = ""; }; - AAAB154113A67E3D006C76FC /* MtGoxPanel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MtGoxPanel.m; path = "MtGoxPanel.m"; sourceTree = ""; }; - AAAB154313A67E4C006C76FC /* TradeHillPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TradeHillPanel.h; path = "TradeHillPanel.h"; sourceTree = ""; }; - AAAB154413A67E4C006C76FC /* TradeHillPanel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TradeHillPanel.m; path = "TradeHillPanel.m"; sourceTree = ""; }; - AAB399B1139B0D8500B9438F /* Wallet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Wallet.h; sourceTree = ""; }; - AAB399B2139B0D8500B9438F /* Wallet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Wallet.m; sourceTree = ""; }; - AAD172621399BA1D00B505B0 /* EMKeychainItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EMKeychainItem.h; sourceTree = ""; }; - AAD172631399BA1D00B505B0 /* EMKeychainItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EMKeychainItem.m; sourceTree = ""; }; + AA79F0A6152AD215002C3EEB /* GraphView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GraphView.h; sourceTree = ""; }; + AA79F0A7152AD215002C3EEB /* GraphView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GraphView.m; sourceTree = ""; }; + AA79F0A8152AD664002C3EEB /* BitTicker.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = BitTicker.entitlements; sourceTree = ""; }; + AAA1992615295E9500025EA2 /* CorePlot.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CorePlot.framework; path = ../../Libraries/CorePlot_1.0/Binaries/MacOS/CorePlot.framework; sourceTree = ""; }; + AAA1992E152969E000025EA2 /* AFHTTPClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFHTTPClient.h; sourceTree = ""; }; + AAA1992F152969E000025EA2 /* AFHTTPClient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFHTTPClient.m; sourceTree = ""; }; + AAA19930152969E000025EA2 /* AFHTTPRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFHTTPRequestOperation.h; sourceTree = ""; }; + AAA19931152969E000025EA2 /* AFHTTPRequestOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFHTTPRequestOperation.m; sourceTree = ""; }; + AAA19932152969E000025EA2 /* AFImageCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFImageCache.h; sourceTree = ""; }; + AAA19933152969E000025EA2 /* AFImageCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFImageCache.m; sourceTree = ""; }; + AAA19934152969E000025EA2 /* AFImageRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFImageRequestOperation.h; sourceTree = ""; }; + AAA19935152969E000025EA2 /* AFImageRequestOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFImageRequestOperation.m; sourceTree = ""; }; + AAA19936152969E000025EA2 /* AFJSONRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFJSONRequestOperation.h; sourceTree = ""; }; + AAA19937152969E000025EA2 /* AFJSONRequestOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFJSONRequestOperation.m; sourceTree = ""; }; + AAA19938152969E000025EA2 /* AFJSONUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFJSONUtilities.h; sourceTree = ""; }; + AAA19939152969E000025EA2 /* AFNetworkActivityIndicatorManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFNetworkActivityIndicatorManager.h; sourceTree = ""; }; + AAA1993A152969E000025EA2 /* AFNetworkActivityIndicatorManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFNetworkActivityIndicatorManager.m; sourceTree = ""; }; + AAA1993B152969E000025EA2 /* AFNetworking.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFNetworking.h; sourceTree = ""; }; + AAA1993C152969E000025EA2 /* AFPropertyListRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFPropertyListRequestOperation.h; sourceTree = ""; }; + AAA1993D152969E000025EA2 /* AFPropertyListRequestOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFPropertyListRequestOperation.m; sourceTree = ""; }; + AAA1993E152969E000025EA2 /* AFURLConnectionOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFURLConnectionOperation.h; sourceTree = ""; }; + AAA1993F152969E000025EA2 /* AFURLConnectionOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFURLConnectionOperation.m; sourceTree = ""; }; + AAA19940152969E000025EA2 /* AFXMLRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFXMLRequestOperation.h; sourceTree = ""; }; + AAA19941152969E000025EA2 /* AFXMLRequestOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFXMLRequestOperation.m; sourceTree = ""; }; AAD1726C1399BD3500B505B0 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; AAD6441F137B680E00589EAC /* Logo.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = Logo.icns; sourceTree = ""; }; /* End PBXFileReference section */ @@ -166,117 +115,21 @@ AA239F771379F17200150707 /* libz.dylib in Frameworks */, AA239F781379F17200150707 /* SystemConfiguration.framework in Frameworks */, AA4BBA2D1379E31B005CE351 /* Cocoa.framework in Frameworks */, + AAA1992715295E9500025EA2 /* CorePlot.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 83405B0D137F5D8A0060CAD4 /* JSON */ = { - isa = PBXGroup; - children = ( - AA6C46BC137E10B2000A1DCB /* JSON.h */, - AA6C46BD137E10B2000A1DCB /* NSObject+JSON.h */, - AA6C46BE137E10B2000A1DCB /* NSObject+JSON.m */, - AA6C46BF137E10B2000A1DCB /* SBJsonParser.h */, - AA6C46C0137E10B2000A1DCB /* SBJsonParser.m */, - AA6C46C1137E10B2000A1DCB /* SBJsonStreamParser.h */, - AA6C46C2137E10B2000A1DCB /* SBJsonStreamParser.m */, - AA6C46C3137E10B2000A1DCB /* SBJsonStreamParserAdapter.h */, - AA6C46C4137E10B2000A1DCB /* SBJsonStreamParserAdapter.m */, - AA6C46C5137E10B2000A1DCB /* SBJsonStreamParserState.h */, - AA6C46C6137E10B2000A1DCB /* SBJsonStreamParserState.m */, - AA6C46C7137E10B2000A1DCB /* SBJsonStreamWriter.h */, - AA6C46C8137E10B2000A1DCB /* SBJsonStreamWriter.m */, - AA6C46C9137E10B2000A1DCB /* SBJsonStreamWriterState.h */, - AA6C46CA137E10B2000A1DCB /* SBJsonStreamWriterState.m */, - AA6C46CB137E10B2000A1DCB /* SBJsonTokeniser.h */, - AA6C46CC137E10B2000A1DCB /* SBJsonTokeniser.m */, - AA6C46CD137E10B2000A1DCB /* SBJsonWriter.h */, - AA6C46CE137E10B2000A1DCB /* SBJsonWriter.m */, - ); - name = JSON; - sourceTree = ""; - }; - 83405B20137F684E0060CAD4 /* Networking */ = { - isa = PBXGroup; - children = ( - 83405B25137F68610060CAD4 /* RequestHandlerDelegate.h */, - 83405B26137F68610060CAD4 /* RequestHandler.h */, - 83405B27137F68610060CAD4 /* RequestHandler.m */, - ); - name = Networking; - sourceTree = ""; - }; - 83405B2B137F68870060CAD4 /* Additions */ = { - isa = PBXGroup; - children = ( - 83405B21137F68610060CAD4 /* TaggedNSURLConnection.h */, - 83405B22137F68610060CAD4 /* TaggedNSURLConnection.m */, - 83405B23137F68610060CAD4 /* NSMutableDictionary+IntegerKeys.h */, - 83405B24137F68610060CAD4 /* NSMutableDictionary+IntegerKeys.m */, - ); - name = Additions; - sourceTree = ""; - }; - 83405B2F137F6CFE0060CAD4 /* Models */ = { - isa = PBXGroup; - children = ( - 83405B30137F6DDF0060CAD4 /* Trade.h */, - 83405B31137F6DDF0060CAD4 /* Trade.m */, - 83405B32137F6DDF0060CAD4 /* Ticker.h */, - 83405B33137F6DDF0060CAD4 /* Ticker.m */, - AAB399B1139B0D8500B9438F /* Wallet.h */, - AAB399B2139B0D8500B9438F /* Wallet.m */, - AAD172621399BA1D00B505B0 /* EMKeychainItem.h */, - AAD172631399BA1D00B505B0 /* EMKeychainItem.m */, - AA1F92A213A52D7D00D5643C /* ToolbarTicker.h */, - AA1F92A313A52D7D00D5643C /* ToolbarTicker.m */, - ); - name = Models; - sourceTree = ""; - }; 837DF12E139B2EC8009987F3 /* Nibs */ = { isa = PBXGroup; children = ( AA4BBA411379E31C005CE351 /* MainMenu.xib */, - 837DF12C139B2E97009987F3 /* SettingsWindow.xib */, - AA1F929F13A526D600D5643C /* MainWindow.xib */, - AA86BF2013A55C0F0089B39E /* MtGoxPanel.xib */, - AA86BF2213A55C870089B39E /* TradeHillPanel.xib */, - AA86BF2413A56A470089B39E /* MainPanelView.xib */, ); name = Nibs; sourceTree = ""; }; - 837DF132139B3B8A009987F3 /* Settings */ = { - isa = PBXGroup; - children = ( - AA6E6C811399F2EB003A4224 /* SharedSettings.h */, - AA6E6C821399F2EB003A4224 /* SharedSettings.m */, - 837DF12F139B319E009987F3 /* SettingsWindow.h */, - 837DF130139B319F009987F3 /* SettingsWindow.m */, - 837DF133139B8E18009987F3 /* SettingsWindowController.h */, - 837DF134139B8E18009987F3 /* SettingsWindowController.m */, - ); - name = Settings; - sourceTree = ""; - }; - AA1F92AB13A5331200D5643C /* MainWindow */ = { - isa = PBXGroup; - children = ( - AA86BF0C13A536250089B39E /* MainWindowController.h */, - AA86BF0D13A536250089B39E /* MainWindowController.m */, - AA201E7913A59C4D0063066E /* WindowPanel.h */, - AA201E7A13A59C4D0063066E /* WindowPanel.m */, - AAAB154013A67E3D006C76FC /* MtGoxPanel.h */, - AAAB154113A67E3D006C76FC /* MtGoxPanel.m */, - AAAB154313A67E4C006C76FC /* TradeHillPanel.h */, - AAAB154413A67E4C006C76FC /* TradeHillPanel.m */, - ); - name = MainWindow; - sourceTree = ""; - }; AA4BBA1D1379E31B005CE351 = { isa = PBXGroup; children = ( @@ -297,6 +150,8 @@ AA4BBA2B1379E31B005CE351 /* Frameworks */ = { isa = PBXGroup; children = ( + AAA1992D152969E000025EA2 /* AFNetworking */, + AAA1992615295E9500025EA2 /* CorePlot.framework */, AAD1726C1399BD3500B505B0 /* Security.framework */, AA4BBA2C1379E31B005CE351 /* Cocoa.framework */, AA4BBA2E1379E31B005CE351 /* Other Frameworks */, @@ -322,18 +177,15 @@ AA4BBA321379E31B005CE351 /* BitTicker */ = { isa = PBXGroup; children = ( - AAAB153F13A67DDB006C76FC /* MenuViews */, - AAAB153C13A67DA0006C76FC /* Markets */, - 83405B2F137F6CFE0060CAD4 /* Models */, - 83405B2B137F68870060CAD4 /* Additions */, - 83405B20137F684E0060CAD4 /* Networking */, - 83405B0D137F5D8A0060CAD4 /* JSON */, - 837DF132139B3B8A009987F3 /* Settings */, - AA1F92AB13A5331200D5643C /* MainWindow */, + AA79F0A8152AD664002C3EEB /* BitTicker.entitlements */, + 83405B37137F6DDF0060CAD4 /* MtGox.h */, + 83405B38137F6DDF0060CAD4 /* MtGox.m */, AA239F4D1379E67300150707 /* StatusItemView.h */, AA239F4E1379E67300150707 /* StatusItemView.m */, - AA435C0C13A2CB550050F307 /* MenuController.h */, - AA435C0D13A2CB550050F307 /* MenuController.m */, + AA435C0C13A2CB550050F307 /* Dropdown.h */, + AA435C0D13A2CB550050F307 /* Dropdown.m */, + AA79F0A6152AD215002C3EEB /* GraphView.h */, + AA79F0A7152AD215002C3EEB /* GraphView.m */, AA4BBA3E1379E31C005CE351 /* BitTickerAppDelegate.h */, AA4BBA3F1379E31C005CE351 /* BitTickerAppDelegate.m */, 837DF12E139B2EC8009987F3 /* Nibs */, @@ -355,31 +207,32 @@ name = "Supporting Files"; sourceTree = ""; }; - AAAB153C13A67DA0006C76FC /* Markets */ = { - isa = PBXGroup; - children = ( - 83405B34137F6DDF0060CAD4 /* BitcoinMarketDelegate.h */, - 83405B35137F6DDF0060CAD4 /* BitcoinMarket.h */, - 83405B36137F6DDF0060CAD4 /* BitcoinMarket.m */, - 83405B37137F6DDF0060CAD4 /* MtGoxMarket.h */, - 83405B38137F6DDF0060CAD4 /* MtGoxMarket.m */, - AA83B79A13A2FF1C000F71A6 /* TradeHillMarket.h */, - AA83B79B13A2FF1C000F71A6 /* TradeHillMarket.m */, - ); - name = Markets; - sourceTree = ""; - }; - AAAB153F13A67DDB006C76FC /* MenuViews */ = { + AAA1992D152969E000025EA2 /* AFNetworking */ = { isa = PBXGroup; children = ( - AA435C1013A2CCA90050F307 /* CustomMenuView.h */, - AA435C1113A2CCA90050F307 /* CustomMenuView.m */, - AA435C2C13A2E0850050F307 /* MtGoxMarketMenuView.h */, - AA435C2D13A2E0860050F307 /* MtGoxMarketMenuView.m */, - AA83B7AA13A30582000F71A6 /* TradeHillMarketMenuView.h */, - AA83B7AB13A30582000F71A6 /* TradeHillMarketMenuView.m */, + AAA1992E152969E000025EA2 /* AFHTTPClient.h */, + AAA1992F152969E000025EA2 /* AFHTTPClient.m */, + AAA19930152969E000025EA2 /* AFHTTPRequestOperation.h */, + AAA19931152969E000025EA2 /* AFHTTPRequestOperation.m */, + AAA19932152969E000025EA2 /* AFImageCache.h */, + AAA19933152969E000025EA2 /* AFImageCache.m */, + AAA19934152969E000025EA2 /* AFImageRequestOperation.h */, + AAA19935152969E000025EA2 /* AFImageRequestOperation.m */, + AAA19936152969E000025EA2 /* AFJSONRequestOperation.h */, + AAA19937152969E000025EA2 /* AFJSONRequestOperation.m */, + AAA19938152969E000025EA2 /* AFJSONUtilities.h */, + AAA19939152969E000025EA2 /* AFNetworkActivityIndicatorManager.h */, + AAA1993A152969E000025EA2 /* AFNetworkActivityIndicatorManager.m */, + AAA1993B152969E000025EA2 /* AFNetworking.h */, + AAA1993C152969E000025EA2 /* AFPropertyListRequestOperation.h */, + AAA1993D152969E000025EA2 /* AFPropertyListRequestOperation.m */, + AAA1993E152969E000025EA2 /* AFURLConnectionOperation.h */, + AAA1993F152969E000025EA2 /* AFURLConnectionOperation.m */, + AAA19940152969E000025EA2 /* AFXMLRequestOperation.h */, + AAA19941152969E000025EA2 /* AFXMLRequestOperation.m */, ); - name = MenuViews; + name = AFNetworking; + path = ../../Libraries/AFNetworking/AFNetworking; sourceTree = ""; }; /* End PBXGroup section */ @@ -392,6 +245,7 @@ AA4BBA241379E31B005CE351 /* Sources */, AA4BBA251379E31B005CE351 /* Frameworks */, AA4BBA261379E31B005CE351 /* Resources */, + AAAB15C113A96DF1006C76FC /* CopyFiles */, ); buildRules = ( ); @@ -408,6 +262,7 @@ AA4BBA1F1379E31B005CE351 /* Project object */ = { isa = PBXProject; attributes = { + LastUpgradeCheck = 0430; ORGANIZATIONNAME = none; }; buildConfigurationList = AA4BBA221379E31B005CE351 /* Build configuration list for PBXProject "BitTicker" */; @@ -436,11 +291,6 @@ AA4BBA431379E31C005CE351 /* MainMenu.xib in Resources */, AAD64420137B680E00589EAC /* Logo.icns in Resources */, AA6E6C4D1399DC3E003A4224 /* Credits.html in Resources */, - 837DF12D139B2E97009987F3 /* SettingsWindow.xib in Resources */, - AA1F92A013A526D600D5643C /* MainWindow.xib in Resources */, - AA86BF2113A55C100089B39E /* MtGoxPanel.xib in Resources */, - AA86BF2313A55C880089B39E /* TradeHillPanel.xib in Resources */, - AA86BF2513A56A470089B39E /* MainPanelView.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -451,40 +301,21 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + AA79F0A9152ADB0C002C3EEB /* GraphView.m in Sources */, AA4BBA3A1379E31C005CE351 /* main.m in Sources */, AA4BBA401379E31C005CE351 /* BitTickerAppDelegate.m in Sources */, AA239F511379E67300150707 /* StatusItemView.m in Sources */, - AA6C46CF137E10B2000A1DCB /* NSObject+JSON.m in Sources */, - AA6C46D0137E10B2000A1DCB /* SBJsonParser.m in Sources */, - AA6C46D1137E10B2000A1DCB /* SBJsonStreamParser.m in Sources */, - AA6C46D2137E10B2000A1DCB /* SBJsonStreamParserAdapter.m in Sources */, - AA6C46D3137E10B2000A1DCB /* SBJsonStreamParserState.m in Sources */, - AA6C46D4137E10B2000A1DCB /* SBJsonStreamWriter.m in Sources */, - AA6C46D5137E10B2000A1DCB /* SBJsonStreamWriterState.m in Sources */, - AA6C46D6137E10B2000A1DCB /* SBJsonTokeniser.m in Sources */, - AA6C46D7137E10B2000A1DCB /* SBJsonWriter.m in Sources */, - 83405B28137F68610060CAD4 /* TaggedNSURLConnection.m in Sources */, - 83405B29137F68610060CAD4 /* NSMutableDictionary+IntegerKeys.m in Sources */, - 83405B2A137F68610060CAD4 /* RequestHandler.m in Sources */, - 83405B39137F6DDF0060CAD4 /* Trade.m in Sources */, - 83405B3A137F6DDF0060CAD4 /* Ticker.m in Sources */, - 83405B3B137F6DDF0060CAD4 /* BitcoinMarket.m in Sources */, - 83405B3C137F6DDF0060CAD4 /* MtGoxMarket.m in Sources */, - AAD172641399BA1E00B505B0 /* EMKeychainItem.m in Sources */, - AA6E6C831399F2EB003A4224 /* SharedSettings.m in Sources */, - AAB399B3139B0D8500B9438F /* Wallet.m in Sources */, - 837DF131139B319F009987F3 /* SettingsWindow.m in Sources */, - 837DF135139B8E19009987F3 /* SettingsWindowController.m in Sources */, - AA435C0E13A2CB550050F307 /* MenuController.m in Sources */, - AA435C1213A2CCAA0050F307 /* CustomMenuView.m in Sources */, - AA435C2E13A2E0860050F307 /* MtGoxMarketMenuView.m in Sources */, - AA83B79C13A2FF1C000F71A6 /* TradeHillMarket.m in Sources */, - AA83B7AC13A30583000F71A6 /* TradeHillMarketMenuView.m in Sources */, - AA1F92A413A52D7D00D5643C /* ToolbarTicker.m in Sources */, - AA86BF0F13A536250089B39E /* MainWindowController.m in Sources */, - AA201E7B13A59C4D0063066E /* WindowPanel.m in Sources */, - AAAB154213A67E3D006C76FC /* MtGoxPanel.m in Sources */, - AAAB154513A67E4C006C76FC /* TradeHillPanel.m in Sources */, + 83405B3C137F6DDF0060CAD4 /* MtGox.m in Sources */, + AA435C0E13A2CB550050F307 /* Dropdown.m in Sources */, + AAA19944152969E000025EA2 /* AFHTTPClient.m in Sources */, + AAA19945152969E000025EA2 /* AFHTTPRequestOperation.m in Sources */, + AAA19946152969E000025EA2 /* AFImageCache.m in Sources */, + AAA19947152969E000025EA2 /* AFImageRequestOperation.m in Sources */, + AAA19948152969E000025EA2 /* AFJSONRequestOperation.m in Sources */, + AAA19949152969E000025EA2 /* AFNetworkActivityIndicatorManager.m in Sources */, + AAA1994A152969E000025EA2 /* AFPropertyListRequestOperation.m in Sources */, + AAA1994B152969E000025EA2 /* AFURLConnectionOperation.m in Sources */, + AAA1994C152969E000025EA2 /* AFXMLRequestOperation.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -546,13 +377,23 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$(ARCHS_STANDARD_64_BIT)"; + CLANG_ENABLE_OBJC_ARC = YES; + CODE_SIGN_ENTITLEMENTS = BitTicker/BitTicker.entitlements; COPY_PHASE_STRIP = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)\"", + "\"$(SRCROOT)/../../Libraries/CorePlot_1.0/Binaries/MacOS\"", + ); GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "BitTicker/BitTicker-Prefix.pch"; + HEADER_SEARCH_PATHS = "\"$(SRCROOT)/../../Libraries/CorePlot_1.0/Binaries/MacOS/CorePlot.framework/Headers/\""; INFOPLIST_FILE = "BitTicker/BitTicker-Info.plist"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; WRAPPER_EXTENSION = app; }; name = Debug; @@ -561,13 +402,23 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$(ARCHS_STANDARD_64_BIT)"; + CLANG_ENABLE_OBJC_ARC = YES; + CODE_SIGN_ENTITLEMENTS = BitTicker/BitTicker.entitlements; COPY_PHASE_STRIP = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)\"", + "\"$(SRCROOT)/../../Libraries/CorePlot_1.0/Binaries/MacOS\"", + ); GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "BitTicker/BitTicker-Prefix.pch"; + HEADER_SEARCH_PATHS = "\"$(SRCROOT)/../../Libraries/CorePlot_1.0/Binaries/MacOS/CorePlot.framework/Headers/\""; INFOPLIST_FILE = "BitTicker/BitTicker-Info.plist"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; WRAPPER_EXTENSION = app; }; name = Release; diff --git a/BitTicker/BitTicker-Info.plist b/BitTicker/BitTicker-Info.plist old mode 100644 new mode 100755 index 35bdb14..2fbc7de --- a/BitTicker/BitTicker-Info.plist +++ b/BitTicker/BitTicker-Info.plist @@ -2,6 +2,8 @@ + + CFBundleDevelopmentRegion en CFBundleExecutable @@ -9,7 +11,7 @@ CFBundleIconFile Logo.icns CFBundleIdentifier - com.xercespartners.${PRODUCT_NAME:rfc1034identifier} + com.infincia.${PRODUCT_NAME:rfc1034identifier} CFBundleInfoDictionaryVersion 6.0 CFBundleName @@ -22,15 +24,17 @@ ???? CFBundleVersion 1 + LSApplicationCategoryType + public.app-category.utilities LSMinimumSystemVersion ${MACOSX_DEPLOYMENT_TARGET} LSUIElement - + + NSHumanReadableCopyright + Copyright 2012 Stephen Oliver <mrsteveman1@gmail.com> NSMainNibFile MainMenu NSPrincipalClass NSApplication - NSHumanReadableCopyright - Copyright 2011 Stephen Oliver <mrsteveman1@gmail.com> diff --git a/BitTicker/BitTicker-Prefix.pch b/BitTicker/BitTicker-Prefix.pch old mode 100644 new mode 100755 index c6b0b38..0cf6435 --- a/BitTicker/BitTicker-Prefix.pch +++ b/BitTicker/BitTicker-Prefix.pch @@ -1,37 +1,10 @@ /* - BitTicker is Copyright 2011 Stephen Oliver - http://github.com/mrsteveman1 - - 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 + BitTicker is Copyright 2012 Stephen Oliver + http://github.com/infincia + + */ #ifdef __OBJC__ #import -#endif - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Debug -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -#define DEBUG_ON // Toggle to DEBUG_OFF or comment out to hide all debug code - -#ifdef DEBUG_ON -#define MSLog(format, ...) CFShow([[NSString stringWithFormat:@"<%@:%d> ",[[NSString stringWithUTF8String:__FILE__] lastPathComponent],__LINE__] stringByAppendingFormat:format, ## __VA_ARGS__]) -#else -#define MSlog(format, ...) -#endif - -#define MSLogRect(rect) MSLog(@"%s x:%.4f, y:%.4f, w:%.4f, h%.4f", #rect, rect.origin.x, rect.origin.y, rect.size.width, rect.size.height) -#define MSLogSize(size) MSLog(@"%s w:%.4f, h:%.4f", #size, size.width, size.height) -#define MSLogPoint(point) MSLog(@"%s x:%.4f, y:%.4f", #point, point.x, point.y) \ No newline at end of file +#endif \ No newline at end of file diff --git a/BitTicker/BitTicker.entitlements b/BitTicker/BitTicker.entitlements new file mode 100644 index 0000000..bc04cfb --- /dev/null +++ b/BitTicker/BitTicker.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.network.client + + + diff --git a/BitTicker/BitTickerAppDelegate.h b/BitTicker/BitTickerAppDelegate.h old mode 100644 new mode 100755 index 7de75be..7e176e8 --- a/BitTicker/BitTickerAppDelegate.h +++ b/BitTicker/BitTickerAppDelegate.h @@ -1,52 +1,19 @@ /* - BitTicker is Copyright 2011 Stephen Oliver - http://github.com/mrsteveman1 + BitTicker is Copyright 2012 Stephen Oliver + http://github.com/infincia - 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 - */ +*/ #import -#import "SharedSettings.h" -#import "MenuController.h" -@class RequestHandler; -@class SettingsWindow; -@class MtGoxMarket; -@class TradeHillMarket; + +@class MtGox; + + + @interface BitTickerAppDelegate : NSObject { - MenuController *menuController; - SharedSettings *sharedSettingManager; - MtGoxMarket *market; - TradeHillMarket *tradehill; - - NSTimer *tickerTimer; - NSTimer *walletTimer; - - NSMutableArray *stats; + MtGox *mtgox; - NSWindowController *settingsWindowController; - NSWindowController *mainWindowController; } -- (void) quitProgram:(id)sender; -- (IBAction)refreshTicker:(id)sender; -- (IBAction)showMainWindow:(id)sender; -- (IBAction)showSettings:(id)sender; -- (IBAction)showAbout:(id)sender; - -@property (retain) NSMutableArray *stats; -@property (nonatomic) NSInteger cancelThread; - @end diff --git a/BitTicker/BitTickerAppDelegate.m b/BitTicker/BitTickerAppDelegate.m old mode 100644 new mode 100755 index 04673f9..c2e6494 --- a/BitTicker/BitTickerAppDelegate.m +++ b/BitTicker/BitTickerAppDelegate.m @@ -1,96 +1,19 @@ /* - BitTicker is Copyright 2011 Stephen Oliver - http://github.com/mrsteveman1 - - 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 - */ - -#import "BitTickerAppDelegate.h" -#import "MtGoxMarket.h" -#import "TradeHillMarket.h" -#import "SharedSettings.h" + BitTicker is Copyright 2012 Stephen Oliver + http://github.com/infincia -#import "SettingsWindowController.h" -#import "SettingsWindow.h" -#import "MainWindowController.h" + */ +#import "BitTickerAppDelegate.h" +#import "MtGox.h" @implementation BitTickerAppDelegate -@synthesize stats; -@synthesize cancelThread; - -- (void)awakeFromNib { - sharedSettingManager = [SharedSettings sharedSettingManager]; - - settingsWindowController = [[SettingsWindowController alloc] init]; - - mainWindowController = [[MainWindowController alloc] init]; - - menuController = [[MenuController alloc] init]; - - MSLog(@"Starting"); - market = [[MtGoxMarket alloc] init]; - tickerTimer = [[NSTimer scheduledTimerWithTimeInterval:30 target:market selector:@selector(fetchTicker) userInfo:nil repeats:YES] retain]; - walletTimer = [[NSTimer scheduledTimerWithTimeInterval:60 target:market selector:@selector(fetchWallet) userInfo:nil repeats:YES] retain]; - [market fetchTicker]; - [market fetchWallet]; - [menuController createMenuForMarket:market]; - - //tradehill = [[TradeHillMarket alloc] init]; - //[menuController createMenuForMarket:tradehill]; - - [menuController addSelectorItems]; -} - -#pragma mark Application delegate -- (void)applicationWillTerminate:(NSNotification *)notification { - [market release]; - [tickerTimer invalidate]; - [tickerTimer release]; - [settingsWindowController release]; - [menuController release]; -} - --(void)settingsWindowClosed { - -} -#pragma mark Actions - -- (IBAction)quitProgram:(id)sender { - [NSApp terminate:self]; -} -- (IBAction)refreshTicker:(id)sender { - [market fetchTicker]; -} - -- (IBAction)showAbout:(id)sender { - [NSApp orderFrontStandardAboutPanel:nil]; - [NSApp activateIgnoringOtherApps:YES]; -} - -- (IBAction)showSettings:(id)sender { - [settingsWindowController.window makeKeyAndOrderFront:nil]; - [NSApp activateIgnoringOtherApps:YES]; -} +- (void)awakeFromNib { -- (IBAction)showMainWindow:(id)sender { - [mainWindowController.window makeKeyAndOrderFront:nil]; - [NSApp activateIgnoringOtherApps:YES]; + mtgox = [[MtGox alloc] init]; } @end diff --git a/BitTicker/BitcoinMarket.h b/BitTicker/BitcoinMarket.h deleted file mode 100644 index c0f9ce7..0000000 --- a/BitTicker/BitcoinMarket.h +++ /dev/null @@ -1,58 +0,0 @@ -// -// BitcoinMarket.h -// Bitcoin Trader -// -// Created by Matt Stith on 4/27/11. -// Copyright 2011 Insomnia Addict. All rights reserved. -// - -#import - -#import "BitcoinMarketDelegate.h" -#import "RequestHandlerDelegate.h" -#import "SharedSettings.h" - -enum kBitcoinMarkets { - eMarketMtGox = 0, - eMarketTradeHill = 1, - // Used for market enumeration. Keep this as the last value. - eNumberOfMarkets = 2 -}; - -@class RequestHandler; - -@interface BitcoinMarket : NSObject { - RequestHandler *requestHandler; - id _delegate; - - NSMutableDictionary *_selectorMap; - SharedSettings *sharedSettingManager; - - NSString *_tradeURL; - NSString *_tickerURL; - NSString *_depthURL; - NSString *_walletURL; - -} --(id)initWithDelegate:(id)delegate; - --(void)downloadJsonDataFromURL:(NSURL*)url callback:(SEL)callback; --(void)downloadJsonDataFromURL:(NSURL*)url withPostData:(NSDictionary*)postData callback:(SEL)callback; - -// Overwrite these in each market --(NSURL*)getTickerDataURL; --(NSURL*)getRecentTradeURL; - --(void)fetchRecentTrades; --(void)fetchTicker; --(void)fetchMarketDepth; --(void)fetchWallet; - -@property (nonatomic, assign) id delegate; - -@property (readonly,nonatomic,retain) NSString *tradeURL; -@property (readonly,nonatomic,retain) NSString *tickerURL; -@property (readonly,nonatomic,retain) NSString *depthURL; -@property (readonly,nonatomic,retain) NSString *walletURL; - -@end diff --git a/BitTicker/BitcoinMarket.m b/BitTicker/BitcoinMarket.m deleted file mode 100644 index 1f148a2..0000000 --- a/BitTicker/BitcoinMarket.m +++ /dev/null @@ -1,135 +0,0 @@ -// -// BitcoinMarket.m -// Bitcoin Trader -// -// Created by Matt Stith on 4/27/11. -// Copyright 2011 Insomnia Addict. All rights reserved. -// - -#import "BitcoinMarket.h" - -#import "RequestHandler.h" - -#import "JSON.h" -#import "SharedSettings.h" -@implementation BitcoinMarket - -@synthesize delegate=_delegate; -@synthesize tickerURL = _tickerURL; -@synthesize tradeURL = _tradeURL; -@synthesize walletURL = _walletURL; -@synthesize depthURL = _depthURL; - --(id)initWithDelegate:(id)delegate { - if (!(self = [super init])) return self; - - requestHandler = [[RequestHandler alloc] initWithDelegate:self]; - self.delegate = delegate; - _selectorMap = [[NSMutableDictionary alloc] init]; - sharedSettingManager = [SharedSettings sharedSettingManager]; - return self; -} - --(void)dealloc { - [requestHandler release]; - [_selectorMap release]; - [super dealloc]; -} - -#pragma mark - Market request methods - -#pragma mark - Downloading methods --(void)downloadJsonDataFromURL:(NSURL*)url callback:(SEL)callback { - [self downloadJsonDataFromURL:url withPostData:nil callback:callback]; -} --(void)downloadJsonDataFromURL:(NSURL *)url withPostData:(NSDictionary*)postData callback:(SEL)callback { - NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url]; - - NSMutableString *body = [NSMutableString string]; - - if (postData) { - [urlRequest setHTTPMethod:@"POST"]; - BOOL appendAmpersand = NO; - for(NSString *key in postData) { - NSString *value = [postData objectForKey:key]; - [body appendFormat:@"%@=%@%@",key,value,appendAmpersand?@"":@"&"]; - if (!appendAmpersand) appendAmpersand = YES; - } - - [urlRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"content-type"]; - } else { - [urlRequest setHTTPMethod:@"GET"]; - } - - [urlRequest setHTTPBody:[body dataUsingEncoding:NSUTF8StringEncoding]]; - - NSInteger newTag = [requestHandler startConnection:urlRequest]; - - NSMethodSignature *signature = [self methodSignatureForSelector:callback]; - NSInvocation *invoke = [NSInvocation invocationWithMethodSignature:signature]; - [invoke setTarget:self]; - [invoke setSelector:callback]; - - [_selectorMap setObject:invoke forIntegerKey:newTag]; - -} - -#pragma mark - MultiConnectionHandlerDelegate methods --(void)request:(NSInteger)tag didFinishWithData:(NSData*)data { - // TODO: Don't assume a JSON response - SBJsonParser *jsonParser = [SBJsonParser new]; - NSString *jsonString = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]; - jsonString = [jsonString stringByTrimmingCharactersInSet:[NSCharacterSet controlCharacterSet]]; - //MSLog(@"About to parse string: %@",jsonString); - - - NSInvocation *invocation = [_selectorMap objectForIntegerKey:tag]; - - [invocation setTarget:self]; - - - NSError *error = nil; - id results = [jsonParser objectWithString:jsonString error:&error]; - - if (error) { - MSLog(@"ERROR from connection %i: %@",tag,error); - id delegate = invocation.target; - [delegate bitcoinMarket:self requestFailedWithError:error]; - MSLog(@"Raw data: %@",data); - } else { - [invocation setArgument:&results atIndex:2]; - [invocation invoke]; - } - - [jsonParser release]; - - [_selectorMap removeObjectForInteger:tag]; -} - --(void)request:(NSInteger)tag didFailWithError:(NSError*)error { - -} - -#pragma mark - Methods for subclasses to overwrite --(NSURL*)getTickerDataURL { - [NSException raise:@"MethodNotOverwrittenException" format:@"%s must be overwritten by BitcoinMarket subclasses",__func__]; - return nil; -} --(NSURL*)getRecentTradeURL { - [NSException raise:@"MethodNotOverwrittenException" format:@"%s must be overwritten by BitcoinMarket subclasses",__func__]; - return nil; -} --(void)fetchRecentTrades { - [NSException raise:@"MethodNotOverwrittenException" format:@"%s must be overwritten by BitcoinMarket subclasses",__func__]; -} --(void)fetchTicker { - [NSException raise:@"MethodNotOverwrittenException" format:@"%s must be overwritten by BitcoinMarket subclasses",__func__]; -} --(void)fetchMarketDepth { - [NSException raise:@"MethodNotOverwrittenException" format:@"%s must be overwritten by BitcoinMarket subclasses",__func__]; -} --(void)fetchWallet { - [NSException raise:@"MethodNotOverwrittenException" format:@"%s must be overwritten by BitcoinMarket subclasses",__func__]; -} - -@end diff --git a/BitTicker/BitcoinMarketDelegate.h b/BitTicker/BitcoinMarketDelegate.h deleted file mode 100644 index 73a1fa1..0000000 --- a/BitTicker/BitcoinMarketDelegate.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// BitcoinMarketDataSource.h -// Bitcoin Trader -// -// Created by Matt Stith on 4/27/11. -// Copyright 2011 Insomnia Addict. All rights reserved. -// - -#import - -@class BitcoinMarket; -@class Ticker; -@class Wallet; -@class Miner; - -@protocol BitcoinMarketDelegate - -@required - -// A request failed for some reason, for example the API being down --(void)bitcoinMarket:(BitcoinMarket*)market requestFailedWithError:(NSError*)error; - -// Request wasn't formatted as expected --(void)bitcoinMarket:(BitcoinMarket*)market didReceiveInvalidResponse:(NSData*)data; - -@optional - --(void)bitcoinMarket:(BitcoinMarket*)market didReceiveTicker:(Ticker*)ticker; --(void)bitcoinMarket:(BitcoinMarket*)market didReceiveRecentTradesData:(NSArray*)trades; --(void)bitcoinMarket:(BitcoinMarket*)market didReceiveWallet:(Wallet*)wallet; --(void)bitcoinMarket:(BitcoinMarket*)market didReceiveMiner:(Miner*)minerdata; -@end diff --git a/BitTicker/Credits.html b/BitTicker/Credits.html old mode 100644 new mode 100755 index d2917da..89db608 --- a/BitTicker/Credits.html +++ b/BitTicker/Credits.html @@ -8,7 +8,7 @@

1EW4tmzG3Xwa7LPmebhrHhAuZWmPe6UKrk

-Source on Github +Source on Github \ No newline at end of file diff --git a/BitTicker/CustomMenuView.h b/BitTicker/CustomMenuView.h deleted file mode 100644 index 0378914..0000000 --- a/BitTicker/CustomMenuView.h +++ /dev/null @@ -1,31 +0,0 @@ -// -// CustomMenuView.h -// BitTicker -// -// Created by steve on 6/10/11. -// Copyright 2011 none. All rights reserved. -// - -#import - - -@interface CustomMenuView : NSView { - - -} - -- (id)initWithFrame:(NSRect)frame; - -@property (retain) NSString *high; -@property (retain) NSString *low; -@property (retain) NSString *vol; -@property (retain) NSString *buy; -@property (retain) NSString *sell; -@property (retain) NSString *last; - -@property (retain) NSString *btc; -@property (retain) NSString *btcusd; -@property (retain) NSString *usd; -@property (retain) NSString *wallet; - -@end diff --git a/BitTicker/CustomMenuView.m b/BitTicker/CustomMenuView.m deleted file mode 100644 index 91ba5e1..0000000 --- a/BitTicker/CustomMenuView.m +++ /dev/null @@ -1,100 +0,0 @@ -// -// CustomMenuView.m -// BitTicker -// -// Created by steve on 6/10/11. -// Copyright 2011 none. All rights reserved. -// - -#import "CustomMenuView.h" - - -@implementation CustomMenuView - -@dynamic high; -@dynamic low; -@dynamic vol; -@dynamic buy; -@dynamic sell; -@dynamic last; - -@dynamic btc; -@dynamic btcusd; -@dynamic usd; -@dynamic wallet; - -- (id)initWithFrame:(NSRect)frame { - self = [super initWithFrame:frame]; - if (self) { - // Initialization code here. - } - - return self; -} - -- (void)dealloc -{ - [super dealloc]; -} - -- (void)drawRect:(NSRect)dirtyRect -{ - // Drawing code here. -} - -#pragma mark - -#pragma mark Properties - -// Override these - --(void)setHigh:(NSString *)string { - [NSException raise:@"MethodNotOverwrittenException" format:@"%s must be overwritten by CustomMenuView subclasses",__func__]; - -} - --(void)setLow:(NSString *)string { - [NSException raise:@"MethodNotOverwrittenException" format:@"%s must be overwritten by CustomMenuView subclasses",__func__]; - -} - --(void)setVol:(NSString *)string { - [NSException raise:@"MethodNotOverwrittenException" format:@"%s must be overwritten by CustomMenuView subclasses",__func__]; - -} - --(void)setBuy:(NSString *)string { - [NSException raise:@"MethodNotOverwrittenException" format:@"%s must be overwritten by CustomMenuView subclasses",__func__]; - -} - --(void)setSell:(NSString *)string { - [NSException raise:@"MethodNotOverwrittenException" format:@"%s must be overwritten by CustomMenuView subclasses",__func__]; - -} - --(void)setLast:(NSString *)string { - [NSException raise:@"MethodNotOverwrittenException" format:@"%s must be overwritten by CustomMenuView subclasses",__func__]; - -} - --(void)setBtc:(NSString *)string { - [NSException raise:@"MethodNotOverwrittenException" format:@"%s must be overwritten by CustomMenuView subclasses",__func__]; - -} - --(void)setBtcusd:(NSString *)string { - [NSException raise:@"MethodNotOverwrittenException" format:@"%s must be overwritten by CustomMenuView subclasses",__func__]; - -} - --(void)setUsd:(NSString *)string { - [NSException raise:@"MethodNotOverwrittenException" format:@"%s must be overwritten by CustomMenuView subclasses",__func__]; - -} - --(void)setWallet:(NSString *)string { - [NSException raise:@"MethodNotOverwrittenException" format:@"%s must be overwritten by CustomMenuView subclasses",__func__]; - -} - -@end diff --git a/BitTicker/Dropdown.h b/BitTicker/Dropdown.h new file mode 100755 index 0000000..a13e73e --- /dev/null +++ b/BitTicker/Dropdown.h @@ -0,0 +1,39 @@ +/* + BitTicker is Copyright 2012 Stephen Oliver + http://github.com/infincia + +*/ + +#import + + +@class StatusItemView; + +@interface Dropdown : NSObject { + NSMenu *trayMenu; + + NSMutableDictionary *_viewDict; + StatusItemView *statusItemView; + NSStatusItem *_statusItem; + + NSNumberFormatter *currencyFormatter; + NSNumberFormatter *volumeFormatter; + + NSNumber *_tickerValue; + + + +} + +@property (copy) NSNumber *tickerValue; + +@property (retain) NSString *high; +@property (retain) NSString *low; +@property (retain) NSString *vol; +@property (retain) NSString *buy; +@property (retain) NSString *sell; +@property (retain) NSString *last; + +@property (assign) IBOutlet NSView *dropdownView; + +@end diff --git a/BitTicker/Dropdown.m b/BitTicker/Dropdown.m new file mode 100755 index 0000000..ccac843 --- /dev/null +++ b/BitTicker/Dropdown.m @@ -0,0 +1,85 @@ +/* + BitTicker is Copyright 2012 Stephen Oliver + http://github.com/infincia + +*/ + +#import "Dropdown.h" +#import "StatusItemView.h" + +@implementation Dropdown + +@synthesize tickerValue = _tickerValue; + + + +@synthesize high; +@synthesize low; +@synthesize vol; +@synthesize buy; +@synthesize sell; +@synthesize last; +@synthesize dropdownView; + + + +- (id)init +{ + self = [super init]; + + volumeFormatter = [[NSNumberFormatter alloc] init]; + volumeFormatter.numberStyle = NSNumberFormatterDecimalStyle; + volumeFormatter.hasThousandSeparators = YES; + + currencyFormatter = [[NSNumberFormatter alloc] init]; + currencyFormatter.numberStyle = NSNumberFormatterCurrencyStyle; + currencyFormatter.currencyCode = @"USD"; // TODO: Base on market currency + currencyFormatter.thousandSeparator = @","; // TODO: Base on local seperator for currency + currencyFormatter.alwaysShowsDecimalSeparator = YES; + currencyFormatter.hasThousandSeparators = YES; + currencyFormatter.minimumFractionDigits = 4; // TODO: Configurable + return self; +} + +-(void)awakeFromNib { + NSLog(@"Awake from nib in dropdown"); + _statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength]; + + statusItemView = [[StatusItemView alloc] init]; + statusItemView.statusItem = _statusItem; + [statusItemView setToolTip:NSLocalizedString(@"BitTicker", @"Status Item Tooltip")]; + + [_statusItem setView:statusItemView]; + + trayMenu = [[NSMenu alloc] initWithTitle:@"Ticker"]; + [statusItemView setMenu:trayMenu]; + NSMenuItem *menuItem = [[NSMenuItem alloc] init]; + [menuItem setView:self.dropdownView]; + [trayMenu addItem:menuItem]; + + + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveTicker:) name:@"MtGox-Ticker" object:nil]; + +} + +-(void)didReceiveTicker:(NSNotification *)notification { + //NSAssert([NSThread currentThread] != [NSThread mainThread],@"Running on main thread!"); + NSLog(@"Dropdown got ticker"); + NSDictionary *ticker = [[notification object] objectForKey:@"ticker"]; + + //dispatch_async(dispatch_get_main_queue(), ^{ + + + [self setHigh:[currencyFormatter stringFromNumber:[ticker objectForKey:@"high"]]]; + [self setLow:[currencyFormatter stringFromNumber:[ticker objectForKey:@"low"]]]; + [self setBuy:[currencyFormatter stringFromNumber:[ticker objectForKey:@"buy"]]]; + [self setSell:[currencyFormatter stringFromNumber:[ticker objectForKey:@"sell"]]]; + [self setLast:[currencyFormatter stringFromNumber:[ticker objectForKey:@"last"]]]; + [self setVol:[volumeFormatter stringFromNumber:[ticker objectForKey:@"vol"]]]; + //}); + + +} + +@end diff --git a/BitTicker/EMKeychainItem.h b/BitTicker/EMKeychainItem.h deleted file mode 100644 index b1df285..0000000 --- a/BitTicker/EMKeychainItem.h +++ /dev/null @@ -1,175 +0,0 @@ -/*Copyright (c) 2009 Extendmac, LLC. - - Permission is hereby granted, free of charge, to any person - obtaining a copy of this software and associated documentation - files (the "Software"), to deal in the Software without - restriction, including without limitation the rights to use, - copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following - conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - OTHER DEALINGS IN THE SOFTWARE. - */ - -//Last Updated February 8th, 2011. - - -#import -#import -#import - -/*! - @abstract EMKeychainItem is a self-contained wrapper class for two-way communication with the keychain. You can add, retrieve, and remove both generic and internet keychain items. - @dicussion All keychain items have a username, password, and optionally a label. - */ -@interface EMKeychainItem : NSObject -{ - @private - NSString *mUsername; - NSString *mPassword; - NSString *mLabel; - - @protected - SecKeychainItemRef mCoreKeychainItem; -} - -/*! - @abstract Returns whether or not errors are logged. - @discussion Errors occur whenever a keychain item fails to appropriately update a property, or when a given keychain item cannot be found. - */ -+ (BOOL)logsErrors; - -//! @abstracts Sets whether or not errors are logged. -+ (void)setLogsErrors:(BOOL)logsErrors; - -//! @abstracts Locks the keychain. -+ (void)lockKeychain; - -//! @abstract Unlocks the keychain. -+ (void)unlockKeychain; - -//! @abstract The keychain item's username. -@property (readwrite, copy) NSString *username; - -//! @abstract The keychain item's password. -@property (readwrite, copy) NSString *password; - -//! @abstract The keychain item's label. -@property (readwrite, copy) NSString *label; - -/*! - @abstract Removes the receiver from the keychain. - @discussion After calling this method, you should generally discard of the receiver. The receiver cannot be "re-added" to the keychain; invoke either addGenericKeychainItemForService:... or addInternetKeychainItemForServer:... instead. - */ -- (void)removeFromKeychain; - -@end - -#pragma mark - - -/*! - @abstract An EMGenericKeychainItem wraps the functionality and data-members associated with a generic keychain item. - @discussion Generic keychain items have a service name in addition to the standard keychain item properties. - */ -@interface EMGenericKeychainItem : EMKeychainItem -{ - @private - NSString *mServiceName; -} - -//! @abstract The keychain item's service name. -@property (readwrite, copy) NSString *serviceName; - -/*! - @abstract Returns, if possible, a generic keychain item that corresponds to the given service. - @param serviceName The service name. Cannot be nil. - @param username The username. Cannot be nil. - @result An EMGenericKeychainItem if the keychain item can be discovered. Otherwise, nil. - */ -+ (EMGenericKeychainItem *)genericKeychainItemForService:(NSString *)serviceName - withUsername:(NSString *)username; - -/*! - @abstract Adds a keychain item for the given service. - @param serviceName The service name. Cannot be nil. - @param username The username. Cannot be nil. - @param password The password to associate with the username and service. Cannot be nil. - @result An EMGenericKeychainItem if the service can be added to the keychain. Otherwise, nil. - */ -+ (EMGenericKeychainItem *)addGenericKeychainItemForService:(NSString *)serviceName - withUsername:(NSString *)username - password:(NSString *)password; -@end - -#pragma mark - - -/*! - @abstract An EMInternetKeychainItem wraps the functionality and data-members associated with an internet keychain item. - @discussion Internet keychain items can optionally have a server, path, port, and protocol in addition to the standard keychain item properties. - */ -@interface EMInternetKeychainItem : EMKeychainItem -{ - @private - NSString *mServer; - NSString *mPath; - NSInteger mPort; - SecProtocolType mProtocol; -} - - -/*! - @abstract Returns, if possible, an internet keychain item that corresponds to the given server. - @param server The server. Cannot be nil. - @param username The username. Cannot be nil. - @param path The path. - @param port The port. - @param protocol The protocol. - @result An EMInternetKeychainItem if the keychain item can be discovered. Otherwise, nil. - */ -+ (EMInternetKeychainItem *)internetKeychainItemForServer:(NSString *)server - withUsername:(NSString *)username - path:(NSString *)path - port:(NSInteger)port - protocol:(SecProtocolType)protocol; - -/*! - @abstract Adds a keychain item for the given server. - @param server The server. Cannot be nil. - @param username The username. Cannot be nil. - @param password The password to associate with the server, username, path, port, and protocol. Cannot be nil. - @param path The path. - @param port The port. - @param protocol The protocol. - @result An EMInternetKeychainItem if the item can be added to the keychain. Otherwise, nil. - */ -+ (EMInternetKeychainItem *)addInternetKeychainItemForServer:(NSString *)server - withUsername:(NSString *)username - password:(NSString *)password - path:(NSString *)path - port:(NSInteger)port - protocol:(SecProtocolType)protocol; - -//! @abstract The keychain item's server. -@property (readwrite, copy) NSString *server; - -//! @abstract The keychain item's path. -@property (readwrite, copy) NSString *path; - -//! @abstract The keychain item's port. -@property (readwrite, assign) NSInteger port; - -//! @abstract The keychain item's protocol. -@property (readwrite, assign) SecProtocolType protocol; - -@end \ No newline at end of file diff --git a/BitTicker/EMKeychainItem.m b/BitTicker/EMKeychainItem.m deleted file mode 100644 index b35e4be..0000000 --- a/BitTicker/EMKeychainItem.m +++ /dev/null @@ -1,540 +0,0 @@ -/*Copyright (c) 2009 Extendmac, LLC. - - Permission is hereby granted, free of charge, to any person - obtaining a copy of this software and associated documentation - files (the "Software"), to deal in the Software without - restriction, including without limitation the rights to use, - copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following - conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - OTHER DEALINGS IN THE SOFTWARE. - */ - -#import "EMKeychainItem.h" - -@interface EMKeychainItem (Private) - -/*! - @abstract Modifies the given attribute to be newValue. - @param attributeTag The attribute's tag. - @param newValue A pointer to the new value. - @param newLength The length of the new value. -*/ -- (void)_modifyAttributeWithTag:(SecItemAttr)attributeTag toBeValue:(void *)newValue ofLength:(UInt32)newLength; - -@end - -@implementation EMKeychainItem - -static BOOL _logsErrors; - -+ (void)lockKeychain -{ - SecKeychainLock(NULL); -} - -+ (void)unlockKeychain -{ - SecKeychainUnlock(NULL, 0, NULL, NO); -} - -+ (BOOL)logsErrors -{ - @synchronized (self) - { - return _logsErrors; - } - return NO; -} - -+ (void)setLogsErrors:(BOOL)logsErrors -{ - @synchronized (self) - { - if (_logsErrors == logsErrors) - return; - - _logsErrors = logsErrors; - } -} - -#pragma mark - - -- (id)_initWithCoreKeychainItem:(SecKeychainItemRef)item - username:(NSString *)username - password:(NSString *)password -{ - if ((self = [super init])) - { - mCoreKeychainItem = item; - mUsername = [username copy]; - mPassword = [password copy]; - - return self; - } - return nil; -} - -- (void)_modifyAttributeWithTag:(SecItemAttr)attributeTag toBeValue:(void *)newValue ofLength:(UInt32)newLength -{ - NSAssert(mCoreKeychainItem, @"Core keychain item is nil. You cannot modify a keychain item that is not in the keychain."); - - SecKeychainAttribute attributes[1]; - attributes[0].tag = attributeTag; - attributes[0].length = newLength; - attributes[0].data = newValue; - - SecKeychainAttributeList attributeList; - attributeList.count = 1; - attributeList.attr = attributes; - - SecKeychainItemModifyAttributesAndData(mCoreKeychainItem, &attributeList, 0, NULL); -} - -- (void)dealloc -{ - [mUsername release]; - [mPassword release]; - [mLabel release]; - - if (mCoreKeychainItem) - CFRelease(mCoreKeychainItem); - - [super dealloc]; -} - -#pragma mark - -#pragma mark General Properties -@dynamic password; -- (NSString *)password -{ - @synchronized (self) - { - return [[mPassword copy] autorelease]; - } -} - -- (void)setPassword:(NSString *)newPassword -{ - @synchronized (self) - { - if (mPassword == newPassword) - return; - - [mPassword release]; - mPassword = [newPassword copy]; - - const char *newPasswordCString = [newPassword UTF8String]; - SecKeychainItemModifyAttributesAndData(mCoreKeychainItem, NULL, (UInt32)strlen(newPasswordCString), (void *)newPasswordCString); - } -} - -#pragma mark - -@dynamic username; -- (NSString *)username -{ - @synchronized (self) - { - return [[mUsername copy] autorelease]; - } -} - -- (void)setUsername:(NSString *)newUsername -{ - @synchronized (self) - { - if (mUsername == newUsername) - return; - - [mUsername release]; - mUsername = [newUsername copy]; - - const char *newUsernameCString = [newUsername UTF8String]; - [self _modifyAttributeWithTag:kSecAccountItemAttr toBeValue:(void *)newUsernameCString ofLength:(UInt32)strlen(newUsernameCString)]; - } -} - -#pragma mark - -@dynamic label; -- (NSString *)label -{ - @synchronized (self) - { - return [[mLabel copy] autorelease]; - } -} - -- (void)setLabel:(NSString *)newLabel -{ - @synchronized (self) - { - if (mLabel == newLabel) - return; - - [mLabel release]; - mLabel = [newLabel copy]; - - const char *newLabelCString = [newLabel UTF8String]; - [self _modifyAttributeWithTag:kSecLabelItemAttr toBeValue:(void *)newLabelCString ofLength:(UInt32)strlen(newLabelCString)]; - } -} - -#pragma mark - -#pragma mark Actions -- (void)removeFromKeychain -{ - NSAssert(mCoreKeychainItem, @"Core keychain item is nil. You cannot remove a keychain item that is not in the keychain already."); - - if (mCoreKeychainItem) - { - OSStatus resultStatus = SecKeychainItemDelete(mCoreKeychainItem); - if (resultStatus == noErr) - { - CFRelease(mCoreKeychainItem); - mCoreKeychainItem = nil; - } - } -} - -@end - -#pragma mark - -@implementation EMGenericKeychainItem - -- (id)_initWithCoreKeychainItem:(SecKeychainItemRef)item - serviceName:(NSString *)serviceName - username:(NSString *)username - password:(NSString *)password -{ - if ((self = [super _initWithCoreKeychainItem:item username:username password:password])) - { - mServiceName = [serviceName copy]; - return self; - } - return nil; -} - -+ (id)_genericKeychainItemWithCoreKeychainItem:(SecKeychainItemRef)coreKeychainItem - forServiceName:(NSString *)serviceName - username:(NSString *)username - password:(NSString *)password -{ - return [[[EMGenericKeychainItem alloc] _initWithCoreKeychainItem:coreKeychainItem - serviceName:serviceName - username:username - password:password] autorelease]; -} - -- (void)dealloc -{ - [mServiceName release]; - - [super dealloc]; -} - -#pragma mark - -+ (EMGenericKeychainItem *)genericKeychainItemForService:(NSString *)serviceName - withUsername:(NSString *)username -{ - if (!serviceName || !username) - return nil; - - const char *serviceNameCString = [serviceName UTF8String]; - const char *usernameCString = [username UTF8String]; - - UInt32 passwordLength = 0; - char *password = nil; - - SecKeychainItemRef item = nil; - OSStatus returnStatus = SecKeychainFindGenericPassword(NULL, (UInt32)strlen(serviceNameCString), serviceNameCString, (UInt32)strlen(usernameCString), usernameCString, &passwordLength, (void **)&password, &item); - if (returnStatus != noErr || !item) - { - if (_logsErrors) - NSLog(@"Error (%@) - %s", NSStringFromSelector(_cmd), GetMacOSStatusErrorString(returnStatus)); - return nil; - } - NSString *passwordString = [[[NSString alloc] initWithData:[NSData dataWithBytes:password length:passwordLength] encoding:NSUTF8StringEncoding] autorelease]; - SecKeychainItemFreeContent(NULL, password); - - return [EMGenericKeychainItem _genericKeychainItemWithCoreKeychainItem:item forServiceName:serviceName username:username password:passwordString]; -} - -+ (EMGenericKeychainItem *)addGenericKeychainItemForService:(NSString *)serviceName - withUsername:(NSString *)username - password:(NSString *)password -{ - if (!serviceName || !username || !password) - return nil; - - const char *serviceNameCString = [serviceName UTF8String]; - const char *usernameCString = [username UTF8String]; - const char *passwordCString = [password UTF8String]; - - SecKeychainItemRef item = nil; - OSStatus returnStatus = SecKeychainAddGenericPassword(NULL, (UInt32)strlen(serviceNameCString), serviceNameCString, (UInt32)strlen(usernameCString), usernameCString, (UInt32)strlen(passwordCString), (void *)passwordCString, &item); - - if (returnStatus != noErr || !item) - { - if (_logsErrors) - NSLog(@"Error (%@) - %s", NSStringFromSelector(_cmd), GetMacOSStatusErrorString(returnStatus)); - return nil; - } - return [EMGenericKeychainItem _genericKeychainItemWithCoreKeychainItem:item forServiceName:serviceName username:username password:password]; -} - -#pragma mark - -#pragma mark Generic Properties -@dynamic serviceName; -- (NSString *)serviceName -{ - @synchronized (self) - { - return [[mServiceName copy] autorelease]; - } -} - -- (void)setServiceName:(NSString *)newServiceName -{ - @synchronized (self) - { - if (mServiceName == newServiceName) - return; - - [mServiceName release]; - mServiceName = [newServiceName copy]; - - const char *newServiceNameCString = [newServiceName UTF8String]; - [self _modifyAttributeWithTag:kSecServiceItemAttr toBeValue:(void *)newServiceNameCString ofLength:(UInt32)strlen(newServiceNameCString)]; - } -} - -@end - -#pragma mark - -@implementation EMInternetKeychainItem - -- (id)_initWithCoreKeychainItem:(SecKeychainItemRef)item - server:(NSString *)server - username:(NSString *)username - password:(NSString *)password - path:(NSString *)path - port:(NSInteger)port - protocol:(SecProtocolType)protocol -{ - if ((self = [super _initWithCoreKeychainItem:item username:username password:password])) - { - mServer = [server copy]; - mPath = [path copy]; - mPort = port; - mProtocol = protocol; - - return self; - } - return nil; -} - -- (void)dealloc -{ - [mServer release]; - [mPath release]; - - [super dealloc]; -} - -+ (id)_internetKeychainItemWithCoreKeychainItem:(SecKeychainItemRef)coreKeychainItem - forServer:(NSString *)server - username:(NSString *)username - password:(NSString *)password - path:(NSString *)path - port:(NSInteger)port - protocol:(SecProtocolType)protocol -{ - return [[[EMInternetKeychainItem alloc] _initWithCoreKeychainItem:coreKeychainItem - server:server - username:username - password:password - path:path - port:port - protocol:protocol] autorelease]; -} - -#pragma mark - -+ (EMInternetKeychainItem *)internetKeychainItemForServer:(NSString *)server - withUsername:(NSString *)username - path:(NSString *)path - port:(NSInteger)port - protocol:(SecProtocolType)protocol -{ - if (!server || !username) - return nil; - - const char *serverCString = [server UTF8String]; - const char *usernameCString = [username UTF8String]; - const char *pathCString = [path UTF8String]; - - if (!path || [path length] == 0) - pathCString = ""; - - UInt32 passwordLength = 0; - char *password = nil; - - SecKeychainItemRef item = nil; - //0 is kSecAuthenticationTypeAny - OSStatus returnStatus = SecKeychainFindInternetPassword(NULL, (UInt32)strlen(serverCString), serverCString, 0, NULL, (UInt32)strlen(usernameCString), usernameCString, (UInt32)strlen(pathCString), pathCString, port, protocol, 0, &passwordLength, (void **)&password, &item); - - if (returnStatus != noErr && protocol == kSecProtocolTypeFTP) - { - //Some clients (like Transmit) still save passwords with kSecProtocolTypeFTPAccount, which was deprecated. Let's check for that. - protocol = kSecProtocolTypeFTPAccount; - returnStatus = SecKeychainFindInternetPassword(NULL, (UInt32)strlen(serverCString), serverCString, 0, NULL, (UInt32)strlen(usernameCString), usernameCString, (UInt32)strlen(pathCString), pathCString, port, protocol, 0, &passwordLength, (void **)&password, &item); - } - - if (returnStatus != noErr || !item) - { - if (_logsErrors) - NSLog(@"Error (%@) - %s", NSStringFromSelector(_cmd), GetMacOSStatusErrorString(returnStatus)); - return nil; - } - NSString *passwordString = [[[NSString alloc] initWithData:[NSData dataWithBytes:password length:passwordLength] encoding:NSUTF8StringEncoding] autorelease]; - SecKeychainItemFreeContent(NULL, password); - - return [EMInternetKeychainItem _internetKeychainItemWithCoreKeychainItem:item forServer:server username:username password:passwordString path:path port:port protocol:protocol]; -} - -+ (EMInternetKeychainItem *)addInternetKeychainItemForServer:(NSString *)server - withUsername:(NSString *)username - password:(NSString *)password - path:(NSString *)path - port:(NSInteger)port - protocol:(SecProtocolType)protocol -{ - if (!username || !server || !password) - return nil; - - const char *serverCString = [server UTF8String]; - const char *usernameCString = [username UTF8String]; - const char *passwordCString = [password UTF8String]; - const char *pathCString = [path UTF8String]; - - if (!path || [path length] == 0) - pathCString = ""; - - SecKeychainItemRef item = nil; - OSStatus returnStatus = SecKeychainAddInternetPassword(NULL, (UInt32)strlen(serverCString), serverCString, 0, NULL, (UInt32)strlen(usernameCString), usernameCString, (UInt32)strlen(pathCString), pathCString, port, protocol, kSecAuthenticationTypeDefault, (UInt32)strlen(passwordCString), (void *)passwordCString, &item); - - if (returnStatus != noErr || !item) - { - if (_logsErrors) - NSLog(@"Error (%@) - %s", NSStringFromSelector(_cmd), GetMacOSStatusErrorString(returnStatus)); - return nil; - } - return [EMInternetKeychainItem _internetKeychainItemWithCoreKeychainItem:item forServer:server username:username password:password path:path port:port protocol:protocol]; -} - -#pragma mark - -#pragma mark Internet Properties -@dynamic server; -- (NSString *)server -{ - @synchronized (self) - { - return [[mServer copy] autorelease]; - } -} - -- (void)setServer:(NSString *)newServer -{ - @synchronized (self) - { - if (mServer == newServer) - return; - - [mServer release]; - mServer = [newServer copy]; - - const char *newServerCString = [newServer UTF8String]; - [self _modifyAttributeWithTag:kSecServerItemAttr toBeValue:(void *)newServerCString ofLength:(UInt32)strlen(newServerCString)]; - } -} - -#pragma mark - -@dynamic path; -- (NSString *)path -{ - @synchronized (self) - { - return [[mPath copy] autorelease]; - } -} - -- (void)setPath:(NSString *)newPath -{ - if (mPath == newPath) - return; - - [mPath release]; - mPath = [newPath copy]; - - const char *newPathCString = [newPath UTF8String]; - [self _modifyAttributeWithTag:kSecPathItemAttr toBeValue:(void *)newPathCString ofLength:(UInt32)strlen(newPathCString)]; -} - -#pragma mark - -@dynamic port; -- (NSInteger)port -{ - @synchronized (self) - { - return mPort; - } -} - -- (void)setPort:(NSInteger)newPort -{ - @synchronized (self) - { - if (mPort == newPort) - return; - - mPort = newPort; - - UInt32 newPortValue = (UInt32)newPort; - [self _modifyAttributeWithTag:kSecPortItemAttr toBeValue:&newPortValue ofLength:sizeof(newPortValue)]; - } -} - -#pragma mark - -@dynamic protocol; -- (SecProtocolType)protocol -{ - @synchronized (self) - { - return mProtocol; - } -} - -- (void)setProtocol:(SecProtocolType)newProtocol -{ - @synchronized (self) - { - if (mProtocol == newProtocol) - return; - - mProtocol = newProtocol; - - [self _modifyAttributeWithTag:kSecProtocolItemAttr toBeValue:&newProtocol ofLength:sizeof(newProtocol)]; - } -} -@end \ No newline at end of file diff --git a/BitTicker/GraphView.h b/BitTicker/GraphView.h new file mode 100755 index 0000000..d13f640 --- /dev/null +++ b/BitTicker/GraphView.h @@ -0,0 +1,38 @@ +/* + BitTicker is Copyright 2012 Stephen Oliver + http://github.com/infincia + +*/ + +#import + +#import "CorePlot.h" + +#define TIMEFRAME_30MIN 100 +#define TIMEFRAME_12HOUR 200 +#define TIMEFRAME_24HOUR 300 + + +@interface GraphView : NSView { + + NSArray *_graphSource; + + + CPTXYGraph *_graph; + CPTGraphHostingView *hostingView; + CPTScatterPlot *dataSourceLinePlot; + + NSInteger timeframe; + +} + +-(IBAction)timeframeChanged:(id)sender; + +@property (nonatomic) NSInteger timeframe; + + +@property (retain) NSArray *graphSource; +@property (nonatomic, retain) CPTXYGraph *graph; + + +@end diff --git a/BitTicker/GraphView.m b/BitTicker/GraphView.m new file mode 100755 index 0000000..6fc709b --- /dev/null +++ b/BitTicker/GraphView.m @@ -0,0 +1,273 @@ +/* + BitTicker is Copyright 2012 Stephen Oliver + http://github.com/infincia + +*/ + +#import "GraphView.h" +#import "CorePlot.h" + + +@implementation GraphView +@synthesize graph = _graph; +@synthesize graphSource = _graphSource; +@synthesize timeframe; + +-(IBAction)timeframeChanged:(id)sender { + + NSSegmentedControl *control = (NSSegmentedControl*)sender; + + if ([control isSelectedForSegment:0]) { + + self.timeframe = TIMEFRAME_30MIN; + } + else if ([control isSelectedForSegment:1]) { + + self.timeframe = TIMEFRAME_12HOUR; + } + else if ([control isSelectedForSegment:2]) { + + self.timeframe = TIMEFRAME_24HOUR; + } + else { + // should never happen + NSAssert(1 == 0,@"Got invalid timeframe from control"); + } + [self configureTimeframe]; + +} + +-(void)awakeFromNib { + + NSInteger tf = [[NSUserDefaults standardUserDefaults] integerForKey:@"timeframe"]; + if (!tf) { + tf = TIMEFRAME_30MIN; + [[NSUserDefaults standardUserDefaults] setInteger:tf forKey:@"timeframe"]; + [[NSUserDefaults standardUserDefaults] synchronize]; + } + self.timeframe = tf; + _graph = [[CPTXYGraph alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)]; + CPTTheme *theme = [CPTTheme themeNamed:kCPTStocksTheme]; + [self.graph applyTheme:theme]; + + self.graph.cornerRadius = 6.0f; + + self.graph.paddingLeft = 0.0f; + self.graph.paddingTop = 0.0f; + self.graph.paddingRight = 0.0f; + self.graph.paddingBottom = 0.0f; + + + //graph.plotAreaFrame.masksToBorder = YES; + + self.graph.plotAreaFrame.cornerRadius = 6.0f; + self.graph.plotAreaFrame.paddingLeft = 45.0f; + self.graph.plotAreaFrame.paddingRight = 12.0f; + + self.graph.plotAreaFrame.paddingTop = 15.0f; + self.graph.plotAreaFrame.paddingBottom = 25.0f; + + //x axis line style + CPTMutableLineStyle *xlineStyle = [CPTMutableLineStyle lineStyle]; + xlineStyle.lineColor = [CPTColor redColor]; + xlineStyle.lineWidth = 1.0f; + + //y axis line style + CPTMutableLineStyle *ylineStyle = [CPTMutableLineStyle lineStyle]; + ylineStyle.lineColor = [CPTColor blueColor]; + ylineStyle.lineWidth = 1.0f; + + CPTXYAxisSet *axisSet = (CPTXYAxisSet *)self.graph.axisSet; + + // Tick locations + NSSet *majorTickLocations = [NSSet setWithObjects:[NSDecimalNumber zero], + [NSDecimalNumber numberWithUnsignedInteger:15], + nil]; + + + axisSet.xAxis.majorIntervalLength = CPTDecimalFromFloat(5); + axisSet.xAxis.minorTicksPerInterval = 1; + axisSet.xAxis.majorTickLineStyle = xlineStyle; + axisSet.xAxis.minorTickLineStyle = xlineStyle; + axisSet.xAxis.axisLineStyle = xlineStyle; + axisSet.xAxis.minorTickLength = 4.0f; + axisSet.xAxis.majorTickLength = 10.0f; + axisSet.xAxis.majorTickLocations = majorTickLocations; + axisSet.xAxis.title = nil; + + + // Text styles + CPTMutableTextStyle *axisTitleTextStyle = [CPTMutableTextStyle textStyle]; + axisTitleTextStyle.fontName = @"Helvetica-Bold"; + axisTitleTextStyle.color = [CPTColor whiteColor]; + axisTitleTextStyle.fontSize = 12.0; + + + axisSet.xAxis.titleTextStyle = axisTitleTextStyle; + axisSet.xAxis.titleOffset = 2.0f; + axisSet.xAxis.labelOffset = 0.0f; + axisSet.xAxis.labelingPolicy = CPTAxisLabelingPolicyAutomatic; + + + axisSet.yAxis.majorIntervalLength = CPTDecimalFromFloat(5); + axisSet.yAxis.minorTicksPerInterval = 0; + //axisSet.yAxis.majorTicksPerInterval = 1; + axisSet.yAxis.majorTickLineStyle = ylineStyle; + axisSet.yAxis.minorTickLineStyle = ylineStyle; + axisSet.yAxis.axisLineStyle = ylineStyle; + axisSet.yAxis.minorTickLength = 4.0f; + axisSet.yAxis.majorTickLength = 10.0f; + axisSet.yAxis.labelOffset = 3.0f; + + + + + NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; + [formatter setMaximumFractionDigits:0]; + [formatter setPositiveSuffix:@"$"]; + axisSet.yAxis.labelFormatter = formatter; + + + // Line plot with gradient fill + dataSourceLinePlot = [[CPTScatterPlot alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)]; + dataSourceLinePlot.identifier = @"Data Source Plot"; + CPTMutableLineStyle *graphlineStyle = [CPTMutableLineStyle lineStyle]; + graphlineStyle.lineColor = [CPTColor whiteColor]; + graphlineStyle.lineWidth = 1.0f; + dataSourceLinePlot.dataLineStyle = graphlineStyle; + + CPTColor *areaColor = [CPTColor colorWithComponentRed:1.0 green:1.0 blue:1.0 alpha:0.6]; + CPTGradient *areaGradient = [CPTGradient gradientWithBeginningColor:areaColor endingColor:[CPTColor whiteColor]]; + areaGradient.angle = 90.0f; + CPTFill *areaGradientFill = [CPTFill fillWithGradient:areaGradient]; + dataSourceLinePlot.areaFill = areaGradientFill; + dataSourceLinePlot.areaBaseValue = CPTDecimalFromDouble(0.0); + + dataSourceLinePlot.dataSource = self; + [self.graph addPlot:dataSourceLinePlot]; + + + hostingView = [[CPTGraphHostingView alloc] initWithFrame:NSRectFromCGRect(CGRectMake(0, 0, self.frame.size.width, self.frame.size.height))]; + + + hostingView.hostedGraph = self.graph; + + [self addSubview:hostingView]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateGraph:) name:@"MtGox-Ticker" object:nil]; + + //[graph reloadData]; + [self configureTimeframe]; + +} + + +- (void) updateGraph:(NSNotification *)notification { + self.graphSource = [[notification object] objectForKey:@"history"]; + + + + [self configureTimeframe]; +} + +-(void)configureTimeframe { + dispatch_async(dispatch_get_main_queue(), ^{ + CPTXYPlotSpace *plotSpace = (CPTXYPlotSpace *)self.graph.defaultPlotSpace; + CPTXYAxisSet *axisSet = (CPTXYAxisSet *)self.graph.axisSet; + + + float width_max = 0.0f; + float beginning = 0.0f; + float total = 1440.0f; + + + switch (self.timeframe) { + case TIMEFRAME_30MIN: + NSLog(@"Updating timeframe for 30 min"); + width_max = 30.0f; + //axisSet.xAxis.title = @"30 Mins"; + break; + case TIMEFRAME_12HOUR: + NSLog(@"Updating timeframe for 12 hours"); + width_max = 1220.0f; + //axisSet.xAxis.title = @"12 Hours"; + break; + case TIMEFRAME_24HOUR: + NSLog(@"Updating timeframe for 24 hours"); + width_max = 1440.0f; + //axisSet.xAxis.title = @"24 Hours"; + break; + default: + NSLog(@"NO TITLE"); + break; + } + + beginning = total - width_max; + NSArray *sortedArray = [self.graphSource sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) { + NSDictionary *one = (NSDictionary*)obj1; + NSDictionary *two = (NSDictionary*)obj2; + return [[one objectForKey:@"high"] compare:[two objectForKey:@"high"]]; + }]; + NSDictionary *highpoint = [sortedArray lastObject]; + NSInteger highestPrice = [[highpoint objectForKey:@"high"] intValue]; + //NSLog(@"Highest so far: %lu",highestRate); + + // Tick locations + NSSet *majorTickLocations = [NSSet setWithObjects:[NSDecimalNumber zero], + [NSDecimalNumber numberWithFloat:width_max], + nil]; + + axisSet.xAxis.majorTickLocations = majorTickLocations; + + + + axisSet.yAxis.majorTickLocations = [NSArray arrayWithObjects:[NSDecimalNumber numberWithFloat:0.0], [NSDecimalNumber numberWithInteger:highestPrice],[NSDecimalNumber numberWithInteger:highestPrice + 3],nil]; + + plotSpace.xRange = [CPTPlotRange plotRangeWithLocation:CPTDecimalFromFloat(beginning) + length:CPTDecimalFromFloat(width_max)]; + + plotSpace.yRange = [CPTPlotRange plotRangeWithLocation:CPTDecimalFromUnsignedInteger(0) + length:CPTDecimalFromUnsignedInteger(highestPrice + 3)]; + + NSLog(@"Beginning: %f, Max: %f",beginning, width_max); + [self.graph reloadData]; + }); +} + + +#pragma mark - +#pragma mark Plot Data Source Methods + +-(NSUInteger)numberOfRecordsForPlot:(CPTPlot *)plot { + NSLog(@"Count: %ld",self.graphSource.count); + return self.graphSource.count; +} + +-(NSNumber *)numberForPlot:(CPTPlot *)plot field:(NSUInteger)fieldEnum recordIndex:(NSUInteger)index { + NSDictionary *ticker = [self.graphSource objectAtIndex:index]; + + double val = [[ticker objectForKey:@"last"] doubleValue]; + double time = 1440 - index; + + //NSLog(@"Graphing price: %f at %f",val,time); + if( fieldEnum == CPTScatterPlotFieldX ) { + return [NSNumber numberWithDouble:time]; + } + else { + return [NSNumber numberWithDouble:val]; + } +} + + +#pragma mark - View lifecycle + + +#ifdef __IPHONE_OS_VERSION_MIN_REQUIRED + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + // Return YES for supported orientations + return YES; +} +#endif + +@end diff --git a/BitTicker/JSON.h b/BitTicker/JSON.h deleted file mode 100755 index 89f1593..0000000 --- a/BitTicker/JSON.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - Copyright (C) 2009-2010 Stig Brautaset. 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 author 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER OR CONTRIBUTORS 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. - */ - -/** - @mainpage A strict JSON parser and generator for Objective-C - - JSON (JavaScript Object Notation) is a lightweight data-interchange - format. This framework provides two apis for parsing and generating - JSON. One standard object-based and a higher level api consisting of - categories added to existing Objective-C classes. - - This framework does its best to be as strict as possible, both in what it accepts and what it generates. For example, it does not support trailing commas in arrays or objects. Nor does it support embedded comments, or anything else not in the JSON specification. This is considered a feature. - - @section Links - - @li Project home page. - @li Online version of the API documentation. - -*/ - - -// This setting of 1 is best if you copy the source into your project. -// The build transforms the 1 to a 0 when building the framework and static lib. - -#if 1 - -#import "SBJsonParser.h" -#import "SBJsonWriter.h" -#import "SBJsonStreamWriter.h" -#import "SBJsonStreamParser.h" -#import "SBJsonStreamParserAdapter.h" -#import "NSObject+JSON.h" - -#else - -#import -#import -#import -#import -#import -#import - -#endif diff --git a/BitTicker/Logo.icns b/BitTicker/Logo.icns old mode 100644 new mode 100755 diff --git a/BitTicker/MainPanelView.xib b/BitTicker/MainPanelView.xib deleted file mode 100644 index f77fe7d..0000000 --- a/BitTicker/MainPanelView.xib +++ /dev/null @@ -1,221 +0,0 @@ - - - - 1060 - 10J869 - 1306 - 1038.35 - 461.00 - - com.apple.InterfaceBuilder.CocoaPlugin - 1306 - - - YES - NSCustomView - NSCustomObject - - - YES - com.apple.InterfaceBuilder.CocoaPlugin - - - YES - - YES - - - - - YES - - MainWindowController - - - FirstResponder - - - NSApplication - - - - 268 - {650, 376} - - - - WindowPanel - - - - - YES - - - _mainPanel - - - - 2 - - - - - YES - - 0 - - - - - - -2 - - - File's Owner - - - -1 - - - First Responder - - - -3 - - - Application - - - 1 - - - - - - - YES - - YES - -1.IBPluginDependency - -2.IBPluginDependency - -3.IBPluginDependency - 1.IBPluginDependency - 1.WindowOrigin - 1.editorWindowContentRectSynchronizationRect - - - YES - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - {628, 654} - {{357, 416}, {480, 272}} - - - - YES - - - - - - YES - - - - - 2 - - - - YES - - MainWindowController - NSWindowController - - YES - - YES - _mainPanel - _mtGoxPanel - _tradeHillPanel - mainView - marketListTable - panelController - - - YES - NSView - NSView - NSView - NSView - NSTableView - NSViewController - - - - YES - - YES - _mainPanel - _mtGoxPanel - _tradeHillPanel - mainView - marketListTable - panelController - - - YES - - _mainPanel - NSView - - - _mtGoxPanel - NSView - - - _tradeHillPanel - NSView - - - mainView - NSView - - - marketListTable - NSTableView - - - panelController - NSViewController - - - - - IBProjectSource - ./Classes/MainWindowController.h - - - - WindowPanel - NSView - - IBProjectSource - ./Classes/WindowPanel.h - - - - - 0 - IBCocoaFramework - - com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3 - - - YES - 3 - - diff --git a/BitTicker/MainWindow.xib b/BitTicker/MainWindow.xib deleted file mode 100644 index 394eb47..0000000 --- a/BitTicker/MainWindow.xib +++ /dev/null @@ -1,606 +0,0 @@ - - - - 1060 - 10J869 - 1306 - 1038.35 - 461.00 - - com.apple.InterfaceBuilder.CocoaPlugin - 1306 - - - YES - NSView - NSSplitView - NSWindowTemplate - NSScrollView - NSTableView - NSTextFieldCell - NSCustomView - NSToolbar - NSTableColumn - NSScroller - NSCustomObject - - - YES - com.apple.InterfaceBuilder.CocoaPlugin - - - YES - - YES - - - - - YES - - MainWindowController - - - FirstResponder - - - NSApplication - - - 4103 - 2 - {{491, 310}, {800, 400}} - 1618477056 - BitTicker - NSWindow - - - C12D5163-4A1D-43FC-B333-0D03CB3309F0 - - - YES - YES - YES - NO - 1 - 1 - - YES - - - - - YES - - - YES - - - YES - - - - - 256 - - YES - - - 274 - - YES - - - 256 - - YES - - - 274 - - YES - - - 2304 - - YES - - - 4396 - {149, 376} - - - - YES - - - -2147483392 - {{223.984, 0}, {16, 17}} - - - YES - - 146 - 40 - 1000 - - 75628096 - 2048 - - - LucidaGrande - 11 - 3100 - - - 3 - MC4zMzMzMzI5ODU2AA - - - 6 - System - headerTextColor - - 3 - MAA - - - - - 69336641 - 2112 - MtGox - - LucidaGrande - 13 - 1044 - - MtGox - - - 6 - System - controlBackgroundColor - - 3 - MC42NjY2NjY2NjY3AA - - - - 1 - MCAwIDAAA - - - 3 - YES - YES - - - - 3 - 2 - - 1 - MC44ODkxOTc0ODgyIDAuODY2OTkyMDQ2NSAxAA - - - 6 - System - gridColor - - 3 - MC41AA - - - 17 - 314572800 - - - 0 - 15 - 0 - NO - 0 - - - {149, 376} - - - - - - 3 - MQA - - 4 - - - - -2147483392 - {{223.984, 17}, {15, 101.993}} - - - - - _doScroller: - 0.99734748010610075 - - - - -2147483392 - {{-100, -100}, {222.984, 15}} - - - - 1 - - _doScroller: - 0.61904761904761907 - - - {149, 376} - - - - 528 - - - - QSAAAEEgAABBmAAAQZgAAA - - - {149, 376} - - - - NSView - - - - 256 - {{150, 0}, {650, 376}} - - - - NSView - - - {{0, 24}, {800, 376}} - - - - YES - 2 - maindivider - - - {{7, 11}, {800, 400}} - - - - 2 - - {{0, 0}, {1280, 778}} - {1e+13, 1e+13} - NO - 24 - - - - - YES - - - window - - - - 54 - - - - mainView - - - - 77 - - - - delegate - - - - 87 - - - - dataSource - - - - 88 - - - - marketListTable - - - - 89 - - - - - YES - - 0 - - - - - - -2 - - - File's Owner - - - -1 - - - First Responder - - - -3 - - - Application - - - 1 - - - YES - - - - - Main Window - BitTicker - - - 2 - - - YES - - - - WindowView - - - 3 - - - YES - - - - - 64 - - - YES - - - - - - - 65 - - - YES - - - - - - 66 - - - YES - - - - - 81 - - - YES - - - - - - Scroll View - Table View - - - 82 - - - - - 83 - - - - - 84 - - - YES - - - - - - 85 - - - YES - - - - - - 86 - - - - - - - YES - - YES - -1.IBPluginDependency - -2.IBPluginDependency - -3.IBPluginDependency - 1.IBNSWindowAutoPositionCentersHorizontal - 1.IBNSWindowAutoPositionCentersVertical - 1.IBPluginDependency - 1.IBWindowTemplateEditedContentRect - 1.NSWindowTemplate.visibleAtLaunch - 1.WindowOrigin - 1.editorWindowContentRectSynchronizationRect - 2.IBPluginDependency - 3.IBPluginDependency - 64.IBPluginDependency - 65.IBPluginDependency - 66.IBPluginDependency - 81.IBPluginDependency - 82.IBPluginDependency - 83.IBPluginDependency - 84.IBPluginDependency - - - YES - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - - - com.apple.InterfaceBuilder.CocoaPlugin - {{357, 418}, {480, 270}} - - {196, 240} - {{357, 418}, {480, 270}} - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - - - - YES - - - - - - YES - - - - - 89 - - - - YES - - MainWindowController - NSWindowController - - YES - - YES - _mainPanel - _mtGoxPanel - _tradeHillPanel - mainView - marketListTable - panelController - - - YES - NSView - NSView - NSView - NSView - NSTableView - NSViewController - - - - YES - - YES - _mainPanel - _mtGoxPanel - _tradeHillPanel - mainView - marketListTable - panelController - - - YES - - _mainPanel - NSView - - - _mtGoxPanel - NSView - - - _tradeHillPanel - NSView - - - mainView - NSView - - - marketListTable - NSTableView - - - panelController - NSViewController - - - - - IBProjectSource - ./Classes/MainWindowController.h - - - - - 0 - IBCocoaFramework - - com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3 - - - YES - 3 - - diff --git a/BitTicker/MainWindowController.h b/BitTicker/MainWindowController.h deleted file mode 100644 index 7ad1285..0000000 --- a/BitTicker/MainWindowController.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// MainWindowController.h -// BitTicker -// -// Created by steve on 6/12/11. -// Copyright 2011 none. All rights reserved. -// - -#import - -@class SharedSettings; - -@interface MainWindowController : NSWindowController { -@private - SharedSettings *sharedSettings; - NSMutableDictionary *_viewDict; - IBOutlet NSTableView *marketListTable; - NSInteger selectedMarket; - NSView *_mainView; - IBOutlet NSView *_mtGoxPanel; - IBOutlet NSView *_tradeHillPanel; - IBOutlet NSView *_mainPanel; - NSView *_currentPanel; - IBOutlet NSViewController *panelController; -} - -@property (retain) NSMutableDictionary *viewDict; - -@property (retain) IBOutlet NSView *mainView; -@property (nonatomic,retain) NSTableView *marketListTable; -@property (retain) NSView *currentPanel; -@end diff --git a/BitTicker/MainWindowController.m b/BitTicker/MainWindowController.m deleted file mode 100644 index e501ed4..0000000 --- a/BitTicker/MainWindowController.m +++ /dev/null @@ -1,109 +0,0 @@ -// -// MainWindowController.m -// BitTicker -// -// Created by steve on 6/12/11. -// Copyright 2011 none. All rights reserved. -// - -#import "MainWindowController.h" - -#import "SharedSettings.h" - -#import "BitcoinMarket.h" -@implementation MainWindowController - -@synthesize mainView = _mainView; - -@synthesize currentPanel = _currentPanel; - -@synthesize marketListTable; - -@synthesize viewDict = _viewDict; - -- (id)init { - if (!(self=[super initWithWindowNibName:@"MainWindow"])) return self; - sharedSettings = [SharedSettings sharedSettingManager]; - panelController = [[NSViewController alloc] initWithNibName:@"PanelController" bundle:nil]; - [NSBundle loadNibNamed:@"MtGoxPanel" owner:self]; - [NSBundle loadNibNamed:@"TradeHillPanel" owner:self]; - [NSBundle loadNibNamed:@"MainPanelView" owner:self]; - self.viewDict = [NSMutableDictionary dictionaryWithCapacity:10]; - [self.viewDict setObject:_mtGoxPanel forKey:[sharedSettings stringForMarket:0]]; - [self.viewDict setObject:_tradeHillPanel forKey:[sharedSettings stringForMarket:1]]; - - return self; -} - -- (void)windowDidLoad { - [super windowDidLoad]; - [self.marketListTable reloadData]; - NSIndexSet *firstMarket = [NSIndexSet indexSetWithIndex:0]; - [self.marketListTable selectRowIndexes:firstMarket byExtendingSelection:NO]; -} - -#pragma mark - Table view - - -- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView { - return eNumberOfMarkets; -} -- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex { - NSString *rowString = [sharedSettings stringForMarket:rowIndex]; - return rowString; - -} - -- (void)tableView:(NSTableView *)aTableView willDisplayCell:(id)aCell forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex { - NSString *rowString = [sharedSettings stringForMarket:rowIndex]; - NSColor *txtColor; - if ([self.marketListTable selectedRow] == rowIndex) { - txtColor = [NSColor whiteColor]; - } - else { - txtColor = [NSColor blackColor]; - } - - NSFont *txtFont = [NSFont boldSystemFontOfSize:14]; - NSDictionary *txtDict = [NSDictionary dictionaryWithObjectsAndKeys: txtFont, NSFontAttributeName, txtColor, NSForegroundColorAttributeName, nil]; - NSAttributedString *attrStr = [[[NSAttributedString alloc] initWithString:rowString attributes:txtDict] autorelease]; - [aCell setAttributedStringValue:attrStr]; -} - -- (void)tableViewSelectionDidChange:(NSNotification *)aNotification { - selectedMarket = [self.marketListTable selectedRow]; - NSString *string = [sharedSettings stringForMarket:selectedMarket]; - NSView *panel = [self.viewDict objectForKey:string]; - if (selectedMarket == -1) { - NSLog(@"Load Main Panel: %@",_mainPanel); - [self.mainView replaceSubview:self.currentPanel with:_mainPanel]; - self.currentPanel = _mainPanel; - } - else { - if (self.currentPanel) { - NSLog(@"Replace: %@",self.currentPanel); - [self.mainView replaceSubview:self.currentPanel with:panel]; - self.currentPanel = panel; - } - else { - NSLog(@"New: %@",panel); - [self.mainView addSubview:panel]; - self.currentPanel = panel; - } - } -} -// Customize table, it's pretty static. User doesn't need to interact. -- (BOOL)tableView:(NSTableView *)aTableView shouldEditTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex { - return NO; -} -- (BOOL)tableView:(NSTableView *)tableView shouldReorderColumn:(NSInteger)columnIndex toColumn:(NSInteger)newColumnIndex { - return NO; -} - -#pragma mark - Actions - -- (void)dealloc { - [super dealloc]; -} - -@end diff --git a/BitTicker/MenuController.h b/BitTicker/MenuController.h deleted file mode 100644 index f8aa1b4..0000000 --- a/BitTicker/MenuController.h +++ /dev/null @@ -1,44 +0,0 @@ -// -// MenuController.h -// BitTicker -// -// Created by steve on 6/10/11. -// Copyright 2011 none. All rights reserved. -// - -#import -#import "SharedSettings.h" -@class StatusItemView; -@class Ticker; -@class BitcoinMarket; - -@interface MenuController : NSObject { - SharedSettings *sharedSettingManager; - NSMenu *trayMenu; - NSMutableDictionary *_viewDict; - StatusItemView *statusItemView; - NSStatusItem *_statusItem; - - NSNumberFormatter *currencyFormatter; - - NSNumber *_tickerValue; - - NSMenuItem *quitItem; - NSMenuItem *aboutItem; - NSMenuItem *settingsItem; - NSMenuItem *mainWindowItem; - NSMenuItem *refreshItem; - NSMenuItem *preferenceItem; - - NSInteger _currentMenuStop; - -} - --(void)createMenuForMarket:(BitcoinMarket*)market; - --(void)addSelectorItems; - -@property (retain, nonatomic) NSNumber *tickerValue; -@property () NSInteger currentMenuStop; -@property (retain) NSMutableDictionary *viewDict; -@end diff --git a/BitTicker/MenuController.m b/BitTicker/MenuController.m deleted file mode 100644 index 0c364b1..0000000 --- a/BitTicker/MenuController.m +++ /dev/null @@ -1,163 +0,0 @@ -// -// MenuController.m -// BitTicker -// -// Created by steve on 6/10/11. -// Copyright 2011 none. All rights reserved. -// - -#import "MenuController.h" -#import "Ticker.h" -#import "Wallet.h" -#import "StatusItemView.h" -#import "CustomMenuView.h" -#import "SharedSettings.h" - -#import "MtGoxMarketMenuView.h" - -#define MENU_VIEW_HEIGHT 105 -#define MENU_VIEW_WIDTH 180 - -@implementation MenuController - -@synthesize tickerValue = _tickerValue; -@synthesize currentMenuStop = _currentMenuStop; -@synthesize viewDict = _viewDict; - -- (id)init -{ - self = [super init]; - if (self) { - self.viewDict = [NSMutableDictionary dictionaryWithCapacity:10]; - sharedSettingManager = [SharedSettings sharedSettingManager]; - currencyFormatter = [[NSNumberFormatter alloc] init]; - currencyFormatter.numberStyle = NSNumberFormatterCurrencyStyle; - currencyFormatter.currencyCode = @"USD"; // TODO: Base on market currency - currencyFormatter.thousandSeparator = @","; // TODO: Base on local seperator for currency - currencyFormatter.alwaysShowsDecimalSeparator = YES; - currencyFormatter.hasThousandSeparators = YES; - currencyFormatter.minimumFractionDigits = 4; // TODO: Configurable - - - _statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength]; - [_statusItem retain]; - - statusItemView = [[StatusItemView alloc] init]; - statusItemView.statusItem = _statusItem; - [statusItemView setToolTip:NSLocalizedString(@"BitTicker", @"Status Item Tooltip")]; - - [_statusItem setView:statusItemView]; - - trayMenu = [[NSMenu alloc] initWithTitle:@"Ticker"]; - [statusItemView setMenu:trayMenu]; - self.currentMenuStop = 0; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveTicker:) name:@"MtGox-Ticker" object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveWallet:) name:@"MtGox-Wallet" object:nil]; - } - - return self; -} - --(void)createMenuForMarket:(BitcoinMarket*)market { - NSMenuItem *menuItem = [[NSMenuItem alloc] init]; - NSString *marketClass = NSStringFromClass([market class]); - - // NOTE: The next line creates an instance of a CustomMenuView - // subclass by looking at the name of the market passed in to - // this method. - CustomMenuView *menuView = [[NSClassFromString([NSString stringWithFormat:@"%@MenuView",marketClass]) alloc] initWithFrame:CGRectMake(0,self.currentMenuStop,MENU_VIEW_WIDTH,MENU_VIEW_HEIGHT)]; - [menuItem setView:menuView]; - [trayMenu addItem:menuItem]; - [trayMenu addItem:[NSMenuItem separatorItem]]; - [self.viewDict setObject:menuView forKey:NSStringFromClass([market class])]; - [menuItem release]; -} - --(void)addSelectorItems { - refreshItem = [trayMenu addItemWithTitle:@"Refresh" - action:@selector(refreshTicker:) - keyEquivalent:@"r"]; - settingsItem = [trayMenu addItemWithTitle: @"Settings" - action: @selector (showSettings:) - keyEquivalent: @"s"]; - mainWindowItem = [trayMenu addItemWithTitle: @"Main Window" - action: @selector (showMainWindow:) - keyEquivalent: @"m"]; - aboutItem = [trayMenu addItemWithTitle: @"About" - action: @selector (showAbout:) - keyEquivalent: @"a"]; - quitItem = [trayMenu addItemWithTitle: @"Quit" - action: @selector (quitProgram:) - keyEquivalent: @"q"]; -} - -- (void)dealloc { - [currencyFormatter release]; - [_statusItem release]; - [statusItemView release]; - [trayMenu release]; - [super dealloc]; -} - -#pragma mark Bitcoin market delegate -// A request failed for some reason, for example the API being down --(void)requestFailedWithError:(NSNotification *)notification { - NSError *error = [notification object]; - MSLog(@"Error: %@",error); -} - -// Request wasn't formatted as expected --(void)didReceiveInvalidResponse:(NSNotification *)notification { - NSData *data = [notification object]; - MSLog(@"Invalid response: %@",data); -} - --(void)didReceiveTicker:(NSNotification *)notification { - Ticker *ticker = [notification object]; - [statusItemView setTickerValue:ticker.last]; - self.tickerValue = ticker.last; - MSLog(@"Got mah ticker: %@",ticker); - - NSNumberFormatter *volumeFormatter = [[NSNumberFormatter alloc] init]; - volumeFormatter.numberStyle = NSNumberFormatterDecimalStyle; - volumeFormatter.hasThousandSeparators = YES; - CustomMenuView *view = [self.viewDict objectForKey:NSStringFromClass( [ticker.market class] ) ] ; - - - [view setHigh:[currencyFormatter stringFromNumber:ticker.high]]; - [view setLow:[currencyFormatter stringFromNumber:ticker.low]]; - [view setBuy:[currencyFormatter stringFromNumber:ticker.buy]]; - [view setSell:[currencyFormatter stringFromNumber:ticker.sell]]; - [view setLast:[currencyFormatter stringFromNumber:ticker.last]]; - [view setVol:[volumeFormatter stringFromNumber:ticker.volume]]; - - [volumeFormatter release]; -} - --(void)didReceiveRecentTradesData:(NSNotification *)notification { - NSArray *trades = [notification object]; - -} - --(void)didReceiveWallet:(NSNotification *)notification { - Wallet *wallet = [notification object]; - id view = [self.viewDict objectForKey:NSStringFromClass( [wallet.market class] ) ] ; - double btc = [wallet.btc doubleValue]; - double usd = [wallet.usd doubleValue]; - double last = [self.tickerValue doubleValue]; - - if (last == 0) { - //no last yet so cant multiply anyway - } - else { - NSNumber *BTCxRate = [NSNumber numberWithDouble:btc*last]; - [view setBtcusd:[currencyFormatter stringFromNumber:BTCxRate]]; - - NSNumber *walletUSD = [NSNumber numberWithDouble:[BTCxRate doubleValue] + usd]; - [view setWallet:[currencyFormatter stringFromNumber:walletUSD]]; - } - [view setBtc:[NSString stringWithFormat:@"%f.04",[wallet.btc floatValue]]]; - [view setUsd:[currencyFormatter stringFromNumber:wallet.usd]]; -} - -@end diff --git a/BitTicker/MtGox.h b/BitTicker/MtGox.h new file mode 100755 index 0000000..aae7329 --- /dev/null +++ b/BitTicker/MtGox.h @@ -0,0 +1,18 @@ +/* + BitTicker is Copyright 2012 Stephen Oliver + http://github.com/infincia + +*/ + +#import +#import "dispatch/dispatch.h" + +@interface MtGox : NSObject { + NSMutableArray *history; + NSThread *loopThread; + dispatch_queue_t queue; + +} + + +@end diff --git a/BitTicker/MtGox.m b/BitTicker/MtGox.m new file mode 100755 index 0000000..1634fc1 --- /dev/null +++ b/BitTicker/MtGox.m @@ -0,0 +1,69 @@ +/* + BitTicker is Copyright 2012 Stephen Oliver + http://github.com/infincia + +*/ + +#import "dispatch/dispatch.h" + +#import "MtGox.h" + +#import "AFJSONRequestOperation.h" + +static NSString *mtgox_ticker_url = @"https://mtgox.com/code/data/ticker.php"; + +@implementation MtGox + +-(id)init { + self = [super init]; + history = [NSMutableArray new]; + + queue = dispatch_queue_create("com.infincia.BitTicker.mtgox.queue", nil); + loopThread = [[NSThread alloc] initWithTarget:self selector:@selector(loop) object:nil]; + [loopThread setName:@"loop"]; + [loopThread start]; + + return self; +} + + +- (void) loop { + @autoreleasepool { + NSLog(@"New loop"); + while (1) { + NSLog(@"Firing loop cycle"); + if ([[NSThread currentThread] isCancelled]) { + NSLog(@"Thread canceled"); + return; + } + NSURL *url = [NSURL URLWithString:mtgox_ticker_url]; + NSURLRequest *request = [NSURLRequest requestWithURL:url]; + AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) { + NSLog(@"Request successful"); + + NSMutableDictionary *message = [NSMutableDictionary new]; + NSDictionary *tickerDict = [JSON objectForKey:@"ticker"]; + + // capped stack, new tickers pushed on top, store 1440 minutes of data + if ([history count] >= 1440) { + [history removeLastObject]; + } + [history insertObject:tickerDict atIndex:0]; + + + [message setObject:tickerDict forKey:@"ticker"]; + [message setObject:history forKey:@"history"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"MtGox-Ticker" object:message]; + NSLog(@"Dispatched ticker data"); + } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) { + // + NSLog(@"Request failed"); + }]; + [operation start]; + [NSThread sleepForTimeInterval:60]; + } + } +} + + +@end diff --git a/BitTicker/MtGoxMarket.h b/BitTicker/MtGoxMarket.h deleted file mode 100644 index d42f558..0000000 --- a/BitTicker/MtGoxMarket.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// MtGoxMarket.h -// Bitcoin Trader -// -// Created by Matt Stith on 4/30/11. -// Copyright 2011 Insomnia Addict. All rights reserved. -// - -#import -#import "BitcoinMarketDelegate.h" -#import "BitcoinMarket.h" - -@interface MtGoxMarket : BitcoinMarket { - -} - --(id)init; - -@end diff --git a/BitTicker/MtGoxMarket.m b/BitTicker/MtGoxMarket.m deleted file mode 100644 index 96968b9..0000000 --- a/BitTicker/MtGoxMarket.m +++ /dev/null @@ -1,121 +0,0 @@ -// -// MtGoxMarket.m -// Bitcoin Trader -// -// Created by Matt Stith on 4/30/11. -// Copyright 2011 Insomnia Addict. All rights reserved. -// - -#import "MtGoxMarket.h" - -#import "Trade.h" -#import "Ticker.h" -#import "Wallet.h" - -#define MTGOX_TICKER_URL @"https://mtgox.com/code/data/ticker.php" -#define MTGOX_TRADES_URL @"https://mtgox.com/code/data/getTrades.php" -#define MTGOX_MARKETDEPTH_URL @"https://mtgox.com/code/data/getDepth.php" -#define MTGOX_WALLET_URL @"https://mtgox.com/code/getFunds.php" - - -@interface MtGoxMarket (Private) --(NSString*)makeURLStringWithSuffix:(NSString*)suffix; -@end - -@implementation MtGoxMarket - --(id)init { - if (!(self = [super initWithDelegate:self])) return self; - _tickerURL = MTGOX_TICKER_URL; - _tradeURL = MTGOX_TRADES_URL; - _depthURL = MTGOX_MARKETDEPTH_URL; - _walletURL = MTGOX_WALLET_URL; - return self; -} - --(void)fetchRecentTrades { - MSLog(@"Fetching recent trades..."); - [self downloadJsonDataFromURL:[NSURL URLWithString:self.tradeURL] callback:@selector(didFetchRecentTrades:)]; -} - --(void)fetchTicker { - MSLog(@"Fetching ticker..."); - [self downloadJsonDataFromURL:[NSURL URLWithString:self.tickerURL] callback:@selector(didFetchTickerData:)]; -} - --(void)fetchMarketDepth { - MSLog(@"Fetching market depth..."); - [self downloadJsonDataFromURL:[NSURL URLWithString:self.depthURL] callback:@selector(didFetchMarketDepth:)]; -} - --(void)fetchWallet { - MSLog(@"Fetching wallet..."); - NSString *username = [sharedSettingManager usernameForMarket:eMarketMtGox]; - NSString *password = [sharedSettingManager passwordForMarket:eMarketMtGox]; - if ([username isEqualToString:@""] || username == nil) { - return; - } - if ([password isEqualToString:@""] || password == nil) { - return; - } - NSDictionary *post = [NSDictionary dictionaryWithObjectsAndKeys:username,@"name",password,@"pass",nil]; - [self downloadJsonDataFromURL:[NSURL URLWithString:self.walletURL] withPostData:post callback:@selector(didFetchWallet:)]; -} - --(void)didFetchRecentTrades:(NSArray*)tradeData { - NSMutableArray *trades = [NSMutableArray array]; - - for (NSDictionary *tradeDict in tradeData) { - Trade *newTrade = [[Trade alloc] init]; - newTrade.amount = [tradeDict objectForKey:@"amount"]; - newTrade.price = [tradeDict objectForKey:@"price"]; - newTrade.tid = [[tradeDict objectForKey:@"tid"] intValue]; - - newTrade.date = [NSDate dateWithTimeIntervalSince1970:[[tradeDict objectForKey:@"amount"] doubleValue]]; - - [trades addObject:newTrade]; - [newTrade release]; - } - MSLog(@"Got %i trades",trades.count); - - // Reverse the trades so 0 is the most recent - NSMutableArray *orderedTrades = [NSMutableArray array]; - NSEnumerator *reverseEnumerator = [trades reverseObjectEnumerator]; - id object; - - while ((object = [reverseEnumerator nextObject])) { - [orderedTrades addObject:object]; - } - - [[NSNotificationCenter defaultCenter] postNotificationName:@"MtGox-Trades" object:orderedTrades]; -} - --(void)didFetchTickerData:(NSDictionary*)tickerData { - NSDictionary *tickerDict = [tickerData objectForKey:@"ticker"]; - - Ticker *ticker = [[Ticker alloc] init]; - ticker.buy = [tickerDict objectForKey:@"buy"]; - ticker.sell = [tickerDict objectForKey:@"sell"]; - ticker.high = [tickerDict objectForKey:@"high"]; - ticker.low = [tickerDict objectForKey:@"low"]; - ticker.last = [tickerDict objectForKey:@"last"]; - ticker.volume = [tickerDict objectForKey:@"vol"]; - ticker.market = self; - [[NSNotificationCenter defaultCenter] postNotificationName:@"MtGox-Ticker" object:ticker]; - [ticker release]; -} - --(void)didFetchMarketDepth:(NSDictionary*)marketDepth { - MSLog(@"Got %i asks and %i bids",[[marketDepth objectForKey:@"asks"] count],[[marketDepth objectForKey:@"bids"] count]); - [[NSNotificationCenter defaultCenter] postNotificationName:@"MtGox-Depth" object:marketDepth]; -} - --(void)didFetchWallet:(NSDictionary *)dictwallet { - Wallet *newwallet = [[Wallet alloc] init]; - newwallet.btc = [dictwallet objectForKey:@"btcs"]; - newwallet.usd = [dictwallet objectForKey:@"usds"]; - newwallet.market = self; - [[NSNotificationCenter defaultCenter] postNotificationName:@"MtGox-Wallet" object:newwallet]; -} - -@end diff --git a/BitTicker/MtGoxMarketMenuView.h b/BitTicker/MtGoxMarketMenuView.h deleted file mode 100644 index e7e14d4..0000000 --- a/BitTicker/MtGoxMarketMenuView.h +++ /dev/null @@ -1,28 +0,0 @@ -// -// MtGoxMarketMenuView.h -// BitTicker -// -// Created by steve on 6/10/11. -// Copyright 2011 none. All rights reserved. -// - -#import -#import "CustomMenuView.h" - -@interface MtGoxMarketMenuView : CustomMenuView { - - NSTextField *highValue; - NSTextField *lowValue; - NSTextField *volValue; - NSTextField *buyValue; - NSTextField *sellValue; - NSTextField *lastValue; - - NSTextField *BTCValue; - NSTextField *BTCxUSDValue; - NSTextField *USDValue; - NSTextField *walletUSDValue; - -} - -@end diff --git a/BitTicker/MtGoxMarketMenuView.m b/BitTicker/MtGoxMarketMenuView.m deleted file mode 100644 index c397b35..0000000 --- a/BitTicker/MtGoxMarketMenuView.m +++ /dev/null @@ -1,302 +0,0 @@ -// -// MtGoxMenuView.m -// BitTicker -// -// Created by steve on 6/10/11. -// Copyright 2011 none. All rights reserved. -// - -#import "MtGoxMarketMenuView.h" -#define menuFont @"LucidaGrande" -#define menuFontSize 12 -#define headerFont @"LucidaGrande-Bold" -#define headerFontSize 13 - -#define menuHeight 15 -#define labelWidth 70 -#define headerWidth 120 -#define valueWidth 60 - -#define labelOffset 20 -#define valueOffset 110 - -@implementation MtGoxMarketMenuView - -- (id)initWithFrame:(NSRect)frame { - CGRect newFrame = frame; - newFrame.size.height = frame.size.height + 60; - self = [super initWithFrame:newFrame]; - if (self) { - NSTextField *sectionLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,153,headerWidth,menuHeight)]; - [sectionLabel setEditable:FALSE]; - [sectionLabel setBordered:NO]; - [sectionLabel setAlignment:NSLeftTextAlignment]; - [sectionLabel setBackgroundColor:[NSColor clearColor]]; - [sectionLabel setStringValue:@"Mt Gox"]; - [sectionLabel setTextColor:[NSColor blueColor]]; - [sectionLabel setFont:[NSFont fontWithName:headerFont size:headerFontSize]]; - [self addSubview:sectionLabel]; - [sectionLabel release]; - - highValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,135,valueWidth,menuHeight)]; - [highValue setEditable:FALSE]; - [highValue setBordered:NO]; - [highValue setAlignment:NSRightTextAlignment]; - [highValue setBackgroundColor:[NSColor clearColor]]; - [highValue setTextColor:[NSColor blackColor]]; - [highValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:highValue]; - - NSTextField *highLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,135,labelWidth,menuHeight)]; - [highLabel setEditable:FALSE]; - [highLabel setBordered:NO]; - [highLabel setAlignment:NSLeftTextAlignment]; - [highLabel setBackgroundColor:[NSColor clearColor]]; - [highLabel setStringValue:@"High:"]; - [highLabel setTextColor:[NSColor blackColor]]; - [highLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:highLabel]; - [highLabel release]; - - lowValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,120,valueWidth,menuHeight)]; - [lowValue setEditable:FALSE]; - [lowValue setBordered:NO]; - [lowValue setAlignment:NSRightTextAlignment]; - [lowValue setBackgroundColor:[NSColor clearColor]]; - [lowValue setTextColor:[NSColor blackColor]]; - [lowValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:lowValue]; - - NSTextField *lowLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,120,labelWidth,menuHeight)]; - [lowLabel setEditable:FALSE]; - [lowLabel setBordered:NO]; - [lowLabel setAlignment:NSLeftTextAlignment]; - [lowLabel setBackgroundColor:[NSColor clearColor]]; - [lowLabel setStringValue:@"Low:"]; - [lowLabel setTextColor:[NSColor blackColor]]; - [lowLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:lowLabel]; - [lowLabel release]; - - buyValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,105,valueWidth,menuHeight)]; - [buyValue setEditable:FALSE]; - [buyValue setBordered:NO]; - [buyValue setAlignment:NSRightTextAlignment]; - [buyValue setBackgroundColor:[NSColor clearColor]]; - [buyValue setTextColor:[NSColor blackColor]]; - [buyValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:buyValue]; - - NSTextField *buyLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,105,labelWidth,menuHeight)]; - [buyLabel setEditable:FALSE]; - [buyLabel setBordered:NO]; - [buyLabel setAlignment:NSLeftTextAlignment]; - [buyLabel setBackgroundColor:[NSColor clearColor]]; - [buyLabel setStringValue:@"Buy:"]; - [buyLabel setTextColor:[NSColor blackColor]]; - [buyLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:buyLabel]; - [buyLabel release]; - - sellValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,90,valueWidth,menuHeight)]; - [sellValue setEditable:FALSE]; - [sellValue setBordered:NO]; - [sellValue setAlignment:NSRightTextAlignment]; - [sellValue setBackgroundColor:[NSColor clearColor]]; - [sellValue setTextColor:[NSColor blackColor]]; - [sellValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:sellValue]; - - NSTextField *sellLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,90,labelWidth,menuHeight)]; - [sellLabel setEditable:FALSE]; - [sellLabel setBordered:NO]; - [sellLabel setAlignment:NSLeftTextAlignment]; - [sellLabel setBackgroundColor:[NSColor clearColor]]; - [sellLabel setStringValue:@"Sell:"]; - [sellLabel setTextColor:[NSColor blackColor]]; - [sellLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:sellLabel]; - [sellLabel release]; - - lastValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,75,valueWidth,menuHeight)]; - [lastValue setEditable:FALSE]; - [lastValue setBordered:NO]; - [lastValue setAlignment:NSRightTextAlignment]; - [lastValue setBackgroundColor:[NSColor clearColor]]; - [lastValue setTextColor:[NSColor blackColor]]; - [lastValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:lastValue]; - - NSTextField *lastLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,75,labelWidth,menuHeight)]; - [lastLabel setEditable:FALSE]; - [lastLabel setBordered:NO]; - [lastLabel setAlignment:NSLeftTextAlignment]; - [lastLabel setBackgroundColor:[NSColor clearColor]]; - [lastLabel setStringValue:@"Last:"]; - [lastLabel setTextColor:[NSColor blackColor]]; - [lastLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:lastLabel]; - [lastLabel release]; - - volValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,60,valueWidth,menuHeight)]; - [volValue setEditable:FALSE]; - [volValue setBordered:NO]; - [volValue setAlignment:NSRightTextAlignment]; - [volValue setBackgroundColor:[NSColor clearColor]]; - [volValue setTextColor:[NSColor blackColor]]; - [volValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:volValue]; - - NSTextField *volLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,60,labelWidth,menuHeight)]; - [volLabel setEditable:FALSE]; - [volLabel setBordered:NO]; - [volLabel setAlignment:NSLeftTextAlignment]; - [volLabel setBackgroundColor:[NSColor clearColor]]; - [volLabel setStringValue:@"Volume:"]; - [volLabel setTextColor:[NSColor blackColor]]; - [volLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:volLabel]; - [volLabel release]; - - BTCValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 45, valueWidth, menuHeight)]; - [BTCValue setEditable:FALSE]; - [BTCValue setBordered:NO]; - [BTCValue setAlignment:NSRightTextAlignment]; - [BTCValue setBackgroundColor:[NSColor clearColor]]; - [BTCValue setTextColor:[NSColor blackColor]]; - [BTCValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:BTCValue]; - - NSTextField *BTCLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,45,labelWidth,menuHeight)]; - [BTCLabel setEditable:FALSE]; - [BTCLabel setBordered:NO]; - [BTCLabel setAlignment:NSLeftTextAlignment]; - [BTCLabel setBackgroundColor:[NSColor clearColor]]; - [BTCLabel setStringValue:@"BTC:"]; - [BTCLabel setTextColor:[NSColor blackColor]]; - [BTCLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:BTCLabel]; - [BTCLabel release]; - - BTCxUSDValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 30, valueWidth, menuHeight)]; - [BTCxUSDValue setEditable:FALSE]; - [BTCxUSDValue setBordered:NO]; - [BTCxUSDValue setAlignment:NSRightTextAlignment]; - [BTCxUSDValue setBackgroundColor:[NSColor clearColor]]; - [BTCxUSDValue setTextColor:[NSColor blackColor]]; - [BTCxUSDValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:BTCxUSDValue]; - - NSTextField *BTCxUSDLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,30,labelWidth,menuHeight)]; - [BTCxUSDLabel setEditable:FALSE]; - [BTCxUSDLabel setBordered:NO]; - [BTCxUSDLabel setAlignment:NSLeftTextAlignment]; - [BTCxUSDLabel setBackgroundColor:[NSColor clearColor]]; - [BTCxUSDLabel setStringValue:@"BTC * Last:"]; - [BTCxUSDLabel setTextColor:[NSColor blackColor]]; - [BTCxUSDLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:BTCxUSDLabel]; - [BTCxUSDLabel release]; - - USDValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 15, valueWidth, menuHeight)]; - [USDValue setEditable:FALSE]; - [USDValue setBordered:NO]; - [USDValue setAlignment:NSRightTextAlignment]; - [USDValue setBackgroundColor:[NSColor clearColor]]; - [USDValue setTextColor:[NSColor blackColor]]; - [USDValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:USDValue]; - - NSTextField *USDLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,15,labelWidth,menuHeight)]; - [USDLabel setEditable:FALSE]; - [USDLabel setBordered:NO]; - [USDLabel setAlignment:NSLeftTextAlignment]; - [USDLabel setBackgroundColor:[NSColor clearColor]]; - [USDLabel setStringValue:@"USD:"]; - [USDLabel setTextColor:[NSColor blackColor]]; - [USDLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:USDLabel]; - [USDLabel release]; - - walletUSDValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 0, valueWidth, menuHeight)]; - [walletUSDValue setEditable:FALSE]; - [walletUSDValue setBordered:NO]; - [walletUSDValue setAlignment:NSRightTextAlignment]; - [walletUSDValue setBackgroundColor:[NSColor clearColor]]; - [walletUSDValue setTextColor:[NSColor blackColor]]; - [walletUSDValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:walletUSDValue]; - - NSTextField *walletLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,0,labelWidth,menuHeight)]; - [walletLabel setEditable:FALSE]; - [walletLabel setBordered:NO]; - [walletLabel setAlignment:NSLeftTextAlignment]; - [walletLabel setBackgroundColor:[NSColor clearColor]]; - [walletLabel setStringValue:@"Total:"]; - [walletLabel setTextColor:[NSColor blackColor]]; - [walletLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:walletLabel]; - [walletLabel release]; - } - - return self; -} - -- (id)init { - self = [super init]; - if (self) { - - } - - return self; -} - -#pragma mark - -#pragma mark Properties - --(void)setHigh:(NSString *)string { - [highValue setStringValue:string]; -} - --(void)setLow:(NSString *)string { - [lowValue setStringValue:string]; -} - --(void)setVol:(NSString *)string { - [volValue setStringValue:string]; -} - --(void)setBuy:(NSString *)string { - [buyValue setStringValue:string]; -} - --(void)setSell:(NSString *)string { - [sellValue setStringValue:string]; -} - --(void)setLast:(NSString *)string { - [lastValue setStringValue:string]; -} - --(void)setBtc:(NSString *)string { - [BTCValue setStringValue:string]; -} - --(void)setBtcusd:(NSString *)string { - [BTCxUSDValue setStringValue:string]; -} - --(void)setUsd:(NSString *)string { - [USDValue setStringValue:string]; -} - --(void)setWallet:(NSString *)string { - [walletUSDValue setStringValue:string]; -} - -- (void)dealloc -{ - [super dealloc]; -} - -@end diff --git a/BitTicker/MtGoxPanel.h b/BitTicker/MtGoxPanel.h deleted file mode 100644 index c3dfa91..0000000 --- a/BitTicker/MtGoxPanel.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// MtGoxPanel.h -// BitTicker -// -// Created by steve on 6/13/11. -// Copyright 2011 none. All rights reserved. -// - -#import -#import "WindowPanel.h" - -@interface MtGoxPanel : WindowPanel { -@private - -} - -@end diff --git a/BitTicker/MtGoxPanel.m b/BitTicker/MtGoxPanel.m deleted file mode 100644 index a162e59..0000000 --- a/BitTicker/MtGoxPanel.m +++ /dev/null @@ -1,31 +0,0 @@ -// -// MtGoxPanel.m -// BitTicker -// -// Created by steve on 6/13/11. -// Copyright 2011 none. All rights reserved. -// - -#import "MtGoxPanel.h" - - -@implementation MtGoxPanel - -- (id)initWithFrame:(NSRect)frame -{ - self = [super initWithFrame:frame]; - if (self) { - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveTicker:) name:@"MtGox-Ticker" object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveWallet:) name:@"MtGox-Wallet" object:nil]; - - } - - return self; -} - -- (void)dealloc -{ - [super dealloc]; -} - -@end diff --git a/BitTicker/MtGoxPanel.xib b/BitTicker/MtGoxPanel.xib deleted file mode 100644 index c01775a..0000000 --- a/BitTicker/MtGoxPanel.xib +++ /dev/null @@ -1,787 +0,0 @@ - - - - 1060 - 10J869 - 1306 - 1038.35 - 461.00 - - com.apple.InterfaceBuilder.CocoaPlugin - 1306 - - - YES - NSCustomView - NSTextField - NSTextFieldCell - NSCustomObject - - - YES - com.apple.InterfaceBuilder.CocoaPlugin - - - YES - - YES - - - - - YES - - MainWindowController - - - FirstResponder - - - NSApplication - - - - 274 - - YES - - - 268 - {{60, 117}, {96, 22}} - - - - YES - - -1804468671 - 272630784 - - - LucidaGrande - 13 - 1044 - - - YES - - 6 - System - textBackgroundColor - - 3 - MQA - - - - 6 - System - textColor - - 3 - MAA - - - - - - - 268 - {{60, 149}, {96, 22}} - - - - YES - - -1804468671 - 272630784 - - - - YES - - - - - - - 268 - {{60, 181}, {96, 22}} - - - - YES - - -1804468671 - 272630784 - - - - YES - - - - - - - 268 - {{60, 213}, {96, 22}} - - - - YES - - -1804468671 - 272630784 - - - - YES - - - - - - - 268 - {{60, 245}, {96, 22}} - - - - YES - - -1804468671 - 272630784 - - - - YES - - - - - - - 268 - {{17, 120}, {38, 17}} - - - - YES - - 68288064 - 272630784 - Low: - - - - 6 - System - controlColor - - 3 - MC42NjY2NjY2NjY3AA - - - - 6 - System - controlTextColor - - - - - - - 268 - {{15, 152}, {42, 17}} - - - - YES - - 68288064 - 272630784 - High: - - - - - - - - - 268 - {{19, 184}, {38, 17}} - - - - YES - - 68288064 - 272630784 - Sell: - - - - - - - - - 268 - {{19, 216}, {38, 17}} - - - - YES - - 68288064 - 272630784 - Buy: - - - - - - - - - 268 - {{17, 248}, {38, 17}} - - - - YES - - 68288064 - 272630784 - Last: - - - - - - - - - 268 - {{17, 312}, {142, 44}} - - - - YES - - 68288064 - 272630784 - Mt Gox - - LucidaGrande - 36 - 16 - - - - - - - - {650, 376} - - - - MtGoxPanel - - - - - YES - - - _mtGoxPanel - - - - 5 - - - - buyField - - - - 32 - - - - highField - - - - 33 - - - - lastField - - - - 34 - - - - lowField - - - - 35 - - - - sellField - - - - 36 - - - - - YES - - 0 - - - - - - -2 - - - File's Owner - - - -1 - - - First Responder - - - -3 - - - Application - - - 1 - - - YES - - - - - - - - - - - - - - Mt Gox Panel - - - 3 - - - YES - - - - - - 4 - - - - - 7 - - - YES - - - - - - 8 - - - - - 9 - - - YES - - - - - - 10 - - - - - 11 - - - YES - - - - - - 12 - - - - - 13 - - - YES - - - - - - 14 - - - - - 15 - - - YES - - - - - - 16 - - - - - 17 - - - YES - - - - - - 18 - - - - - 19 - - - YES - - - - - - 20 - - - - - 21 - - - YES - - - - - - 22 - - - - - 23 - - - YES - - - - - - 24 - - - - - 25 - - - YES - - - - - - 26 - - - - - - - YES - - YES - -1.IBPluginDependency - -2.IBPluginDependency - -3.IBPluginDependency - 1.IBPluginDependency - 1.WindowOrigin - 1.editorWindowContentRectSynchronizationRect - 10.IBPluginDependency - 11.IBPluginDependency - 12.IBPluginDependency - 13.IBPluginDependency - 14.IBPluginDependency - 15.IBPluginDependency - 16.IBPluginDependency - 17.IBPluginDependency - 18.IBPluginDependency - 19.IBPluginDependency - 20.IBPluginDependency - 21.IBPluginDependency - 22.IBPluginDependency - 23.IBPluginDependency - 24.IBPluginDependency - 25.IBPluginDependency - 26.IBPluginDependency - 3.IBPluginDependency - 4.IBPluginDependency - 7.IBPluginDependency - 8.IBPluginDependency - 9.IBPluginDependency - - - YES - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - {628, 654} - {{357, 416}, {480, 272}} - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - - - - YES - - - - - - YES - - - - - 36 - - - - YES - - MainWindowController - NSWindowController - - YES - - YES - _mainPanel - _mtGoxPanel - _tradeHillPanel - mainView - marketListTable - panelController - - - YES - NSView - NSView - NSView - NSView - NSTableView - NSViewController - - - - YES - - YES - _mainPanel - _mtGoxPanel - _tradeHillPanel - mainView - marketListTable - panelController - - - YES - - _mainPanel - NSView - - - _mtGoxPanel - NSView - - - _tradeHillPanel - NSView - - - mainView - NSView - - - marketListTable - NSTableView - - - panelController - NSViewController - - - - - IBProjectSource - ./Classes/MainWindowController.h - - - - MtGoxPanel - WindowPanel - - IBProjectSource - ./Classes/MtGoxPanel.h - - - - WindowPanel - NSView - - YES - - YES - buyField - highField - label - lastField - lowField - sellField - - - YES - NSTextField - NSTextField - NSTextField - NSTextField - NSTextField - NSTextField - - - - YES - - YES - buyField - highField - label - lastField - lowField - sellField - - - YES - - buyField - NSTextField - - - highField - NSTextField - - - label - NSTextField - - - lastField - NSTextField - - - lowField - NSTextField - - - sellField - NSTextField - - - - - IBProjectSource - ./Classes/WindowPanel.h - - - - - 0 - IBCocoaFramework - - com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3 - - - YES - 3 - - diff --git a/BitTicker/NSMutableArray+Shift.h b/BitTicker/NSMutableArray+Shift.h new file mode 100755 index 0000000..09e818f --- /dev/null +++ b/BitTicker/NSMutableArray+Shift.h @@ -0,0 +1,15 @@ +// +// NSMutableArray+Shift.h +// BitTicker +// +// Created by steve on 6/15/11. +// Copyright 2011 none. All rights reserved. +// + +#import + + +@interface NSMutableArray (ShiftExtension) +// returns the first element of self and removes it +-(id)shift; +@end \ No newline at end of file diff --git a/BitTicker/NSMutableArray+Shift.m b/BitTicker/NSMutableArray+Shift.m new file mode 100755 index 0000000..d3a0cae --- /dev/null +++ b/BitTicker/NSMutableArray+Shift.m @@ -0,0 +1,19 @@ +// +// NSMutableArray+Shift.m +// BitTicker +// +// Created by steve on 6/15/11. +// Copyright 2011 none. All rights reserved. +// + +#import "NSMutableArray+Shift.h" + + +@implementation NSMutableArray (ShiftExtension) +-(id)shift { + if([self count] < 1) return nil; + id obj = [[[self objectAtIndex:0] retain] autorelease]; + [self removeObjectAtIndex:0]; + return obj; +} +@end diff --git a/BitTicker/NSMutableDictionary+IntegerKeys.h b/BitTicker/NSMutableDictionary+IntegerKeys.h deleted file mode 100644 index d0c68c0..0000000 --- a/BitTicker/NSMutableDictionary+IntegerKeys.h +++ /dev/null @@ -1,16 +0,0 @@ -// -// NSMutableDictionary+IntegerKeys.h -// Bitcoin Trader -// -// Created by Matt Stith on 4/30/11. -// Copyright 2011 Insomnia Addict. All rights reserved. -// - -#import - - -@interface NSMutableDictionary (NSMutableDictionary_IntegerKeys) --(void)setObject:(id)anObject forIntegerKey:(NSInteger)aKey; --(id)objectForIntegerKey:(NSInteger)aKey; --(void)removeObjectForInteger:(NSInteger)aKey; -@end diff --git a/BitTicker/NSMutableDictionary+IntegerKeys.m b/BitTicker/NSMutableDictionary+IntegerKeys.m deleted file mode 100644 index 4073488..0000000 --- a/BitTicker/NSMutableDictionary+IntegerKeys.m +++ /dev/null @@ -1,24 +0,0 @@ -// -// NSMutableDictionary+IntegerKeys.m -// Bitcoin Trader -// -// Created by Matt Stith on 4/30/11. -// Copyright 2011 Insomnia Addict. All rights reserved. -// - -#import "NSMutableDictionary+IntegerKeys.h" - - -@implementation NSMutableDictionary (NSMutableDictionary_IntegerKeys) - --(void)setObject:(id)anObject forIntegerKey:(NSInteger)aKey { - [self setObject:anObject forKey:[NSNumber numberWithInteger:aKey]]; -} - --(id)objectForIntegerKey:(NSInteger)aKey { - return [self objectForKey:[NSNumber numberWithInteger:aKey]]; -} --(void)removeObjectForInteger:(NSInteger)aKey { - [self removeObjectForKey:[NSNumber numberWithInteger:aKey]]; -} -@end diff --git a/BitTicker/NSObject+JSON.h b/BitTicker/NSObject+JSON.h deleted file mode 100755 index 4a9e760..0000000 --- a/BitTicker/NSObject+JSON.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - Copyright (C) 2009 Stig Brautaset. 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 author 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER OR CONTRIBUTORS 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 - -#pragma mark JSON Writing - -/// Adds JSON generation to NSArray -@interface NSArray (NSArray_SBJsonWriting) - -/// Returns a string containing the receiver encoded in JSON. -- (NSString *)JSONRepresentation; - -@end - - -/// Adds JSON generation to NSArray -@interface NSDictionary (NSDictionary_SBJsonWriting) - -/// Returns a string containing the receiver encoded in JSON. -- (NSString *)JSONRepresentation; - -@end - -#pragma mark JSON Parsing - -/// Adds JSON parsing methods to NSString -@interface NSString (NSString_SBJsonParsing) - -/// Returns the NSDictionary or NSArray represented by the receiver's JSON representation, or nil on error -- (id)JSONValue; - -@end - - diff --git a/BitTicker/NSObject+JSON.m b/BitTicker/NSObject+JSON.m deleted file mode 100755 index 9c7ebaf..0000000 --- a/BitTicker/NSObject+JSON.m +++ /dev/null @@ -1,60 +0,0 @@ -/* - Copyright (C) 2009 Stig Brautaset. 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 author 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER OR CONTRIBUTORS 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 "NSObject+JSON.h" -#import "SBJsonWriter.h" -#import "SBJsonParser.h" - -@implementation NSObject (NSObject_SBJsonWriting) - -- (NSString *)JSONRepresentation { - SBJsonWriter *jsonWriter = [SBJsonWriter new]; - NSString *json = [jsonWriter stringWithObject:self]; - if (!json) - NSLog(@"-JSONRepresentation failed. Error is: %@", jsonWriter.error); - [jsonWriter release]; - return json; -} - -@end - - - -@implementation NSString (NSString_SBJsonParsing) - -- (id)JSONValue { - SBJsonParser *jsonParser = [SBJsonParser new]; - id repr = [jsonParser objectWithString:self]; - if (!repr) - NSLog(@"-JSONValue failed. Error is: %@", jsonParser.error); - [jsonParser release]; - return repr; -} - -@end diff --git a/BitTicker/RequestHandler.h b/BitTicker/RequestHandler.h deleted file mode 100644 index fd8ec99..0000000 --- a/BitTicker/RequestHandler.h +++ /dev/null @@ -1,34 +0,0 @@ -// -// RequestHandler.h -// Bitcoin Trader -// -// Created by Matt Stith on 4/30/11. -// Copyright 2011 Insomnia Addict. All rights reserved. -// -// Wrapper around NSURLRequest which will handle multiple requests -// a little more gracefully - -#import - -#import "RequestHandlerDelegate.h" -#import "NSMutableDictionary+IntegerKeys.h" - - -@interface RequestHandler : NSObject { - id _delegate; - NSInteger _currentTag; - NSMutableDictionary *_connectionData; - NSMutableDictionary *_connections; // For sanity in dealloc - NSInteger _activeRequests; -} - -// Initializer --(id)initWithDelegate:(id)requestDelegate; - -// Starts processing a connection, returns a tag --(NSInteger)startConnection:(NSURLRequest*)newRequest; - --(void)cancelAllRequests; - -@property (nonatomic, assign) iddelegate; -@end diff --git a/BitTicker/RequestHandler.m b/BitTicker/RequestHandler.m deleted file mode 100644 index 2176db5..0000000 --- a/BitTicker/RequestHandler.m +++ /dev/null @@ -1,103 +0,0 @@ -// -// RequestHandler.m -// Bitcoin Trader -// -// Created by Matt Stith on 4/30/11. -// Copyright 2011 Insomnia Addict. All rights reserved. -// - -#import "RequestHandler.h" - -#import "TaggedNSURLConnection.h" - -@implementation RequestHandler -@synthesize delegate=_delegate; - --(id)initWithDelegate:(id)requestDelegate { - if (!(self = [super init])) return self; - - if (!requestDelegate) { - MSLog(@"RequestHandler requires a delegate!"); - [self release]; - return false; - } - - self.delegate = requestDelegate; - _connectionData = [[NSMutableDictionary alloc] init]; - - _currentTag = 0; - _activeRequests = 0; - - return self; -} - --(void)dealloc { - [self cancelAllRequests]; - [_connections release]; - [_connectionData release]; - [super dealloc]; -} - --(NSInteger)startConnection:(NSURLRequest*)newRequest { - if (!newRequest) { - MSLog(@"No request passed to startConnection"); - return false; - } - - TaggedNSURLConnection *connection = [[TaggedNSURLConnection alloc] initWithRequest:newRequest delegate:self]; - - NSInteger newTag = _currentTag++; - connection.tag = newTag; - - [_connections setObject:connection forIntegerKey:newTag]; - [_connectionData setObject:[NSMutableData dataWithLength:0] forIntegerKey:newTag]; - [connection start]; - _activeRequests++; - - [connection release]; - - return newTag; -} --(void)cancelAllRequests { - for(NSString *key in _connections) { - TaggedNSURLConnection *connection = [_connections objectForKey:key]; - [connection cancel]; - } - _activeRequests = 0; -} -#pragma mark - NSURLConnection methods -- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { - TaggedNSURLConnection *taggedConnection = (TaggedNSURLConnection*)connection; - - NSMutableData *existingData = [_connectionData objectForIntegerKey:taggedConnection.tag]; - [existingData appendData:data]; -} - -- (void)connectionDidFinishLoading:(NSURLConnection *)connection { - TaggedNSURLConnection *taggedConnection = (TaggedNSURLConnection*)connection; - _activeRequests--; - - [_delegate request:taggedConnection.tag didFinishWithData:[_connectionData objectForIntegerKey:taggedConnection.tag]]; - - [_connections removeObjectForInteger:taggedConnection.tag]; - [_connectionData removeObjectForInteger:taggedConnection.tag]; - -} - -- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { - //TaggedNSURLConnection *taggedConnection = (TaggedNSURLConnection*)connection; - - //MSLog(@"Connection %i got response %@", taggedConnection.tag, response); -} - -- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { - TaggedNSURLConnection *taggedConnection = (TaggedNSURLConnection*)connection; - MSLog(@"Connection %i failed with error %@", taggedConnection.tag, error); - _activeRequests--; - [_delegate request:taggedConnection.tag didFailWithError:error]; - - [_connections removeObjectForInteger:taggedConnection.tag]; - [_connectionData removeObjectForInteger:taggedConnection.tag]; - -} -@end diff --git a/BitTicker/RequestHandlerDelegate.h b/BitTicker/RequestHandlerDelegate.h deleted file mode 100644 index 1a04ef0..0000000 --- a/BitTicker/RequestHandlerDelegate.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// RequestHandlerDelegate.h -// Bitcoin Trader -// -// Created by Matt Stith on 4/30/11. -// Copyright 2011 Insomnia Addict. All rights reserved. -// - -#import - -@protocol RequestHandlerDelegate - --(void)request:(NSInteger)tag didFinishWithData:(NSData*)data; --(void)request:(NSInteger)tag didFailWithError:(NSError*)error; -@end diff --git a/BitTicker/SBJsonParser.h b/BitTicker/SBJsonParser.h deleted file mode 100755 index 8f3d0a3..0000000 --- a/BitTicker/SBJsonParser.h +++ /dev/null @@ -1,113 +0,0 @@ -/* - Copyright (C) 2009 Stig Brautaset. 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 author 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER OR CONTRIBUTORS 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 - -/** - @brief The JSON parser class. - - JSON is mapped to Objective-C types in the following way: - - @li Null -> NSNull - @li String -> NSMutableString - @li Array -> NSMutableArray - @li Object -> NSMutableDictionary - @li Boolean -> NSNumber (initialised with -initWithBool:) - @li Number -> (NSNumber | NSDecimalNumber) - - Since Objective-C doesn't have a dedicated class for boolean values, these turns into NSNumber - instances. These are initialised with the -initWithBool: method, and - round-trip back to JSON properly. (They won't silently suddenly become 0 or 1; they'll be - represented as 'true' and 'false' again.) - - As an optimisation short JSON integers turn into NSNumber instances, while complex ones turn into NSDecimalNumber instances. - We can thus avoid any loss of precision as JSON allows ridiculously large numbers. - - */ - -@interface SBJsonParser : NSObject { - id value; - NSString *error; - NSUInteger depth, maxDepth; - -} - -/** - @brief The maximum recursing depth. - - Defaults to 512. If the input is nested deeper than this the input will be deemed to be - malicious and the parser returns nil, signalling an error. ("Nested too deep".) You can - turn off this security feature by setting the maxDepth value to 0. - */ -@property NSUInteger maxDepth; - -/** - @brief Return an error trace, or nil if there was no errors. - - Note that this method returns the trace of the last method that failed. - You need to check the return value of the call you're making to figure out - if the call actually failed, before you know call this method. - */ -@property(copy) NSString *error; - -/** - @brief Return the object represented by the given NSData object. - - The data *must* be UTF8 encoded. - @param data the data to parse. - - */ -- (id)objectWithData:(NSData*)data; - -/** - @brief Return the object represented by the given string - - Returns the object represented by the passed-in string or nil on error. The returned object can be - a string, number, boolean, null, array or dictionary. - - @param repr the json string to parse - */ -- (id)objectWithString:(NSString *)repr; - -/** - @brief Return the object represented by the given string - - Returns the object represented by the passed-in string or nil on error. The returned object can be - a string, number, boolean, null, array or dictionary. - - @param jsonText the json string to parse - @param error pointer to an NSError object to populate on error - */ - -- (id)objectWithString:(NSString*)jsonText - error:(NSError**)error; - -@end - - diff --git a/BitTicker/SBJsonParser.m b/BitTicker/SBJsonParser.m deleted file mode 100755 index 24ccb86..0000000 --- a/BitTicker/SBJsonParser.m +++ /dev/null @@ -1,120 +0,0 @@ -/* - Copyright (C) 2009,2010 Stig Brautaset. 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 author 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER OR CONTRIBUTORS 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 "SBJsonParser.h" -#import "SBJsonStreamParser.h" -#import "SBJsonStreamParserAdapter.h" - -@interface SBJsonParser () -@end - - -@implementation SBJsonParser - -@synthesize maxDepth; -@synthesize error; - -#pragma mark SBJsonStreamParserAdapterDelegate - -- (void)parser:(SBJsonStreamParser*)parser foundArray:(NSArray *)array { - value = [array retain]; -} - -- (void)parser:(SBJsonStreamParser*)parser foundObject:(NSDictionary *)dict { - value = [dict retain]; -} - -- (id)init { - self = [super init]; - if (self) - self.maxDepth = 512; - return self; -} - -- (void)dealloc { - [error release]; - [super dealloc]; -} - -#pragma mark Methods - -- (id)objectWithData:(NSData *)data { - - if (!data) { - self.error = @"Input was 'nil'"; - return nil; - } - - SBJsonStreamParserAdapter *adapter = [SBJsonStreamParserAdapter new]; - adapter.delegate = self; - - SBJsonStreamParser *parser = [SBJsonStreamParser new]; - parser.maxDepth = self.maxDepth; - parser.delegate = adapter; - - id retval = nil; - switch ([parser parse:data]) { - case SBJsonStreamParserComplete: - retval = [value autorelease]; - break; - - case SBJsonStreamParserWaitingForData: - self.error = @"Didn't find full object before EOF"; - break; - - case SBJsonStreamParserError: - self.error = parser.error; - break; - } - - - [adapter release]; - [parser release]; - - return retval; -} - -- (id)objectWithString:(NSString *)repr { - return [self objectWithData:[repr dataUsingEncoding:NSUTF8StringEncoding]]; -} - -- (id)objectWithString:(NSString*)repr error:(NSError**)error_ { - id tmp = [self objectWithString:repr]; - if (tmp) - return tmp; - - if (error_) { - NSDictionary *ui = [NSDictionary dictionaryWithObjectsAndKeys:error, NSLocalizedDescriptionKey, nil]; - *error_ = [NSError errorWithDomain:@"org.brautaset.json.parser.ErrorDomain" code:0 userInfo:ui]; - } - - return nil; -} - -@end diff --git a/BitTicker/SBJsonStreamParser.h b/BitTicker/SBJsonStreamParser.h deleted file mode 100755 index efc4e54..0000000 --- a/BitTicker/SBJsonStreamParser.h +++ /dev/null @@ -1,135 +0,0 @@ -/* - Copyright (c) 2010, Stig Brautaset. - 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 the author 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT - HOLDER OR CONTRIBUTORS 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 - -@class SBJsonTokeniser; -@class SBJsonStreamParser; -@class SBJsonStreamParserState; - -typedef enum { - SBJsonStreamParserComplete, - SBJsonStreamParserWaitingForData, - SBJsonStreamParserError, -} SBJsonStreamParserStatus; - - -/** - @brief Delegate for interacting directly with the stream parser - - You will most likely find it much more convenient to implement the - SBJsonStreamParserAdapterDelegate protocol instead. - */ -@protocol SBJsonStreamParserDelegate - -/// Called when object start is found -- (void)parserFoundObjectStart:(SBJsonStreamParser*)parser; - -/// Called when object key is found -- (void)parser:(SBJsonStreamParser*)parser foundObjectKey:(NSString*)key; - -/// Called when object end is found -- (void)parserFoundObjectEnd:(SBJsonStreamParser*)parser; - -/// Called when array start is found -- (void)parserFoundArrayStart:(SBJsonStreamParser*)parser; - -/// Called when array end is found -- (void)parserFoundArrayEnd:(SBJsonStreamParser*)parser; - -/// Called when a boolean value is found -- (void)parser:(SBJsonStreamParser*)parser foundBoolean:(BOOL)x; - -/// Called when a null value is found -- (void)parserFoundNull:(SBJsonStreamParser*)parser; - -/// Called when a number is found -- (void)parser:(SBJsonStreamParser*)parser foundNumber:(NSNumber*)num; - -/// Called when a string is found -- (void)parser:(SBJsonStreamParser*)parser foundString:(NSString*)string; - -@end - - -/** - @brief JSON Stream-parser class - - */ -@interface SBJsonStreamParser : NSObject { - BOOL multi; - id delegate; - SBJsonTokeniser *tokeniser; - SBJsonStreamParserState **states; - NSUInteger depth, maxDepth; - NSString *error; -} - -/** - @brief Expect multiple documents separated by whitespace - - If you set this property to true the parser will never return SBJsonStreamParserComplete. - Once an object is completed it will expect another object to follow, separated only by whitespace. - - @see The TwitterStream example project. - */ -@property BOOL multi; - -/// Set this to the object you want to receive messages -@property (assign) id delegate; - -/// The current depth in the json document (each [ and { each count 1) -@property (readonly) NSUInteger depth; - -/// The max depth to allow the parser to reach -@property NSUInteger maxDepth; - -/// @internal -@property (readonly) SBJsonStreamParserState **states; - -/// Holds the error after SBJsonStreamParserError was returned -@property (copy) NSString *error; - -/** - @brief Parse some JSON - - The JSON is assumed to be UTF8 encoded. This can be a full JSON document, or a part of one. - - @return - @li SBJsonStreamParserComplete if a full document was found - @li SBJsonStreamParserWaitingForData if a partial document was found and more data is required to complete it - @li SBJsonStreamParserError if an error occured. (See the error property for details in this case.) - - */ -- (SBJsonStreamParserStatus)parse:(NSData*)data; - -@end diff --git a/BitTicker/SBJsonStreamParser.m b/BitTicker/SBJsonStreamParser.m deleted file mode 100755 index b6315a1..0000000 --- a/BitTicker/SBJsonStreamParser.m +++ /dev/null @@ -1,273 +0,0 @@ -/* - Copyright (c) 2010, Stig Brautaset. - 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 the author 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT - HOLDER OR CONTRIBUTORS 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 "SBJsonStreamParser.h" -#import "SBJsonTokeniser.h" -#import "SBJsonStreamParserState.h" - - -@implementation SBJsonStreamParser - -@synthesize multi; -@synthesize error; -@synthesize delegate; -@dynamic maxDepth; -@synthesize states; -@synthesize depth; - -#pragma mark Housekeeping - -- (id)init { - self = [super init]; - if (self) { - tokeniser = [SBJsonTokeniser new]; - maxDepth = 512; - states = calloc(maxDepth, sizeof(SBJsonStreamParserState*)); - NSAssert(states, @"States not initialised"); - states[0] = [SBJsonStreamParserStateStart sharedInstance]; - } - return self; -} - -- (void)dealloc { - self.error = nil; - free(states); - [tokeniser release]; - [super dealloc]; -} - -#pragma mark Methods - -- (NSString*)tokenName:(sbjson_token_t)token { - switch (token) { - case sbjson_token_array_start: - return @"start of array"; - break; - - case sbjson_token_array_end: - return @"end of array"; - break; - - case sbjson_token_double: - case sbjson_token_integer: - return @"number"; - break; - - case sbjson_token_string: - case sbjson_token_string_encoded: - return @"string"; - break; - - case sbjson_token_true: - case sbjson_token_false: - return @"boolean"; - break; - - case sbjson_token_null: - return @"null"; - break; - - case sbjson_token_key_value_separator: - return @"key-value separator"; - break; - - case sbjson_token_separator: - return @"value separator"; - break; - - case sbjson_token_object_start: - return @"start of object"; - break; - - case sbjson_token_object_end: - return @"end of object"; - break; - - case sbjson_token_eof: - case sbjson_token_error: - break; - } - NSAssert(NO, @"Should not get here"); - return @""; -} - - -- (SBJsonStreamParserStatus)parse:(NSData *)data_ { - [tokeniser appendData:data_]; - - const char *buf; - NSUInteger len; - - for (;;) { - if ([states[depth] parserShouldStop:self]) - return [states[depth] parserShouldReturn:self]; - - sbjson_token_t tok = [tokeniser next]; - - switch (tok) { - case sbjson_token_eof: - return SBJsonStreamParserWaitingForData; - break; - - case sbjson_token_error: - states[depth] = kSBJsonStreamParserStateError; - self.error = tokeniser.error; - return SBJsonStreamParserError; - break; - - default: - - if (![states[depth] parser:self shouldAcceptToken:tok]) { - NSString *tokenName = [self tokenName:tok]; - NSString *stateName = [states[depth] name]; - NSLog(@"STATE: %@", states[depth]); - self.error = [NSString stringWithFormat:@"Token '%@' not expected %@", tokenName, stateName]; - states[depth] = kSBJsonStreamParserStateError; - return SBJsonStreamParserError; - } - - switch (tok) { - case sbjson_token_object_start: - if (depth >= maxDepth) { - self.error = [NSString stringWithFormat:@"Parser exceeded max depth of %lu", maxDepth]; - states[depth] = kSBJsonStreamParserStateError; - - } else { - [delegate parserFoundObjectStart:self]; - states[++depth] = kSBJsonStreamParserStateObjectStart; - } - break; - - case sbjson_token_object_end: - [states[--depth] parser:self shouldTransitionTo:tok]; - [delegate parserFoundObjectEnd:self]; - break; - - case sbjson_token_array_start: - if (depth >= maxDepth) { - self.error = [NSString stringWithFormat:@"Parser exceeded max depth of %lu", maxDepth]; - states[depth] = kSBJsonStreamParserStateError; - } else { - [delegate parserFoundArrayStart:self]; - states[++depth] = kSBJsonStreamParserStateArrayStart; - } - break; - - case sbjson_token_array_end: - [states[--depth] parser:self shouldTransitionTo:tok]; - [delegate parserFoundArrayEnd:self]; - break; - - case sbjson_token_separator: - case sbjson_token_key_value_separator: - [states[depth] parser:self shouldTransitionTo:tok]; - break; - - case sbjson_token_true: - [delegate parser:self foundBoolean:YES]; - [states[depth] parser:self shouldTransitionTo:tok]; - break; - - case sbjson_token_false: - [delegate parser:self foundBoolean:NO]; - [states[depth] parser:self shouldTransitionTo:tok]; - break; - - case sbjson_token_null: - [delegate parserFoundNull:self]; - [states[depth] parser:self shouldTransitionTo:tok]; - break; - - case sbjson_token_integer: - case sbjson_token_double: - if ([tokeniser getToken:&buf length:&len]) { - NSNumber *number; - if (tok == sbjson_token_integer && len < 12) { - char *e = NULL; - long l = strtol(buf, &e, 0); - NSAssert((e-buf) == len, @"unexpected length"); - number = [NSNumber numberWithLong:l]; - - } else if (tok == sbjson_token_double && len < 7) { - char *e = NULL; - double d = strtod(buf, &e); - NSAssert((e-buf) == len, @"unexpected length"); - number = [NSNumber numberWithDouble:d]; - - } else { - NSData *data = [NSData dataWithBytes:buf length:len]; - NSString *string = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]; - number = [[[NSDecimalNumber alloc] initWithString:string] autorelease]; - } - NSParameterAssert(number); - [delegate parser:self foundNumber:number]; - - } - [states[depth] parser:self shouldTransitionTo:tok]; - break; - - case sbjson_token_string: - case sbjson_token_string_encoded: { - NSString *string; - if (tok == sbjson_token_string) { - [tokeniser getToken:&buf length:&len]; - string = [[[NSString alloc] initWithBytes:buf+1 length:len-2 encoding:NSUTF8StringEncoding] autorelease]; - } else { - string = [tokeniser getDecodedStringToken]; - } - NSParameterAssert(string); - if ([states[depth] needKey]) - [delegate parser:self foundObjectKey:string]; - else - [delegate parser:self foundString:string]; - [states[depth] parser:self shouldTransitionTo:tok]; - break; - } - default: - break; - } - break; - } - } - return SBJsonStreamParserComplete; -} - -#pragma mark Private methods - -- (void)setMaxDepth:(NSUInteger)x { - NSAssert(x, @"maxDepth must be greater than 0"); - maxDepth = x; - states = realloc(states, x); - NSAssert(states, @"Failed to reallocate more memory for states"); -} - -@end diff --git a/BitTicker/SBJsonStreamParserAdapter.h b/BitTicker/SBJsonStreamParserAdapter.h deleted file mode 100755 index 3fcf571..0000000 --- a/BitTicker/SBJsonStreamParserAdapter.h +++ /dev/null @@ -1,88 +0,0 @@ -/* - Copyright (c) 2010, Stig Brautaset. - 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 the author 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT - HOLDER OR CONTRIBUTORS 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 -#import "SBJsonStreamParser.h" - -typedef enum { - SBJsonStreamParserAdapterNone, - SBJsonStreamParserAdapterArray, - SBJsonStreamParserAdapterObject, -} SBJsonStreamParserAdapterType; - -/** - @brief Delegate for getting objects & arrays from the stream parser adapter - - You will most likely find it much more convenient to implement this - than the raw SBJsonStreamParserDelegate protocol. - - @see The TwitterStream example project. - */ -@protocol SBJsonStreamParserAdapterDelegate - -/// Called when a JSON array is found -- (void)parser:(SBJsonStreamParser*)parser foundArray:(NSArray*)array; - -/// Called when a JSON object is found -- (void)parser:(SBJsonStreamParser*)parser foundObject:(NSDictionary*)dict; - -@end - - -@interface SBJsonStreamParserAdapter : NSObject { - id delegate; - NSUInteger skip, depth; - __weak NSMutableArray *array; - __weak NSMutableDictionary *dict; - NSMutableArray *keyStack; - NSMutableArray *stack; - - SBJsonStreamParserAdapterType currentType; -} - -/** - @brief How many levels to skip - - This is useful for parsing HUGE JSON documents, particularly if it consists of an - outer array and multiple objects. - - If you set this to N it will skip the outer N levels and call the -parser:foundArray: - or -parser:foundObject: methods for each of the inner objects, as appropriate. - - @see The StreamParserIntegrationTest.m file for examples -*/ -@property NSUInteger skip; - -/// Set this to the object you want to receive messages -@property (assign) id delegate; - -@end diff --git a/BitTicker/SBJsonStreamParserAdapter.m b/BitTicker/SBJsonStreamParserAdapter.m deleted file mode 100755 index 3b5e3dd..0000000 --- a/BitTicker/SBJsonStreamParserAdapter.m +++ /dev/null @@ -1,171 +0,0 @@ -/* - Copyright (c) 2010, Stig Brautaset. - 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 the author 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT - HOLDER OR CONTRIBUTORS 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 "SBJsonStreamParserAdapter.h" - -@interface SBJsonStreamParserAdapter () - -- (void)pop; -- (void)parser:(SBJsonStreamParser*)parser found:(id)obj; - -@end - - - -@implementation SBJsonStreamParserAdapter - -@synthesize delegate; -@synthesize skip; - -#pragma mark Housekeeping - -- (id)init { - self = [super init]; - if (self) { - keyStack = [[NSMutableArray alloc] initWithCapacity:32]; - stack = [[NSMutableArray alloc] initWithCapacity:32]; - - currentType = SBJsonStreamParserAdapterNone; - } - return self; -} - -- (void)dealloc { - [keyStack release]; - [stack release]; - [super dealloc]; -} - -#pragma mark Private methods - -- (void)pop { - [stack removeLastObject]; - array = nil; - dict = nil; - currentType = SBJsonStreamParserAdapterNone; - - id value = [stack lastObject]; - - if ([value isKindOfClass:[NSArray class]]) { - array = value; - currentType = SBJsonStreamParserAdapterArray; - } else if ([value isKindOfClass:[NSDictionary class]]) { - dict = value; - currentType = SBJsonStreamParserAdapterObject; - } -} - -- (void)parser:(SBJsonStreamParser*)parser found:(id)obj { - NSParameterAssert(obj); - - switch (currentType) { - case SBJsonStreamParserAdapterArray: - [array addObject:obj]; - break; - - case SBJsonStreamParserAdapterObject: - NSParameterAssert(keyStack.count); - [dict setObject:obj forKey:[keyStack lastObject]]; - [keyStack removeLastObject]; - break; - - case SBJsonStreamParserAdapterNone: - if ([obj isKindOfClass:[NSArray class]]) { - [delegate parser:parser foundArray:obj]; - } else { - [delegate parser:parser foundObject:obj]; - } - break; - - default: - break; - } -} - - -#pragma mark Delegate methods - -- (void)parserFoundObjectStart:(SBJsonStreamParser*)parser { - if (++depth > skip) { - dict = [[NSMutableDictionary new] autorelease]; - [stack addObject:dict]; - currentType = SBJsonStreamParserAdapterObject; - } -} - -- (void)parser:(SBJsonStreamParser*)parser foundObjectKey:(NSString*)key_ { - [keyStack addObject:key_]; -} - -- (void)parserFoundObjectEnd:(SBJsonStreamParser*)parser { - if (depth-- > skip) { - id value = [dict retain]; - [self pop]; - [self parser:parser found:value]; - [value release]; - } -} - -- (void)parserFoundArrayStart:(SBJsonStreamParser*)parser { - if (++depth > skip) { - array = [[NSMutableArray new] autorelease]; - [stack addObject:array]; - currentType = SBJsonStreamParserAdapterArray; - } -} - -- (void)parserFoundArrayEnd:(SBJsonStreamParser*)parser { - if (depth-- > skip) { - id value = [array retain]; - [self pop]; - [self parser:parser found:value]; - [value release]; - } -} - -- (void)parser:(SBJsonStreamParser*)parser foundBoolean:(BOOL)x { - [self parser:parser found:[NSNumber numberWithBool:x]]; -} - -- (void)parserFoundNull:(SBJsonStreamParser*)parser { - [self parser:parser found:[NSNull null]]; -} - -- (void)parser:(SBJsonStreamParser*)parser foundNumber:(NSNumber*)num { - [self parser:parser found:num]; -} - -- (void)parser:(SBJsonStreamParser*)parser foundString:(NSString*)string { - [self parser:parser found:string]; -} - -@end diff --git a/BitTicker/SBJsonStreamParserState.h b/BitTicker/SBJsonStreamParserState.h deleted file mode 100755 index 1103ed0..0000000 --- a/BitTicker/SBJsonStreamParserState.h +++ /dev/null @@ -1,89 +0,0 @@ -/* - Copyright (c) 2010, Stig Brautaset. - 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 the author 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT - HOLDER OR CONTRIBUTORS 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 - -#import "SBJsonTokeniser.h" -#import "SBJsonStreamParser.h" - -@interface SBJsonStreamParserState : NSObject - -- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token; -- (BOOL)parserShouldStop:(SBJsonStreamParser*)parser; -- (SBJsonStreamParserStatus)parserShouldReturn:(SBJsonStreamParser*)parser; -- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok; -- (BOOL)needKey; - -- (NSString*)name; - -@end - -@interface SBJsonStreamParserStateStart : SBJsonStreamParserState -+ (id)sharedInstance; -@end - -@interface SBJsonStreamParserStateComplete : SBJsonStreamParserState -@end - -@interface SBJsonStreamParserStateError : SBJsonStreamParserState -@end - - -@interface SBJsonStreamParserStateObjectStart : SBJsonStreamParserState -@end - -@interface SBJsonStreamParserStateObjectGotKey : SBJsonStreamParserState -@end - -@interface SBJsonStreamParserStateObjectSeparator : SBJsonStreamParserState -@end - -@interface SBJsonStreamParserStateObjectGotValue : SBJsonStreamParserState -@end - -@interface SBJsonStreamParserStateObjectNeedKey : SBJsonStreamParserState -@end - -@interface SBJsonStreamParserStateArrayStart : SBJsonStreamParserState -@end - -@interface SBJsonStreamParserStateArrayGotValue : SBJsonStreamParserState -@end - -@interface SBJsonStreamParserStateArrayNeedValue : SBJsonStreamParserState -@end - -extern SBJsonStreamParserStateStart *kSBJsonStreamParserStateStart; -extern SBJsonStreamParserStateError *kSBJsonStreamParserStateError; -extern SBJsonStreamParserStateObjectStart *kSBJsonStreamParserStateObjectStart; -extern SBJsonStreamParserStateArrayStart *kSBJsonStreamParserStateArrayStart; - diff --git a/BitTicker/SBJsonStreamParserState.m b/BitTicker/SBJsonStreamParserState.m deleted file mode 100755 index a25c664..0000000 --- a/BitTicker/SBJsonStreamParserState.m +++ /dev/null @@ -1,370 +0,0 @@ -/* - Copyright (c) 2010, Stig Brautaset. - 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 the author 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT - HOLDER OR CONTRIBUTORS 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 "SBJsonStreamParserState.h" -#import "SBJsonStreamParser.h" - -SBJsonStreamParserStateStart *kSBJsonStreamParserStateStart; -SBJsonStreamParserStateError *kSBJsonStreamParserStateError; -static SBJsonStreamParserStateComplete *kSBJsonStreamParserStateComplete; - -SBJsonStreamParserStateObjectStart *kSBJsonStreamParserStateObjectStart; -static SBJsonStreamParserStateObjectGotKey *kSBJsonStreamParserStateObjectGotKey; -static SBJsonStreamParserStateObjectSeparator *kSBJsonStreamParserStateObjectSeparator; -static SBJsonStreamParserStateObjectGotValue *kSBJsonStreamParserStateObjectGotValue; -static SBJsonStreamParserStateObjectNeedKey *kSBJsonStreamParserStateObjectNeedKey; - -SBJsonStreamParserStateArrayStart *kSBJsonStreamParserStateArrayStart; -static SBJsonStreamParserState *kSBJsonStreamParserStateArrayGotValue; -static SBJsonStreamParserState *kSBJsonStreamParserStateArrayNeedValue; - -@implementation SBJsonStreamParserState - -- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token { - return NO; -} - -- (BOOL)parserShouldStop:(SBJsonStreamParser*)parser { - return NO; -} - -- (SBJsonStreamParserStatus)parserShouldReturn:(SBJsonStreamParser*)parser { - return SBJsonStreamParserWaitingForData; -} - -- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok {} - -- (BOOL)needKey { - return NO; -} - -- (NSString*)name { - return @""; -} - -@end - -#pragma mark - - -@implementation SBJsonStreamParserStateStart - -+ (id)sharedInstance { - if (!kSBJsonStreamParserStateStart) { - kSBJsonStreamParserStateStart = [[SBJsonStreamParserStateStart alloc] init]; - kSBJsonStreamParserStateError = [[SBJsonStreamParserStateError alloc] init]; - kSBJsonStreamParserStateComplete = [[SBJsonStreamParserStateComplete alloc] init]; - kSBJsonStreamParserStateObjectStart = [[SBJsonStreamParserStateObjectStart alloc] init]; - kSBJsonStreamParserStateObjectGotKey = [[SBJsonStreamParserStateObjectGotKey alloc] init]; - kSBJsonStreamParserStateObjectSeparator = [[SBJsonStreamParserStateObjectSeparator alloc] init]; - kSBJsonStreamParserStateObjectGotValue = [[SBJsonStreamParserStateObjectGotValue alloc] init]; - kSBJsonStreamParserStateObjectNeedKey = [[SBJsonStreamParserStateObjectNeedKey alloc] init]; - kSBJsonStreamParserStateArrayStart = [[SBJsonStreamParserStateArrayStart alloc] init]; - kSBJsonStreamParserStateArrayGotValue = [[SBJsonStreamParserStateArrayGotValue alloc] init]; - kSBJsonStreamParserStateArrayNeedValue = [[SBJsonStreamParserStateArrayNeedValue alloc] init]; - } - return kSBJsonStreamParserStateStart; -} - -- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token { - return token == sbjson_token_array_start || token == sbjson_token_object_start; -} - -- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok { - - SBJsonStreamParserState *state = nil; - switch (tok) { - case sbjson_token_array_start: - state = kSBJsonStreamParserStateArrayStart; - break; - - case sbjson_token_object_start: - state = kSBJsonStreamParserStateObjectStart; - break; - - case sbjson_token_array_end: - case sbjson_token_object_end: - if (parser.multi) - state = parser.states[parser.depth]; - else - state = kSBJsonStreamParserStateComplete; - break; - - case sbjson_token_eof: - return; - - default: - state = kSBJsonStreamParserStateError; - break; - } - - - parser.states[parser.depth] = state; -} - -- (NSString*)name { return @"before outer-most array or object"; } - -@end - -#pragma mark - - -@implementation SBJsonStreamParserStateComplete - -- (NSString*)name { return @"after outer-most array or object"; } - -- (BOOL)parserShouldStop:(SBJsonStreamParser*)parser { - return YES; -} - -- (SBJsonStreamParserStatus)parserShouldReturn:(SBJsonStreamParser*)parser { - return SBJsonStreamParserComplete; -} - -@end - -#pragma mark - - -@implementation SBJsonStreamParserStateError - -- (NSString*)name { return @"in error"; } - -- (BOOL)parserShouldStop:(SBJsonStreamParser*)parser { - return YES; -} - -- (SBJsonStreamParserStatus)parserShouldReturn:(SBJsonStreamParser*)parser { - return SBJsonStreamParserError; -} - -@end - -#pragma mark - - -@implementation SBJsonStreamParserStateObjectStart - -- (NSString*)name { return @"at beginning of object"; } - -- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token { - switch (token) { - case sbjson_token_object_end: - case sbjson_token_string: - case sbjson_token_string_encoded: - return YES; - break; - default: - return NO; - break; - } -} - -- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok { - parser.states[parser.depth] = kSBJsonStreamParserStateObjectGotKey; -} - -- (BOOL)needKey { - return YES; -} - -@end - -#pragma mark - - -@implementation SBJsonStreamParserStateObjectGotKey - -- (NSString*)name { return @"after object key"; } - -- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token { - return token == sbjson_token_key_value_separator; -} - -- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok { - parser.states[parser.depth] = kSBJsonStreamParserStateObjectSeparator; -} - -@end - -#pragma mark - - -@implementation SBJsonStreamParserStateObjectSeparator - -- (NSString*)name { return @"as object value"; } - -- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token { - switch (token) { - case sbjson_token_object_start: - case sbjson_token_array_start: - case sbjson_token_true: - case sbjson_token_false: - case sbjson_token_null: - case sbjson_token_integer: - case sbjson_token_double: - case sbjson_token_string: - case sbjson_token_string_encoded: - return YES; - break; - - default: - return NO; - break; - } -} - -- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok { - parser.states[parser.depth] = kSBJsonStreamParserStateObjectGotValue; -} - -@end - -#pragma mark - - -@implementation SBJsonStreamParserStateObjectGotValue - -- (NSString*)name { return @"after object value"; } - -- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token { - switch (token) { - case sbjson_token_object_end: - case sbjson_token_separator: - return YES; - break; - default: - return NO; - break; - } -} - -- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok { - parser.states[parser.depth] = kSBJsonStreamParserStateObjectNeedKey; -} - - -@end - -#pragma mark - - -@implementation SBJsonStreamParserStateObjectNeedKey - -- (NSString*)name { return @"in place of object key"; } - -- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token { - switch (token) { - case sbjson_token_string: - case sbjson_token_string_encoded: - return YES; - break; - default: - return NO; - break; - } -} - -- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok { - parser.states[parser.depth] = kSBJsonStreamParserStateObjectGotKey; -} - -- (BOOL)needKey { - return YES; -} - -@end - -#pragma mark - - -@implementation SBJsonStreamParserStateArrayStart - -- (NSString*)name { return @"at array start"; } - -- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token { - switch (token) { - case sbjson_token_object_end: - case sbjson_token_key_value_separator: - case sbjson_token_separator: - return NO; - break; - - default: - return YES; - break; - } -} - -- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok { - parser.states[parser.depth] = kSBJsonStreamParserStateArrayGotValue; -} - -@end - -#pragma mark - - -@implementation SBJsonStreamParserStateArrayGotValue - -- (NSString*)name { return @"after array value"; } - - -- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token { - return token == sbjson_token_array_end || token == sbjson_token_separator; -} - -- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok { - if (tok == sbjson_token_separator) - parser.states[parser.depth] = kSBJsonStreamParserStateArrayNeedValue; -} - -@end - -#pragma mark - - -@implementation SBJsonStreamParserStateArrayNeedValue - -- (NSString*)name { return @"as array value"; } - - -- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token { - switch (token) { - case sbjson_token_array_end: - case sbjson_token_key_value_separator: - case sbjson_token_object_end: - case sbjson_token_separator: - return NO; - break; - - default: - return YES; - break; - } -} - -- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok { - parser.states[parser.depth] = kSBJsonStreamParserStateArrayGotValue; -} - -@end - diff --git a/BitTicker/SBJsonStreamWriter.h b/BitTicker/SBJsonStreamWriter.h deleted file mode 100755 index b7a0372..0000000 --- a/BitTicker/SBJsonStreamWriter.h +++ /dev/null @@ -1,163 +0,0 @@ -/* - Copyright (c) 2010, Stig Brautaset. - 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 the author 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT - HOLDER OR CONTRIBUTORS 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 - -/// Enable JSON writing for non-native objects -@interface NSObject (SBProxyForJson) - -/** - @brief Allows generation of JSON for otherwise unsupported classes. - - If you have a custom class that you want to create a JSON representation for you can implement this method in your class. It should return a representation of your object defined in terms of objects that can be translated into JSON. For example, a Person object might implement it like this: - - @code - - (id)proxyForJson { - return [NSDictionary dictionaryWithObjectsAndKeys: - name, @"name", - phone, @"phone", - email, @"email", - nil]; - } - @endcode - - */ -- (id)proxyForJson; - -@end - -@class SBJsonStreamWriterState; - -/** - @brief The Stream Writer class. - - Accepts a stream of messages and writes JSON of these to an internal data object. At any time you can retrieve the amount of data up to now, for example if you want to start sending data to a file before you're finished generating the JSON. - - A range of high-, mid- and low-level methods. You can mix and match calls to these. For example, you may want to call -writeArrayOpen to start an array and then repeatedly call -writeObject: with an object. - - In JSON the keys of an object must be strings. NSDictionary keys need not be, but attempting to convert an NSDictionary with non-string keys into JSON will result in an error. - - NSNumber instances created with the +initWithBool: method are converted into the JSON boolean "true" and "false" values, and vice versa. Any other NSNumber instances are converted to a JSON number the way you would expect. - - */ - -@interface SBJsonStreamWriter : NSObject { - NSString *error; - SBJsonStreamWriterState **states; - NSMutableData *data; - NSUInteger depth, maxDepth; - BOOL sortKeys, humanReadable; -} - -/** - @brief The data written to the stream so far. - - This is a mutable object. This means that you can write a chunk of its - contents to an NSOutputStream, then chop as many bytes as you wrote off - the beginning of the buffer. - */ -@property(readonly) NSMutableData *data; - -@property(readonly) NSObject **states; -@property(readonly) NSUInteger depth; - -/** - @brief The maximum recursing depth. - - Defaults to 512. If the input is nested deeper than this the input will be deemed to be - malicious and the parser returns nil, signalling an error. ("Nested too deep".) You can - turn off this security feature by setting the maxDepth value to 0. - */ -@property NSUInteger maxDepth; - -/** - @brief Whether we are generating human-readable (multiline) JSON. - - Set whether or not to generate human-readable JSON. The default is NO, which produces - JSON without any whitespace between tokens. If set to YES, generates human-readable - JSON with linebreaks after each array value and dictionary key/value pair, indented two - spaces per nesting level. - */ -@property BOOL humanReadable; - -/** - @brief Whether or not to sort the dictionary keys in the output. - - If this is set to YES, the dictionary keys in the JSON output will be in sorted order. - (This is useful if you need to compare two structures, for example.) The default is NO. - */ -@property BOOL sortKeys; - -/** - @brief Contains the error description after an error has occured. - */ -@property (copy) NSString *error; - -/** @brief Write an NSDictionary to the JSON stream. - */ -- (BOOL)writeObject:(NSDictionary*)dict; - -/** - @brief Write an NSArray to the JSON stream. - */ -- (BOOL)writeArray:(NSArray *)array; - -/// Start writing an Object to the stream -- (BOOL)writeObjectOpen; - -/// Close the current object being written -- (BOOL)writeObjectClose; - -/// Start writing an Array to the stream -- (BOOL)writeArrayOpen; - -/// Close the current Array being written -- (BOOL)writeArrayClose; - -/// Write a null to the stream -- (BOOL)writeNull; - -/// Write a boolean to the stream -- (BOOL)writeBool:(BOOL)x; - -/// Write a Number to the stream -- (BOOL)writeNumber:(NSNumber*)n; - -/// Write a String to the stream -- (BOOL)writeString:(NSString*)s; - -@end - -@interface SBJsonStreamWriter (Private) -- (BOOL)writeValue:(id)v; -@end - diff --git a/BitTicker/SBJsonStreamWriter.m b/BitTicker/SBJsonStreamWriter.m deleted file mode 100755 index 1ac124f..0000000 --- a/BitTicker/SBJsonStreamWriter.m +++ /dev/null @@ -1,372 +0,0 @@ -/* - Copyright (c) 2010, Stig Brautaset. - 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 the author 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT - HOLDER OR CONTRIBUTORS 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 "SBJsonStreamWriter.h" -#import "SBJsonStreamWriterState.h" - -static NSMutableDictionary *stringCache; -static NSDecimalNumber *notANumber; - -@implementation SBJsonStreamWriter - -@synthesize error; -@dynamic depth; -@dynamic maxDepth; -@synthesize states; -@synthesize humanReadable; -@synthesize sortKeys; - -+ (void)initialize { - notANumber = [NSDecimalNumber notANumber]; - stringCache = [NSMutableDictionary new]; -} - -#pragma mark Housekeeping - -- (id)init { - self = [super init]; - if (self) { - data = [[NSMutableData alloc] initWithCapacity:1024u]; - maxDepth = 512; - states = calloc(maxDepth, sizeof(SBJsonStreamWriterState*)); - NSAssert(states, @"States not initialised"); - - states[0] = [SBJsonStreamWriterStateStart sharedInstance]; - } - return self; -} - -- (void)dealloc { - self.error = nil; - free(states); - [data release]; - [super dealloc]; -} - -#pragma mark Methods - -- (BOOL)writeObject:(NSDictionary *)dict { - if (![self writeObjectOpen]) - return NO; - - NSArray *keys = [dict allKeys]; - if (sortKeys) - keys = [keys sortedArrayUsingSelector:@selector(compare:)]; - - for (id k in keys) { - if (![k isKindOfClass:[NSString class]]) { - self.error = [NSString stringWithFormat:@"JSON object key must be string: %@", k]; - return NO; - } - - if (![self writeString:k]) - return NO; - if (![self writeValue:[dict objectForKey:k]]) - return NO; - } - - return [self writeObjectClose]; -} - -- (BOOL)writeArray:(NSArray*)array { - if (![self writeArrayOpen]) - return NO; - for (id v in array) - if (![self writeValue:v]) - return NO; - return [self writeArrayClose]; -} - - -- (BOOL)writeObjectOpen { - SBJsonStreamWriterState *s = states[depth]; - if ([s isInvalidState:self]) return NO; - if ([s expectingKey:self]) return NO; - [s appendSeparator:self]; - if (humanReadable && depth) [s appendWhitespace:self]; - - if (maxDepth && ++depth > maxDepth) { - self.error = @"Nested too deep"; - return NO; - } - - states[depth] = kSBJsonStreamWriterStateObjectStart; - [data appendBytes:"{" length:1]; - return YES; -} - -- (BOOL)writeObjectClose { - SBJsonStreamWriterState *state = states[depth--]; - if ([state isInvalidState:self]) return NO; - if (humanReadable) [state appendWhitespace:self]; - [data appendBytes:"}" length:1]; - [states[depth] transitionState:self]; - return YES; -} - -- (BOOL)writeArrayOpen { - SBJsonStreamWriterState *s = states[depth]; - if ([s isInvalidState:self]) return NO; - if ([s expectingKey:self]) return NO; - [s appendSeparator:self]; - if (humanReadable && depth) [s appendWhitespace:self]; - - if (maxDepth && ++depth > maxDepth) { - self.error = @"Nested too deep"; - return NO; - } - - states[depth] = kSBJsonStreamWriterStateArrayStart; - [data appendBytes:"[" length:1]; - return YES; -} - -- (BOOL)writeArrayClose { - SBJsonStreamWriterState *state = states[depth--]; - if ([state isInvalidState:self]) return NO; - if ([state expectingKey:self]) return NO; - if (humanReadable) [state appendWhitespace:self]; - - [data appendBytes:"]" length:1]; - [states[depth] transitionState:self]; - return YES; -} - -- (BOOL)writeNull { - SBJsonStreamWriterState *s = states[depth]; - if ([s isInvalidState:self]) return NO; - if ([s expectingKey:self]) return NO; - [s appendSeparator:self]; - if (humanReadable) [s appendWhitespace:self]; - - [data appendBytes:"null" length:4]; - [s transitionState:self]; - return YES; -} - -- (BOOL)writeBool:(BOOL)x { - SBJsonStreamWriterState *s = states[depth]; - if ([s isInvalidState:self]) return NO; - if ([s expectingKey:self]) return NO; - [s appendSeparator:self]; - if (humanReadable) [s appendWhitespace:self]; - - if (x) - [data appendBytes:"true" length:4]; - else - [data appendBytes:"false" length:5]; - [s transitionState:self]; - return YES; -} - - -- (BOOL)writeValue:(id)o { - if ([o isKindOfClass:[NSDictionary class]]) { - return [self writeObject:o]; - - } else if ([o isKindOfClass:[NSArray class]]) { - return [self writeArray:o]; - - } else if ([o isKindOfClass:[NSString class]]) { - [self writeString:o]; - return YES; - - } else if ([o isKindOfClass:[NSNumber class]]) { - return [self writeNumber:o]; - - } else if ([o isKindOfClass:[NSNull class]]) { - return [self writeNull]; - - } else if ([o respondsToSelector:@selector(proxyForJson)]) { - return [self writeValue:[o proxyForJson]]; - - } - - self.error = [NSString stringWithFormat:@"JSON serialisation not supported for %@", [o class]]; - return NO; -} - -static const char *strForChar(int c) { - switch (c) { - case 0: return "\\u0000"; break; - case 1: return "\\u0001"; break; - case 2: return "\\u0002"; break; - case 3: return "\\u0003"; break; - case 4: return "\\u0004"; break; - case 5: return "\\u0005"; break; - case 6: return "\\u0006"; break; - case 7: return "\\u0007"; break; - case 8: return "\\b"; break; - case 9: return "\\t"; break; - case 10: return "\\n"; break; - case 11: return "\\u000b"; break; - case 12: return "\\f"; break; - case 13: return "\\r"; break; - case 14: return "\\u000e"; break; - case 15: return "\\u000f"; break; - case 16: return "\\u0010"; break; - case 17: return "\\u0011"; break; - case 18: return "\\u0012"; break; - case 19: return "\\u0013"; break; - case 20: return "\\u0014"; break; - case 21: return "\\u0015"; break; - case 22: return "\\u0016"; break; - case 23: return "\\u0017"; break; - case 24: return "\\u0018"; break; - case 25: return "\\u0019"; break; - case 26: return "\\u001a"; break; - case 27: return "\\u001b"; break; - case 28: return "\\u001c"; break; - case 29: return "\\u001d"; break; - case 30: return "\\u001e"; break; - case 31: return "\\u001f"; break; - case 34: return "\\\""; break; - case 92: return "\\\\"; break; - } - NSLog(@"FUTFUTFUT: -->'%c'<---", c); - return "FUTFUTFUT"; -} - -- (BOOL)writeString:(NSString*)string { - SBJsonStreamWriterState *s = states[depth]; - if ([s isInvalidState:self]) return NO; - [s appendSeparator:self]; - if (humanReadable) [s appendWhitespace:self]; - - NSMutableData *buf = [stringCache objectForKey:string]; - if (buf) { - [data appendBytes:[buf bytes] length:[buf length]]; - [s transitionState:self]; - return YES; - } - - NSUInteger len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; - const char *utf8 = [string UTF8String]; - NSUInteger written = 0, i = 0; - - buf = [NSMutableData dataWithCapacity:len * 1.1f]; - [buf appendBytes:"\"" length:1]; - - for (i = 0; i < len; i++) { - int c = utf8[i]; - BOOL isControlChar = c >= 0 && c < 32; - if (isControlChar || c == '"' || c == '\\') { - if (i - written) - [buf appendBytes:utf8 + written length:i - written]; - written = i + 1; - - const char *t = strForChar(c); - [buf appendBytes:t length:strlen(t)]; - } - } - - if (i - written) - [buf appendBytes:utf8 + written length:i - written]; - - [buf appendBytes:"\"" length:1]; - [data appendBytes:[buf bytes] length:[buf length]]; - [stringCache setObject:buf forKey:string]; - [s transitionState:self]; - return YES; -} - -- (BOOL)writeNumber:(NSNumber*)number { - if ((CFBooleanRef)number == kCFBooleanTrue || (CFBooleanRef)number == kCFBooleanFalse) - return [self writeBool:[number boolValue]]; - - SBJsonStreamWriterState *s = states[depth]; - if ([s isInvalidState:self]) return NO; - if ([s expectingKey:self]) return NO; - [s appendSeparator:self]; - if (humanReadable) [s appendWhitespace:self]; - - if ((CFNumberRef)number == kCFNumberPositiveInfinity) { - self.error = @"+Infinity is not a valid number in JSON"; - return NO; - - } else if ((CFNumberRef)number == kCFNumberNegativeInfinity) { - self.error = @"-Infinity is not a valid number in JSON"; - return NO; - - } else if ((CFNumberRef)number == kCFNumberNaN) { - self.error = @"NaN is not a valid number in JSON"; - return NO; - - } else if (number == notANumber) { - self.error = @"NaN is not a valid number in JSON"; - return NO; - } - - const char *objcType = [number objCType]; - char num[64]; - size_t len; - - switch (objcType[0]) { - case 'c': case 'i': case 's': case 'l': case 'q': - len = sprintf(num, "%lld", [number longLongValue]); - break; - case 'C': case 'I': case 'S': case 'L': case 'Q': - len = sprintf(num, "%llu", [number unsignedLongLongValue]); - break; - case 'f': case 'd': default: - if ([number isKindOfClass:[NSDecimalNumber class]]) { - char const *utf8 = [[number stringValue] UTF8String]; - [data appendBytes:utf8 length: strlen(utf8)]; - [s transitionState:self]; - return YES; - } - len = sprintf(num, "%g", [number doubleValue]); - break; - } - [data appendBytes:num length: len]; - [s transitionState:self]; - return YES; -} - -#pragma mark Private methods - -- (NSUInteger)depth { - return depth; -} - -- (void)setMaxDepth:(NSUInteger)x { - NSAssert(x, @"maxDepth must be greater than 0"); - maxDepth = x; - states = realloc(states, x); - NSAssert(states, @"Failed to reallocate more memory for states"); -} - -- (NSMutableData*)data { - return data; -} - -@end diff --git a/BitTicker/SBJsonStreamWriterState.h b/BitTicker/SBJsonStreamWriterState.h deleted file mode 100755 index 23310ab..0000000 --- a/BitTicker/SBJsonStreamWriterState.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - Copyright (c) 2010, Stig Brautaset. - 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 the author 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT - HOLDER OR CONTRIBUTORS 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 - -@class SBJsonStreamWriter; - -@interface SBJsonStreamWriterState : NSObject -- (BOOL)isInvalidState:(SBJsonStreamWriter*)writer; -- (void)appendSeparator:(SBJsonStreamWriter*)writer; -- (BOOL)expectingKey:(SBJsonStreamWriter*)writer; -- (void)transitionState:(SBJsonStreamWriter*)writer; -- (void)appendWhitespace:(SBJsonStreamWriter*)writer; -@end - -@interface SBJsonStreamWriterStateObjectStart : SBJsonStreamWriterState -@end - -@interface SBJsonStreamWriterStateObjectKey : SBJsonStreamWriterStateObjectStart -@end - -@interface SBJsonStreamWriterStateObjectValue : SBJsonStreamWriterState -@end - -@interface SBJsonStreamWriterStateArrayStart : SBJsonStreamWriterState -@end - -@interface SBJsonStreamWriterStateArrayValue : SBJsonStreamWriterState -@end - -@interface SBJsonStreamWriterStateStart : SBJsonStreamWriterState -+ (id)sharedInstance; -@end - -@interface SBJsonStreamWriterStateComplete : SBJsonStreamWriterState -@end - -@interface SBJsonStreamWriterStateError : SBJsonStreamWriterState -@end - -extern SBJsonStreamWriterStateStart *kSBJsonStreamWriterStateStart; -extern SBJsonStreamWriterStateComplete *kSBJsonStreamWriterStateComplete; -extern SBJsonStreamWriterStateError *kSBJsonStreamWriterStateError; -extern SBJsonStreamWriterStateObjectStart *kSBJsonStreamWriterStateObjectStart; -extern SBJsonStreamWriterStateArrayStart *kSBJsonStreamWriterStateArrayStart; - diff --git a/BitTicker/SBJsonStreamWriterState.m b/BitTicker/SBJsonStreamWriterState.m deleted file mode 100755 index 711efa0..0000000 --- a/BitTicker/SBJsonStreamWriterState.m +++ /dev/null @@ -1,132 +0,0 @@ -/* - Copyright (c) 2010, Stig Brautaset. - 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 the author 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT - HOLDER OR CONTRIBUTORS 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 "SBJsonStreamWriterState.h" -#import "SBJsonStreamWriter.h" - -// States -SBJsonStreamWriterStateStart *kSBJsonStreamWriterStateStart; -SBJsonStreamWriterStateComplete *kSBJsonStreamWriterStateComplete; -SBJsonStreamWriterStateError *kSBJsonStreamWriterStateError; - -SBJsonStreamWriterStateObjectStart *kSBJsonStreamWriterStateObjectStart; -static SBJsonStreamWriterStateObjectKey *kSBJsonStreamWriterStateObjectKey; -static SBJsonStreamWriterStateObjectValue *kSBJsonStreamWriterStateObjectValue; - -SBJsonStreamWriterStateArrayStart *kSBJsonStreamWriterStateArrayStart; -static SBJsonStreamWriterStateArrayValue *kSBJsonStreamWriterStateArrayValue; - -@implementation SBJsonStreamWriterState -- (BOOL)isInvalidState:(SBJsonStreamWriter*)writer { return NO; } -- (void)appendSeparator:(SBJsonStreamWriter*)writer {} -- (BOOL)expectingKey:(SBJsonStreamWriter*)writer { return NO; } -- (void)transitionState:(SBJsonStreamWriter *)writer {} -- (void)appendWhitespace:(SBJsonStreamWriter*)writer { - [writer.data appendBytes:"\n" length:1]; - for (int i = 0; i < writer.depth; i++) - [writer.data appendBytes:" " length:2]; -} -@end - -@implementation SBJsonStreamWriterStateObjectStart -- (void)transitionState:(SBJsonStreamWriter *)writer { - writer.states[writer.depth] = kSBJsonStreamWriterStateObjectValue; -} -- (BOOL)expectingKey:(SBJsonStreamWriter *)writer { - writer.error = @"JSON object key must be string"; - return YES; -} -@end - -@implementation SBJsonStreamWriterStateObjectKey -- (void)appendSeparator:(SBJsonStreamWriter *)writer { - [writer.data appendBytes:"," length:1]; -} -@end - -@implementation SBJsonStreamWriterStateObjectValue -- (void)appendSeparator:(SBJsonStreamWriter *)writer { - [writer.data appendBytes:":" length:1]; -} -- (void)transitionState:(SBJsonStreamWriter *)writer { - writer.states[writer.depth] = kSBJsonStreamWriterStateObjectKey; -} -- (void)appendWhitespace:(SBJsonStreamWriter *)writer { - [writer.data appendBytes:" " length:1]; -} -@end - -@implementation SBJsonStreamWriterStateArrayStart -- (void)transitionState:(SBJsonStreamWriter *)writer { - writer.states[writer.depth] = kSBJsonStreamWriterStateArrayValue; -} -@end - -@implementation SBJsonStreamWriterStateArrayValue -- (void)appendSeparator:(SBJsonStreamWriter *)writer { - [writer.data appendBytes:"," length:1]; -} -@end - -@implementation SBJsonStreamWriterStateStart - -+ (id)sharedInstance { - if (!kSBJsonStreamWriterStateStart) { - kSBJsonStreamWriterStateStart = [SBJsonStreamWriterStateStart new]; - kSBJsonStreamWriterStateComplete = [SBJsonStreamWriterStateComplete new]; - kSBJsonStreamWriterStateError = [SBJsonStreamWriterStateError new]; - kSBJsonStreamWriterStateObjectStart = [SBJsonStreamWriterStateObjectStart new]; - kSBJsonStreamWriterStateObjectKey = [SBJsonStreamWriterStateObjectKey new]; - kSBJsonStreamWriterStateObjectValue = [SBJsonStreamWriterStateObjectValue new]; - kSBJsonStreamWriterStateArrayStart = [SBJsonStreamWriterStateArrayStart new]; - kSBJsonStreamWriterStateArrayValue = [SBJsonStreamWriterStateArrayValue new]; - } - return kSBJsonStreamWriterStateStart; -} - -- (void)transitionState:(SBJsonStreamWriter *)writer { - writer.states[writer.depth] = kSBJsonStreamWriterStateComplete; -} -- (void)appendSeparator:(SBJsonStreamWriter *)writer { -} -@end - -@implementation SBJsonStreamWriterStateComplete -- (BOOL)isInvalidState:(SBJsonStreamWriter*)writer { - writer.error = @"Stream is closed"; - return YES; -} -@end - -@implementation SBJsonStreamWriterStateError -@end - diff --git a/BitTicker/SBJsonTokeniser.h b/BitTicker/SBJsonTokeniser.h deleted file mode 100755 index f00cfcb..0000000 --- a/BitTicker/SBJsonTokeniser.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - Copyright (c) 2010, Stig Brautaset. - 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 the author 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT - HOLDER OR CONTRIBUTORS 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 - -typedef enum { - sbjson_token_eof, - sbjson_token_error, - sbjson_token_object_start, - sbjson_token_key_value_separator, - sbjson_token_object_end, - sbjson_token_array_start, - sbjson_token_array_end, - sbjson_token_separator, - sbjson_token_string, - sbjson_token_string_encoded, - sbjson_token_integer, - sbjson_token_double, - sbjson_token_true, - sbjson_token_false, - sbjson_token_null, -} sbjson_token_t; - -@interface SBJsonTokeniser : NSObject { - NSUInteger tokenStart, tokenLength; - NSMutableData *buf; - const char *bufbytes; - NSUInteger bufbytesLength; - NSString *error; -} - -@property(copy, readonly) NSString *error; - -- (void)appendData:(NSData*)data; - -- (sbjson_token_t)next; -- (BOOL)getToken:(const char **)utf8 length:(NSUInteger*)length; -- (NSString*)getDecodedStringToken; - -@end diff --git a/BitTicker/SBJsonTokeniser.m b/BitTicker/SBJsonTokeniser.m deleted file mode 100755 index 99ca8b0..0000000 --- a/BitTicker/SBJsonTokeniser.m +++ /dev/null @@ -1,476 +0,0 @@ -/* - Copyright (c) 2010, Stig Brautaset. - 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 the author 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT - HOLDER OR CONTRIBUTORS 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 "SBJsonTokeniser.h" - - -#define isDigit(x) (*x >= '0' && *x <= '9') -#define skipDigits(x) while (isDigit(x)) x++ - - -@interface SBJsonTokeniser () - -@property (copy) NSString *error; - -- (void)skipWhitespace; - -- (sbjson_token_t)match:(const char *)utf8 ofLength:(NSUInteger)len andReturn:(sbjson_token_t)tok; -- (sbjson_token_t)matchString; -- (sbjson_token_t)matchNumber; - -- (int)parseUnicodeEscape:(const char *)bytes index:(NSUInteger *)index; - -@end - - -@implementation SBJsonTokeniser - -@synthesize error; - -#pragma mark Housekeeping - -- (id)init { - self = [super init]; - if (self) { - tokenStart = tokenLength = 0; - buf = [[NSMutableData alloc] initWithCapacity:4096]; - } - return self; -} - -- (void)dealloc { - self.error = nil; - [buf release]; - [super dealloc]; -} - -#pragma mark Methods - -- (void)appendData:(NSData *)data { - - // Remove previous NUL char - if (buf.length) - buf.length = buf.length - 1; - - if (tokenStart) { - // Remove stuff in the front of the offset - [buf replaceBytesInRange:NSMakeRange(0, tokenStart) withBytes:"" length:0]; - tokenStart = 0; - } - - [buf appendData:data]; - - // Append NUL byte to simplify logic - [buf appendBytes:"\0" length:1]; - - bufbytes = [buf bytes]; - bufbytesLength = [buf length]; -} - -- (BOOL)getToken:(const char **)utf8 length:(NSUInteger *)len { - if (!tokenLength) - return NO; - - *len = tokenLength; - *utf8 = bufbytes + tokenStart; - return YES; -} - -- (NSString*)getDecodedStringToken { - NSUInteger len; - const char *bytes; - [self getToken:&bytes length:&len]; - - len -= 1; - - NSMutableData *data = [NSMutableData dataWithCapacity:len * 1.1]; - - char c; - NSUInteger i = 1; -again: while (i < len) { - switch (c = bytes[i++]) { - case '\\': - switch (c = bytes[i++]) { - case '\\': - case '/': - case '"': - break; - - case 'b': - c = '\b'; - break; - - case 'n': - c = '\n'; - break; - - case 'r': - c = '\r'; - break; - - case 't': - c = '\t'; - break; - - case 'f': - c = '\f'; - break; - - case 'u': { - int hi = [self parseUnicodeEscape:bytes index:&i]; - if (hi < 0) - return nil; - - unichar ch = hi; - NSString *s = [NSString stringWithCharacters:&ch length:1]; - [data appendData:[s dataUsingEncoding:NSUTF8StringEncoding]]; - goto again; - break; - } - - default: - NSAssert(NO, @"Should never get here"); - break; - } - break; - - case 0 ... 0x1F: - self.error = @"Unescaped control chars"; - return nil; - break; - - default: - break; - } - [data appendBytes:&c length:1]; - } - - return [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]; -} - - -- (sbjson_token_t)next { - tokenStart += tokenLength; - tokenLength = 0; - - [self skipWhitespace]; - - switch (*(bufbytes + tokenStart)) { - case '\0': - return sbjson_token_eof; - break; - - case '[': - tokenLength = 1; - return sbjson_token_array_start; - break; - - case ']': - tokenLength = 1; - return sbjson_token_array_end; - break; - - case '{': - tokenLength = 1; - return sbjson_token_object_start; - break; - - case ':': - tokenLength = 1; - return sbjson_token_key_value_separator; - break; - - case '}': - tokenLength = 1; - return sbjson_token_object_end; - break; - - case ',': - tokenLength = 1; - return sbjson_token_separator; - break; - - case 'n': - return [self match:"null" ofLength:4 andReturn:sbjson_token_null]; - break; - - case 't': - return [self match:"true" ofLength:4 andReturn:sbjson_token_true]; - break; - - case 'f': - return [self match:"false" ofLength:5 andReturn:sbjson_token_false]; - break; - - case '"': - return [self matchString]; - break; - - case '-': - case '0' ... '9': - return [self matchNumber]; - break; - - case '+': - self.error = [NSString stringWithFormat:@"Leading + is illegal in numbers at offset %u", tokenStart]; - return sbjson_token_error; - break; - } - - self.error = [NSString stringWithFormat:@"Unrecognised leading character at offset %u", tokenStart]; - return sbjson_token_error; -} - -#pragma mark Private methods - -- (void)skipWhitespace { - while (tokenStart < bufbytesLength) { - switch (bufbytes[tokenStart]) { - case ' ': - case '\t': - case '\n': - case '\r': - case '\f': - case '\v': - tokenStart++; - break; - default: - return; - break; - } - } -} - -- (sbjson_token_t)match:(const char *)utf8 ofLength:(NSUInteger)len andReturn:(sbjson_token_t)tok { - if (buf.length - tokenStart - 1 < len) - return sbjson_token_eof; - - if (strncmp(bufbytes + tokenStart, utf8, len)) { - NSString *format = [NSString stringWithFormat:@"Expected '%%s' but found '%%.%us'.", len]; - self.error = [NSString stringWithFormat:format, utf8, bufbytes + tokenStart]; - return sbjson_token_error; - } - - tokenLength = len; - return tok; -} - - -- (int)decodeHexQuad:(const char *)hexQuad { - char c; - int ret = 0; - for (int i = 0; i < 4; i++) { - ret *= 16; - switch (c = hexQuad[i]) { - case '\0': - return -2; - break; - - case '0' ... '9': - ret += c - '0'; - break; - - case 'a' ... 'f': - ret += 10 + c - 'a'; - break; - - case 'A' ... 'F': - ret += 10 + c - 'A'; - break; - - default: - self.error = @"XXX illegal digit in hex char"; - return -1; - break; - } - } - return ret; -} - -- (int)parseUnicodeEscape:(const char *)bytes index:(NSUInteger *)index { - int hi = [self decodeHexQuad:bytes + *index]; - if (hi == -2) return -2; // EOF - if (hi < 0) { - self.error = @"Missing hex quad"; - return -1; - } - *index += 4; - - if (hi >= 0xd800) { // high surrogate char? - if (hi < 0xdc00) { // yes - expect a low char - int lo = -1; - if (bytes[(*index)++] == '\\' && bytes[(*index)++] == 'u') - lo = [self decodeHexQuad:bytes + *index]; - - if (lo < 0) { - self.error = @"Missing low character in surrogate pair"; - return -1; - } - *index += 4; - - if (lo < 0xdc00 || lo >= 0xdfff) { - self.error = @"Invalid low surrogate char"; - return -1; - } - - hi = (hi - 0xd800) * 0x400 + (lo - 0xdc00) + 0x10000; - - } else if (hi < 0xe000) { - self.error = @"Invalid high character in surrogate pair"; - return -1; - } - } - return hi; -} - - -- (sbjson_token_t)matchString { - sbjson_token_t ret = sbjson_token_string; - - const char *bytes = bufbytes + tokenStart; - NSUInteger idx = 1; - NSUInteger maxIdx = buf.length - 2 - tokenStart; - - while (idx <= maxIdx) { - switch (bytes[idx++]) { - case 0 ... 0x1F: - self.error = [NSString stringWithFormat:@"Unescaped control char 0x%0.2X", (int)bytes[idx-1]]; - return sbjson_token_error; - break; - - case '\\': - ret = sbjson_token_string_encoded; - - if (idx >= maxIdx) - return sbjson_token_eof; - - switch (bytes[idx++]) { - case 'b': - case 't': - case 'n': - case 'r': - case 'f': - case 'v': - case '"': - case '\\': - case '/': - // Valid escape sequence - break; - - case 'u': { - int ch = [self parseUnicodeEscape:bytes index:&idx]; - if (ch == -2) - return sbjson_token_eof; - if (ch == -1) - return sbjson_token_error; - break; - } - default: - self.error = [NSString stringWithFormat:@"Broken escape character at index %u in token starting at offset %u", idx-1, tokenStart]; - return sbjson_token_error; - break; - } - break; - - case '"': - tokenLength = idx; - return ret; - break; - - default: - // any other character - break; - } - } - - return sbjson_token_eof; -} - -- (sbjson_token_t)matchNumber { - - sbjson_token_t ret = sbjson_token_integer; - const char *c = bufbytes + tokenStart; - - if (*c == '-') { - c++; - if (!isDigit(c)) { - self.error = @"No digits after initial minus"; - return sbjson_token_error; - } - } - - if (*c == '0') { - c++; - if (isDigit(c)) { - self.error = [NSString stringWithFormat:@"Leading zero is illegal in number at offset %u", tokenStart]; - return sbjson_token_error; - } - } - - skipDigits(c); - - - if (*c == '.') { - ret = sbjson_token_double; - c++; - - if (!isDigit(c) && *c) { - self.error = [NSString stringWithFormat:@"No digits after decimal point at offset %u", tokenStart]; - return sbjson_token_error; - } - - skipDigits(c); - } - - if (*c == 'e' || *c == 'E') { - ret = sbjson_token_double; - c++; - - if (*c == '-' || *c == '+') - c++; - - if (!isDigit(c) && *c) { - self.error = [NSString stringWithFormat:@"No digits after exponent mark at offset %u", tokenStart]; - return sbjson_token_error; - } - - skipDigits(c); - } - - if (!*c) - return sbjson_token_eof; - - tokenLength = c - (bufbytes + tokenStart); - return ret; -} - -@end diff --git a/BitTicker/SBJsonWriter.h b/BitTicker/SBJsonWriter.h deleted file mode 100755 index 393b85f..0000000 --- a/BitTicker/SBJsonWriter.h +++ /dev/null @@ -1,132 +0,0 @@ -/* - Copyright (C) 2009 Stig Brautaset. 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 author 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER OR CONTRIBUTORS 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 - -/** - @brief The JSON writer class. - - Objective-C types are mapped to JSON types in the following way: - - @li NSNull -> Null - @li NSString -> String - @li NSArray -> Array - @li NSDictionary -> Object - @li NSNumber (-initWithBool:) -> Boolean - @li NSNumber -> Number - - In JSON the keys of an object must be strings. NSDictionary keys need - not be, but attempting to convert an NSDictionary with non-string keys - into JSON will throw an exception. - - NSNumber instances created with the +initWithBool: method are - converted into the JSON boolean "true" and "false" values, and vice - versa. Any other NSNumber instances are converted to a JSON number the - way you would expect. - - */ -@interface SBJsonWriter : NSObject { - -@protected - NSString *error; - NSUInteger maxDepth; - -@private - BOOL sortKeys, humanReadable; -} - -/** - @brief The maximum recursing depth. - - Defaults to 512. If the input is nested deeper than this the input will be deemed to be - malicious and the parser returns nil, signalling an error. ("Nested too deep".) You can - turn off this security feature by setting the maxDepth value to 0. - */ -@property NSUInteger maxDepth; - -/** - @brief Return an error trace, or nil if there was no errors. - - Note that this method returns the trace of the last method that failed. - You need to check the return value of the call you're making to figure out - if the call actually failed, before you know call this method. - */ -@property(copy) NSString *error; - -/** - @brief Whether we are generating human-readable (multiline) JSON. - - Set whether or not to generate human-readable JSON. The default is NO, which produces - JSON without any whitespace. (Except inside strings.) If set to YES, generates human-readable - JSON with linebreaks after each array value and dictionary key/value pair, indented two - spaces per nesting level. - */ -@property BOOL humanReadable; - -/** - @brief Whether or not to sort the dictionary keys in the output. - - If this is set to YES, the dictionary keys in the JSON output will be in sorted order. - (This is useful if you need to compare two structures, for example.) The default is NO. - */ -@property BOOL sortKeys; - -/** - @brief Return JSON representation for the given object. - - Returns a string containing JSON representation of the passed in value, or nil on error. - If nil is returned and @p error is not NULL, @p *error can be interrogated to find the cause of the error. - - @param value any instance that can be represented as JSON text. - */ -- (NSString*)stringWithObject:(id)value; - -/** - @brief Return JSON representation for the given object. - - Returns an NSData object containing JSON represented as UTF8 text, or nil on error. - - @param value any instance that can be represented as JSON text. - */ -- (NSData*)dataWithObject:(id)value; - -/** - @brief Return JSON representation (or fragment) for the given object. - - Returns a string containing JSON representation of the passed in value, or nil on error. - If nil is returned and @p error is not NULL, @p *error can be interrogated to find the cause of the error. - - @param value any instance that can be represented as a JSON fragment - @param error pointer to object to be populated with NSError on failure - - */- (NSString*)stringWithObject:(id)value - error:(NSError**)error; - - -@end diff --git a/BitTicker/SBJsonWriter.m b/BitTicker/SBJsonWriter.m deleted file mode 100755 index 8321786..0000000 --- a/BitTicker/SBJsonWriter.m +++ /dev/null @@ -1,102 +0,0 @@ -/* - Copyright (C) 2009 Stig Brautaset. 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 author 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER OR CONTRIBUTORS 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 "SBJsonWriter.h" -#import "SBJsonStreamWriter.h" - -@implementation SBJsonWriter - -@synthesize sortKeys; -@synthesize humanReadable; - -@synthesize error; -@synthesize maxDepth; - -- (id)init { - self = [super init]; - if (self) - self.maxDepth = 512; - return self; -} - -- (void)dealloc { - [error release]; - [super dealloc]; -} - -- (NSString*)stringWithObject:(id)value { - NSData *data = [self dataWithObject:value]; - if (data) - return [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]; - return nil; -} - -- (NSString*)stringWithObject:(id)value error:(NSError**)error_ { - NSString *tmp = [self stringWithObject:value]; - if (tmp) - return tmp; - - if (error_) { - NSDictionary *ui = [NSDictionary dictionaryWithObjectsAndKeys:error, NSLocalizedDescriptionKey, nil]; - *error_ = [NSError errorWithDomain:@"org.brautaset.json.parser.ErrorDomain" code:0 userInfo:ui]; - } - - return nil; -} - -- (NSData*)dataWithObject:(id)object { - SBJsonStreamWriter *streamWriter = [[[SBJsonStreamWriter alloc] init] autorelease]; - streamWriter.sortKeys = self.sortKeys; - streamWriter.maxDepth = self.maxDepth; - streamWriter.humanReadable = self.humanReadable; - - BOOL ok = NO; - if ([object isKindOfClass:[NSDictionary class]]) - ok = [streamWriter writeObject:object]; - - else if ([object isKindOfClass:[NSArray class]]) - ok = [streamWriter writeArray:object]; - - else if ([object respondsToSelector:@selector(proxyForJson)]) - return [self dataWithObject:[object proxyForJson]]; - else { - self.error = @"Not valid type for JSON"; - return nil; - } - - if (ok) - return streamWriter.data; - - self.error = streamWriter.error; - return nil; -} - - - -@end diff --git a/BitTicker/SettingsWindow.h b/BitTicker/SettingsWindow.h deleted file mode 100644 index 0d4e5b5..0000000 --- a/BitTicker/SettingsWindow.h +++ /dev/null @@ -1,33 +0,0 @@ -// -// SettingsWindowController.h -// BitTicker -// -// Created by Matt Stith on 6/4/11. -// Copyright 2011 none. All rights reserved. -// - -#import - -@class SharedSettings; - -@interface SettingsWindow : NSWindow { - IBOutlet NSTableView *marketListTable; - IBOutlet NSButton *enabledCheckbox; - IBOutlet NSTextField *marketLabel; - IBOutlet NSTextField *usernameField; - IBOutlet NSSecureTextField *passwordField; - IBOutlet NSTextField *apiKeyField; - -@private - -} - - -@property (nonatomic,retain) NSTableView *marketListTable; -@property (nonatomic,retain) NSButton *enabledCheckbox; -@property (nonatomic,retain) NSTextField *marketLabel; -@property (nonatomic,retain) NSTextField *usernameField; -@property (nonatomic,retain) NSSecureTextField *passwordField; -@property (nonatomic,retain) NSTextField *apiKeyField; - -@end diff --git a/BitTicker/SettingsWindow.m b/BitTicker/SettingsWindow.m deleted file mode 100644 index 79fded0..0000000 --- a/BitTicker/SettingsWindow.m +++ /dev/null @@ -1,22 +0,0 @@ -// -// SettingsWindowController.m -// BitTicker -// -// Created by Matt Stith on 6/4/11. -// Copyright 2011 none. All rights reserved. -// - -#import "SettingsWindow.h" - -#import "SharedSettings.h" -#import "BitcoinMarket.h" - -@implementation SettingsWindow -@synthesize marketListTable; -@synthesize enabledCheckbox; -@synthesize marketLabel; -@synthesize usernameField; -@synthesize passwordField; -@synthesize apiKeyField; - -@end diff --git a/BitTicker/SettingsWindow.xib b/BitTicker/SettingsWindow.xib deleted file mode 100644 index 6bb9052..0000000 --- a/BitTicker/SettingsWindow.xib +++ /dev/null @@ -1,975 +0,0 @@ - - - - 1060 - 10J869 - 1306 - 1038.35 - 461.00 - - com.apple.InterfaceBuilder.CocoaPlugin - 1306 - - - YES - NSScroller - NSTableHeaderView - NSButton - NSScrollView - NSTextFieldCell - NSButtonCell - NSSecureTextFieldCell - NSTableView - NSCustomObject - NSSecureTextField - NSView - NSWindowTemplate - NSTableColumn - NSTextField - - - YES - com.apple.InterfaceBuilder.CocoaPlugin - - - YES - - YES - - - - - YES - - SettingsWindowController - - - FirstResponder - - - NSApplication - - - 15 - 2 - {{196, 240}, {540, 165}} - 544735232 - Settings - SettingsWindow - - - - 256 - - YES - - - 272 - - YES - - - 2304 - - YES - - - 256 - {103, 107} - - - - YES - - - 256 - {103, 17} - - - - - - - - -2147483392 - {{224, 0}, {16, 17}} - - - - - - YES - - 100 - 40 - 1000 - - 75628096 - 2048 - Market - - LucidaGrande - 11 - 3100 - - - 3 - MC4zMzMzMzI5ODU2AA - - - 6 - System - headerTextColor - - 3 - MAA - - - - - 337772096 - 2048 - Text Cell - - LucidaGrande - 13 - 1044 - - - - 6 - System - controlBackgroundColor - - 3 - MC42NjY2NjY2NjY3AA - - - - 6 - System - controlTextColor - - - - 3 - YES - YES - - - - 3 - 2 - - 3 - MQA - - - 6 - System - gridColor - - 3 - MC41AA - - - 17 - -700448768 - - - 4 - 15 - 0 - YES - 0 - - - {{1, 17}, {103, 107}} - - - - - - 4 - - - - -2147483392 - {{224, 17}, {15, 102}} - - - - - _doScroller: - 37 - 0.1947367936372757 - - - - -2147483392 - {{1, 78}, {103, 15}} - - - - 1 - - _doScroller: - 1 - 0.99038461538461542 - - - - 2304 - - YES - - - {{1, 0}, {103, 17}} - - - - - - 4 - - - - {{20, 20}, {105, 125}} - - - - 562 - - - - - - QSAAAEEgAABBmAAAQZgAAA - - - - 268 - {{131, 121}, {72, 18}} - - - - YES - - -2080244224 - 0 - Enabled - - - 1211912703 - 2 - - NSImage - NSSwitch - - - NSSwitch - - - - 200 - 25 - - - - - 268 - {{206, 116}, {257, 29}} - - - - YES - - 68288064 - 272630784 - MtGox - - LucidaGrande - 24 - 16 - - - - 6 - System - controlColor - - - - - - - - 268 - {{130, 89}, {71, 17}} - - - - YES - - 68288064 - 272630784 - Username: - - - - - - - - - 268 - {{133, 57}, {68, 17}} - - - - YES - - 68288064 - 272630784 - Password: - - - - - - - - - 268 - {{145, 29}, {68, 17}} - - - - YES - - 68288064 - 272630784 - API Key: - - - - - - - - - 268 - {{206, 86}, {314, 22}} - - - - YES - - -1804468671 - 272630784 - - - - YES - - 6 - System - textBackgroundColor - - - - 6 - System - textColor - - - - - - - 268 - {{206, 54}, {314, 22}} - - - - 1 - YES - - 343014976 - 272630848 - - - - YES - - - - YES - NSAllRomanInputSourcesLocaleIdentifier - - - - - - 268 - {{206, 24}, {314, 22}} - - - - 2 - YES - - -1804468671 - 272630784 - - - - YES - - - - - - {{7, 11}, {540, 165}} - - - - - {{0, 0}, {1280, 778}} - {1e+13, 1e+13} - - - - - YES - - - enabledCheckbox - - - - 43 - - - - marketLabel - - - - 44 - - - - marketListTable - - - - 45 - - - - passwordField - - - - 46 - - - - usernameField - - - - 47 - - - - window - - - - 50 - - - - delegate - - - - 51 - - - - dataSource - - - - 52 - - - - delegate - - - - 53 - - - - delegate - - - - 54 - - - - enabledDidChange: - - - - 56 - - - - delegate - - - - 61 - - - - apiKeyField - - - - 62 - - - - - YES - - 0 - - - - - - -2 - - - File's Owner - - - -1 - - - First Responder - - - -3 - - - Application - - - 1 - - - YES - - - - Window - Settings - - - 2 - - - YES - - - - - - - - - - - - - - 3 - - - YES - - - - - - - - - 4 - - - - - 5 - - - - - 6 - - - - - 7 - - - YES - - - - - - 8 - - - YES - - - - - - 11 - - - - - 12 - - - YES - - - - - - 13 - - - - - 14 - - - YES - - - - - - 15 - - - - - 16 - - - YES - - - - - - 17 - - - - - 18 - - - YES - - - - - - 19 - - - - - 20 - - - YES - - - - - - 21 - - - - - 36 - - - YES - - - - - - 37 - - - - - 57 - - - YES - - - - StaticText - API Key: - - - 58 - - - - - 59 - - - YES - - - - - - 60 - - - - - - - YES - - YES - -1.IBPluginDependency - -2.IBPluginDependency - -3.IBPluginDependency - 1.IBPluginDependency - 1.IBWindowTemplateEditedContentRect - 1.NSWindowTemplate.visibleAtLaunch - 1.WindowOrigin - 1.editorWindowContentRectSynchronizationRect - 12.IBPluginDependency - 13.IBPluginDependency - 14.IBPluginDependency - 15.IBPluginDependency - 16.IBPluginDependency - 17.IBPluginDependency - 18.IBPluginDependency - 19.IBPluginDependency - 2.IBPluginDependency - 20.IBAttributePlaceholdersKey - 20.IBPluginDependency - 21.IBPluginDependency - 3.IBPluginDependency - 36.IBAttributePlaceholdersKey - 36.IBPluginDependency - 37.IBPluginDependency - 4.IBPluginDependency - 5.IBPluginDependency - 57.IBPluginDependency - 58.IBPluginDependency - 59.IBAttributePlaceholdersKey - 59.IBPluginDependency - 6.IBPluginDependency - 60.IBPluginDependency - 7.IBPluginDependency - - - YES - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - {{357, 418}, {480, 270}} - - {196, 240} - {{357, 418}, {480, 270}} - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - - YES - - - - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - - YES - - - - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - - YES - - - - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - - - - YES - - - - - - YES - - - - - 62 - - - - YES - - SettingsWindow - NSWindow - - YES - - YES - apiKeyField - enabledCheckbox - marketLabel - marketListTable - passwordField - usernameField - - - YES - NSTextField - NSButton - NSTextField - NSTableView - NSSecureTextField - NSTextField - - - - YES - - YES - apiKeyField - enabledCheckbox - marketLabel - marketListTable - passwordField - usernameField - - - YES - - apiKeyField - NSTextField - - - enabledCheckbox - NSButton - - - marketLabel - NSTextField - - - marketListTable - NSTableView - - - passwordField - NSSecureTextField - - - usernameField - NSTextField - - - - - IBProjectSource - ./Classes/SettingsWindow.h - - - - SettingsWindowController - NSWindowController - - enabledDidChange: - id - - - enabledDidChange: - - enabledDidChange: - id - - - - IBProjectSource - ./Classes/SettingsWindowController.h - - - - - 0 - IBCocoaFramework - - com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3 - - - YES - 3 - - NSSwitch - {15, 15} - - - diff --git a/BitTicker/SettingsWindowController.h b/BitTicker/SettingsWindowController.h deleted file mode 100644 index 86f5057..0000000 --- a/BitTicker/SettingsWindowController.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// SettingsWindowController.h -// BitTicker -// -// Created by Matt Stith on 6/5/11. -// Copyright 2011 none. All rights reserved. -// - -#import - -@class SettingsWindow; -@class SharedSettings; - -@interface SettingsWindowController : NSWindowController { -@private - SharedSettings *sharedSettings; - - // Same as self.window, but typecasted - SettingsWindow *settingsWindow; - - NSInteger selectedMarket; - -} - --(IBAction)enabledDidChange:(id)sender; - -@end diff --git a/BitTicker/SettingsWindowController.m b/BitTicker/SettingsWindowController.m deleted file mode 100644 index 572e0c5..0000000 --- a/BitTicker/SettingsWindowController.m +++ /dev/null @@ -1,100 +0,0 @@ -// -// SettingsWindowController.m -// BitTicker -// -// Created by Matt Stith on 6/5/11. -// Copyright 2011 none. All rights reserved. -// - -#import "SettingsWindowController.h" - -#import "SettingsWindow.h" -#import "SharedSettings.h" - -#import "BitcoinMarket.h" - -@implementation SettingsWindowController - - -- (id)init { - if (!(self=[super initWithWindowNibName:@"SettingsWindow"])) return self; - sharedSettings = [SharedSettings sharedSettingManager]; - return self; -} - -- (void)dealloc -{ - [super dealloc]; -} - -- (void)windowDidLoad -{ - [super windowDidLoad]; - settingsWindow = (SettingsWindow*)self.window; - - NSTableView *table = settingsWindow.marketListTable; - - table.allowsMultipleSelection = NO; - table.allowsColumnReordering = NO; - table.allowsColumnResizing = NO; - table.allowsEmptySelection = NO; - table.allowsColumnSelection = NO; - - [table reloadData]; - - NSIndexSet *firstMarket = [NSIndexSet indexSetWithIndex:0]; - [table selectRowIndexes:firstMarket byExtendingSelection:NO]; -} -#pragma mark - Table view -- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView { - MSLog(@"Number of rows: %i",eNumberOfMarkets); - return eNumberOfMarkets; -} -- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex { - return [sharedSettings stringForMarket:rowIndex]; -} -- (void)tableViewSelectionDidChange:(NSNotification *)aNotification { - selectedMarket = [settingsWindow.marketListTable selectedRow]; - BOOL enabled = [sharedSettings isMarketEnabled:selectedMarket]; - NSString *username = [sharedSettings usernameForMarket:selectedMarket]; - NSString *password = [sharedSettings passwordForMarket:selectedMarket]; - NSString *apiKey = [sharedSettings apiKeyForMarket:selectedMarket]; - - settingsWindow.enabledCheckbox.state = enabled? NSOnState : NSOffState; - - if (username == nil) username = @""; - if (password == nil) password = @""; - if (apiKey == nil) apiKey = @""; - - settingsWindow.usernameField.stringValue = username; - settingsWindow.passwordField.stringValue = password; - settingsWindow.apiKeyField.stringValue = apiKey; - - settingsWindow.marketLabel.stringValue = [sharedSettings stringForMarket:selectedMarket]; -} -// Customize table, it's pretty static. User doesn't need to interact. -- (BOOL)tableView:(NSTableView *)aTableView shouldEditTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex { - return NO; -} -- (BOOL)tableView:(NSTableView *)tableView shouldReorderColumn:(NSInteger)columnIndex toColumn:(NSInteger)newColumnIndex { - return NO; -} -#pragma mark - Actions --(IBAction)enabledDidChange:(id)sender { - BOOL enabled = [sender state] == NSOnState; - [sharedSettings setIsEnabled:enabled forMarket:selectedMarket]; -} -- (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)fieldEditor { - if ([control tag] == 0) { - [sharedSettings setUsername:[fieldEditor string] forMarket:selectedMarket]; - MSLog(@"Username changed"); - } else if ([control tag] == 1) { - [sharedSettings setPassword:[fieldEditor string] forMarket:selectedMarket]; - MSLog(@"Password changed"); - } else if ([control tag] == 2) { - [sharedSettings setAPIKey:[fieldEditor string] forMarket:selectedMarket]; - MSLog(@"API key changed"); - } - return YES; -} -@end diff --git a/BitTicker/SharedSettings.h b/BitTicker/SharedSettings.h deleted file mode 100644 index 5fc3cf8..0000000 --- a/BitTicker/SharedSettings.h +++ /dev/null @@ -1,33 +0,0 @@ -// -// SharedSettings.h -// -// Copyright 2011 Stephen Oliver . All rights reserved. -// - -#import - -@class EMGenericKeychainItem; - -@interface SharedSettings : NSObject { - NSMutableDictionary *keychainItems; -} -+ (id)sharedSettingManager; --(EMGenericKeychainItem*)keychainItemForService:(NSInteger)service; - --(BOOL)isMarketEnabled:(NSInteger)market; --(void)setIsEnabled:(BOOL)enabled forMarket:(NSInteger)market; - --(NSString*)usernameForMarket:(NSInteger)market; --(void)setUsername:(NSString*)username forMarket:(NSInteger)market; - --(NSString*)passwordForMarket:(NSInteger)market; --(void)setPassword:(NSString*)password forMarket:(NSInteger)market; - --(NSString*)apiKeyForMarket:(NSInteger)market; --(void)setAPIKey:(NSString*)newAPIKey forMarket:(NSInteger)market; - --(NSString*)stringForMarket:(NSInteger)market; - - - -@end \ No newline at end of file diff --git a/BitTicker/SharedSettings.m b/BitTicker/SharedSettings.m deleted file mode 100644 index 1b05ff2..0000000 --- a/BitTicker/SharedSettings.m +++ /dev/null @@ -1,150 +0,0 @@ -// -// SharedSettings.m -// -// Copyright 2011 Stephen Oliver . All rights reserved. -// - -#import "SharedSettings.h" -#import "EMKeychainItem.h" - -#import "BitcoinMarket.h" - -static SharedSettings *sharedSettingManager = nil; - -@implementation SharedSettings - -- (id)init { - if (!(self = [super init])) return self; - - [EMGenericKeychainItem setLogsErrors:YES]; - keychainItems = [[NSMutableDictionary alloc] init]; - - return self; -} - --(EMGenericKeychainItem*)keychainItemForService:(NSInteger)service { - NSString *serviceString = [@"BitTicker-" stringByAppendingString:[self stringForMarket:service]]; - - EMGenericKeychainItem *item = [keychainItems objectForKey:serviceString]; - - if (!item) { - // We don't have one cached, check if one exists for the username - NSString *username = [self usernameForMarket:service]; - if (!username) { - // Username isn't in defaults, obviously password isn't either. - item = [EMGenericKeychainItem genericKeychainItemForService:serviceString withUsername:@""]; - if (!item) { - // getting a generic/blank item didnt work either so it must not exist - item = [EMGenericKeychainItem addGenericKeychainItemForService:serviceString withUsername:@"" password:@""]; - } - } else { - item = [EMGenericKeychainItem genericKeychainItemForService:serviceString withUsername:username]; - - if (!item) { - // We still don't have it. Make a new one. - item = [EMGenericKeychainItem addGenericKeychainItemForService:serviceString withUsername:username password:@""]; - } - } - } - [keychainItems setObject:item forKey:serviceString]; - return item; -} - --(BOOL)isMarketEnabled:(NSInteger)market { - NSString *enabledKey = [NSString stringWithFormat:@"%@-enabled",[self stringForMarket:market]]; - return [[NSUserDefaults standardUserDefaults] boolForKey:enabledKey]; -} --(void)setIsEnabled:(BOOL)enabled forMarket:(NSInteger)market { - NSString *enabledKey = [NSString stringWithFormat:@"%@-enabled",[self stringForMarket:market]]; - [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:enabledKey]; -} - --(NSString*)usernameForMarket:(NSInteger)market { - NSString *usernameKey = [NSString stringWithFormat:@"%@-username",[self stringForMarket:market]]; - return [[NSUserDefaults standardUserDefaults] stringForKey:usernameKey]; -} --(void)setUsername:(NSString*)username forMarket:(NSInteger)market { - NSString *usernameKey = [NSString stringWithFormat:@"%@-username",[self stringForMarket:market]]; - - // Check for old username/password in keychain - EMGenericKeychainItem *keychainItem = [self keychainItemForService:market]; - - // If there is one, we need to update it. - if (keychainItem) { - keychainItem.username = username; - } - - [[NSUserDefaults standardUserDefaults] setObject:username forKey:usernameKey]; - [[NSUserDefaults standardUserDefaults] synchronize]; -} - --(NSString*)apiKeyForMarket:(NSInteger)market { - NSString *apiKey = [NSString stringWithFormat:@"%@-apiKey",[self stringForMarket:market]]; - return [[NSUserDefaults standardUserDefaults] stringForKey:apiKey]; -} --(void)setAPIKey:(NSString*)newAPIKey forMarket:(NSInteger)market { - NSString *apiKey = [NSString stringWithFormat:@"%@-apiKey",[self stringForMarket:market]]; - [[NSUserDefaults standardUserDefaults] setObject:newAPIKey forKey:apiKey]; - [[NSUserDefaults standardUserDefaults] synchronize]; -} - --(NSString*)passwordForMarket:(NSInteger)market { - EMGenericKeychainItem *keychainItem = [self keychainItemForService:market]; - return keychainItem.password; -} --(void)setPassword:(NSString*)password forMarket:(NSInteger)market { - EMGenericKeychainItem *keychainItem = [self keychainItemForService:market]; - keychainItem.password = password; -} - --(NSString*)stringForMarket:(NSInteger)market { - switch (market) { - case eMarketMtGox: - return @"MtGox"; - break; - case eMarketTradeHill: - return @"TradeHill"; - break; - default: - return @"Unknown"; - break; - } -} - -#pragma mark - Singleton jazz - -+ (id)sharedSettingManager { - @synchronized(self) { - if(sharedSettingManager == nil) - sharedSettingManager = [[super allocWithZone:NULL] init]; - } - return sharedSettingManager; -} - -+ (id)allocWithZone:(NSZone *)zone { - return [[self sharedSettingManager] retain]; -} -- (id)copyWithZone:(NSZone *)zone { - return self; -} -- (id)retain { - return self; -} -- (NSUInteger)retainCount { - return UINT_MAX; //denotes an object that cannot be released -} - -- (void)release { - // never release -} -- (id)autorelease { - return self; -} - - --(void)dealloc { - [keychainItems release]; - [super dealloc]; -} - -@end \ No newline at end of file diff --git a/BitTicker/StatusItemView.h b/BitTicker/StatusItemView.h old mode 100644 new mode 100755 index e754bdc..62caba6 --- a/BitTicker/StatusItemView.h +++ b/BitTicker/StatusItemView.h @@ -1,21 +1,8 @@ /* - BitTicker is Copyright 2011 Stephen Oliver - http://github.com/mrsteveman1 + BitTicker is Copyright 2012 Stephen Oliver + http://github.com/infincia - 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 - */ +*/ #import @@ -30,12 +17,13 @@ BOOL isMenuVisible; BOOL isAnimating; BOOL firstTick; + NSNumberFormatter *currencyFormatter; } -@property (retain, nonatomic) NSStatusItem *statusItem; -@property (retain, nonatomic) NSNumber *tickerValue; -@property (retain, nonatomic) NSNumber *previousTickerValue; -@property (retain, nonatomic) NSTimer *colorTimer; +@property (retain) NSStatusItem *statusItem; +@property (retain) NSNumber *tickerValue; +@property (retain) NSNumber *previousTickerValue; +@property (retain) NSTimer *colorTimer; - (void)setTickerValue:(NSNumber *)value; diff --git a/BitTicker/StatusItemView.m b/BitTicker/StatusItemView.m old mode 100644 new mode 100755 index bc8e026..6e9bdea --- a/BitTicker/StatusItemView.m +++ b/BitTicker/StatusItemView.m @@ -1,21 +1,8 @@ /* - BitTicker is Copyright 2011 Stephen Oliver - http://github.com/mrsteveman1 + BitTicker is Copyright 2012 Stephen Oliver + http://github.com/infincia - 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 - */ +*/ #import "StatusItemView.h" #import @@ -43,7 +30,7 @@ - (void)drawRect:(NSRect)rect { NSColor *white = [NSColor whiteColor]; [fontAttributes setObject:font forKey:NSFontAttributeName]; - NSString *tickerPretty = [NSString stringWithFormat:@"$%0.2f",[tickerValue floatValue]]; + NSString *tickerPretty = [currencyFormatter stringFromNumber:tickerValue]; CGSize expected = [tickerPretty sizeWithAttributes:fontAttributes]; CGRect newFrame = self.frame; newFrame.size.width = expected.width + 5; @@ -76,36 +63,39 @@ - (void)drawRect:(NSRect)rect { [fontAttributes setObject:foreground_color forKey:NSForegroundColorAttributeName]; [tickerPretty drawAtPoint:point withAttributes:fontAttributes]; } - - [fontAttributes release]; } - (id)initWithFrame:(NSRect)frame { CGRect newFrame = CGRectMake(0,0,StatusItemWidth,[[NSStatusBar systemStatusBar] thickness]); self = [super initWithFrame:newFrame]; - if (self) { - firstTick = YES; - self.previousTickerValue = [NSNumber numberWithInt:0]; - self.tickerValue = [NSNumber numberWithInt:0]; - flashColor = [[NSColor blackColor] retain]; - currentColor = [[NSColor blackColor] retain]; - lastUpdated = [[NSDate date] retain]; - statusItem = nil; - isMenuVisible = NO; - isAnimating = NO; - [statusItem setLength:StatusItemWidth]; - } - return self; -} + + firstTick = YES; + self.previousTickerValue = [NSNumber numberWithInt:0]; + self.tickerValue = [NSNumber numberWithInt:0]; + flashColor = [NSColor blackColor]; + currentColor = [NSColor blackColor]; + lastUpdated = [NSDate date]; + statusItem = nil; + isMenuVisible = NO; + isAnimating = NO; + [statusItem setLength:StatusItemWidth]; + + currencyFormatter = [[NSNumberFormatter alloc] init]; + currencyFormatter.numberStyle = NSNumberFormatterCurrencyStyle; + currencyFormatter.currencyCode = @"USD"; // TODO: Base on market currency + currencyFormatter.thousandSeparator = @","; // TODO: Base on local seperator for currency + currencyFormatter.alwaysShowsDecimalSeparator = YES; + currencyFormatter.hasThousandSeparators = YES; + currencyFormatter.minimumFractionDigits = 2; // TODO: Configurable + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveTicker:) name:@"MtGox-Ticker" object:nil]; -- (void)dealloc { - [statusItem release]; - [tickerValue release]; - [super dealloc]; + return self; } - (void)mouseDown:(NSEvent *)event { + NSLog(@"Menu click"); [[self menu] setDelegate:self]; [statusItem popUpStatusItemMenu:[self menu]]; [self setNeedsDisplay:YES]; @@ -141,30 +131,23 @@ - (void)updateFade { } - (void)setTickerValue:(NSNumber *)value { - - [previousTickerValue release]; previousTickerValue = tickerValue; - - [value retain]; tickerValue = value; - - double current = [tickerValue doubleValue]; - double previous = [previousTickerValue doubleValue]; BOOL animate_color = YES; if(firstTick){ firstTick = NO; animate_color = NO; - } else if(current > previous){ - [flashColor release]; - flashColor = [[NSColor greenColor] retain]; - [currentColor release]; - currentColor = [[NSColor colorWithDeviceRed:0 green:0.55 blue:0 alpha:1.0] retain]; + } else if(tickerValue > previousTickerValue){ + + flashColor = [NSColor greenColor]; + + currentColor = [NSColor colorWithDeviceRed:0 green:0.55 blue:0 alpha:1.0]; NSLog(@"Going green..."); - } else if(current < previous){ - [flashColor release]; - flashColor = [[NSColor redColor] retain]; - [currentColor release]; - currentColor = [[NSColor colorWithDeviceRed:0.55 green:0 blue:0 alpha:1.0] retain]; + } else if(tickerValue < previousTickerValue){ + + flashColor = [NSColor redColor]; + + currentColor = [NSColor colorWithDeviceRed:0.55 green:0 blue:0 alpha:1.0]; NSLog(@"Going red..."); } else { @@ -173,14 +156,13 @@ - (void)setTickerValue:(NSNumber *)value { if(animate_color){ - self.colorTimer = [[NSTimer scheduledTimerWithTimeInterval:(1.0/ColorFadeFramerate) + self.colorTimer = [NSTimer scheduledTimerWithTimeInterval:(1.0/ColorFadeFramerate) target:self selector:@selector(updateFade) userInfo:nil - repeats:YES] retain]; + repeats:YES]; - [lastUpdated release]; - lastUpdated = [[NSDate date] retain]; + lastUpdated = [NSDate date]; isAnimating = YES; } @@ -190,6 +172,15 @@ - (void)setTickerValue:(NSNumber *)value { } +-(void)didReceiveTicker:(NSNotification *)notification { + NSDictionary *ticker = [[notification object] objectForKey:@"ticker"]; + + dispatch_async(dispatch_get_main_queue(), ^{ + [self setTickerValue:[ticker objectForKey:@"last"]]; + }); + +} + - (NSNumber *)tickerValue { return tickerValue; } diff --git a/BitTicker/TaggedNSURLConnection.h b/BitTicker/TaggedNSURLConnection.h deleted file mode 100644 index 9aed7f8..0000000 --- a/BitTicker/TaggedNSURLConnection.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// TaggedNSUrlConnection.h -// Bitcoin Trader -// -// Created by Matt Stith on 4/30/11. -// Copyright 2011 Insomnia Addict. All rights reserved. -// -// NSURLConnection with an integer tag, for easier identification - -#import - - -@interface TaggedNSURLConnection : NSURLConnection { - NSInteger _tag; -} -@property (nonatomic) NSInteger tag; -@end diff --git a/BitTicker/TaggedNSURLConnection.m b/BitTicker/TaggedNSURLConnection.m deleted file mode 100644 index bb2d40f..0000000 --- a/BitTicker/TaggedNSURLConnection.m +++ /dev/null @@ -1,14 +0,0 @@ -// -// TaggedNSUrlConnection.m -// Bitcoin Trader -// -// Created by Matt Stith on 4/30/11. -// Copyright 2011 Insomnia Addict. All rights reserved. -// - -#import "TaggedNSURLConnection.h" - - -@implementation TaggedNSURLConnection -@synthesize tag=_tag; -@end diff --git a/BitTicker/Ticker.h b/BitTicker/Ticker.h deleted file mode 100644 index d98785f..0000000 --- a/BitTicker/Ticker.h +++ /dev/null @@ -1,29 +0,0 @@ -// -// Ticker.h -// Bitcoin Trader -// -// Created by Matt Stith on 4/30/11. -// Copyright 2011 Insomnia Addict. All rights reserved. -// - -#import -@class BitcoinMarket; - - -@interface Ticker : NSObject { - NSNumber *_high; - NSNumber *_low; - NSNumber *_volume; - NSNumber *_buy; - NSNumber *_sell; - NSNumber *_last; - BitcoinMarket *_market; -} -@property (retain) BitcoinMarket *market; -@property (nonatomic, retain) NSNumber *high; -@property (nonatomic, retain) NSNumber *low; -@property (nonatomic, retain) NSNumber *volume; -@property (nonatomic, retain) NSNumber *buy; -@property (nonatomic, retain) NSNumber *sell; -@property (nonatomic, retain) NSNumber *last; -@end diff --git a/BitTicker/Ticker.m b/BitTicker/Ticker.m deleted file mode 100644 index 4149235..0000000 --- a/BitTicker/Ticker.m +++ /dev/null @@ -1,25 +0,0 @@ -// -// Ticker.m -// Bitcoin Trader -// -// Created by Matt Stith on 4/30/11. -// Copyright 2011 Insomnia Addict. All rights reserved. -// - -#import "Ticker.h" -#import "BitcoinMarket.h" - -@implementation Ticker -@synthesize high=_high; -@synthesize low=_low; -@synthesize volume=_volume; -@synthesize buy=_buy; -@synthesize sell=_sell; -@synthesize last=_last; -@synthesize market=_market; - --(NSString*)description { - return [NSString stringWithFormat:@"",_last, _high, _low, _volume]; -} - -@end diff --git a/BitTicker/ToolbarTicker.h b/BitTicker/ToolbarTicker.h deleted file mode 100644 index 7b012a9..0000000 --- a/BitTicker/ToolbarTicker.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// ToolbarTicker.h -// BitTicker -// -// Created by steve on 6/12/11. -// Copyright 2011 none. All rights reserved. -// - -#import - - -@interface ToolbarTicker : NSToolbarItem { -@private - -} - -@end diff --git a/BitTicker/ToolbarTicker.m b/BitTicker/ToolbarTicker.m deleted file mode 100644 index 0ed6693..0000000 --- a/BitTicker/ToolbarTicker.m +++ /dev/null @@ -1,29 +0,0 @@ -// -// ToolbarTicker.m -// BitTicker -// -// Created by steve on 6/12/11. -// Copyright 2011 none. All rights reserved. -// - -#import "ToolbarTicker.h" - - -@implementation ToolbarTicker - -- (id)init -{ - self = [super init]; - if (self) { - // Initialization code here. - } - - return self; -} - -- (void)dealloc -{ - [super dealloc]; -} - -@end diff --git a/BitTicker/Trade.h b/BitTicker/Trade.h deleted file mode 100644 index 616a9fb..0000000 --- a/BitTicker/Trade.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// Trade.h -// Bitcoin Trader -// -// Created by Matt Stith on 4/27/11. -// Copyright 2011 Insomnia Addict. All rights reserved. -// - -#import -@class BitcoinMarket; - -@interface Trade : NSObject { - NSInteger _tid; // Trade ID - NSNumber *_price; // Price in USD - NSNumber *_amount; // Amount bought/sold in BTC - NSDate *_date; // Time/date that trade was fulfilled - BitcoinMarket *_market; -} - -@property (retain) BitcoinMarket *market; - -@property (nonatomic) NSInteger tid; -@property (nonatomic, retain) NSNumber *price; -@property (nonatomic, retain) NSNumber *amount; -@property (nonatomic, retain) NSDate *date; - -@end diff --git a/BitTicker/Trade.m b/BitTicker/Trade.m deleted file mode 100644 index e240e36..0000000 --- a/BitTicker/Trade.m +++ /dev/null @@ -1,21 +0,0 @@ -// -// Trade.m -// Bitcoin Trader -// -// Created by Matt Stith on 4/27/11. -// Copyright 2011 Insomnia Addict. All rights reserved. -// - -#import "Trade.h" -#import "BitcoinMarket.h" - -@implementation Trade - -@synthesize tid=_tid; -@synthesize date=_date; -@synthesize price=_price; -@synthesize amount=_amount; - -@synthesize market=_market; - -@end diff --git a/BitTicker/TradeHillMarket.h b/BitTicker/TradeHillMarket.h deleted file mode 100644 index 9fcf126..0000000 --- a/BitTicker/TradeHillMarket.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// TradeHillMarket.h -// BitTicker -// -// Created by steve on 6/10/11. -// Copyright 2011 none. All rights reserved. -// - -#import - -#import "BitcoinMarket.h" - -#import "BitcoinMarket.h" - -@interface TradeHillMarket : BitcoinMarket { - -} - --(id)init; - -@end diff --git a/BitTicker/TradeHillMarket.m b/BitTicker/TradeHillMarket.m deleted file mode 100644 index 9726904..0000000 --- a/BitTicker/TradeHillMarket.m +++ /dev/null @@ -1,126 +0,0 @@ -// -// TradeHillMarket.m -// BitTicker -// -// Created by steve on 6/10/11. -// Copyright 2011 none. All rights reserved. -// - -#import "TradeHillMarket.h" - -#import "Trade.h" -#import "Ticker.h" -#import "Wallet.h" - -// TODO: they have URLs for multiple currencies... -#define TRADEHILL_TICKER_URL @"" -#define TRADEHILL_TRADES_URL @"https://www.tradehill.com/API/USD/Trades" -#define TRADEHILL_MARKETDEPTH_URL @"https://www.tradehill.com/API/USD/Orderbook" -#define TRADEHILL_WALLET_URL @"" - -@implementation TradeHillMarket - --(id)init { - if (!(self = [super initWithDelegate:self])) return self; - _tickerURL = TRADEHILL_TICKER_URL; - _tradeURL = TRADEHILL_TRADES_URL; - _depthURL = TRADEHILL_MARKETDEPTH_URL; - _walletURL = TRADEHILL_WALLET_URL; - return self; -} - --(void)fetchRecentTrades { - MSLog(@"Fetching recent trades..."); - [self downloadJsonDataFromURL:[NSURL URLWithString:self.tradeURL] callback:@selector(didFetchRecentTrades:)]; -} - --(void)fetchTicker { - MSLog(@"Fetching ticker..."); - [self downloadJsonDataFromURL:[NSURL URLWithString:self.tickerURL] callback:@selector(didFetchTickerData:)]; -} - --(void)fetchMarketDepth { - MSLog(@"Fetching market depth..."); - [self downloadJsonDataFromURL:[NSURL URLWithString:self.depthURL] callback:@selector(didFetchMarketDepth:)]; -} - --(void)fetchWallet { - MSLog(@"Fetching wallet..."); - NSString *username = [sharedSettingManager usernameForMarket:eMarketTradeHill]; - NSString *password = [sharedSettingManager passwordForMarket:eMarketTradeHill]; - if ([username isEqualToString:@""] || username == nil) { - return; - } - if ([password isEqualToString:@""] || password == nil) { - return; - } - NSDictionary *post = [NSDictionary dictionaryWithObjectsAndKeys:username,@"name",password,@"pass",nil]; - [self downloadJsonDataFromURL:[NSURL URLWithString:self.walletURL] withPostData:post callback:@selector(didFetchWallet:)]; -} - --(void)didFetchRecentTrades:(NSArray*)tradeData { - NSMutableArray *trades = [NSMutableArray array]; - - for (NSDictionary *tradeDict in tradeData) { - Trade *newTrade = [[Trade alloc] init]; - newTrade.amount = [tradeDict objectForKey:@"amount"]; - newTrade.price = [tradeDict objectForKey:@"price"]; - newTrade.tid = [[tradeDict objectForKey:@"tid"] intValue]; - - newTrade.date = [NSDate dateWithTimeIntervalSince1970:[[tradeDict objectForKey:@"amount"] doubleValue]]; - - [trades addObject:newTrade]; - [newTrade release]; - } - MSLog(@"Got %i trades",trades.count); - - // Reverse the trades so 0 is the most recent - NSMutableArray *orderedTrades = [NSMutableArray array]; - NSEnumerator *reverseEnumerator = [trades reverseObjectEnumerator]; - id object; - - while ((object = [reverseEnumerator nextObject])) { - [orderedTrades addObject:object]; - } - [[NSNotificationCenter defaultCenter] postNotificationName:@"TradeHill-Trades" object:tradeData]; - -} - --(void)didFetchTickerData:(NSDictionary*)tickerData { - NSDictionary *tickerDict = [tickerData objectForKey:@"ticker"]; - - Ticker *ticker = [[Ticker alloc] init]; - // Dont know if these are correct yet - ticker.buy = [tickerDict objectForKey:@"buy"]; - ticker.sell = [tickerDict objectForKey:@"sell"]; - ticker.high = [tickerDict objectForKey:@"high"]; - ticker.low = [tickerDict objectForKey:@"low"]; - ticker.last = [tickerDict objectForKey:@"last"]; - ticker.volume = [tickerDict objectForKey:@"vol"]; - ticker.market = self; - [[NSNotificationCenter defaultCenter] postNotificationName:@"TradeHill-Ticker" object:ticker]; - - [ticker release]; -} - --(void)didFetchMarketDepth:(NSDictionary*)marketDepth { - MSLog(@"Got %i asks and %i bids",[[marketDepth objectForKey:@"asks"] count],[[marketDepth objectForKey:@"bids"] count]); -} - --(void)didFetchWallet:(NSDictionary *)dictwallet { - Wallet *newwallet = [[Wallet alloc] init]; - - // these might need to be changed once their API is available - newwallet.btc = [dictwallet objectForKey:@"btcs"]; - newwallet.usd = [dictwallet objectForKey:@"usds"]; - newwallet.market = self; - [[NSNotificationCenter defaultCenter] postNotificationName:@"TradeHill-Wallet" object:newwallet]; -} - - -- (void)dealloc -{ - [super dealloc]; -} - -@end diff --git a/BitTicker/TradeHillMarketMenuView.h b/BitTicker/TradeHillMarketMenuView.h deleted file mode 100644 index 9b3fc17..0000000 --- a/BitTicker/TradeHillMarketMenuView.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// TradeHillMarketMenuView.h -// BitTicker -// -// Created by steve on 6/10/11. -// Copyright 2011 none. All rights reserved. -// - -#import -#import "CustomMenuView.h" - -@interface TradeHillMarketMenuView : CustomMenuView { - NSTextField *highValue; - NSTextField *lowValue; - NSTextField *volValue; - NSTextField *buyValue; - NSTextField *sellValue; - NSTextField *lastValue; - - NSTextField *BTCValue; - NSTextField *BTCxUSDValue; - NSTextField *USDValue; - NSTextField *walletUSDValue; - -} - -@end diff --git a/BitTicker/TradeHillMarketMenuView.m b/BitTicker/TradeHillMarketMenuView.m deleted file mode 100644 index 14196ab..0000000 --- a/BitTicker/TradeHillMarketMenuView.m +++ /dev/null @@ -1,301 +0,0 @@ -// -// TradeHillMarketMenuView.m -// BitTicker -// -// Created by steve on 6/10/11. -// Copyright 2011 none. All rights reserved. -// - -#import "TradeHillMarketMenuView.h" -#define menuFont @"LucidaGrande" -#define menuFontSize 12 -#define headerFont @"LucidaGrande-Bold" -#define headerFontSize 13 - -#define menuHeight 15 -#define labelWidth 70 -#define headerWidth 120 -#define valueWidth 60 - -#define labelOffset 20 -#define valueOffset 110 - -@implementation TradeHillMarketMenuView - -- (id)initWithFrame:(NSRect)frame { - CGRect newFrame = frame; - newFrame.size.height = frame.size.height + 60; - self = [super initWithFrame:newFrame]; - if (self) { - NSTextField *sectionLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,153,headerWidth,menuHeight)]; - [sectionLabel setEditable:FALSE]; - [sectionLabel setBordered:NO]; - [sectionLabel setAlignment:NSLeftTextAlignment]; - [sectionLabel setBackgroundColor:[NSColor clearColor]]; - [sectionLabel setStringValue:@"Trade Hill"]; - [sectionLabel setTextColor:[NSColor blueColor]]; - [sectionLabel setFont:[NSFont fontWithName:headerFont size:headerFontSize]]; - [self addSubview:sectionLabel]; - [sectionLabel release]; - - highValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,135,valueWidth,menuHeight)]; - [highValue setEditable:FALSE]; - [highValue setBordered:NO]; - [highValue setAlignment:NSRightTextAlignment]; - [highValue setBackgroundColor:[NSColor clearColor]]; - [highValue setTextColor:[NSColor blackColor]]; - [highValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:highValue]; - - NSTextField *highLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,135,labelWidth,menuHeight)]; - [highLabel setEditable:FALSE]; - [highLabel setBordered:NO]; - [highLabel setAlignment:NSLeftTextAlignment]; - [highLabel setBackgroundColor:[NSColor clearColor]]; - [highLabel setStringValue:@"High:"]; - [highLabel setTextColor:[NSColor blackColor]]; - [highLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:highLabel]; - [highLabel release]; - - lowValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,120,valueWidth,menuHeight)]; - [lowValue setEditable:FALSE]; - [lowValue setBordered:NO]; - [lowValue setAlignment:NSRightTextAlignment]; - [lowValue setBackgroundColor:[NSColor clearColor]]; - [lowValue setTextColor:[NSColor blackColor]]; - [lowValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:lowValue]; - - NSTextField *lowLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,120,labelWidth,menuHeight)]; - [lowLabel setEditable:FALSE]; - [lowLabel setBordered:NO]; - [lowLabel setAlignment:NSLeftTextAlignment]; - [lowLabel setBackgroundColor:[NSColor clearColor]]; - [lowLabel setStringValue:@"Low:"]; - [lowLabel setTextColor:[NSColor blackColor]]; - [lowLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:lowLabel]; - [lowLabel release]; - - buyValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,105,valueWidth,menuHeight)]; - [buyValue setEditable:FALSE]; - [buyValue setBordered:NO]; - [buyValue setAlignment:NSRightTextAlignment]; - [buyValue setBackgroundColor:[NSColor clearColor]]; - [buyValue setTextColor:[NSColor blackColor]]; - [buyValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:buyValue]; - - NSTextField *buyLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,105,labelWidth,menuHeight)]; - [buyLabel setEditable:FALSE]; - [buyLabel setBordered:NO]; - [buyLabel setAlignment:NSLeftTextAlignment]; - [buyLabel setBackgroundColor:[NSColor clearColor]]; - [buyLabel setStringValue:@"Buy:"]; - [buyLabel setTextColor:[NSColor blackColor]]; - [buyLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:buyLabel]; - [buyLabel release]; - - sellValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,90,valueWidth,menuHeight)]; - [sellValue setEditable:FALSE]; - [sellValue setBordered:NO]; - [sellValue setAlignment:NSRightTextAlignment]; - [sellValue setBackgroundColor:[NSColor clearColor]]; - [sellValue setTextColor:[NSColor blackColor]]; - [sellValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:sellValue]; - - NSTextField *sellLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,90,labelWidth,menuHeight)]; - [sellLabel setEditable:FALSE]; - [sellLabel setBordered:NO]; - [sellLabel setAlignment:NSLeftTextAlignment]; - [sellLabel setBackgroundColor:[NSColor clearColor]]; - [sellLabel setStringValue:@"Sell:"]; - [sellLabel setTextColor:[NSColor blackColor]]; - [sellLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:sellLabel]; - [sellLabel release]; - - lastValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,75,valueWidth,menuHeight)]; - [lastValue setEditable:FALSE]; - [lastValue setBordered:NO]; - [lastValue setAlignment:NSRightTextAlignment]; - [lastValue setBackgroundColor:[NSColor clearColor]]; - [lastValue setTextColor:[NSColor blackColor]]; - [lastValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:lastValue]; - - NSTextField *lastLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,75,labelWidth,menuHeight)]; - [lastLabel setEditable:FALSE]; - [lastLabel setBordered:NO]; - [lastLabel setAlignment:NSLeftTextAlignment]; - [lastLabel setBackgroundColor:[NSColor clearColor]]; - [lastLabel setStringValue:@"Last:"]; - [lastLabel setTextColor:[NSColor blackColor]]; - [lastLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:lastLabel]; - [lastLabel release]; - - volValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset,60,valueWidth,menuHeight)]; - [volValue setEditable:FALSE]; - [volValue setBordered:NO]; - [volValue setAlignment:NSRightTextAlignment]; - [volValue setBackgroundColor:[NSColor clearColor]]; - [volValue setTextColor:[NSColor blackColor]]; - [volValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:volValue]; - - NSTextField *volLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,60,labelWidth,menuHeight)]; - [volLabel setEditable:FALSE]; - [volLabel setBordered:NO]; - [volLabel setAlignment:NSLeftTextAlignment]; - [volLabel setBackgroundColor:[NSColor clearColor]]; - [volLabel setStringValue:@"Volume:"]; - [volLabel setTextColor:[NSColor blackColor]]; - [volLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:volLabel]; - [volLabel release]; - - BTCValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 45, valueWidth, menuHeight)]; - [BTCValue setEditable:FALSE]; - [BTCValue setBordered:NO]; - [BTCValue setAlignment:NSRightTextAlignment]; - [BTCValue setBackgroundColor:[NSColor clearColor]]; - [BTCValue setTextColor:[NSColor blackColor]]; - [BTCValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:BTCValue]; - - NSTextField *BTCLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,45,labelWidth,menuHeight)]; - [BTCLabel setEditable:FALSE]; - [BTCLabel setBordered:NO]; - [BTCLabel setAlignment:NSLeftTextAlignment]; - [BTCLabel setBackgroundColor:[NSColor clearColor]]; - [BTCLabel setStringValue:@"BTC:"]; - [BTCLabel setTextColor:[NSColor blackColor]]; - [BTCLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:BTCLabel]; - [BTCLabel release]; - - BTCxUSDValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 30, valueWidth, menuHeight)]; - [BTCxUSDValue setEditable:FALSE]; - [BTCxUSDValue setBordered:NO]; - [BTCxUSDValue setAlignment:NSRightTextAlignment]; - [BTCxUSDValue setBackgroundColor:[NSColor clearColor]]; - [BTCxUSDValue setTextColor:[NSColor blackColor]]; - [BTCxUSDValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:BTCxUSDValue]; - - NSTextField *BTCxUSDLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,30,labelWidth,menuHeight)]; - [BTCxUSDLabel setEditable:FALSE]; - [BTCxUSDLabel setBordered:NO]; - [BTCxUSDLabel setAlignment:NSLeftTextAlignment]; - [BTCxUSDLabel setBackgroundColor:[NSColor clearColor]]; - [BTCxUSDLabel setStringValue:@"BTC * Last:"]; - [BTCxUSDLabel setTextColor:[NSColor blackColor]]; - [BTCxUSDLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:BTCxUSDLabel]; - [BTCxUSDLabel release]; - - USDValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 15, valueWidth, menuHeight)]; - [USDValue setEditable:FALSE]; - [USDValue setBordered:NO]; - [USDValue setAlignment:NSRightTextAlignment]; - [USDValue setBackgroundColor:[NSColor clearColor]]; - [USDValue setTextColor:[NSColor blackColor]]; - [USDValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:USDValue]; - - NSTextField *USDLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,15,labelWidth,menuHeight)]; - [USDLabel setEditable:FALSE]; - [USDLabel setBordered:NO]; - [USDLabel setAlignment:NSLeftTextAlignment]; - [USDLabel setBackgroundColor:[NSColor clearColor]]; - [USDLabel setStringValue:@"USD:"]; - [USDLabel setTextColor:[NSColor blackColor]]; - [USDLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:USDLabel]; - [USDLabel release]; - - walletUSDValue = [[NSTextField alloc] initWithFrame:CGRectMake(valueOffset, 0, valueWidth, menuHeight)]; - [walletUSDValue setEditable:FALSE]; - [walletUSDValue setBordered:NO]; - [walletUSDValue setAlignment:NSRightTextAlignment]; - [walletUSDValue setBackgroundColor:[NSColor clearColor]]; - [walletUSDValue setTextColor:[NSColor blackColor]]; - [walletUSDValue setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:walletUSDValue]; - - NSTextField *walletLabel = [[NSTextField alloc] initWithFrame:CGRectMake(labelOffset,0,labelWidth,menuHeight)]; - [walletLabel setEditable:FALSE]; - [walletLabel setBordered:NO]; - [walletLabel setAlignment:NSLeftTextAlignment]; - [walletLabel setBackgroundColor:[NSColor clearColor]]; - [walletLabel setStringValue:@"Total:"]; - [walletLabel setTextColor:[NSColor blackColor]]; - [walletLabel setFont:[NSFont fontWithName:menuFont size:menuFontSize]]; - [self addSubview:walletLabel]; - [walletLabel release]; - } - - return self; -} - -- (id)init { - self = [super init]; - if (self) { - - } - - return self; -} - -#pragma mark - -#pragma mark Properties - --(void)setHigh:(NSString *)string { - [highValue setStringValue:string]; -} - --(void)setLow:(NSString *)string { - [lowValue setStringValue:string]; -} - --(void)setVol:(NSString *)string { - [volValue setStringValue:string]; -} - --(void)setBuy:(NSString *)string { - [buyValue setStringValue:string]; -} - --(void)setSell:(NSString *)string { - [sellValue setStringValue:string]; -} - --(void)setLast:(NSString *)string { - [lastValue setStringValue:string]; -} - --(void)setBtc:(NSString *)string { - [BTCValue setStringValue:string]; -} - --(void)setBtcusd:(NSString *)string { - [BTCxUSDValue setStringValue:string]; -} - --(void)setUsd:(NSString *)string { - [USDValue setStringValue:string]; -} - --(void)setWallet:(NSString *)string { - [walletUSDValue setStringValue:string]; -} - -- (void)dealloc -{ - [super dealloc]; -} -@end diff --git a/BitTicker/TradeHillPanel.h b/BitTicker/TradeHillPanel.h deleted file mode 100644 index 75d1672..0000000 --- a/BitTicker/TradeHillPanel.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// TradeHillPanel.h -// BitTicker -// -// Created by steve on 6/13/11. -// Copyright 2011 none. All rights reserved. -// - -#import -#import "WindowPanel.h" - -@interface TradeHillPanel : WindowPanel { -@private - -} - -@end diff --git a/BitTicker/TradeHillPanel.m b/BitTicker/TradeHillPanel.m deleted file mode 100644 index 1c194a8..0000000 --- a/BitTicker/TradeHillPanel.m +++ /dev/null @@ -1,30 +0,0 @@ -// -// TradeHillPanel.m -// BitTicker -// -// Created by steve on 6/13/11. -// Copyright 2011 none. All rights reserved. -// - -#import "TradeHillPanel.h" - - -@implementation TradeHillPanel - -- (id)initWithFrame:(NSRect)frame -{ - self = [super initWithFrame:frame]; - if (self) { - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveTicker:) name:@"TradeHill-Ticker" object:nil]; - - } - - return self; -} - -- (void)dealloc -{ - [super dealloc]; -} - -@end diff --git a/BitTicker/TradeHillPanel.xib b/BitTicker/TradeHillPanel.xib deleted file mode 100644 index 84fb343..0000000 --- a/BitTicker/TradeHillPanel.xib +++ /dev/null @@ -1,355 +0,0 @@ - - - - 1060 - 10J869 - 1306 - 1038.35 - 461.00 - - com.apple.InterfaceBuilder.CocoaPlugin - 1306 - - - YES - NSCustomView - NSTextField - NSTextFieldCell - NSCustomObject - - - YES - com.apple.InterfaceBuilder.CocoaPlugin - - - YES - - YES - - - - - YES - - MainWindowController - - - FirstResponder - - - NSApplication - - - - 274 - - YES - - - 268 - {{17, 339}, {66, 17}} - - - - YES - - 68288064 - 272630784 - Trade Hill - - LucidaGrande - 13 - 1044 - - - - 6 - System - controlColor - - 3 - MC42NjY2NjY2NjY3AA - - - - 6 - System - controlTextColor - - 3 - MAA - - - - - - {650, 376} - - - - TradeHillPanel - - - - - YES - - - _tradeHillPanel - - - - 5 - - - - - YES - - 0 - - - - - - -2 - - - File's Owner - - - -1 - - - First Responder - - - -3 - - - Application - - - 1 - - - YES - - - - Trade Hill Panel - - - 3 - - - YES - - - - - - 4 - - - - - - - YES - - YES - -1.IBPluginDependency - -2.IBPluginDependency - -3.IBPluginDependency - 1.IBPluginDependency - 1.WindowOrigin - 1.editorWindowContentRectSynchronizationRect - 3.IBPluginDependency - 4.IBPluginDependency - - - YES - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - {628, 654} - {{357, 416}, {480, 272}} - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - - - - YES - - - - - - YES - - - - - 5 - - - - YES - - MainWindowController - NSWindowController - - YES - - YES - _mainPanel - _mtGoxPanel - _tradeHillPanel - mainView - marketListTable - panelController - - - YES - NSView - NSView - NSView - NSView - NSTableView - NSViewController - - - - YES - - YES - _mainPanel - _mtGoxPanel - _tradeHillPanel - mainView - marketListTable - panelController - - - YES - - _mainPanel - NSView - - - _mtGoxPanel - NSView - - - _tradeHillPanel - NSView - - - mainView - NSView - - - marketListTable - NSTableView - - - panelController - NSViewController - - - - - IBProjectSource - ./Classes/MainWindowController.h - - - - TradeHillPanel - WindowPanel - - IBProjectSource - ./Classes/TradeHillPanel.h - - - - WindowPanel - NSView - - YES - - YES - buyField - highField - label - lastField - lowField - sellField - - - YES - NSTextField - NSTextField - NSTextField - NSTextField - NSTextField - NSTextField - - - - YES - - YES - buyField - highField - label - lastField - lowField - sellField - - - YES - - buyField - NSTextField - - - highField - NSTextField - - - label - NSTextField - - - lastField - NSTextField - - - lowField - NSTextField - - - sellField - NSTextField - - - - - IBProjectSource - ./Classes/WindowPanel.h - - - - - 0 - IBCocoaFramework - - com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3 - - - YES - 3 - - diff --git a/BitTicker/Wallet.h b/BitTicker/Wallet.h deleted file mode 100644 index be41fa2..0000000 --- a/BitTicker/Wallet.h +++ /dev/null @@ -1,25 +0,0 @@ -// -// Wallet.h -// BitTicker -// -// Created by steve on 6/4/11. -// Copyright 2011 none. All rights reserved. -// - -#import -@class BitcoinMarket; - -@interface Wallet : NSObject { - NSInteger _mid; // Market ID, 0 = MtGox - NSNumber *_btc; // Wallet BTC contents - NSNumber *_usd; // Wallet USD contents - BitcoinMarket *_market; -} - -@property (retain) BitcoinMarket *market; - -@property (nonatomic) NSInteger mid; -@property (nonatomic, retain) NSNumber *btc; -@property (nonatomic, retain) NSNumber *usd; - -@end diff --git a/BitTicker/Wallet.m b/BitTicker/Wallet.m deleted file mode 100644 index f3bf79a..0000000 --- a/BitTicker/Wallet.m +++ /dev/null @@ -1,20 +0,0 @@ -// -// Wallet.m -// BitTicker -// -// Created by steve on 6/4/11. -// Copyright 2011 none. All rights reserved. -// - -#import "Wallet.h" -#import "BitcoinMarket.h" - -@implementation Wallet - -@synthesize mid = _mid; -@synthesize btc = _btc; -@synthesize usd = _usd; - -@synthesize market=_market; - -@end diff --git a/BitTicker/WindowPanel.h b/BitTicker/WindowPanel.h deleted file mode 100644 index bf0cd5e..0000000 --- a/BitTicker/WindowPanel.h +++ /dev/null @@ -1,28 +0,0 @@ -// -// WindowPanel.h -// BitTicker -// -// Created by steve on 6/12/11. -// Copyright 2011 none. All rights reserved. -// - -#import -@class Ticker; -@class Wallet; - -@interface WindowPanel : NSView { -@private - IBOutlet NSTextField *label; - IBOutlet NSTextField *lastField; - IBOutlet NSTextField *buyField; - IBOutlet NSTextField *sellField; - IBOutlet NSTextField *highField; - IBOutlet NSTextField *lowField; - NSNumberFormatter *currencyFormatter; -} - --(void)didReceiveTicker:(NSNotification *)notification; - --(void)didReceiveWallet:(NSNotification *)notification; - -@end diff --git a/BitTicker/WindowPanel.m b/BitTicker/WindowPanel.m deleted file mode 100644 index 5b8cf0a..0000000 --- a/BitTicker/WindowPanel.m +++ /dev/null @@ -1,71 +0,0 @@ -// -// WindowPanel.m -// BitTicker -// -// Created by steve on 6/12/11. -// Copyright 2011 none. All rights reserved. -// - -#import "WindowPanel.h" -#import "Ticker.h" -#import "Wallet.h" - - -@implementation WindowPanel - -- (id)initWithFrame:(NSRect)frame -{ - self = [super initWithFrame:frame]; - if (self) { - currencyFormatter = [[NSNumberFormatter alloc] init]; - currencyFormatter.numberStyle = NSNumberFormatterCurrencyStyle; - currencyFormatter.currencyCode = @"USD"; // TODO: Base on market currency - currencyFormatter.thousandSeparator = @","; // TODO: Base on local seperator for currency - currencyFormatter.alwaysShowsDecimalSeparator = YES; - currencyFormatter.hasThousandSeparators = YES; - currencyFormatter.minimumFractionDigits = 4; // TODO: Configurable - } - - return self; -} - --(void)didReceiveTicker:(NSNotification *)notification { - Ticker *ticker = [notification object]; - [lastField setStringValue:[currencyFormatter stringFromNumber:ticker.last]]; - [highField setStringValue:[currencyFormatter stringFromNumber:ticker.high]]; - [lowField setStringValue:[currencyFormatter stringFromNumber:ticker.low]]; - [buyField setStringValue:[currencyFormatter stringFromNumber:ticker.buy]]; - [sellField setStringValue:[currencyFormatter stringFromNumber:ticker.sell]]; - -} - --(void)didReceiveWallet:(NSNotification *)notification { - Wallet *wallet = [notification object]; -} - - -// A request failed for some reason, for example the API being down --(void)requestFailedWithError:(NSNotification *)notification { - NSError *error = [notification object]; -} - -// Request wasn't formatted as expected --(void)didReceiveInvalidResponse:(NSNotification *)notification { - NSData *data = [notification object]; -} - --(void)didReceiveRecentTradesData:(NSNotification *)notification { - NSArray *trades = [notification object]; -} - -- (void)dealloc { - [[NSNotificationCenter defaultCenter] removeObserver:self]; - [super dealloc]; -} - -- (void)drawRect:(NSRect)dirtyRect -{ - // Drawing code here. -} - -@end diff --git a/BitTicker/en.lproj/InfoPlist.strings b/BitTicker/en.lproj/InfoPlist.strings old mode 100644 new mode 100755 index a48065a..1746d7e --- a/BitTicker/en.lproj/InfoPlist.strings +++ b/BitTicker/en.lproj/InfoPlist.strings @@ -1,18 +1,6 @@ /* - BitTicker is Copyright 2011 Stephen Oliver - http://github.com/mrsteveman1 - - 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 - */ + BitTicker is Copyright 2012 Stephen Oliver + http://github.com/infincia + + + */ \ No newline at end of file diff --git a/BitTicker/en.lproj/MainMenu.xib b/BitTicker/en.lproj/MainMenu.xib old mode 100644 new mode 100755 index 804ec40..7d6ffaa --- a/BitTicker/en.lproj/MainMenu.xib +++ b/BitTicker/en.lproj/MainMenu.xib @@ -1,32 +1,37 @@ - 1060 - 10J869 - 1306 - 1038.35 - 461.00 + 1070 + 11D50 + 2182 + 1138.32 + 568.00 com.apple.InterfaceBuilder.CocoaPlugin - 1306 + 2182 YES - NSUserDefaultsController - NSMenu - NSMenuItem + NSSegmentedControl + NSButton + NSImageView + NSTextFieldCell + NSButtonCell + NSBox + NSImageCell NSCustomObject + NSCustomView + NSUserDefaultsController + NSSegmentedCell + NSTextField YES com.apple.InterfaceBuilder.CocoaPlugin - YES - - YES - - + PluginDependencyRecalculationVersion + YES @@ -45,88 +50,469 @@ NSFontManager - - AMainMenu - + + YES + + + Dropdown + + + + 268 + YES - - - BitTicker - - 1048576 - 2147483647 - - NSImage - NSMenuCheckmark - - - NSImage - NSMenuMixedState - - submenuAction: - - BitTicker - + + + 268 + {{27, 220}, {278, 24}} + + + _NS:9 + YES + + 67239424 + 0 + + LucidaGrande + 13 + 16 + + _NS:9 + + YES - - - Refresh - r - 1048576 - 2147483647 - - + + 96 + 30 Min + 0 - - - About BitTicker - a - 1048576 - 2147483647 - - + + 12 Hours + 1 + YES + 0 - - - Settings - s - 1048576 - 2147483647 - - + + 96 + 24 Hours + 0 - - - YES - YES - - - 1048576 - 2147483647 - - + + 1 + 1 + + + + + 268 + {{35, 14}, {111, 18}} + + + _NS:3944 + YES + + 67239488 + 272631872 + BitTicker + + LucidaGrande-Bold + 14 + 16 + + _NS:3944 + + + 6 + System + controlColor + + 3 + MC42NjY2NjY2NjY3AA - - - Quit BitTicker - q - 1048576 - 2147483647 - - + + + 6 + System + controlTextColor + + 3 + MAA + + + + + + + 12 + + YES + + + 274 + + YES + + + 268 + {{15, 114}, {38, 17}} + + + _NS:3944 + YES + + 68288064 + 272630784 + High: + + LucidaGrande + 13 + 1044 + + _NS:3944 + + + + + + + + 268 + {{132, 114}, {149, 17}} + + + _NS:3944 + YES + + 68288064 + 71304192 + Label + + _NS:3944 + + + + + + + + 268 + {{132, 89}, {149, 17}} + + + _NS:3944 + YES + + 68288064 + 71304192 + Label + + _NS:3944 + + + + + + + + 268 + {{15, 89}, {34, 17}} + + + _NS:3944 + YES + + 68288064 + 272630784 + Low: + + _NS:3944 + + + + + + + + 268 + {{15, 64}, {31, 17}} + + + _NS:3944 + YES + + 68288064 + 272630784 + Buy: + + _NS:3944 + + + + + + + + 268 + {{132, 64}, {149, 17}} + + + _NS:3944 + YES + + 68288064 + 71304192 + Label + + _NS:3944 + + + + + + + + 268 + {{15, 39}, {30, 17}} + + + _NS:3944 + YES + + 68288064 + 272630784 + Sell: + + _NS:3944 + + + + + + + + 268 + {{109, 39}, {172, 17}} + + + _NS:3944 + YES + + 68288064 + 71304192 + Label + + _NS:3944 + + + + + + + + 268 + {{15, 14}, {97, 17}} + + + _NS:3944 + YES + + 68288064 + 272630784 + Trade Volume: + + _NS:3944 + + + + + + + + 268 + {{109, 14}, {172, 17}} + + + _NS:3944 + YES + + 68288064 + 71304192 + Label + + _NS:3944 + + + + + + + + 268 + + YES + + YES + Apple PDF pasteboard type + Apple PICT pasteboard type + Apple PNG pasteboard type + NSFilenamesPboardType + NeXT Encapsulated PostScript v1.2 pasteboard type + NeXT TIFF v4.0 pasteboard type + + + {{15, -40}, {24, 24}} + + + _NS:2141 + YES + + 130560 + 33554432 + _NS:2141 + 0 + 0 + 0 + NO + + YES + + + + 268 + {{44, -36}, {237, 17}} + + + _NS:3944 + YES + + 67239488 + 272631808 + + + + _NS:3944 + + + + + + + {{1, 1}, {296, 148}} + + + _NS:21 + + + {{17, 43}, {298, 164}} + + + _NS:18 + {0, 0} + + 67239424 + 0 + Trade data + + LucidaGrande + 11 + 3100 + + + 6 + System + textBackgroundColor + + 3 + MQA + + + + 3 + MCAwLjgwMDAwMDAxMTkAA + + + + 1 + 0 + 2 + NO + + + + 12 + + YES + + + 274 + + YES + + + 268 + {{2, 3}, {292, 151}} + + + _NS:1192 + GraphView + + {{1, 1}, {296, 153}} + + + _NS:21 + + + {{17, 258}, {298, 169}} + + + _NS:18 + {0, 0} + + 67239424 + 0 + Last price + + + + 3 + MCAwLjgwMDAwMDAxMTkAA - _NSAppleMenu + + + 1 + 0 + 2 + NO + + + + 268 + {{271, 12}, {41, 25}} + + + _NS:287 + YES + + -2080244224 + 134217728 + Quit + + _NS:287 + + -2038152961 + 163 + + + 400 + 75 - _NSMainMenu - - - YES - - - SharedSettings + {332, 435} + + + NSView @@ -142,35 +528,138 @@ - orderFrontStandardAboutPanel: - - + terminate: + + - 685 + 785 - terminate: - - + timeframeChanged: + + - 686 + 836 - - refreshTicker: - - + + value: high + + + + + + value: high + value + high + 2 + - 743 + 820 - - showSettings: - - + + value: low + + + + + + value: low + value + low + 2 + - 744 + 823 + + + + value: buy + + + + + + value: buy + value + buy + 2 + + + 826 + + + + value: sell + + + + + + value: sell + value + sell + 2 + + + 829 + + + + value: vol + + + + + + value: vol + value + vol + 2 + + + 832 + + + + dropdownView + + + + 833 + + + + selectedIndex: values.timeframe + + + + + + selectedIndex: values.timeframe + selectedIndex + values.timeframe + + YES + + YES + NSNoSelectionPlaceholder + NSNotApplicablePlaceholder + NSNullPlaceholder + + + YES + + + + + + 2 + + + 841 @@ -178,7 +667,9 @@ YES 0 - + + YES + @@ -211,74 +702,274 @@ - 540 - + 705 + + + + + 745 + YES - + + + + + - 546 - + 748 + YES - + - + - 553 - + 749 + YES - - - - - + - + - 557 - - + 750 + + + YES + + + + + + + + + + + + + + - 561 - - - Menu Item - Quit BitTicker + 751 + + + YES + + + - 564 - - - Menu Item - About BitTicker + 752 + + + YES + + - 705 - - + 753 + + + YES + + + + + + 754 + + + YES + + + + + + 755 + + + YES + + + + + + 756 + + + YES + + + + + + 757 + + + YES + + + + + + 758 + + + YES + + + - 719 - + 759 + + + YES + + + + + + 760 + + + YES + + + + + + 761 + + + YES + + + + + + 762 + + + YES + + + + + + 763 + + + YES + + + + + + 764 + + + YES + + + + + + 769 + + + + + 770 + + + + + 771 + + + + + 772 + + + + + 773 + + + + + 774 + + + + + 775 + + + + + 776 + + + + + 777 + + + + + 778 + + + + + 779 + + + + + 780 + + + + + 781 + + + + + 782 + + + + + 817 + - 740 - - - Menu Item - Settings + 834 + + + YES + + + - 742 - - - Menu Item - Refresh + 835 + + @@ -291,56 +982,88 @@ -3.IBPluginDependency 420.IBPluginDependency 494.IBPluginDependency - 540.IBEditorWindowLastContentRect - 540.IBPluginDependency - 540.ImportedFromIB2 - 540.WindowOrigin - 540.editorWindowContentRectSynchronizationRect - 546.IBPluginDependency - 546.ImportedFromIB2 - 553.IBEditorWindowLastContentRect - 553.IBPluginDependency - 553.ImportedFromIB2 - 553.editorWindowContentRectSynchronizationRect - 557.IBPluginDependency - 557.ImportedFromIB2 - 561.IBPluginDependency - 561.ImportedFromIB2 - 564.IBPluginDependency - 564.ImportedFromIB2 705.IBPluginDependency - 719.IBPluginDependency - 740.IBPluginDependency - 742.IBPluginDependency + 745.IBPluginDependency + 748.IBPluginDependency + 749.IBPluginDependency + 750.IBPluginDependency + 751.IBPluginDependency + 752.IBPluginDependency + 753.IBPluginDependency + 754.IBPluginDependency + 755.IBPluginDependency + 756.IBPluginDependency + 757.IBPluginDependency + 758.IBPluginDependency + 759.IBPluginDependency + 760.IBPluginDependency + 761.IBPluginDependency + 762.IBPluginDependency + 763.IBPluginDependency + 764.IBPluginDependency + 769.IBPluginDependency + 770.IBPluginDependency + 771.IBPluginDependency + 772.IBPluginDependency + 773.IBPluginDependency + 774.IBPluginDependency + 775.IBPluginDependency + 776.IBPluginDependency + 777.IBPluginDependency + 778.IBPluginDependency + 779.IBPluginDependency + 780.IBPluginDependency + 781.IBPluginDependency + 782.IBPluginDependency + 817.IBPluginDependency + 834.IBPluginDependency + 835.IBNSSegmentedControlInspectorSelectedSegmentMetadataKey + 835.IBPluginDependency - + YES com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin - {{380, 836}, {512, 20}} com.apple.InterfaceBuilder.CocoaPlugin - - {74, 862} - {{6, 978}, {478, 20}} com.apple.InterfaceBuilder.CocoaPlugin - - {{286, 129}, {275, 183}} com.apple.InterfaceBuilder.CocoaPlugin - - {{23, 794}, {245, 183}} com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin @@ -355,92 +1078,16 @@ - 744 - - - - YES - - BitTickerAppDelegate - NSObject - - YES - - YES - refreshTicker: - showAbout: - showSettings: - - - YES - id - id - id - - - - YES - - YES - refreshTicker: - showAbout: - showSettings: - - - YES - - refreshTicker: - id - - - showAbout: - id - - - showSettings: - id - - - - - IBProjectSource - ./Classes/BitTickerAppDelegate.h - - - - SharedSettings - NSObject - - IBProjectSource - ./Classes/SharedSettings.h - - - + 841 + 0 IBCocoaFramework - - com.apple.InterfaceBuilder.CocoaPlugin.macosx - - com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3 YES 3 - - YES - - YES - NSMenuCheckmark - NSMenuMixedState - - - YES - {9, 8} - {7, 2} - - diff --git a/BitTicker/main.m b/BitTicker/main.m old mode 100644 new mode 100755 index ee834c6..888eae9 --- a/BitTicker/main.m +++ b/BitTicker/main.m @@ -1,20 +1,8 @@ /* - BitTicker is Copyright 2011 Stephen Oliver - http://github.com/mrsteveman1 - - 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 + BitTicker is Copyright 2012 Stephen Oliver + http://github.com/infincia + + */ #import diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7f158f8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,30 @@ + +// Copyright (c) 2012 Stephen Oliver +// + +/* + + 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 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 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. + + + */ diff --git a/README b/README deleted file mode 100644 index d6a4ac3..0000000 --- a/README +++ /dev/null @@ -1,16 +0,0 @@ -BitTicker is Copyright 2011 Stephen Oliver -http://github.com/mrsteveman1 - -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 diff --git a/README.Markdown b/README.Markdown new file mode 100755 index 0000000..4afedff --- /dev/null +++ b/README.Markdown @@ -0,0 +1 @@ +### BitTicker is a Bitcoin stock ticker built to work with MtGox Bitcoin market. It is currently being overhauled but it should work fine. \ No newline at end of file From d2e41798da67724f6d088aad83a195bbb44d3dcf Mon Sep 17 00:00:00 2001 From: mrsteveman1 Date: Thu, 31 May 2012 22:25:41 -0400 Subject: [PATCH 42/46] readme --- README.Markdown | 1 - 1 file changed, 1 deletion(-) delete mode 100755 README.Markdown diff --git a/README.Markdown b/README.Markdown deleted file mode 100755 index 4afedff..0000000 --- a/README.Markdown +++ /dev/null @@ -1 +0,0 @@ -### BitTicker is a Bitcoin stock ticker built to work with MtGox Bitcoin market. It is currently being overhauled but it should work fine. \ No newline at end of file From f4c4085624c415285c6a04243fded78875e43398 Mon Sep 17 00:00:00 2001 From: mrsteveman1 Date: Thu, 31 May 2012 22:25:57 -0400 Subject: [PATCH 43/46] readme --- README.markdown | 1 + 1 file changed, 1 insertion(+) create mode 100755 README.markdown diff --git a/README.markdown b/README.markdown new file mode 100755 index 0000000..4afedff --- /dev/null +++ b/README.markdown @@ -0,0 +1 @@ +### BitTicker is a Bitcoin stock ticker built to work with MtGox Bitcoin market. It is currently being overhauled but it should work fine. \ No newline at end of file From b1ae9fd2e6395426fc0256999991ae603575e216 Mon Sep 17 00:00:00 2001 From: mrsteveman1 Date: Thu, 31 May 2012 22:26:51 -0400 Subject: [PATCH 44/46] readme --- README.markdown | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 4afedff..5a65ab9 100755 --- a/README.markdown +++ b/README.markdown @@ -1 +1,5 @@ -### BitTicker is a Bitcoin stock ticker built to work with MtGox Bitcoin market. It is currently being overhauled but it should work fine. \ No newline at end of file +### BitTicker + +BitTicker is a Bitcoin stock ticker built to work with MtGox Bitcoin market. + +The binary version available from the download button should work fine, but the source code is in a state of transition so don't plan on it being usable yet. From 4712087612f71d04fb43c8b43d620793ce36cfc0 Mon Sep 17 00:00:00 2001 From: mrsteveman1 Date: Thu, 2 Aug 2012 00:55:12 -0400 Subject: [PATCH 45/46] move afnetworking and coreplot binary into the project tree, clean some things up --- AFNetworking/AFHTTPClient.h | 516 ++++++++++++ AFNetworking/AFHTTPClient.m | 793 ++++++++++++++++++ AFNetworking/AFHTTPRequestOperation.h | 154 ++++ AFNetworking/AFHTTPRequestOperation.m | 335 ++++++++ AFNetworking/AFImageRequestOperation.h | 115 +++ AFNetworking/AFImageRequestOperation.m | 238 ++++++ AFNetworking/AFJSONRequestOperation.h | 66 ++ AFNetworking/AFJSONRequestOperation.m | 138 +++ AFNetworking/AFJSONUtilities.h | 26 + AFNetworking/AFJSONUtilities.m | 217 +++++ .../AFNetworkActivityIndicatorManager.h | 66 ++ .../AFNetworkActivityIndicatorManager.m | 129 +++ AFNetworking/AFNetworking.h | 44 + AFNetworking/AFPropertyListRequestOperation.h | 68 ++ AFNetworking/AFPropertyListRequestOperation.m | 147 ++++ AFNetworking/AFURLConnectionOperation.h | 244 ++++++ AFNetworking/AFURLConnectionOperation.m | 579 +++++++++++++ AFNetworking/AFXMLRequestOperation.h | 89 ++ AFNetworking/AFXMLRequestOperation.m | 177 ++++ BitTicker.xcodeproj/project.pbxproj | 139 ++- BitTicker/Dropdown.h | 16 +- BitTicker/Dropdown.m | 21 +- BitTicker/GraphView.h | 4 +- BitTicker/MtGox.m | 2 +- BitTicker/StatusItemView.h | 8 +- CorePlot.framework/CorePlot | 1 + CorePlot.framework/Headers | 1 + CorePlot.framework/PrivateHeaders | 1 + CorePlot.framework/Resources | 1 + CorePlot.framework/Versions/A/CorePlot | Bin 0 -> 1422096 bytes .../Versions/A/Headers/CPTAnnotation.h | 34 + .../A/Headers/CPTAnnotationHostLayer.h | 19 + .../Versions/A/Headers/CPTAxis.h | 265 ++++++ .../Versions/A/Headers/CPTAxisLabel.h | 34 + .../Versions/A/Headers/CPTAxisSet.h | 20 + .../Versions/A/Headers/CPTAxisTitle.h | 7 + .../Versions/A/Headers/CPTBarPlot.h | 134 +++ .../Versions/A/Headers/CPTBorderedLayer.h | 16 + .../Versions/A/Headers/CPTColor.h | 42 + .../Versions/A/Headers/CPTColorSpace.h | 21 + .../Versions/A/Headers/CPTConstraints.h | 38 + .../CPTDecimalNumberValueTransformer.h | 6 + .../Versions/A/Headers/CPTDefinitions.h | 136 +++ .../Versions/A/Headers/CPTExceptions.h | 10 + .../Versions/A/Headers/CPTFill.h | 38 + .../Versions/A/Headers/CPTGradient.h | 100 +++ .../Versions/A/Headers/CPTGraph.h | 136 +++ .../Versions/A/Headers/CPTGraphHostingView.h | 12 + .../Versions/A/Headers/CPTImage.h | 36 + .../Versions/A/Headers/CPTLayer.h | 109 +++ .../Versions/A/Headers/CPTLayerAnnotation.h | 20 + .../Versions/A/Headers/CPTLegend.h | 135 +++ .../Versions/A/Headers/CPTLegendEntry.h | 40 + .../Versions/A/Headers/CPTLimitBand.h | 25 + .../Versions/A/Headers/CPTLineCap.h | 69 ++ .../Versions/A/Headers/CPTLineStyle.h | 37 + .../Versions/A/Headers/CPTMutableLineStyle.h | 17 + .../CPTMutableNumericData+TypeConversion.h | 23 + .../A/Headers/CPTMutableNumericData.h | 29 + .../Versions/A/Headers/CPTMutablePlotRange.h | 29 + .../Versions/A/Headers/CPTMutableShadow.h | 14 + .../Versions/A/Headers/CPTMutableTextStyle.h | 14 + .../A/Headers/CPTNumericData+TypeConversion.h | 23 + .../Versions/A/Headers/CPTNumericData.h | 56 ++ .../Versions/A/Headers/CPTNumericDataType.h | 44 + .../Versions/A/Headers/CPTPathExtensions.h | 15 + .../Versions/A/Headers/CPTPieChart.h | 132 +++ .../A/Headers/CPTPlatformSpecificCategories.h | 26 + .../A/Headers/CPTPlatformSpecificDefines.h | 6 + .../A/Headers/CPTPlatformSpecificFunctions.h | 33 + .../Versions/A/Headers/CPTPlot.h | 249 ++++++ .../Versions/A/Headers/CPTPlotArea.h | 56 ++ .../Versions/A/Headers/CPTPlotAreaFrame.h | 16 + .../Versions/A/Headers/CPTPlotRange.h | 65 ++ .../Versions/A/Headers/CPTPlotSpace.h | 166 ++++ .../A/Headers/CPTPlotSpaceAnnotation.h | 16 + .../Versions/A/Headers/CPTPlotSymbol.h | 74 ++ .../Versions/A/Headers/CPTRangePlot.h | 50 ++ .../Versions/A/Headers/CPTResponder.h | 53 ++ .../Versions/A/Headers/CPTScatterPlot.h | 130 +++ .../Versions/A/Headers/CPTShadow.h | 27 + .../Versions/A/Headers/CPTTextLayer.h | 29 + .../Versions/A/Headers/CPTTextStyle.h | 53 ++ .../Versions/A/Headers/CPTTheme.h | 53 ++ .../Versions/A/Headers/CPTTimeFormatter.h | 19 + .../Versions/A/Headers/CPTTradingRangePlot.h | 70 ++ .../Versions/A/Headers/CPTUtilities.h | 121 +++ .../Versions/A/Headers/CPTXYAxis.h | 18 + .../Versions/A/Headers/CPTXYAxisSet.h | 12 + .../Versions/A/Headers/CPTXYGraph.h | 16 + .../Versions/A/Headers/CPTXYPlotSpace.h | 25 + .../Versions/A/Headers/CorePlot.h | 61 ++ .../A/PrivateHeaders/CPTAxisLabelGroup.h | 7 + .../A/PrivateHeaders/CPTGridLineGroup.h | 14 + .../Versions/A/PrivateHeaders/CPTGridLines.h | 15 + .../Versions/A/PrivateHeaders/CPTPlotGroup.h | 14 + .../A/PrivateHeaders/NSCoderExtensions.h | 37 + .../NSDecimalNumberExtensions.h | 9 + .../A/PrivateHeaders/NSNumberExtensions.h | 16 + .../A/PrivateHeaders/_CPTConstraintsFixed.h | 26 + .../PrivateHeaders/_CPTConstraintsRelative.h | 24 + .../A/PrivateHeaders/_CPTDarkGradientTheme.h | 6 + .../Versions/A/PrivateHeaders/_CPTFillColor.h | 20 + .../A/PrivateHeaders/_CPTFillGradient.h | 22 + .../Versions/A/PrivateHeaders/_CPTFillImage.h | 22 + .../A/PrivateHeaders/_CPTPlainBlackTheme.h | 6 + .../A/PrivateHeaders/_CPTPlainWhiteTheme.h | 6 + .../A/PrivateHeaders/_CPTSlateTheme.h | 6 + .../A/PrivateHeaders/_CPTStocksTheme.h | 6 + .../Versions/A/PrivateHeaders/_CPTXYTheme.h | 6 + .../Resources/English.lproj/InfoPlist.strings | Bin 0 -> 92 bytes .../Versions/A/Resources/Info.plist | 38 + .../Versions/A/Resources/License.txt | 9 + CorePlot.framework/Versions/Current | 1 + 114 files changed, 7924 insertions(+), 100 deletions(-) create mode 100644 AFNetworking/AFHTTPClient.h create mode 100644 AFNetworking/AFHTTPClient.m create mode 100644 AFNetworking/AFHTTPRequestOperation.h create mode 100644 AFNetworking/AFHTTPRequestOperation.m create mode 100644 AFNetworking/AFImageRequestOperation.h create mode 100644 AFNetworking/AFImageRequestOperation.m create mode 100644 AFNetworking/AFJSONRequestOperation.h create mode 100644 AFNetworking/AFJSONRequestOperation.m create mode 100644 AFNetworking/AFJSONUtilities.h create mode 100644 AFNetworking/AFJSONUtilities.m create mode 100644 AFNetworking/AFNetworkActivityIndicatorManager.h create mode 100644 AFNetworking/AFNetworkActivityIndicatorManager.m create mode 100644 AFNetworking/AFNetworking.h create mode 100644 AFNetworking/AFPropertyListRequestOperation.h create mode 100644 AFNetworking/AFPropertyListRequestOperation.m create mode 100644 AFNetworking/AFURLConnectionOperation.h create mode 100644 AFNetworking/AFURLConnectionOperation.m create mode 100644 AFNetworking/AFXMLRequestOperation.h create mode 100644 AFNetworking/AFXMLRequestOperation.m create mode 120000 CorePlot.framework/CorePlot create mode 120000 CorePlot.framework/Headers create mode 120000 CorePlot.framework/PrivateHeaders create mode 120000 CorePlot.framework/Resources create mode 100755 CorePlot.framework/Versions/A/CorePlot create mode 100644 CorePlot.framework/Versions/A/Headers/CPTAnnotation.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTAnnotationHostLayer.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTAxis.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTAxisLabel.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTAxisSet.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTAxisTitle.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTBarPlot.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTBorderedLayer.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTColor.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTColorSpace.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTConstraints.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTDecimalNumberValueTransformer.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTDefinitions.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTExceptions.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTFill.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTGradient.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTGraph.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTGraphHostingView.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTImage.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTLayer.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTLayerAnnotation.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTLegend.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTLegendEntry.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTLimitBand.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTLineCap.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTLineStyle.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTMutableLineStyle.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTMutableNumericData+TypeConversion.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTMutableNumericData.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTMutablePlotRange.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTMutableShadow.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTMutableTextStyle.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTNumericData+TypeConversion.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTNumericData.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTNumericDataType.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTPathExtensions.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTPieChart.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTPlatformSpecificCategories.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTPlatformSpecificDefines.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTPlatformSpecificFunctions.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTPlot.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTPlotArea.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTPlotAreaFrame.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTPlotRange.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTPlotSpace.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTPlotSpaceAnnotation.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTPlotSymbol.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTRangePlot.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTResponder.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTScatterPlot.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTShadow.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTTextLayer.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTTextStyle.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTTheme.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTTimeFormatter.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTTradingRangePlot.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTUtilities.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTXYAxis.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTXYAxisSet.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTXYGraph.h create mode 100644 CorePlot.framework/Versions/A/Headers/CPTXYPlotSpace.h create mode 100644 CorePlot.framework/Versions/A/Headers/CorePlot.h create mode 100644 CorePlot.framework/Versions/A/PrivateHeaders/CPTAxisLabelGroup.h create mode 100644 CorePlot.framework/Versions/A/PrivateHeaders/CPTGridLineGroup.h create mode 100644 CorePlot.framework/Versions/A/PrivateHeaders/CPTGridLines.h create mode 100644 CorePlot.framework/Versions/A/PrivateHeaders/CPTPlotGroup.h create mode 100644 CorePlot.framework/Versions/A/PrivateHeaders/NSCoderExtensions.h create mode 100644 CorePlot.framework/Versions/A/PrivateHeaders/NSDecimalNumberExtensions.h create mode 100644 CorePlot.framework/Versions/A/PrivateHeaders/NSNumberExtensions.h create mode 100644 CorePlot.framework/Versions/A/PrivateHeaders/_CPTConstraintsFixed.h create mode 100644 CorePlot.framework/Versions/A/PrivateHeaders/_CPTConstraintsRelative.h create mode 100644 CorePlot.framework/Versions/A/PrivateHeaders/_CPTDarkGradientTheme.h create mode 100644 CorePlot.framework/Versions/A/PrivateHeaders/_CPTFillColor.h create mode 100644 CorePlot.framework/Versions/A/PrivateHeaders/_CPTFillGradient.h create mode 100644 CorePlot.framework/Versions/A/PrivateHeaders/_CPTFillImage.h create mode 100644 CorePlot.framework/Versions/A/PrivateHeaders/_CPTPlainBlackTheme.h create mode 100644 CorePlot.framework/Versions/A/PrivateHeaders/_CPTPlainWhiteTheme.h create mode 100644 CorePlot.framework/Versions/A/PrivateHeaders/_CPTSlateTheme.h create mode 100644 CorePlot.framework/Versions/A/PrivateHeaders/_CPTStocksTheme.h create mode 100644 CorePlot.framework/Versions/A/PrivateHeaders/_CPTXYTheme.h create mode 100644 CorePlot.framework/Versions/A/Resources/English.lproj/InfoPlist.strings create mode 100644 CorePlot.framework/Versions/A/Resources/Info.plist create mode 100644 CorePlot.framework/Versions/A/Resources/License.txt create mode 120000 CorePlot.framework/Versions/Current diff --git a/AFNetworking/AFHTTPClient.h b/AFNetworking/AFHTTPClient.h new file mode 100644 index 0000000..cdc1666 --- /dev/null +++ b/AFNetworking/AFHTTPClient.h @@ -0,0 +1,516 @@ +// AFHTTPClient.h +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import + +@class AFHTTPRequestOperation; +@protocol AFHTTPClientOperation; +@protocol AFMultipartFormData; + +/** + Posted when network reachability changes. + The notification object is an `NSNumber` object containing the boolean value for the current network reachability. + This notification contains no information in the `userInfo` dictionary. + + @warning In order for network reachability to be monitored, include the `SystemConfiguration` framework in the active target's "Link Binary With Library" build phase, and add `#import ` to the header prefix of the project (Prefix.pch). + */ +#ifdef _SYSTEMCONFIGURATION_H +extern NSString * const AFNetworkingReachabilityDidChangeNotification; +#endif + +/** + Specifies network reachability of the client to its `baseURL` domain. + */ +#ifdef _SYSTEMCONFIGURATION_H +typedef enum { + AFNetworkReachabilityStatusUnknown = -1, + AFNetworkReachabilityStatusNotReachable = 0, + AFNetworkReachabilityStatusReachableViaWWAN = 1, + AFNetworkReachabilityStatusReachableViaWiFi = 2, +} AFNetworkReachabilityStatus; +#endif + +/** + Specifies the method used to encode parameters into request body. + */ +typedef enum { + AFFormURLParameterEncoding, + AFJSONParameterEncoding, + AFPropertyListParameterEncoding, +} AFHTTPClientParameterEncoding; + +/** + Returns a string, replacing certain characters with the equivalent percent escape sequence based on the specified encoding. + + @param string The string to URL encode + @param encoding The encoding to use for the replacement. If you are uncertain of the correct encoding, you should use UTF-8 (NSUTF8StringEncoding), which is the encoding designated by RFC 3986 as the correct encoding for use in URLs. + + @discussion The characters escaped are all characters that are not legal URL characters (based on RFC 3986), including any whitespace, punctuation, or special characters. + + @return A URL-encoded string. If it does not need to be modified (no percent escape sequences are missing), this function may merely return string argument. + */ +extern NSString * AFURLEncodedStringFromStringWithEncoding(NSString *string, NSStringEncoding encoding); + +/** + Returns a query string constructed by a set of parameters, using the specified encoding. + + @param parameters The parameters used to construct the query string + @param encoding The encoding to use in constructing the query string. If you are uncertain of the correct encoding, you should use UTF-8 (NSUTF8StringEncoding), which is the encoding designated by RFC 3986 as the correct encoding for use in URLs. + + @discussion Query strings are constructed by collecting each key-value pair, URL-encoding a string representation of the key-value pair, and then joining the components with "&". + + + If a key-value pair has a an `NSArray` for its value, each member of the array will be represented in the format `key[]=value1&key[]value2`. Otherwise, the key-value pair will be formatted as "key=value". String representations of both keys and values are derived using the `-description` method. The constructed query string does not include the ? character used to delimit the query component. + + @return A URL-encoded query string + */ +extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *parameters, NSStringEncoding encoding); + +/** + `AFHTTPClient` captures the common patterns of communicating with an web application over HTTP. It encapsulates information like base URL, authorization credentials, and HTTP headers, and uses them to construct and manage the execution of HTTP request operations. + + ## Automatic Content Parsing + + Instances of `AFHTTPClient` may specify which types of requests it expects and should handle by registering HTTP operation classes for automatic parsing. Registered classes will determine whether they can handle a particular request, and then construct a request operation accordingly in `enqueueHTTPRequestOperationWithRequest:success:failure`. See `AFHTTPClientOperation` for further details. + + ## Subclassing Notes + + In most cases, one should create an `AFHTTPClient` subclass for each website or web application that your application communicates with. It is often useful, also, to define a class method that returns a singleton shared HTTP client in each subclass, that persists authentication credentials and other configuration across the entire application. + + ## Methods to Override + + To change the behavior of all url request construction for an `AFHTTPClient` subclass, override `requestWithMethod:path:parameters`. + + To change the behavior of all request operation construction for an `AFHTTPClient` subclass, override `enqueueHTTPRequestOperationWithRequest:success:failure`. + + ## Default Headers + + By default, `AFHTTPClient` sets the following HTTP headers: + + - `Accept-Encoding: gzip` + - `Accept-Language: ([NSLocale preferredLanguages]), en-us;q=0.8` + - `User-Agent: (generated user agent)` + + You can override these HTTP headers or define new ones using `setDefaultHeader:value:`. + + ## URL Construction Using Relative Paths + + Both `requestWithMethod:path:parameters` and `multipartFormRequestWithMethod:path:parameters:constructingBodyWithBlock:` construct URLs from the path relative to the `baseURL`, using `NSURL +URLWithString:relativeToURL:`. Below are a few examples of how `baseURL` and relative paths interract: + + NSURL *baseURL = [NSURL URLWithString:@"http://example.com/v1/"]; + [NSURL URLWithString:@"foo" relativeToURL:baseURL]; // http://example.com/v1/foo + [NSURL URLWithString:@"foo?bar=baz" relativeToURL:baseURL]; // http://example.com/v1/foo?bar=baz + [NSURL URLWithString:@"/foo" relativeToURL:baseURL]; // http://example.com/foo + [NSURL URLWithString:@"foo/" relativeToURL:baseURL]; // http://example.com/v1/foo + [NSURL URLWithString:@"/foo/" relativeToURL:baseURL]; // http://example.com/foo/ + [NSURL URLWithString:@"http://example2.com/" relativeToURL:baseURL]; // http://example2.com/ + + */ +@interface AFHTTPClient : NSObject + +///--------------------------------------- +/// @name Accessing HTTP Client Properties +///--------------------------------------- + +/** + The url used as the base for paths specified in methods such as `getPath:parameteres:success:failure` + */ +@property (readonly, nonatomic, retain) NSURL *baseURL; + +/** + The string encoding used in constructing url requests. This is `NSUTF8StringEncoding` by default. + */ +@property (nonatomic, assign) NSStringEncoding stringEncoding; + +/** + The `AFHTTPClientParameterEncoding` value corresponding to how parameters are encoded into a request body. This is `AFFormURLParameterEncoding` by default. + + @warning JSON encoding will automatically use JSONKit, SBJSON, YAJL, or NextiveJSON, if provided. Otherwise, the built-in `NSJSONSerialization` class is used, if available (iOS 5.0 and Mac OS 10.7). If the build target does not either support `NSJSONSerialization` or include a third-party JSON library, a runtime exception will be thrown when attempting to encode parameters as JSON. + */ +@property (nonatomic, assign) AFHTTPClientParameterEncoding parameterEncoding; + +/** + The operation queue which manages operations enqueued by the HTTP client. + */ +@property (readonly, nonatomic, retain) NSOperationQueue *operationQueue; + +/** + The reachability status from the device to the current `baseURL` of the `AFHTTPClient`. + + @warning This property requires the `SystemConfiguration` framework. Add it in the active target's "Link Binary With Library" build phase, and add `#import ` to the header prefix of the project (Prefix.pch). + */ +#ifdef _SYSTEMCONFIGURATION_H +@property (readonly, nonatomic, assign) AFNetworkReachabilityStatus networkReachabilityStatus; +#endif + +///--------------------------------------------- +/// @name Creating and Initializing HTTP Clients +///--------------------------------------------- + +/** + Creates and initializes an `AFHTTPClient` object with the specified base URL. + + @param url The base URL for the HTTP client. This argument must not be nil. + + @return The newly-initialized HTTP client + */ ++ (AFHTTPClient *)clientWithBaseURL:(NSURL *)url; + +/** + Initializes an `AFHTTPClient` object with the specified base URL. + + @param url The base URL for the HTTP client. This argument must not be nil. + + @discussion This is the designated initializer. + + @return The newly-initialized HTTP client + */ +- (id)initWithBaseURL:(NSURL *)url; + +///----------------------------------- +/// @name Managing Reachability Status +///----------------------------------- + +/** + Sets a callback to be executed when the network availability of the `baseURL` host changes. + + @param block A block object to be executed when the network availability of the `baseURL` host changes.. This block has no return value and takes a single argument which represents the various reachability states from the device to the `baseURL`. + + @warning This method requires the `SystemConfiguration` framework. Add it in the active target's "Link Binary With Library" build phase, and add `#import ` to the header prefix of the project (Prefix.pch). + */ +#ifdef _SYSTEMCONFIGURATION_H +- (void)setReachabilityStatusChangeBlock:(void (^)(AFNetworkReachabilityStatus status))block; +#endif + +///------------------------------- +/// @name Managing HTTP Operations +///------------------------------- + +/** + Attempts to register a subclass of `AFHTTPRequestOperation`, adding it to a chain to automatically generate request operations from a URL request. + + @param The subclass of `AFHTTPRequestOperation` to register + + @return `YES` if the registration is successful, `NO` otherwise. The only failure condition is if `operationClass` does is not a subclass of `AFHTTPRequestOperation`. + + @discussion When `enqueueHTTPRequestOperationWithRequest:success:failure` is invoked, each registered class is consulted in turn to see if it can handle the specific request. The first class to return `YES` when sent a `canProcessRequest:` message is used to create an operation using `initWithURLRequest:` and do `setCompletionBlockWithSuccess:failure:`. There is no guarantee that all registered classes will be consulted. Classes are consulted in the reverse order of their registration. Attempting to register an already-registered class will move it to the top of the list. + + @see `AFHTTPClientOperation` + */ +- (BOOL)registerHTTPOperationClass:(Class)operationClass; + +/** + Unregisters the specified subclass of `AFHTTPRequestOperation`. + + @param The class conforming to the `AFHTTPClientOperation` protocol to unregister + + @discussion After this method is invoked, `operationClass` is no longer consulted when `requestWithMethod:path:parameters` is invoked. + */ +- (void)unregisterHTTPOperationClass:(Class)operationClass; + +///---------------------------------- +/// @name Managing HTTP Header Values +///---------------------------------- + +/** + Returns the value for the HTTP headers set in request objects created by the HTTP client. + + @param header The HTTP header to return the default value for + + @return The default value for the HTTP header, or `nil` if unspecified + */ +- (NSString *)defaultValueForHeader:(NSString *)header; + +/** + Sets the value for the HTTP headers set in request objects made by the HTTP client. If `nil`, removes the existing value for that header. + + @param header The HTTP header to set a default value for + @param value The value set as default for the specified header, or `nil + */ +- (void)setDefaultHeader:(NSString *)header value:(NSString *)value; + +/** + Sets the "Authorization" HTTP header set in request objects made by the HTTP client to a basic authentication value with Base64-encoded username and password. This overwrites any existing value for this header. + + @param username The HTTP basic auth username + @param password The HTTP basic auth password + */ +- (void)setAuthorizationHeaderWithUsername:(NSString *)username password:(NSString *)password; + +/** + Sets the "Authorization" HTTP header set in request objects made by the HTTP client to a token-based authentication value, such as an OAuth access token. This overwrites any existing value for this header. + + @param token The authentication token + */ +- (void)setAuthorizationHeaderWithToken:(NSString *)token; + +/** + Clears any existing value for the "Authorization" HTTP header. + */ +- (void)clearAuthorizationHeader; + +///------------------------------- +/// @name Creating Request Objects +///------------------------------- + +/** + Creates an `NSMutableURLRequest` object with the specified HTTP method and path. + + If the HTTP method is `GET`, the parameters will be used to construct a url-encoded query string that is appended to the request's URL. Otherwise, the parameters will be encoded according to the value of the `parameterEncoding` property, and set as the request body. + + @param method The HTTP method for the request, such as `GET`, `POST`, `PUT`, or `DELETE`. + @param path The path to be appended to the HTTP client's base URL and used as the request URL. + @param parameters The parameters to be either set as a query string for `GET` requests, or the request HTTP body. + + @return An `NSMutableURLRequest` object + */ +- (NSMutableURLRequest *)requestWithMethod:(NSString *)method + path:(NSString *)path + parameters:(NSDictionary *)parameters; + +/** + Creates an `NSMutableURLRequest` object with the specified HTTP method and path, and constructs a `multipart/form-data` HTTP body, using the specified parameters and multipart form data block. See http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.2 + + @param method The HTTP method for the request. Must be either `POST`, `PUT`, or `DELETE`. + @param path The path to be appended to the HTTP client's base URL and used as the request URL. + @param parameters The parameters to be encoded and set in the request HTTP body. + @param block A block that takes a single argument and appends data to the HTTP body. The block argument is an object adopting the `AFMultipartFormData` protocol. This can be used to upload files, encode HTTP body as JSON or XML, or specify multiple values for the same parameter, as one might for array values. + + @discussion The multipart form data is constructed synchronously in the specified block, so in cases where large amounts of data are being added to the request, you should consider performing this method in the background. Likewise, the form data is constructed in-memory, so it may be advantageous to instead write parts of the form data to a file and stream the request body using the `HTTPBodyStream` property of `NSURLRequest`. + + @warning An exception will be raised if the specified method is not `POST`, `PUT` or `DELETE`. + + @return An `NSMutableURLRequest` object + */ +- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method + path:(NSString *)path + parameters:(NSDictionary *)parameters + constructingBodyWithBlock:(void (^)(id formData))block; + +///------------------------------- +/// @name Creating HTTP Operations +///------------------------------- + +/** + Creates an `AFHTTPRequestOperation`. + + In order to determine what kind of operation is created, each registered subclass conforming to the `AFHTTPClient` protocol is consulted (in reverse order of when they were specified) to see if it can handle the specific request. The first class to return `YES` when sent a `canProcessRequest:` message is used to generate an operation using `HTTPRequestOperationWithRequest:success:failure:`. + + @param request The request object to be loaded asynchronously during execution of the operation. + @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the created request operation and the object created from the response data of request. + @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the resonse data. This block has no return value and takes two arguments:, the created request operation and the `NSError` object describing the network or parsing error that occurred. + */ +- (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)request + success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success + failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure; + +///---------------------------------------- +/// @name Managing Enqueued HTTP Operations +///---------------------------------------- + +/** + Enqueues an `AFHTTPRequestOperation` to the HTTP client's operation queue. + + @param operation The HTTP request operation to be enqueued. + */ +- (void)enqueueHTTPRequestOperation:(AFHTTPRequestOperation *)operation; + +/** + Cancels all operations in the HTTP client's operation queue whose URLs match the specified HTTP method and path. + + @param method The HTTP method to match for the cancelled requests, such as `GET`, `POST`, `PUT`, or `DELETE`. If `nil`, all request operations with URLs matching the path will be cancelled. + @param url The path to match for the cancelled requests. + */ +- (void)cancelAllHTTPOperationsWithMethod:(NSString *)method path:(NSString *)path; + +///--------------------------------------- +/// @name Batching HTTP Request Operations +///--------------------------------------- + +/** + Creates and enqueues an `AFHTTPRequestOperation` to the HTTP client's operation queue for each specified request object into a batch. When each request operation finishes, the specified progress block is executed, until all of the request operations have finished, at which point the completion block also executes. + + @param requests The `NSURLRequest` objects used to create and enqueue operations. + @param progressBlock A block object to be executed upon the completion of each request operation in the batch. This block has no return value and takes two arguments: the number of operations that have already finished execution, and the total number of operations. + @param completionBlock A block object to be executed upon the completion of all of the request operations in the batch. This block has no return value and takes a single argument: the batched request operations. + + @discussion Operations are created by passing the specified `NSURLRequest` objects in `requests`, using `-HTTPRequestOperationWithRequest:success:failure:`, with `nil` for both the `success` and `failure` parameters. + */ +- (void)enqueueBatchOfHTTPRequestOperationsWithRequests:(NSArray *)requests + progressBlock:(void (^)(NSUInteger numberOfCompletedOperations, NSUInteger totalNumberOfOperations))progressBlock + completionBlock:(void (^)(NSArray *operations))completionBlock; + +/** + Enqueues the specified request operations into a batch. When each request operation finishes, the specified progress block is executed, until all of the request operations have finished, at which point the completion block also executes. + + @param operations The request operations used to be batched and enqueued. + @param progressBlock A block object to be executed upon the completion of each request operation in the batch. This block has no return value and takes two arguments: the number of operations that have already finished execution, and the total number of operations. + @param completionBlock A block object to be executed upon the completion of all of the request operations in the batch. This block has no return value and takes a single argument: the batched request operations. + */ +- (void)enqueueBatchOfHTTPRequestOperations:(NSArray *)operations + progressBlock:(void (^)(NSUInteger numberOfCompletedOperations, NSUInteger totalNumberOfOperations))progressBlock + completionBlock:(void (^)(NSArray *operations))completionBlock; + +///--------------------------- +/// @name Making HTTP Requests +///--------------------------- + +/** + Creates an `AFHTTPRequestOperation` with a `GET` request, and enqueues it to the HTTP client's operation queue. + + @param path The path to be appended to the HTTP client's base URL and used as the request URL. + @param parameters The parameters to be encoded and appended as the query string for the request URL. + @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the created request operation and the object created from the response data of request. + @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the resonse data. This block has no return value and takes two arguments:, the created request operation and the `NSError` object describing the network or parsing error that occurred. + + @see HTTPRequestOperationWithRequest:success:failure + */ +- (void)getPath:(NSString *)path + parameters:(NSDictionary *)parameters + success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success + failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure; + +/** + Creates an `AFHTTPRequestOperation` with a `POST` request, and enqueues it to the HTTP client's operation queue. + + @param path The path to be appended to the HTTP client's base URL and used as the request URL. + @param parameters The parameters to be encoded and set in the request HTTP body. + @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the created request operation and the object created from the response data of request. + @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the resonse data. This block has no return value and takes two arguments:, the created request operation and the `NSError` object describing the network or parsing error that occurred. + + @see HTTPRequestOperationWithRequest:success:failure + */ +- (void)postPath:(NSString *)path + parameters:(NSDictionary *)parameters + success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success + failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure; + +/** + Creates an `AFHTTPRequestOperation` with a `PUT` request, and enqueues it to the HTTP client's operation queue. + + @param path The path to be appended to the HTTP client's base URL and used as the request URL. + @param parameters The parameters to be encoded and set in the request HTTP body. + @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the created request operation and the object created from the response data of request. + @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the resonse data. This block has no return value and takes two arguments:, the created request operation and the `NSError` object describing the network or parsing error that occurred. + + @see HTTPRequestOperationWithRequest:success:failure + */ +- (void)putPath:(NSString *)path + parameters:(NSDictionary *)parameters + success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success + failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure; + +/** + Creates an `AFHTTPRequestOperation` with a `DELETE` request, and enqueues it to the HTTP client's operation queue. + + @param path The path to be appended to the HTTP client's base URL and used as the request URL. + @param parameters The parameters to be encoded and set in the request HTTP body. + @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the created request operation and the object created from the response data of request. + @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the resonse data. This block has no return value and takes two arguments:, the created request operation and the `NSError` object describing the network or parsing error that occurred. + + @see HTTPRequestOperationWithRequest:success:failure + */ +- (void)deletePath:(NSString *)path + parameters:(NSDictionary *)parameters + success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success + failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure; + +/** + Creates an `AFHTTPRequestOperation` with a `PATCH` request, and enqueues it to the HTTP client's operation queue. + + @param path The path to be appended to the HTTP client's base URL and used as the request URL. + @param parameters The parameters to be encoded and set in the request HTTP body. + @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the created request operation and the object created from the response data of request. + @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the resonse data. This block has no return value and takes two arguments:, the created request operation and the `NSError` object describing the network or parsing error that occurred. + + @see HTTPRequestOperationWithRequest:success:failure + */ +- (void)patchPath:(NSString *)path + parameters:(NSDictionary *)parameters + success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success + failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure; +@end + +#pragma mark - + +/** + The `AFMultipartFormData` protocol defines the methods supported by the parameter in the block argument of `multipartFormRequestWithMethod:path:parameters:constructingBodyWithBlock:`. + + @see `AFHTTPClient -multipartFormRequestWithMethod:path:parameters:constructingBodyWithBlock:` + */ +@protocol AFMultipartFormData + +/** + Appends HTTP headers, followed by the encoded data and the multipart form boundary. + + @param headers The HTTP headers to be appended to the form data. + @param body The data to be encoded and appended to the form data. + */ +- (void)appendPartWithHeaders:(NSDictionary *)headers body:(NSData *)body; + +/** + Appends the HTTP headers `Content-Disposition: form-data; name=#{name}"`, followed by the encoded data and the multipart form boundary. + + @param data The data to be encoded and appended to the form data. + @param name The name to be associated with the specified data. This parameter must not be `nil`. + */ +- (void)appendPartWithFormData:(NSData *)data name:(NSString *)name; + +/** + Appends the HTTP header `Content-Disposition: file; filename=#{filename}; name=#{name}"` and `Content-Type: #{mimeType}`, followed by the encoded file data and the multipart form boundary. + + @param data The data to be encoded and appended to the form data. + @param name The name to be associated with the specified data. This parameter must not be `nil`. + @param mimeType The MIME type of the specified data. (For example, the MIME type for a JPEG image is image/jpeg.) For a list of valid MIME types, see http://www.iana.org/assignments/media-types/. This parameter must not be `nil`. + @param filename The filename to be associated with the specified data. This parameter must not be `nil`. + */ +- (void)appendPartWithFileData:(NSData *)data name:(NSString *)name fileName:(NSString *)fileName mimeType:(NSString *)mimeType; + +/** + Appends the HTTP header `Content-Disposition: file; filename=#{generated filename}; name=#{name}"` and `Content-Type: #{generated mimeType}`, followed by the encoded file data and the multipart form boundary. + + @param fileURL The URL corresponding to the file whose content will be appended to the form. + @param name The name to be associated with the specified data. This parameter must not be `nil`. + @param error If an error occurs, upon return contains an `NSError` object that describes the problem. + + @return `YES` if the file data was successfully appended, otherwise `NO`. + + @discussion The filename and MIME type for this data in the form will be automatically generated, using `NSURLResponse` `-suggestedFilename` and `-MIMEType`, respectively. + */ +- (BOOL)appendPartWithFileURL:(NSURL *)fileURL name:(NSString *)name error:(NSError **)error; + +/** + Appends encoded data to the form data. + + @param data The data to be encoded and appended to the form data. + */ +- (void)appendData:(NSData *)data; + +/** + Appends a string to the form data. + + @param string The string to be encoded and appended to the form data. + */ +- (void)appendString:(NSString *)string; + +@end + diff --git a/AFNetworking/AFHTTPClient.m b/AFNetworking/AFHTTPClient.m new file mode 100644 index 0000000..b375c51 --- /dev/null +++ b/AFNetworking/AFHTTPClient.m @@ -0,0 +1,793 @@ +// AFHTTPClient.m +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import + +#import "AFHTTPClient.h" +#import "AFHTTPRequestOperation.h" +#import "AFJSONUtilities.h" + +#import + +#if __IPHONE_OS_VERSION_MIN_REQUIRED +#import +#endif + +#ifdef _SYSTEMCONFIGURATION_H +#import +#import +#import +#import +#import +#import +#endif + +NSString * const AFNetworkingReachabilityDidChangeNotification = @"com.alamofire.networking.reachability.change"; + +static NSString * const kAFMultipartFormLineDelimiter = @"\r\n"; // CRLF +static NSString * const kAFMultipartFormBoundary = @"Boundary+0xAbCdEfGbOuNdArY"; + +@interface AFMultipartFormData : NSObject { +@private + NSStringEncoding _stringEncoding; + NSMutableData *_mutableData; +} + +@property (readonly) NSData *data; + +- (id)initWithStringEncoding:(NSStringEncoding)encoding; + +@end + +#pragma mark - + +#ifdef _SYSTEMCONFIGURATION_H +typedef SCNetworkReachabilityRef AFNetworkReachabilityRef; +typedef void (^AFNetworkReachabilityStatusBlock)(AFNetworkReachabilityStatus status); +#else +typedef id AFNetworkReachabilityRef; +#endif + +typedef void (^AFCompletionBlock)(void); + +static NSUInteger const kAFHTTPClientDefaultMaxConcurrentOperationCount = 4; + +static NSString * AFBase64EncodedStringFromString(NSString *string) { + NSData *data = [NSData dataWithBytes:[string UTF8String] length:[string lengthOfBytesUsingEncoding:NSUTF8StringEncoding]]; + NSUInteger length = [data length]; + NSMutableData *mutableData = [NSMutableData dataWithLength:((length + 2) / 3) * 4]; + + uint8_t *input = (uint8_t *)[data bytes]; + uint8_t *output = (uint8_t *)[mutableData mutableBytes]; + + for (NSUInteger i = 0; i < length; i += 3) { + NSUInteger value = 0; + for (NSUInteger j = i; j < (i + 3); j++) { + value <<= 8; + if (j < length) { + value |= (0xFF & input[j]); + } + } + + static uint8_t const kAFBase64EncodingTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + NSUInteger idx = (i / 3) * 4; + output[idx + 0] = kAFBase64EncodingTable[(value >> 18) & 0x3F]; + output[idx + 1] = kAFBase64EncodingTable[(value >> 12) & 0x3F]; + output[idx + 2] = (i + 1) < length ? kAFBase64EncodingTable[(value >> 6) & 0x3F] : '='; + output[idx + 3] = (i + 2) < length ? kAFBase64EncodingTable[(value >> 0) & 0x3F] : '='; + } + + return [[[NSString alloc] initWithData:mutableData encoding:NSASCIIStringEncoding] autorelease]; +} + +NSString * AFURLEncodedStringFromStringWithEncoding(NSString *string, NSStringEncoding encoding) { + static NSString * const kAFLegalCharactersToBeEscaped = @"?!@#$^&%*+=,:;'\"`<>()[]{}/\\|~ "; + + /* + The documentation for `CFURLCreateStringByAddingPercentEscapes` suggests that one should "pre-process" URL strings with unpredictable sequences that may already contain percent escapes. However, if the string contains an unescaped sequence with '%' appearing without an escape code (such as when representing percentages like "42%"), `stringByReplacingPercentEscapesUsingEncoding` will return `nil`. Thus, the string is only unescaped if there are no invalid percent-escaped sequences. + */ + NSString *unescapedString = [string stringByReplacingPercentEscapesUsingEncoding:encoding]; + if (unescapedString) { + string = unescapedString; + } + + return [(NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, (CFStringRef)kAFLegalCharactersToBeEscaped, CFStringConvertNSStringEncodingToEncoding(encoding)) autorelease]; +} + +#pragma mark - + +@interface AFQueryStringComponent : NSObject { +@private + NSString *_key; + NSString *_value; +} + +@property (readwrite, nonatomic, retain) id key; +@property (readwrite, nonatomic, retain) id value; + +- (id)initWithKey:(id)key value:(id)value; +- (NSString *)URLEncodedStringValueWithEncoding:(NSStringEncoding)stringEncoding; + +@end + +@implementation AFQueryStringComponent +@synthesize key = _key; +@synthesize value = _value; + +- (id)initWithKey:(id)key value:(id)value { + self = [super init]; + if (!self) { + return nil; + } + + self.key = key; + self.value = value; + + return self; +} + +- (void)dealloc { + [_key release]; + [_value release]; + [super dealloc]; +} + +- (NSString *)URLEncodedStringValueWithEncoding:(NSStringEncoding)stringEncoding { + return [NSString stringWithFormat:@"%@=%@", self.key, AFURLEncodedStringFromStringWithEncoding([self.value description], stringEncoding)]; +} + +@end + +#pragma mark - + +extern NSArray * AFQueryStringComponentsFromKeyAndValue(NSString *key, id value); +extern NSArray * AFQueryStringComponentsFromKeyAndDictionaryValue(NSString *key, NSDictionary *value); +extern NSArray * AFQueryStringComponentsFromKeyAndArrayValue(NSString *key, NSArray *value); + +NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *parameters, NSStringEncoding stringEncoding) { + NSMutableArray *mutableComponents = [NSMutableArray array]; + for (AFQueryStringComponent *component in AFQueryStringComponentsFromKeyAndValue(nil, parameters)) { + [mutableComponents addObject:[component URLEncodedStringValueWithEncoding:stringEncoding]]; + } + + return [mutableComponents componentsJoinedByString:@"&"]; +} + +NSArray * AFQueryStringComponentsFromKeyAndValue(NSString *key, id value) { + NSMutableArray *mutableQueryStringComponents = [NSMutableArray array]; + + if([value isKindOfClass:[NSDictionary class]]) { + [mutableQueryStringComponents addObjectsFromArray:AFQueryStringComponentsFromKeyAndDictionaryValue(key, value)]; + } else if([value isKindOfClass:[NSArray class]]) { + [mutableQueryStringComponents addObjectsFromArray:AFQueryStringComponentsFromKeyAndArrayValue(key, value)]; + } else { + [mutableQueryStringComponents addObject:[[[AFQueryStringComponent alloc] initWithKey:key value:value] autorelease]]; + } + + return mutableQueryStringComponents; +} + +NSArray * AFQueryStringComponentsFromKeyAndDictionaryValue(NSString *key, NSDictionary *value){ + NSMutableArray *mutableQueryStringComponents = [NSMutableArray array]; + + [value enumerateKeysAndObjectsUsingBlock:^(id nestedKey, id nestedValue, BOOL *stop) { + [mutableQueryStringComponents addObjectsFromArray:AFQueryStringComponentsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)]; + }]; + + return mutableQueryStringComponents; +} + +NSArray * AFQueryStringComponentsFromKeyAndArrayValue(NSString *key, NSArray *value) { + NSMutableArray *mutableQueryStringComponents = [NSMutableArray array]; + + [value enumerateObjectsUsingBlock:^(id nestedValue, NSUInteger idx, BOOL *stop) { + [mutableQueryStringComponents addObjectsFromArray:AFQueryStringComponentsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)]; + }]; + + return mutableQueryStringComponents; +} + +static NSString * AFJSONStringFromParameters(NSDictionary *parameters) { + NSError *error = nil; + NSData *JSONData = AFJSONEncode(parameters, &error); + + if (!error) { + return [[[NSString alloc] initWithData:JSONData encoding:NSUTF8StringEncoding] autorelease]; + } else { + return nil; + } +} + +static NSString * AFPropertyListStringFromParameters(NSDictionary *parameters) { + NSString *propertyListString = nil; + NSError *error = nil; + + NSData *propertyListData = [NSPropertyListSerialization dataWithPropertyList:parameters format:NSPropertyListXMLFormat_v1_0 options:0 error:&error]; + if (!error) { + propertyListString = [[[NSString alloc] initWithData:propertyListData encoding:NSUTF8StringEncoding] autorelease]; + } + + return propertyListString; +} + +@interface AFHTTPClient () +@property (readwrite, nonatomic, retain) NSURL *baseURL; +@property (readwrite, nonatomic, retain) NSMutableArray *registeredHTTPOperationClassNames; +@property (readwrite, nonatomic, retain) NSMutableDictionary *defaultHeaders; +@property (readwrite, nonatomic, retain) NSOperationQueue *operationQueue; +#ifdef _SYSTEMCONFIGURATION_H +@property (readwrite, nonatomic, assign) AFNetworkReachabilityRef networkReachability; +@property (readwrite, nonatomic, assign) AFNetworkReachabilityStatus networkReachabilityStatus; +@property (readwrite, nonatomic, copy) AFNetworkReachabilityStatusBlock networkReachabilityStatusBlock; +#endif + +#ifdef _SYSTEMCONFIGURATION_H +- (void)startMonitoringNetworkReachability; +- (void)stopMonitoringNetworkReachability; +#endif +@end + +@implementation AFHTTPClient +@synthesize baseURL = _baseURL; +@synthesize stringEncoding = _stringEncoding; +@synthesize parameterEncoding = _parameterEncoding; +@synthesize registeredHTTPOperationClassNames = _registeredHTTPOperationClassNames; +@synthesize defaultHeaders = _defaultHeaders; +@synthesize operationQueue = _operationQueue; +#ifdef _SYSTEMCONFIGURATION_H +@synthesize networkReachability = _networkReachability; +@synthesize networkReachabilityStatus = _networkReachabilityStatus; +@synthesize networkReachabilityStatusBlock = _networkReachabilityStatusBlock; +#endif + ++ (AFHTTPClient *)clientWithBaseURL:(NSURL *)url { + return [[[self alloc] initWithBaseURL:url] autorelease]; +} + +- (id)initWithBaseURL:(NSURL *)url { + self = [super init]; + if (!self) { + return nil; + } + + self.baseURL = url; + + self.stringEncoding = NSUTF8StringEncoding; + self.parameterEncoding = AFFormURLParameterEncoding; + + self.registeredHTTPOperationClassNames = [NSMutableArray array]; + + self.defaultHeaders = [NSMutableDictionary dictionary]; + + // Accept-Encoding HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3 + [self setDefaultHeader:@"Accept-Encoding" value:@"gzip"]; + + // Accept-Language HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4 + NSString *preferredLanguageCodes = [[NSLocale preferredLanguages] componentsJoinedByString:@", "]; + [self setDefaultHeader:@"Accept-Language" value:[NSString stringWithFormat:@"%@, en-us;q=0.8", preferredLanguageCodes]]; + +#if __IPHONE_OS_VERSION_MIN_REQUIRED + // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43 + [self setDefaultHeader:@"User-Agent" value:[NSString stringWithFormat:@"%@/%@ (%@, %@ %@, %@, Scale/%f)", [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleIdentifierKey], [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleVersionKey], @"unknown", [[UIDevice currentDevice] systemName], [[UIDevice currentDevice] systemVersion], [[UIDevice currentDevice] model], ([[UIScreen mainScreen] respondsToSelector:@selector(scale)] ? [[UIScreen mainScreen] scale] : 1.0)]]; +#elif __MAC_OS_X_VERSION_MIN_REQUIRED + [self setDefaultHeader:@"User-Agent" value:[NSString stringWithFormat:@"%@/%@ (%@)", [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleIdentifierKey], [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleVersionKey], @"unknown"]]; +#endif + +#ifdef _SYSTEMCONFIGURATION_H + self.networkReachabilityStatus = AFNetworkReachabilityStatusUnknown; + [self startMonitoringNetworkReachability]; +#endif + + self.operationQueue = [[[NSOperationQueue alloc] init] autorelease]; + [self.operationQueue setMaxConcurrentOperationCount:kAFHTTPClientDefaultMaxConcurrentOperationCount]; + + return self; +} + +- (void)dealloc { +#ifdef _SYSTEMCONFIGURATION_H + [self stopMonitoringNetworkReachability]; + [_networkReachabilityStatusBlock release]; +#endif + + [_baseURL release]; + [_registeredHTTPOperationClassNames release]; + [_defaultHeaders release]; + [_operationQueue release]; + + [super dealloc]; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p, baseURL: %@, defaultHeaders: %@, registeredOperationClasses: %@, operationQueue: %@>", NSStringFromClass([self class]), self, [self.baseURL absoluteString], self.defaultHeaders, self.registeredHTTPOperationClassNames, self.operationQueue]; +} + +#pragma mark - + +#ifdef _SYSTEMCONFIGURATION_H +static BOOL AFURLHostIsIPAddress(NSURL *url) { + struct sockaddr_in sa_in; + struct sockaddr_in6 sa_in6; + + return [url host] && (inet_pton(AF_INET, [[url host] UTF8String], &sa_in) == 1 || inet_pton(AF_INET6, [[url host] UTF8String], &sa_in6) == 1); +} + +static AFNetworkReachabilityStatus AFNetworkReachabilityStatusForFlags(SCNetworkReachabilityFlags flags) { + BOOL isReachable = ((flags & kSCNetworkReachabilityFlagsReachable) != 0); + BOOL needsConnection = ((flags & kSCNetworkReachabilityFlagsConnectionRequired) != 0); + BOOL isNetworkReachable = (isReachable && !needsConnection); + + AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusUnknown; + if(isNetworkReachable == NO){ + status = AFNetworkReachabilityStatusNotReachable; + } +#if TARGET_OS_IPHONE + else if((flags & kSCNetworkReachabilityFlagsIsWWAN) != 0){ + status = AFNetworkReachabilityStatusReachableViaWWAN; + } +#endif + else { + status = AFNetworkReachabilityStatusReachableViaWiFi; + } + + return status; +} + +static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) { + AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags); + AFNetworkReachabilityStatusBlock block = (AFNetworkReachabilityStatusBlock)info; + if (block) { + block(status); + } + + [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingReachabilityDidChangeNotification object:[NSNumber numberWithInt:status]]; +} + +static const void * AFNetworkReachabilityRetainCallback(const void *info) { + return [(AFNetworkReachabilityStatusBlock)info copy]; +} + +static void AFNetworkReachabilityReleaseCallback(const void *info) { + [(AFNetworkReachabilityStatusBlock)info release]; +} + +- (void)startMonitoringNetworkReachability { + [self stopMonitoringNetworkReachability]; + + if (!self.baseURL) { + return; + } + + self.networkReachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, [[self.baseURL host] UTF8String]); + + AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status){ + self.networkReachabilityStatus = status; + if (self.networkReachabilityStatusBlock) { + self.networkReachabilityStatusBlock(status); + } + }; + + SCNetworkReachabilityContext context = {0, callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL}; + SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context); + SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), (CFStringRef)NSRunLoopCommonModes); + + /* Network reachability monitoring does not establish a baseline for IP addresses as it does for hostnames, so if the base URL host is an IP address, the initial reachability callback is manually triggered. + */ + if (AFURLHostIsIPAddress(self.baseURL)) { + SCNetworkReachabilityFlags flags; + SCNetworkReachabilityGetFlags(self.networkReachability, &flags); + dispatch_async(dispatch_get_main_queue(), ^{ + AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags); + callback(status); + }); + } +} + +- (void)stopMonitoringNetworkReachability { + if (_networkReachability) { + SCNetworkReachabilityUnscheduleFromRunLoop(_networkReachability, CFRunLoopGetMain(), (CFStringRef)NSRunLoopCommonModes); + CFRelease(_networkReachability); + } +} + +- (void)setReachabilityStatusChangeBlock:(void (^)(AFNetworkReachabilityStatus status))block { + self.networkReachabilityStatusBlock = block; +} +#endif + +#pragma mark - + +- (BOOL)registerHTTPOperationClass:(Class)operationClass { + if (![operationClass isSubclassOfClass:[AFHTTPRequestOperation class]]) { + return NO; + } + + NSString *className = NSStringFromClass(operationClass); + [self.registeredHTTPOperationClassNames removeObject:className]; + [self.registeredHTTPOperationClassNames insertObject:className atIndex:0]; + + return YES; +} + +- (void)unregisterHTTPOperationClass:(Class)operationClass { + NSString *className = NSStringFromClass(operationClass); + [self.registeredHTTPOperationClassNames removeObject:className]; +} + +#pragma mark - + +- (NSString *)defaultValueForHeader:(NSString *)header { + return [self.defaultHeaders valueForKey:header]; +} + +- (void)setDefaultHeader:(NSString *)header value:(NSString *)value { + [self.defaultHeaders setValue:value forKey:header]; +} + +- (void)setAuthorizationHeaderWithUsername:(NSString *)username password:(NSString *)password { + NSString *basicAuthCredentials = [NSString stringWithFormat:@"%@:%@", username, password]; + [self setDefaultHeader:@"Authorization" value:[NSString stringWithFormat:@"Basic %@", AFBase64EncodedStringFromString(basicAuthCredentials)]]; +} + +- (void)setAuthorizationHeaderWithToken:(NSString *)token { + [self setDefaultHeader:@"Authorization" value:[NSString stringWithFormat:@"Token token=\"%@\"", token]]; +} + +- (void)clearAuthorizationHeader { + [self.defaultHeaders removeObjectForKey:@"Authorization"]; +} + +#pragma mark - + +- (NSMutableURLRequest *)requestWithMethod:(NSString *)method + path:(NSString *)path + parameters:(NSDictionary *)parameters +{ + NSURL *url = [NSURL URLWithString:path relativeToURL:self.baseURL]; + NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] initWithURL:url] autorelease]; + [request setHTTPMethod:method]; + [request setAllHTTPHeaderFields:self.defaultHeaders]; + + if (parameters) { + if ([method isEqualToString:@"GET"] || [method isEqualToString:@"HEAD"] || [method isEqualToString:@"DELETE"]) { + url = [NSURL URLWithString:[[url absoluteString] stringByAppendingFormat:[path rangeOfString:@"?"].location == NSNotFound ? @"?%@" : @"&%@", AFQueryStringFromParametersWithEncoding(parameters, self.stringEncoding)]]; + [request setURL:url]; + } else { + NSString *charset = (NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding(self.stringEncoding)); + switch (self.parameterEncoding) { + case AFFormURLParameterEncoding:; + [request setValue:[NSString stringWithFormat:@"application/x-www-form-urlencoded; charset=%@", charset] forHTTPHeaderField:@"Content-Type"]; + [request setHTTPBody:[AFQueryStringFromParametersWithEncoding(parameters, self.stringEncoding) dataUsingEncoding:self.stringEncoding]]; + break; + case AFJSONParameterEncoding:; + [request setValue:[NSString stringWithFormat:@"application/json; charset=%@", charset] forHTTPHeaderField:@"Content-Type"]; + [request setHTTPBody:[AFJSONStringFromParameters(parameters) dataUsingEncoding:self.stringEncoding]]; + break; + case AFPropertyListParameterEncoding:; + [request setValue:[NSString stringWithFormat:@"application/x-plist; charset=%@", charset] forHTTPHeaderField:@"Content-Type"]; + [request setHTTPBody:[AFPropertyListStringFromParameters(parameters) dataUsingEncoding:self.stringEncoding]]; + break; + } + } + } + + return request; +} + +- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method + path:(NSString *)path + parameters:(NSDictionary *)parameters + constructingBodyWithBlock:(void (^)(id formData))block +{ + NSMutableURLRequest *request = [self requestWithMethod:method path:path parameters:nil]; + __block AFMultipartFormData *formData = [[AFMultipartFormData alloc] initWithStringEncoding:self.stringEncoding]; + + for (AFQueryStringComponent *component in AFQueryStringComponentsFromKeyAndValue(nil, parameters)) { + NSData *data = nil; + if ([component.value isKindOfClass:[NSData class]]) { + data = component.value; + } else { + data = [[component.value description] dataUsingEncoding:self.stringEncoding]; + } + + [formData appendPartWithFormData:data name:[component.key description]]; + } + + if (block) { + block(formData); + } + + [request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", kAFMultipartFormBoundary] forHTTPHeaderField:@"Content-Type"]; + [request setHTTPBody:[formData data]]; + + [formData autorelease]; + + return request; +} + +- (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)urlRequest + success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success + failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure +{ + AFHTTPRequestOperation *operation = nil; + NSString *className = nil; + NSEnumerator *enumerator = [self.registeredHTTPOperationClassNames reverseObjectEnumerator]; + while (!operation && (className = [enumerator nextObject])) { + Class op_class = NSClassFromString(className); + if (op_class && [op_class canProcessRequest:urlRequest]) { + operation = [[(AFHTTPRequestOperation *)[op_class alloc] initWithRequest:urlRequest] autorelease]; + } + } + + if (!operation) { + operation = [[[AFHTTPRequestOperation alloc] initWithRequest:urlRequest] autorelease]; + } + + [operation setCompletionBlockWithSuccess:success failure:failure]; + + return operation; +} + +#pragma mark - + +- (void)enqueueHTTPRequestOperation:(AFHTTPRequestOperation *)operation { + [self.operationQueue addOperation:operation]; +} + +- (void)cancelAllHTTPOperationsWithMethod:(NSString *)method path:(NSString *)path { + for (NSOperation *operation in [self.operationQueue operations]) { + if (![operation isKindOfClass:[AFHTTPRequestOperation class]]) { + continue; + } + + if ((!method || [method isEqualToString:[[(AFHTTPRequestOperation *)operation request] HTTPMethod]]) && [path isEqualToString:[[[(AFHTTPRequestOperation *)operation request] URL] path]]) { + [operation cancel]; + } + } +} + +- (void)enqueueBatchOfHTTPRequestOperationsWithRequests:(NSArray *)requests + progressBlock:(void (^)(NSUInteger numberOfCompletedOperations, NSUInteger totalNumberOfOperations))progressBlock + completionBlock:(void (^)(NSArray *operations))completionBlock +{ + NSMutableArray *mutableOperations = [NSMutableArray array]; + for (NSURLRequest *request in requests) { + AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithRequest:request success:nil failure:nil]; + [mutableOperations addObject:operation]; + } + + [self enqueueBatchOfHTTPRequestOperations:mutableOperations progressBlock:progressBlock completionBlock:completionBlock]; +} + +- (void)enqueueBatchOfHTTPRequestOperations:(NSArray *)operations + progressBlock:(void (^)(NSUInteger numberOfCompletedOperations, NSUInteger totalNumberOfOperations))progressBlock + completionBlock:(void (^)(NSArray *operations))completionBlock +{ + __block dispatch_group_t dispatchGroup = dispatch_group_create(); + dispatch_retain(dispatchGroup); + NSBlockOperation *batchedOperation = [NSBlockOperation blockOperationWithBlock:^{ + dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^{ + if (completionBlock) { + completionBlock(operations); + } + }); + dispatch_release(dispatchGroup); + }]; + + NSPredicate *finishedOperationPredicate = [NSPredicate predicateWithFormat:@"isFinished == YES"]; + + for (AFHTTPRequestOperation *operation in operations) { + AFCompletionBlock originalCompletionBlock = [[operation.completionBlock copy] autorelease]; + operation.completionBlock = ^{ + dispatch_queue_t queue = operation.successCallbackQueue ? operation.successCallbackQueue : dispatch_get_main_queue(); + dispatch_group_async(dispatchGroup, queue, ^{ + if (originalCompletionBlock) { + originalCompletionBlock(); + } + + if (progressBlock) { + progressBlock([[operations filteredArrayUsingPredicate:finishedOperationPredicate] count], [operations count]); + } + + dispatch_group_leave(dispatchGroup); + }); + }; + + dispatch_group_enter(dispatchGroup); + [batchedOperation addDependency:operation]; + + [self enqueueHTTPRequestOperation:operation]; + } + [self.operationQueue addOperation:batchedOperation]; +} + +#pragma mark - + +- (void)getPath:(NSString *)path + parameters:(NSDictionary *)parameters + success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success + failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure +{ + NSURLRequest *request = [self requestWithMethod:@"GET" path:path parameters:parameters]; + AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithRequest:request success:success failure:failure]; + [self enqueueHTTPRequestOperation:operation]; +} + +- (void)postPath:(NSString *)path + parameters:(NSDictionary *)parameters + success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success + failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure +{ + NSURLRequest *request = [self requestWithMethod:@"POST" path:path parameters:parameters]; + AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithRequest:request success:success failure:failure]; + [self enqueueHTTPRequestOperation:operation]; +} + +- (void)putPath:(NSString *)path + parameters:(NSDictionary *)parameters + success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success + failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure +{ + NSURLRequest *request = [self requestWithMethod:@"PUT" path:path parameters:parameters]; + AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithRequest:request success:success failure:failure]; + [self enqueueHTTPRequestOperation:operation]; +} + +- (void)deletePath:(NSString *)path + parameters:(NSDictionary *)parameters + success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success + failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure +{ + NSURLRequest *request = [self requestWithMethod:@"DELETE" path:path parameters:parameters]; + AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithRequest:request success:success failure:failure]; + [self enqueueHTTPRequestOperation:operation]; +} + +- (void)patchPath:(NSString *)path + parameters:(NSDictionary *)parameters + success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success + failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure +{ + NSURLRequest *request = [self requestWithMethod:@"PATCH" path:path parameters:parameters]; + AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithRequest:request success:success failure:failure]; + [self enqueueHTTPRequestOperation:operation]; +} + +@end + +#pragma mark - + +static NSString * const kAFMultipartFormCRLF = @"\r\n"; + +static inline NSString * AFMultipartFormInitialBoundary() { + return [NSString stringWithFormat:@"--%@%@", kAFMultipartFormBoundary, kAFMultipartFormCRLF]; +} + +static inline NSString * AFMultipartFormEncapsulationBoundary() { + return [NSString stringWithFormat:@"%@--%@%@", kAFMultipartFormCRLF, kAFMultipartFormBoundary, kAFMultipartFormCRLF]; +} + +static inline NSString * AFMultipartFormFinalBoundary() { + return [NSString stringWithFormat:@"%@--%@--%@%@", kAFMultipartFormCRLF, kAFMultipartFormBoundary, kAFMultipartFormCRLF, kAFMultipartFormCRLF]; +} + +@interface AFMultipartFormData () +@property (readwrite, nonatomic, assign) NSStringEncoding stringEncoding; +@property (readwrite, nonatomic, retain) NSMutableData *mutableData; +@end + +@implementation AFMultipartFormData +@synthesize stringEncoding = _stringEncoding; +@synthesize mutableData = _mutableData; + +- (id)initWithStringEncoding:(NSStringEncoding)encoding { + self = [super init]; + if (!self) { + return nil; + } + + self.stringEncoding = encoding; + self.mutableData = [NSMutableData dataWithLength:0]; + + return self; +} + +- (void)dealloc { + [_mutableData release]; + [super dealloc]; +} + +- (NSData *)data { + NSMutableData *finalizedData = [NSMutableData dataWithData:self.mutableData]; + [finalizedData appendData:[AFMultipartFormFinalBoundary() dataUsingEncoding:self.stringEncoding]]; + return finalizedData; +} + +#pragma mark - AFMultipartFormData + +- (void)appendPartWithHeaders:(NSDictionary *)headers body:(NSData *)body { + if ([self.mutableData length] == 0) { + [self appendString:AFMultipartFormInitialBoundary()]; + } else { + [self appendString:AFMultipartFormEncapsulationBoundary()]; + } + + for (NSString *field in [headers allKeys]) { + [self appendString:[NSString stringWithFormat:@"%@: %@%@", field, [headers valueForKey:field], kAFMultipartFormCRLF]]; + } + + [self appendString:kAFMultipartFormCRLF]; + [self appendData:body]; +} + +- (void)appendPartWithFormData:(NSData *)data name:(NSString *)name { + NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary]; + [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"", name] forKey:@"Content-Disposition"]; + + [self appendPartWithHeaders:mutableHeaders body:data]; +} + +- (void)appendPartWithFileData:(NSData *)data name:(NSString *)name fileName:(NSString *)fileName mimeType:(NSString *)mimeType { + NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary]; + [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"]; + [mutableHeaders setValue:mimeType forKey:@"Content-Type"]; + + [self appendPartWithHeaders:mutableHeaders body:data]; +} + +- (BOOL)appendPartWithFileURL:(NSURL *)fileURL name:(NSString *)name error:(NSError **)error { + if (![fileURL isFileURL]) { + NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; + [userInfo setValue:fileURL forKey:NSURLErrorFailingURLErrorKey]; + [userInfo setValue:NSLocalizedString(@"Expected URL to be a file URL", nil) forKey:NSLocalizedFailureReasonErrorKey]; + if (error != NULL) { + *error = [[[NSError alloc] initWithDomain:AFNetworkingErrorDomain code:NSURLErrorBadURL userInfo:userInfo] autorelease]; + } + + return NO; + } + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:fileURL]; + [request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData]; + + NSURLResponse *response = nil; + NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:error]; + + if (data && response) { + [self appendPartWithFileData:data name:name fileName:[response suggestedFilename] mimeType:[response MIMEType]]; + + return YES; + } else { + return NO; + } +} + +- (void)appendData:(NSData *)data { + [self.mutableData appendData:data]; +} + +- (void)appendString:(NSString *)string { + [self appendData:[string dataUsingEncoding:self.stringEncoding]]; +} + +@end diff --git a/AFNetworking/AFHTTPRequestOperation.h b/AFNetworking/AFHTTPRequestOperation.h new file mode 100644 index 0000000..9cab0e5 --- /dev/null +++ b/AFNetworking/AFHTTPRequestOperation.h @@ -0,0 +1,154 @@ +// AFHTTPRequestOperation.h +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import +#import "AFURLConnectionOperation.h" + +/** + Returns a set of MIME types detected in an HTTP `Accept` or `Content-Type` header. + */ +extern NSSet * AFContentTypesFromHTTPHeader(NSString *string); + +extern NSString * AFCreateIncompleteDownloadDirectoryPath(void); + +/** + `AFHTTPRequestOperation` is a subclass of `AFURLConnectionOperation` for requests using the HTTP or HTTPS protocols. It encapsulates the concept of acceptable status codes and content types, which determine the success or failure of a request. + */ +@interface AFHTTPRequestOperation : AFURLConnectionOperation + +///---------------------------------------------- +/// @name Getting HTTP URL Connection Information +///---------------------------------------------- + +/** + The last HTTP response received by the operation's connection. + */ +@property (readonly, nonatomic, retain) NSHTTPURLResponse *response; + +/** + Set a target file for the response, will stream directly into this destination. + Defaults to nil, which will use a memory stream. Will create a new outputStream on change. + + Note: Changing this while the request is not in ready state will be ignored. + */ +@property (nonatomic, copy) NSString *responseFilePath; + + +/** + Expected total length. This is different than expectedContentLength if the file is resumed. + On regular requests, this is equal to self.response.expectedContentLength unless we resume a request. + + Note: this can also be -1 if the file size is not sent (*) + */ +@property (assign, readonly) long long totalContentLength; + +/** + Indicator for the file offset on partial/resumed downloads. + This is greater than zero if the file download is resumed. + */ +@property (assign, readonly) long long offsetContentLength; + + +///---------------------------------------------------------- +/// @name Managing And Checking For Acceptable HTTP Responses +///---------------------------------------------------------- + +/** + A Boolean value that corresponds to whether the status code of the response is within the specified set of acceptable status codes. Returns `YES` if `acceptableStatusCodes` is `nil`. + */ +@property (readonly) BOOL hasAcceptableStatusCode; + +/** + A Boolean value that corresponds to whether the MIME type of the response is among the specified set of acceptable content types. Returns `YES` if `acceptableContentTypes` is `nil`. + */ +@property (readonly) BOOL hasAcceptableContentType; + +/** + The callback dispatch queue on success. If `NULL` (default), the main queue is used. + */ +@property (nonatomic) dispatch_queue_t successCallbackQueue; + +/** + The callback dispatch queue on failure. If `NULL` (default), the main queue is used. + */ +@property (nonatomic) dispatch_queue_t failureCallbackQueue; + +///------------------------------------------------------------- +/// @name Managing Accceptable HTTP Status Codes & Content Types +///------------------------------------------------------------- + +/** + Returns an `NSIndexSet` object containing the ranges of acceptable HTTP status codes. When non-`nil`, the operation will set the `error` property to an error in `AFErrorDomain`. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html + + By default, this is the range 200 to 299, inclusive. + */ ++ (NSIndexSet *)acceptableStatusCodes; + +/** + Adds status codes to the set of acceptable HTTP status codes returned by `+acceptableStatusCodes` in subsequent calls by this class and its descendents. + + @param statusCodes The status codes to be added to the set of acceptable HTTP status codes + */ ++ (void)addAcceptableStatusCodes:(NSIndexSet *)statusCodes; + +/** + Returns an `NSSet` object containing the acceptable MIME types. When non-`nil`, the operation will set the `error` property to an error in `AFErrorDomain`. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17 + + By default, this is `nil`. + */ ++ (NSSet *)acceptableContentTypes; + +/** + Adds content types to the set of acceptable MIME types returned by `+acceptableContentTypes` in subsequent calls by this class and its descendents. + + @param contentTypes The content types to be added to the set of acceptable MIME types + */ ++ (void)addAcceptableContentTypes:(NSSet *)contentTypes; + + +///----------------------------------------------------- +/// @name Determining Whether A Request Can Be Processed +///----------------------------------------------------- + +/** + A Boolean value determining whether or not the class can process the specified request. For example, `AFJSONRequestOperation` may check to make sure the content type was `application/json` or the URL path extension was `.json`. + + @param urlRequest The request that is determined to be supported or not supported for this class. + */ ++ (BOOL)canProcessRequest:(NSURLRequest *)urlRequest; + +///----------------------------------------------------------- +/// @name Setting Completion Block Success / Failure Callbacks +///----------------------------------------------------------- + +/** + Sets the `completionBlock` property with a block that executes either the specified success or failure block, depending on the state of the request on completion. If `error` returns a value, which can be caused by an unacceptable status code or content type, then `failure` is executed. Otherwise, `success` is executed. + + @param success The block to be executed on the completion of a successful request. This block has no return value and takes two arguments: the receiver operation and the object constructed from the response data of the request. + @param failure The block to be executed on the completion of an unsuccessful request. This block has no return value and takes two arguments: the receiver operation and the error that occured during the request. + + @discussion This method should be overridden in subclasses in order to specify the response object passed into the success block. + */ +- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success + failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure; + +@end diff --git a/AFNetworking/AFHTTPRequestOperation.m b/AFNetworking/AFHTTPRequestOperation.m new file mode 100644 index 0000000..bc9b2b7 --- /dev/null +++ b/AFNetworking/AFHTTPRequestOperation.m @@ -0,0 +1,335 @@ +// AFHTTPRequestOperation.m +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "AFHTTPRequestOperation.h" +#import + +NSString * const kAFNetworkingIncompleteDownloadDirectoryName = @"Incomplete"; + +NSSet * AFContentTypesFromHTTPHeader(NSString *string) { + static NSCharacterSet *_skippedCharacterSet = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _skippedCharacterSet = [[NSCharacterSet characterSetWithCharactersInString:@" ,"] retain]; + }); + + if (!string) { + return nil; + } + + NSScanner *scanner = [NSScanner scannerWithString:string]; + scanner.charactersToBeSkipped = _skippedCharacterSet; + + NSMutableSet *mutableContentTypes = [NSMutableSet set]; + while (![scanner isAtEnd]) { + NSString *contentType = nil; + if ([scanner scanUpToString:@";" intoString:&contentType]) { + [scanner scanUpToString:@"," intoString:nil]; + } + + if (contentType) { + [mutableContentTypes addObject:contentType]; + } + } + + return [NSSet setWithSet:mutableContentTypes]; +} + +static void AFSwizzleClassMethodWithImplementation(Class klass, SEL selector, IMP implementation) { + Method originalMethod = class_getClassMethod(klass, selector); + if (method_getImplementation(originalMethod) != implementation) { + class_replaceMethod(objc_getMetaClass([NSStringFromClass(klass) UTF8String]), selector, implementation, method_getTypeEncoding(originalMethod)); + } +} + +static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) { + NSMutableString *string = [NSMutableString string]; + + NSRange range = NSMakeRange([indexSet firstIndex], 1); + while (range.location != NSNotFound) { + NSUInteger nextIndex = [indexSet indexGreaterThanIndex:range.location]; + while (nextIndex == range.location + range.length) { + range.length++; + nextIndex = [indexSet indexGreaterThanIndex:nextIndex]; + } + + if (string.length) { + [string appendString:@","]; + } + + if (range.length == 1) { + [string appendFormat:@"%u", range.location]; + } else { + NSUInteger firstIndex = range.location; + NSUInteger lastIndex = firstIndex + range.length - 1; + [string appendFormat:@"%u-%u", firstIndex, lastIndex]; + } + + range.location = nextIndex; + range.length = 1; + } + + return string; +} + +NSString * AFCreateIncompleteDownloadDirectoryPath(void) { + static NSString *incompleteDownloadPath; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSString *tempDirectory = NSTemporaryDirectory(); + incompleteDownloadPath = [[tempDirectory stringByAppendingPathComponent:kAFNetworkingIncompleteDownloadDirectoryName] retain]; + + NSError *error = nil; + NSFileManager *fileMan = [[NSFileManager alloc] init]; + if(![fileMan createDirectoryAtPath:incompleteDownloadPath withIntermediateDirectories:YES attributes:nil error:&error]) { + NSLog(@"Failed to create incomplete downloads directory at %@", incompleteDownloadPath); + } + [fileMan release]; + }); + + return incompleteDownloadPath; +} + +#pragma mark - + +@interface AFHTTPRequestOperation () +@property (readwrite, nonatomic, retain) NSURLRequest *request; +@property (readwrite, nonatomic, retain) NSHTTPURLResponse *response; +@property (readwrite, nonatomic, retain) NSError *HTTPError; +@property (assign) long long totalContentLength; +@property (assign) long long offsetContentLength; +@end + +@implementation AFHTTPRequestOperation +@synthesize HTTPError = _HTTPError; +@synthesize responseFilePath = _responseFilePath; +@synthesize successCallbackQueue = _successCallbackQueue; +@synthesize failureCallbackQueue = _failureCallbackQueue; +@synthesize totalContentLength = _totalContentLength; +@synthesize offsetContentLength = _offsetContentLength; +@dynamic request; +@dynamic response; + +- (void)dealloc { + [_HTTPError release]; + + if (_successCallbackQueue) { + dispatch_release(_successCallbackQueue); + _successCallbackQueue = NULL; + } + + if (_failureCallbackQueue) { + dispatch_release(_failureCallbackQueue); + _failureCallbackQueue = NULL; + } + + [super dealloc]; +} + +- (NSError *)error { + if (self.response && !self.HTTPError) { + if (![self hasAcceptableStatusCode]) { + NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; + [userInfo setValue:[NSString stringWithFormat:NSLocalizedString(@"Expected status code in (%@), got %d", nil), AFStringFromIndexSet([[self class] acceptableStatusCodes]), [self.response statusCode]] forKey:NSLocalizedDescriptionKey]; + [userInfo setValue:[self.request URL] forKey:NSURLErrorFailingURLErrorKey]; + + self.HTTPError = [[[NSError alloc] initWithDomain:AFNetworkingErrorDomain code:NSURLErrorBadServerResponse userInfo:userInfo] autorelease]; + } else if ([self.responseData length] > 0 && ![self hasAcceptableContentType]) { // Don't invalidate content type if there is no content + NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; + [userInfo setValue:[NSString stringWithFormat:NSLocalizedString(@"Expected content type %@, got %@", nil), [[self class] acceptableContentTypes], [self.response MIMEType]] forKey:NSLocalizedDescriptionKey]; + [userInfo setValue:[self.request URL] forKey:NSURLErrorFailingURLErrorKey]; + + self.HTTPError = [[[NSError alloc] initWithDomain:AFNetworkingErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:userInfo] autorelease]; + } + } + + if (self.HTTPError) { + return self.HTTPError; + } else { + return [super error]; + } +} + +- (void)pause { + unsigned long long offset = 0; + if ([self.outputStream propertyForKey:NSStreamFileCurrentOffsetKey]) { + offset = [[self.outputStream propertyForKey:NSStreamFileCurrentOffsetKey] unsignedLongLongValue]; + } else { + offset = [[self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey] length]; + } + + NSMutableURLRequest *mutableURLRequest = [[self.request mutableCopy] autorelease]; + if ([[self.response allHeaderFields] valueForKey:@"ETag"]) { + [mutableURLRequest setValue:[[self.response allHeaderFields] valueForKey:@"ETag"] forHTTPHeaderField:@"If-Range"]; + } + [mutableURLRequest setValue:[NSString stringWithFormat:@"bytes=%llu-", offset] forHTTPHeaderField:@"Range"]; + self.request = mutableURLRequest; + + [super pause]; +} + +- (BOOL)hasAcceptableStatusCode { + return ![[self class] acceptableStatusCodes] || [[[self class] acceptableStatusCodes] containsIndex:[self.response statusCode]]; +} + +- (BOOL)hasAcceptableContentType { + return ![[self class] acceptableContentTypes] || [[[self class] acceptableContentTypes] containsObject:[self.response MIMEType]]; +} + +- (void)setSuccessCallbackQueue:(dispatch_queue_t)successCallbackQueue { + if (successCallbackQueue != _successCallbackQueue) { + if (_successCallbackQueue) { + dispatch_release(_successCallbackQueue); + _successCallbackQueue = NULL; + } + + if (successCallbackQueue) { + dispatch_retain(successCallbackQueue); + _successCallbackQueue = successCallbackQueue; + } + } +} + +- (void)setFailureCallbackQueue:(dispatch_queue_t)failureCallbackQueue { + if (failureCallbackQueue != _failureCallbackQueue) { + if (_failureCallbackQueue) { + dispatch_release(_failureCallbackQueue); + _failureCallbackQueue = NULL; + } + + if (failureCallbackQueue) { + dispatch_retain(failureCallbackQueue); + _failureCallbackQueue = failureCallbackQueue; + } + } +} + +- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success + failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure +{ + self.completionBlock = ^ { + if ([self isCancelled]) { + return; + } + + if (self.error) { + if (failure) { + dispatch_async(self.failureCallbackQueue ? self.failureCallbackQueue : dispatch_get_main_queue(), ^{ + failure(self, self.error); + }); + } + } else { + if (success) { + dispatch_async(self.successCallbackQueue ? self.successCallbackQueue : dispatch_get_main_queue(), ^{ + success(self, self.responseData); + }); + } + } + }; +} + +- (void)setResponseFilePath:(NSString *)responseFilePath { + if ([self isReady] && responseFilePath != _responseFilePath) { + [_responseFilePath release]; + _responseFilePath = [responseFilePath retain]; + + if (responseFilePath) { + self.outputStream = [NSOutputStream outputStreamToFileAtPath:responseFilePath append:NO]; + }else { + self.outputStream = [NSOutputStream outputStreamToMemory]; + } + } +} + +#pragma mark - AFHTTPRequestOperation + +static id AFStaticClassValueImplementation(id self, SEL _cmd) { + return objc_getAssociatedObject([self class], _cmd); +} + ++ (NSIndexSet *)acceptableStatusCodes { + return [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)]; +} + ++ (void)addAcceptableStatusCodes:(NSIndexSet *)statusCodes { + NSMutableIndexSet *mutableStatusCodes = [[[NSMutableIndexSet alloc] initWithIndexSet:[self acceptableStatusCodes]] autorelease]; + [mutableStatusCodes addIndexes:statusCodes]; + SEL selector = @selector(acceptableStatusCodes); + AFSwizzleClassMethodWithImplementation([self class], selector, (IMP)AFStaticClassValueImplementation); + objc_setAssociatedObject([self class], selector, mutableStatusCodes, OBJC_ASSOCIATION_COPY_NONATOMIC); +} + ++ (NSSet *)acceptableContentTypes { + return nil; +} + ++ (void)addAcceptableContentTypes:(NSSet *)contentTypes { + NSMutableSet *mutableContentTypes = [[[NSMutableSet alloc] initWithSet:[self acceptableContentTypes] copyItems:YES] autorelease]; + [mutableContentTypes unionSet:contentTypes]; + SEL selector = @selector(acceptableContentTypes); + AFSwizzleClassMethodWithImplementation([self class], selector, (IMP)AFStaticClassValueImplementation); + objc_setAssociatedObject([self class], selector, mutableContentTypes, OBJC_ASSOCIATION_COPY_NONATOMIC); +} + ++ (BOOL)canProcessRequest:(NSURLRequest *)request { + if (![[self class] isEqual:[AFHTTPRequestOperation class]]) { + return YES; + } + + return [[self acceptableContentTypes] intersectsSet:AFContentTypesFromHTTPHeader([request valueForHTTPHeaderField:@"Accept"])]; +} + +#pragma mark - NSURLConnectionDelegate + +- (void)connection:(NSURLConnection *)connection +didReceiveResponse:(NSURLResponse *)response +{ + self.response = (NSHTTPURLResponse *)response; + + // 206 = Partial Content. + long long totalContentLength = self.response.expectedContentLength; + long long fileOffset = 0; + if ([self.response statusCode] != 206) { + if ([self.outputStream propertyForKey:NSStreamFileCurrentOffsetKey]) { + [self.outputStream setProperty:[NSNumber numberWithInteger:0] forKey:NSStreamFileCurrentOffsetKey]; + } else { + if ([[self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey] length] > 0) { + self.outputStream = [NSOutputStream outputStreamToMemory]; + } + } + }else { + NSString *contentRange = [self.response.allHeaderFields valueForKey:@"Content-Range"]; + if ([contentRange hasPrefix:@"bytes"]) { + NSArray *bytes = [contentRange componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@" -/"]]; + if ([bytes count] == 4) { + fileOffset = [[bytes objectAtIndex:1] longLongValue]; + totalContentLength = [[bytes objectAtIndex:2] longLongValue] ?: -1; // if this is *, it's converted to 0, but -1 is default. + } + } + + } + self.offsetContentLength = MAX(fileOffset, 0); + self.totalContentLength = totalContentLength; + [self.outputStream open]; +} + +@end diff --git a/AFNetworking/AFImageRequestOperation.h b/AFNetworking/AFImageRequestOperation.h new file mode 100644 index 0000000..7a7f78a --- /dev/null +++ b/AFNetworking/AFImageRequestOperation.h @@ -0,0 +1,115 @@ +// AFImageRequestOperation.h +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import +#import "AFHTTPRequestOperation.h" + +#import + +#if __IPHONE_OS_VERSION_MIN_REQUIRED +#import +#elif __MAC_OS_X_VERSION_MIN_REQUIRED +#import +#endif + +/** + `AFImageRequestOperation` is a subclass of `AFHTTPRequestOperation` for downloading an processing images. + + ## Acceptable Content Types + + By default, `AFImageRequestOperation` accepts the following MIME types, which correspond to the image formats supported by UIImage or NSImage: + + - `image/tiff` + - `image/jpeg` + - `image/gif` + - `image/png` + - `image/ico` + - `image/x-icon` + - `image/bmp` + - `image/x-bmp` + - `image/x-xbitmap` + - `image/x-win-bitmap` + */ +@interface AFImageRequestOperation : AFHTTPRequestOperation + +/** + An image constructed from the response data. If an error occurs during the request, `nil` will be returned, and the `error` property will be set to the error. + */ +#if __IPHONE_OS_VERSION_MIN_REQUIRED +@property (readonly, nonatomic, retain) UIImage *responseImage; +#elif __MAC_OS_X_VERSION_MIN_REQUIRED +@property (readonly, nonatomic, retain) NSImage *responseImage; +#endif + +#if __IPHONE_OS_VERSION_MIN_REQUIRED +/** + The scale factor used when interpreting the image data to construct `responseImage`. Specifying a scale factor of 1.0 results in an image whose size matches the pixel-based dimensions of the image. Applying a different scale factor changes the size of the image as reported by the size property. This is set to the value of `[[UIScreen mainScreen] scale]` by default, which automatically scales images for retina displays, for instance. + */ +@property (nonatomic, assign) CGFloat imageScale; +#endif + +/** + An image constructed from the response data. If an error occurs during the request, `nil` will be returned, and the `error` property will be set to the error. + */ + +/** + Creates and returns an `AFImageRequestOperation` object and sets the specified success callback. + + @param urlRequest The request object to be loaded asynchronously during execution of the operation. + @param success A block object to be executed when the request finishes successfully. This block has no return value and takes a single arguments, the image created from the response data of the request. + + @return A new image request operation + */ +#if __IPHONE_OS_VERSION_MIN_REQUIRED ++ (AFImageRequestOperation *)imageRequestOperationWithRequest:(NSURLRequest *)urlRequest + success:(void (^)(UIImage *image))success; +#elif __MAC_OS_X_VERSION_MIN_REQUIRED ++ (AFImageRequestOperation *)imageRequestOperationWithRequest:(NSURLRequest *)urlRequest + success:(void (^)(NSImage *image))success; +#endif + +/** + Creates and returns an `AFImageRequestOperation` object and sets the specified success callback. + + @param urlRequest The request object to be loaded asynchronously during execution of the operation. + @param imageProcessingBlock A block object to be executed after the image request finishes successfully, but before the image is returned in the `success` block. This block takes a single argument, the image loaded from the response body, and returns the processed image. + @param cacheName The cache name to be associated with the image. `AFImageCache` associates objects by URL and cache name, allowing for multiple versions of the same image to be cached. + @param success A block object to be executed when the request finishes successfully, with a status code in the 2xx range, and with an acceptable content type (e.g. `image/png`). This block has no return value and takes three arguments: the request object of the operation, the response for the request, and the image created from the response data. + @param failure A block object to be executed when the request finishes unsuccessfully. This block has no return value and takes three arguments: the request object of the operation, the response for the request, and the error associated with the cause for the unsuccessful operation. + + @return A new image request operation + */ +#if __IPHONE_OS_VERSION_MIN_REQUIRED ++ (AFImageRequestOperation *)imageRequestOperationWithRequest:(NSURLRequest *)urlRequest + imageProcessingBlock:(UIImage *(^)(UIImage *))imageProcessingBlock + cacheName:(NSString *)cacheNameOrNil + success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image))success + failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure; +#elif __MAC_OS_X_VERSION_MIN_REQUIRED ++ (AFImageRequestOperation *)imageRequestOperationWithRequest:(NSURLRequest *)urlRequest + imageProcessingBlock:(NSImage *(^)(NSImage *))imageProcessingBlock + cacheName:(NSString *)cacheNameOrNil + success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSImage *image))success + failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure; +#endif + +@end diff --git a/AFNetworking/AFImageRequestOperation.m b/AFNetworking/AFImageRequestOperation.m new file mode 100644 index 0000000..3821046 --- /dev/null +++ b/AFNetworking/AFImageRequestOperation.m @@ -0,0 +1,238 @@ +// AFImageRequestOperation.m +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "AFImageRequestOperation.h" + +static dispatch_queue_t af_image_request_operation_processing_queue; +static dispatch_queue_t image_request_operation_processing_queue() { + if (af_image_request_operation_processing_queue == NULL) { + af_image_request_operation_processing_queue = dispatch_queue_create("com.alamofire.networking.image-request.processing", 0); + } + + return af_image_request_operation_processing_queue; +} + +@interface AFImageRequestOperation () +#if __IPHONE_OS_VERSION_MIN_REQUIRED +@property (readwrite, nonatomic, retain) UIImage *responseImage; +#elif __MAC_OS_X_VERSION_MIN_REQUIRED +@property (readwrite, nonatomic, retain) NSImage *responseImage; +#endif +@end + +@implementation AFImageRequestOperation +@synthesize responseImage = _responseImage; +#if __IPHONE_OS_VERSION_MIN_REQUIRED +@synthesize imageScale = _imageScale; +#endif + +#if __IPHONE_OS_VERSION_MIN_REQUIRED ++ (AFImageRequestOperation *)imageRequestOperationWithRequest:(NSURLRequest *)urlRequest + success:(void (^)(UIImage *image))success +{ + return [self imageRequestOperationWithRequest:urlRequest imageProcessingBlock:nil cacheName:nil success:^(NSURLRequest __unused *request, NSHTTPURLResponse __unused *response, UIImage *image) { + if (success) { + success(image); + } + } failure:nil]; +} +#elif __MAC_OS_X_VERSION_MIN_REQUIRED ++ (AFImageRequestOperation *)imageRequestOperationWithRequest:(NSURLRequest *)urlRequest + success:(void (^)(NSImage *image))success +{ + return [self imageRequestOperationWithRequest:urlRequest imageProcessingBlock:nil cacheName:nil success:^(NSURLRequest __unused *request, NSHTTPURLResponse __unused *response, NSImage *image) { + if (success) { + success(image); + } + } failure:nil]; +} +#endif + + +#if __IPHONE_OS_VERSION_MIN_REQUIRED ++ (AFImageRequestOperation *)imageRequestOperationWithRequest:(NSURLRequest *)urlRequest + imageProcessingBlock:(UIImage *(^)(UIImage *))imageProcessingBlock + cacheName:(NSString *)cacheNameOrNil + success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image))success + failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure +{ + AFImageRequestOperation *requestOperation = [[[AFImageRequestOperation alloc] initWithRequest:urlRequest] autorelease]; + [requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { + if (success) { + UIImage *image = responseObject; + if (imageProcessingBlock) { + dispatch_async(image_request_operation_processing_queue(), ^(void) { + UIImage *processedImage = imageProcessingBlock(image); + + dispatch_async(dispatch_get_main_queue(), ^(void) { + success(operation.request, operation.response, processedImage); + }); + }); + } else { + success(operation.request, operation.response, image); + } + } + } failure:^(AFHTTPRequestOperation *operation, NSError *error) { + if (failure) { + failure(operation.request, operation.response, error); + } + }]; + + + return requestOperation; +} +#elif __MAC_OS_X_VERSION_MIN_REQUIRED ++ (AFImageRequestOperation *)imageRequestOperationWithRequest:(NSURLRequest *)urlRequest + imageProcessingBlock:(NSImage *(^)(NSImage *))imageProcessingBlock + cacheName:(NSString *)cacheNameOrNil + success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSImage *image))success + failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure +{ + AFImageRequestOperation *requestOperation = [[[AFImageRequestOperation alloc] initWithRequest:urlRequest] autorelease]; + [requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { + if (success) { + NSImage *image = responseObject; + if (imageProcessingBlock) { + dispatch_async(image_request_operation_processing_queue(), ^(void) { + NSImage *processedImage = imageProcessingBlock(image); + + dispatch_async(dispatch_get_main_queue(), ^(void) { + success(operation.request, operation.response, processedImage); + }); + }); + } else { + success(operation.request, operation.response, image); + } + } + } failure:^(AFHTTPRequestOperation *operation, NSError *error) { + if (failure) { + failure(operation.request, operation.response, error); + } + }]; + + return requestOperation; +} +#endif + +- (id)initWithRequest:(NSURLRequest *)urlRequest { + self = [super initWithRequest:urlRequest]; + if (!self) { + return nil; + } + +#if __IPHONE_OS_VERSION_MIN_REQUIRED + self.imageScale = [[UIScreen mainScreen] scale]; +#endif + + return self; +} + +- (void)dealloc { + [_responseImage release]; + [super dealloc]; +} + +#if __IPHONE_OS_VERSION_MIN_REQUIRED +- (UIImage *)responseImage { + if (!_responseImage && [self.responseData length] > 0 && [self isFinished]) { + UIImage *image = [UIImage imageWithData:self.responseData]; + + self.responseImage = [UIImage imageWithCGImage:[image CGImage] scale:self.imageScale orientation:image.imageOrientation]; + } + + return _responseImage; +} + +- (void)setImageScale:(CGFloat)imageScale { + if (imageScale == _imageScale) { + return; + } + + _imageScale = imageScale; + + self.responseImage = nil; +} +#elif __MAC_OS_X_VERSION_MIN_REQUIRED +- (NSImage *)responseImage { + if (!_responseImage && [self.responseData length] > 0 && [self isFinished]) { + // Ensure that the image is set to it's correct pixel width and height + NSBitmapImageRep *bitimage = [[NSBitmapImageRep alloc] initWithData:self.responseData]; + self.responseImage = [[[NSImage alloc] initWithSize:NSMakeSize([bitimage pixelsWide], [bitimage pixelsHigh])] autorelease]; + [self.responseImage addRepresentation:bitimage]; + [bitimage release]; + } + + return _responseImage; +} +#endif + +#pragma mark - AFHTTPClientOperation + ++ (NSSet *)acceptableContentTypes { + return [NSSet setWithObjects:@"image/tiff", @"image/jpeg", @"image/gif", @"image/png", @"image/ico", @"image/x-icon", @"image/bmp", @"image/x-bmp", @"image/x-xbitmap", @"image/x-win-bitmap", nil]; +} + ++ (BOOL)canProcessRequest:(NSURLRequest *)request { + static NSSet * _acceptablePathExtension = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _acceptablePathExtension = [[NSSet alloc] initWithObjects:@"tif", @"tiff", @"jpg", @"jpeg", @"gif", @"png", @"ico", @"bmp", @"cur", nil]; + }); + + return [_acceptablePathExtension containsObject:[[request URL] pathExtension]] || [super canProcessRequest:request]; +} + +- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success + failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure +{ + self.completionBlock = ^ { + if ([self isCancelled]) { + return; + } + + dispatch_async(image_request_operation_processing_queue(), ^(void) { + if (self.error) { + if (failure) { + dispatch_async(self.failureCallbackQueue ? self.failureCallbackQueue : dispatch_get_main_queue(), ^{ + failure(self, self.error); + }); + } + } else { + if (success) { +#if __IPHONE_OS_VERSION_MIN_REQUIRED + UIImage *image = nil; +#elif __MAC_OS_X_VERSION_MIN_REQUIRED + NSImage *image = nil; +#endif + + image = self.responseImage; + + dispatch_async(self.successCallbackQueue ? self.successCallbackQueue : dispatch_get_main_queue(), ^{ + success(self, image); + }); + } + } + }); + }; +} + +@end diff --git a/AFNetworking/AFJSONRequestOperation.h b/AFNetworking/AFJSONRequestOperation.h new file mode 100644 index 0000000..a1191f9 --- /dev/null +++ b/AFNetworking/AFJSONRequestOperation.h @@ -0,0 +1,66 @@ +// AFJSONRequestOperation.h +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import +#import "AFHTTPRequestOperation.h" + +/** + `AFJSONRequestOperation` is a subclass of `AFHTTPRequestOperation` for downloading and working with JSON response data. + + ## Acceptable Content Types + + By default, `AFJSONRequestOperation` accepts the following MIME types, which includes the official standard, `application/json`, as well as other commonly-used types: + + - `application/json` + - `text/json` + + @warning JSON parsing will automatically use JSONKit, SBJSON, YAJL, or NextiveJSON, if provided. Otherwise, the built-in `NSJSONSerialization` class is used, if available (iOS 5.0 and Mac OS 10.7). If the build target does not either support `NSJSONSerialization` or include a third-party JSON library, a runtime exception will be thrown when attempting to parse a JSON request. + */ +@interface AFJSONRequestOperation : AFHTTPRequestOperation + +///---------------------------- +/// @name Getting Response Data +///---------------------------- + +/** + A JSON object constructed from the response data. If an error occurs while parsing, `nil` will be returned, and the `error` property will be set to the error. + */ +@property (readonly, nonatomic, retain) id responseJSON; + +///---------------------------------- +/// @name Creating Request Operations +///---------------------------------- + +/** + Creates and returns an `AFJSONRequestOperation` object and sets the specified success and failure callbacks. + + @param urlRequest The request object to be loaded asynchronously during execution of the operation + @param success A block object to be executed when the operation finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the JSON object created from the response data of request. + @param failure A block object to be executed when the operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the resonse data as JSON. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the error describing the network or parsing error that occurred. + + @return A new JSON request operation + */ ++ (AFJSONRequestOperation *)JSONRequestOperationWithRequest:(NSURLRequest *)urlRequest + success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, id JSON))success + failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON))failure; + +@end diff --git a/AFNetworking/AFJSONRequestOperation.m b/AFNetworking/AFJSONRequestOperation.m new file mode 100644 index 0000000..bcdcb7f --- /dev/null +++ b/AFNetworking/AFJSONRequestOperation.m @@ -0,0 +1,138 @@ +// AFJSONRequestOperation.m +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "AFJSONRequestOperation.h" +#import "AFJSONUtilities.h" + +static dispatch_queue_t af_json_request_operation_processing_queue; +static dispatch_queue_t json_request_operation_processing_queue() { + if (af_json_request_operation_processing_queue == NULL) { + af_json_request_operation_processing_queue = dispatch_queue_create("com.alamofire.networking.json-request.processing", 0); + } + + return af_json_request_operation_processing_queue; +} + +@interface AFJSONRequestOperation () +@property (readwrite, nonatomic, retain) id responseJSON; +@property (readwrite, nonatomic, retain) NSError *JSONError; +@end + +@implementation AFJSONRequestOperation +@synthesize responseJSON = _responseJSON; +@synthesize JSONError = _JSONError; + ++ (AFJSONRequestOperation *)JSONRequestOperationWithRequest:(NSURLRequest *)urlRequest + success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, id JSON))success + failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON))failure +{ + AFJSONRequestOperation *requestOperation = [[[self alloc] initWithRequest:urlRequest] autorelease]; + [requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { + if (success) { + success(operation.request, operation.response, responseObject); + } + } failure:^(AFHTTPRequestOperation *operation, NSError *error) { + if (failure) { + failure(operation.request, operation.response, error, [(AFJSONRequestOperation *)operation responseJSON]); + } + }]; + + return requestOperation; +} + +- (void)dealloc { + [_responseJSON release]; + [_JSONError release]; + [super dealloc]; +} + +- (id)responseJSON { + if (!_responseJSON && [self.responseData length] > 0 && [self isFinished] && !self.JSONError) { + NSError *error = nil; + + if ([self.responseData length] == 0) { + self.responseJSON = nil; + } else { + self.responseJSON = AFJSONDecode(self.responseData, &error); + } + + self.JSONError = error; + } + + return _responseJSON; +} + +- (NSError *)error { + if (_JSONError) { + return _JSONError; + } else { + return [super error]; + } +} + +#pragma mark - AFHTTPRequestOperation + ++ (NSSet *)acceptableContentTypes { + return [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil]; +} + ++ (BOOL)canProcessRequest:(NSURLRequest *)request { + return [[[request URL] pathExtension] isEqualToString:@"json"] || [super canProcessRequest:request]; +} + +- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success + failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure +{ + self.completionBlock = ^ { + if ([self isCancelled]) { + return; + } + + if (self.error) { + if (failure) { + dispatch_async(self.failureCallbackQueue ? self.failureCallbackQueue : dispatch_get_main_queue(), ^{ + failure(self, self.error); + }); + } + } else { + dispatch_async(json_request_operation_processing_queue(), ^{ + id JSON = self.responseJSON; + + if (self.JSONError) { + if (failure) { + dispatch_async(self.failureCallbackQueue ? self.failureCallbackQueue : dispatch_get_main_queue(), ^{ + failure(self, self.error); + }); + } + } else { + if (success) { + dispatch_async(self.successCallbackQueue ? self.successCallbackQueue : dispatch_get_main_queue(), ^{ + success(self, JSON); + }); + } + } + }); + } + }; +} + +@end diff --git a/AFNetworking/AFJSONUtilities.h b/AFNetworking/AFJSONUtilities.h new file mode 100644 index 0000000..ece26a0 --- /dev/null +++ b/AFNetworking/AFJSONUtilities.h @@ -0,0 +1,26 @@ +// AFJSONUtilities.h +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import + +extern NSData * AFJSONEncode(id object, NSError **error); +extern id AFJSONDecode(NSData *data, NSError **error); diff --git a/AFNetworking/AFJSONUtilities.m b/AFNetworking/AFJSONUtilities.m new file mode 100644 index 0000000..3377fc4 --- /dev/null +++ b/AFNetworking/AFJSONUtilities.m @@ -0,0 +1,217 @@ +// AFJSONUtilities.m +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "AFJSONUtilities.h" + +NSData * AFJSONEncode(id object, NSError **error) { + NSData *data = nil; + + SEL _JSONKitSelector = NSSelectorFromString(@"JSONDataWithOptions:error:"); + SEL _YAJLSelector = NSSelectorFromString(@"yajl_JSONString"); + + id _SBJsonWriterClass = NSClassFromString(@"SBJsonWriter"); + SEL _SBJsonWriterSelector = NSSelectorFromString(@"dataWithObject:"); + + id _NXJsonSerializerClass = NSClassFromString(@"NXJsonSerializer"); + SEL _NXJsonSerializerSelector = NSSelectorFromString(@"serialize:"); + + id _NSJSONSerializationClass = NSClassFromString(@"NSJSONSerialization"); + SEL _NSJSONSerializationSelector = NSSelectorFromString(@"dataWithJSONObject:options:error:"); + +#ifdef _AFNETWORKING_PREFER_NSJSONSERIALIZATION_ + if (_NSJSONSerializationClass && [_NSJSONSerializationClass respondsToSelector:_NSJSONSerializationSelector]) { + goto _af_nsjson_encode; + } +#endif + + if (_JSONKitSelector && [object respondsToSelector:_JSONKitSelector]) { + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[object methodSignatureForSelector:_JSONKitSelector]]; + invocation.target = object; + invocation.selector = _JSONKitSelector; + + NSUInteger serializeOptionFlags = 0; + [invocation setArgument:&serializeOptionFlags atIndex:2]; // arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation + if (error != NULL) { + [invocation setArgument:error atIndex:3]; + } + + [invocation invoke]; + [invocation getReturnValue:&data]; + } else if (_SBJsonWriterClass && [_SBJsonWriterClass instancesRespondToSelector:_SBJsonWriterSelector]) { + id writer = [[_SBJsonWriterClass alloc] init]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[writer methodSignatureForSelector:_SBJsonWriterSelector]]; + invocation.target = writer; + invocation.selector = _SBJsonWriterSelector; + + [invocation setArgument:&object atIndex:2]; // arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation + + [invocation invoke]; + [invocation getReturnValue:&data]; + [writer release]; + } else if (_YAJLSelector && [object respondsToSelector:_YAJLSelector]) { + @try { + NSString *JSONString = nil; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[object methodSignatureForSelector:_YAJLSelector]]; + invocation.target = object; + invocation.selector = _YAJLSelector; + + [invocation invoke]; + [invocation getReturnValue:&JSONString]; + + data = [JSONString dataUsingEncoding:NSUTF8StringEncoding]; + } + @catch (NSException *exception) { + *error = [[[NSError alloc] initWithDomain:NSStringFromClass([exception class]) code:0 userInfo:[exception userInfo]] autorelease]; + } + } else if (_NXJsonSerializerClass && [_NXJsonSerializerClass respondsToSelector:_NXJsonSerializerSelector]) { + NSString *JSONString = nil; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[_NXJsonSerializerClass methodSignatureForSelector:_NXJsonSerializerSelector]]; + invocation.target = _NXJsonSerializerClass; + invocation.selector = _NXJsonSerializerSelector; + + [invocation setArgument:&object atIndex:2]; // arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation + + [invocation invoke]; + [invocation getReturnValue:&JSONString]; + data = [JSONString dataUsingEncoding:NSUTF8StringEncoding]; + } else if (_NSJSONSerializationClass && [_NSJSONSerializationClass respondsToSelector:_NSJSONSerializationSelector]) { +#ifdef _AFNETWORKING_PREFER_NSJSONSERIALIZATION_ + _af_nsjson_encode:; +#endif + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[_NSJSONSerializationClass methodSignatureForSelector:_NSJSONSerializationSelector]]; + invocation.target = _NSJSONSerializationClass; + invocation.selector = _NSJSONSerializationSelector; + + [invocation setArgument:&object atIndex:2]; // arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation + NSUInteger writeOptions = 0; + [invocation setArgument:&writeOptions atIndex:3]; + if (error != NULL) { + [invocation setArgument:error atIndex:4]; + } + + [invocation invoke]; + [invocation getReturnValue:&data]; + } else { + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:NSLocalizedString(@"Please either target a platform that supports NSJSONSerialization or add one of the following libraries to your project: JSONKit, SBJSON, or YAJL", nil) forKey:NSLocalizedRecoverySuggestionErrorKey]; + [[NSException exceptionWithName:NSInternalInconsistencyException reason:NSLocalizedString(@"No JSON generation functionality available", nil) userInfo:userInfo] raise]; + } + + return data; +} + +id AFJSONDecode(NSData *data, NSError **error) { + id JSON = nil; + + SEL _JSONKitSelector = NSSelectorFromString(@"objectFromJSONDataWithParseOptions:error:"); + SEL _YAJLSelector = NSSelectorFromString(@"yajl_JSONWithOptions:error:"); + + id _SBJSONParserClass = NSClassFromString(@"SBJsonParser"); + SEL _SBJSONParserSelector = NSSelectorFromString(@"objectWithData:"); + + id _NSJSONSerializationClass = NSClassFromString(@"NSJSONSerialization"); + SEL _NSJSONSerializationSelector = NSSelectorFromString(@"JSONObjectWithData:options:error:"); + + id _NXJsonParserClass = NSClassFromString(@"NXJsonParser"); + SEL _NXJsonParserSelector = NSSelectorFromString(@"parseData:error:ignoreNulls:"); + + +#ifdef _AFNETWORKING_PREFER_NSJSONSERIALIZATION_ + if (_NSJSONSerializationClass && [_NSJSONSerializationClass respondsToSelector:_NSJSONSerializationSelector]) { + goto _af_nsjson_decode; + } +#endif + + if (_JSONKitSelector && [data respondsToSelector:_JSONKitSelector]) { + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[data methodSignatureForSelector:_JSONKitSelector]]; + invocation.target = data; + invocation.selector = _JSONKitSelector; + + NSUInteger parseOptionFlags = 0; + [invocation setArgument:&parseOptionFlags atIndex:2]; // arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation + if (error != NULL) { + [invocation setArgument:&error atIndex:3]; + } + + [invocation invoke]; + [invocation getReturnValue:&JSON]; + } else if (_SBJSONParserClass && [_SBJSONParserClass instancesRespondToSelector:_SBJSONParserSelector]) { + id parser = [[_SBJSONParserClass alloc] init]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[parser methodSignatureForSelector:_SBJSONParserSelector]]; + invocation.target = parser; + invocation.selector = _SBJSONParserSelector; + + [invocation setArgument:&data atIndex:2]; // arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation + + [invocation invoke]; + [invocation getReturnValue:&JSON]; + [parser release]; + } else if (_YAJLSelector && [data respondsToSelector:_YAJLSelector]) { + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[data methodSignatureForSelector:_YAJLSelector]]; + invocation.target = data; + invocation.selector = _YAJLSelector; + + NSUInteger yajlParserOptions = 0; + [invocation setArgument:&yajlParserOptions atIndex:2]; // arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation + if (error != NULL) { + [invocation setArgument:&error atIndex:3]; + } + + [invocation invoke]; + [invocation getReturnValue:&JSON]; + } else if (_NXJsonParserClass && [_NXJsonParserClass respondsToSelector:_NXJsonParserSelector]) { + NSNumber *nullOption = [NSNumber numberWithBool:YES]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[_NXJsonParserClass methodSignatureForSelector:_NXJsonParserSelector]]; + invocation.target = _NXJsonParserClass; + invocation.selector = _NXJsonParserSelector; + + [invocation setArgument:&data atIndex:2]; // arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation + if (error != NULL) { + [invocation setArgument:&error atIndex:3]; + } + [invocation setArgument:&nullOption atIndex:4]; + + [invocation invoke]; + [invocation getReturnValue:&JSON]; + } else if (_NSJSONSerializationClass && [_NSJSONSerializationClass respondsToSelector:_NSJSONSerializationSelector]) { +#ifdef _AFNETWORKING_PREFER_NSJSONSERIALIZATION_ + _af_nsjson_decode:; +#endif + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[_NSJSONSerializationClass methodSignatureForSelector:_NSJSONSerializationSelector]]; + invocation.target = _NSJSONSerializationClass; + invocation.selector = _NSJSONSerializationSelector; + + [invocation setArgument:&data atIndex:2]; // arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation + NSUInteger readOptions = 0; + [invocation setArgument:&readOptions atIndex:3]; + if (error != NULL) { + [invocation setArgument:&error atIndex:4]; + } + + [invocation invoke]; + [invocation getReturnValue:&JSON]; + } else { + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:NSLocalizedString(@"Please either target a platform that supports NSJSONSerialization or add one of the following libraries to your project: JSONKit, SBJSON, or YAJL", nil) forKey:NSLocalizedRecoverySuggestionErrorKey]; + [[NSException exceptionWithName:NSInternalInconsistencyException reason:NSLocalizedString(@"No JSON parsing functionality available", nil) userInfo:userInfo] raise]; + } + + return JSON; +} diff --git a/AFNetworking/AFNetworkActivityIndicatorManager.h b/AFNetworking/AFNetworkActivityIndicatorManager.h new file mode 100644 index 0000000..4a5640c --- /dev/null +++ b/AFNetworking/AFNetworkActivityIndicatorManager.h @@ -0,0 +1,66 @@ +// AFNetworkActivityIndicatorManager.h +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import + +#import + +#if __IPHONE_OS_VERSION_MIN_REQUIRED +#import + +/** + `AFNetworkActivityIndicatorManager` manages the state of the network activity indicator in the status bar. When enabled, it will listen for notifications indicating that a network request operation has started or finished, and start or stop animating the indicator accordingly. The number of active requests is incremented and decremented much like a stack or a semaphore, and the activity indicator will animate so long as that number is greater than zero. + */ +@interface AFNetworkActivityIndicatorManager : NSObject + +/** + A Boolean value indicating whether the manager is enabled. + + @discussion If YES, the manager will change status bar network activity indicator according to network operation notifications it receives. The default value is NO. + */ +@property (nonatomic, assign, getter = isEnabled) BOOL enabled; + +/** + A Boolean value indicating whether the network activity indicator is currently displayed in the status bar. + */ +@property (readonly, nonatomic, assign) BOOL isNetworkActivityIndicatorVisible; + +/** + Returns the shared network activity indicator manager object for the system. + + @return The systemwide network activity indicator manager. + */ ++ (AFNetworkActivityIndicatorManager *)sharedManager; + +/** + Increments the number of active network requests. If this number was zero before incrementing, this will start animating the status bar network activity indicator. + */ +- (void)incrementActivityCount; + +/** + Decrements the number of active network requests. If this number becomes zero before decrementing, this will stop animating the status bar network activity indicator. + */ +- (void)decrementActivityCount; + +@end + +#endif diff --git a/AFNetworking/AFNetworkActivityIndicatorManager.m b/AFNetworking/AFNetworkActivityIndicatorManager.m new file mode 100644 index 0000000..01a6851 --- /dev/null +++ b/AFNetworking/AFNetworkActivityIndicatorManager.m @@ -0,0 +1,129 @@ +// AFNetworkActivityIndicatorManager.m +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "AFNetworkActivityIndicatorManager.h" + +#import "AFHTTPRequestOperation.h" +#import + +#if __IPHONE_OS_VERSION_MIN_REQUIRED +static NSTimeInterval const kAFNetworkActivityIndicatorInvisibilityDelay = 0.25; + +@interface AFNetworkActivityIndicatorManager () +@property (readwrite, nonatomic, assign) NSInteger activityCount; +@property (readwrite, nonatomic, retain) NSTimer *activityIndicatorVisibilityTimer; +@property (readonly, getter = isNetworkActivityIndicatorVisible) BOOL networkActivityIndicatorVisible; + +- (void)updateNetworkActivityIndicatorVisibility; +@end + +@implementation AFNetworkActivityIndicatorManager +@synthesize activityCount = _activityCount; +@synthesize activityIndicatorVisibilityTimer = _activityIndicatorVisibilityTimer; +@synthesize enabled = _enabled; +@dynamic networkActivityIndicatorVisible; + ++ (AFNetworkActivityIndicatorManager *)sharedManager { + static AFNetworkActivityIndicatorManager *_sharedManager = nil; + static dispatch_once_t oncePredicate; + dispatch_once(&oncePredicate, ^{ + _sharedManager = [[self alloc] init]; + }); + + return _sharedManager; +} + +- (id)init { + self = [super init]; + if (!self) { + return nil; + } + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(incrementActivityCount) name:AFNetworkingOperationDidStartNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(decrementActivityCount) name:AFNetworkingOperationDidFinishNotification object:nil]; + + return self; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [_activityIndicatorVisibilityTimer invalidate]; + [_activityIndicatorVisibilityTimer release]; _activityIndicatorVisibilityTimer = nil; + + [super dealloc]; +} + +- (void)updateNetworkActivityIndicatorVisibilityDelayed { + if (self.enabled) { + // Delay hiding of activity indicator for a short interval, to avoid flickering + if (![self isNetworkActivityIndicatorVisible]) { + [self.activityIndicatorVisibilityTimer invalidate]; + self.activityIndicatorVisibilityTimer = [NSTimer timerWithTimeInterval:kAFNetworkActivityIndicatorInvisibilityDelay target:self selector:@selector(updateNetworkActivityIndicatorVisibility) userInfo:nil repeats:NO]; + [[NSRunLoop currentRunLoop] addTimer:self.activityIndicatorVisibilityTimer forMode:NSRunLoopCommonModes]; + } else { + [self updateNetworkActivityIndicatorVisibility]; + } + } +} + +- (BOOL)isNetworkActivityIndicatorVisible { + return _activityCount > 0; +} + +- (void)updateNetworkActivityIndicatorVisibility { + dispatch_async(dispatch_get_main_queue(), ^{ + [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:[self isNetworkActivityIndicatorVisible]]; + }); +} + +// Not exposed, but used if activityCount is set via KVC. +- (void)setActivityCount:(NSInteger)activityCount { + __sync_swap(&_activityCount, activityCount); + [self updateNetworkActivityIndicatorVisibilityDelayed]; +} + +- (void)incrementActivityCount { + [self willChangeValueForKey:@"activityCount"]; + OSAtomicIncrement32((int32_t*)&_activityCount); + [self didChangeValueForKey:@"activityCount"]; + [self updateNetworkActivityIndicatorVisibilityDelayed]; +} + +- (void)decrementActivityCount { + [self willChangeValueForKey:@"activityCount"]; + bool success; + do { + int32_t currentCount = _activityCount; + success = OSAtomicCompareAndSwap32(currentCount, MIN(currentCount - 1, currentCount), &_activityCount); + } while(!success); + [self didChangeValueForKey:@"activityCount"]; + [self updateNetworkActivityIndicatorVisibilityDelayed]; +} + ++ (NSSet *)keyPathsForValuesAffectingIsNetworkActivityIndicatorVisible { + return [NSSet setWithObject:@"activityCount"]; +} + +@end + +#endif diff --git a/AFNetworking/AFNetworking.h b/AFNetworking/AFNetworking.h new file mode 100644 index 0000000..49e596c --- /dev/null +++ b/AFNetworking/AFNetworking.h @@ -0,0 +1,44 @@ +// AFNetworking.h +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import +#import + +#ifndef _AFNETWORKING_ +#define _AFNETWORKING_ + +#import "AFURLConnectionOperation.h" + +#import "AFHTTPRequestOperation.h" +#import "AFJSONRequestOperation.h" +#import "AFXMLRequestOperation.h" +#import "AFPropertyListRequestOperation.h" +#import "AFHTTPClient.h" + +#import "AFImageRequestOperation.h" + +#if __IPHONE_OS_VERSION_MIN_REQUIRED +#import "AFNetworkActivityIndicatorManager.h" +#import "UIImageView+AFNetworking.h" +#endif + +#endif /* _AFNETWORKING_ */ diff --git a/AFNetworking/AFPropertyListRequestOperation.h b/AFNetworking/AFPropertyListRequestOperation.h new file mode 100644 index 0000000..aa6e471 --- /dev/null +++ b/AFNetworking/AFPropertyListRequestOperation.h @@ -0,0 +1,68 @@ +// AFPropertyListRequestOperation.h +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import +#import "AFHTTPRequestOperation.h" + +/** + `AFPropertyListRequestOperation` is a subclass of `AFHTTPRequestOperation` for downloading and deserializing objects with property list (plist) response data. + + ## Acceptable Content Types + + By default, `AFPropertyListRequestOperation` accepts the following MIME types: + + - `application/x-plist` + */ +@interface AFPropertyListRequestOperation : AFHTTPRequestOperation + +///---------------------------- +/// @name Getting Response Data +///---------------------------- + +/** + An object deserialized from a plist constructed using the response data. + */ +@property (readonly, nonatomic, retain) id responsePropertyList; + +///-------------------------------------- +/// @name Managing Property List Behavior +///-------------------------------------- + +/** + One of the `NSPropertyListMutabilityOptions` options, specifying the mutability of objects deserialized from the property list. By default, this is `NSPropertyListImmutable`. + */ +@property (nonatomic, assign) NSPropertyListReadOptions propertyListReadOptions; + +/** + Creates and returns an `AFPropertyListRequestOperation` object and sets the specified success and failure callbacks. + + @param urlRequest The request object to be loaded asynchronously during execution of the operation + @param success A block object to be executed when the operation finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the object deserialized from a plist constructed using the response data. + @param failure A block object to be executed when the operation finishes unsuccessfully, or that finishes successfully, but encountered an error while deserializing the object from a property list. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the error describing the network or parsing error that occurred. + + @return A new property list request operation + */ ++ (AFPropertyListRequestOperation *)propertyListRequestOperationWithRequest:(NSURLRequest *)urlRequest + success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, id propertyList))success + failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id propertyList))failure; + +@end diff --git a/AFNetworking/AFPropertyListRequestOperation.m b/AFNetworking/AFPropertyListRequestOperation.m new file mode 100644 index 0000000..ecca1af --- /dev/null +++ b/AFNetworking/AFPropertyListRequestOperation.m @@ -0,0 +1,147 @@ +// AFPropertyListRequestOperation.m +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "AFPropertyListRequestOperation.h" + +static dispatch_queue_t af_property_list_request_operation_processing_queue; +static dispatch_queue_t property_list_request_operation_processing_queue() { + if (af_property_list_request_operation_processing_queue == NULL) { + af_property_list_request_operation_processing_queue = dispatch_queue_create("com.alamofire.networking.property-list-request.processing", 0); + } + + return af_property_list_request_operation_processing_queue; +} + +@interface AFPropertyListRequestOperation () +@property (readwrite, nonatomic, retain) id responsePropertyList; +@property (readwrite, nonatomic, assign) NSPropertyListFormat propertyListFormat; +@property (readwrite, nonatomic, retain) NSError *propertyListError; +@end + +@implementation AFPropertyListRequestOperation +@synthesize responsePropertyList = _responsePropertyList; +@synthesize propertyListReadOptions = _propertyListReadOptions; +@synthesize propertyListFormat = _propertyListFormat; +@synthesize propertyListError = _propertyListError; + ++ (AFPropertyListRequestOperation *)propertyListRequestOperationWithRequest:(NSURLRequest *)request + success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, id propertyList))success + failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id propertyList))failure +{ + AFPropertyListRequestOperation *requestOperation = [[[self alloc] initWithRequest:request] autorelease]; + [requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { + if (success) { + success(operation.request, operation.response, responseObject); + } + } failure:^(AFHTTPRequestOperation *operation, NSError *error) { + if (failure) { + failure(operation.request, operation.response, error, [(AFPropertyListRequestOperation *)operation responsePropertyList]); + } + }]; + + return requestOperation; +} + +- (id)initWithRequest:(NSURLRequest *)urlRequest { + self = [super initWithRequest:urlRequest]; + if (!self) { + return nil; + } + + self.propertyListReadOptions = NSPropertyListImmutable; + + return self; +} + +- (void)dealloc { + [_responsePropertyList release]; + [_propertyListError release]; + [super dealloc]; +} + +- (id)responsePropertyList { + if (!_responsePropertyList && [self.responseData length] > 0 && [self isFinished]) { + NSPropertyListFormat format; + NSError *error = nil; + self.responsePropertyList = [NSPropertyListSerialization propertyListWithData:self.responseData options:self.propertyListReadOptions format:&format error:&error]; + self.propertyListFormat = format; + self.propertyListError = error; + } + + return _responsePropertyList; +} + +- (NSError *)error { + if (_propertyListError) { + return _propertyListError; + } else { + return [super error]; + } +} + +#pragma mark - AFHTTPRequestOperation + ++ (NSSet *)acceptableContentTypes { + return [NSSet setWithObjects:@"application/x-plist", nil]; +} + ++ (BOOL)canProcessRequest:(NSURLRequest *)request { + return [[[request URL] pathExtension] isEqualToString:@"plist"] || [super canProcessRequest:request]; +} + +- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success + failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure +{ + self.completionBlock = ^ { + if ([self isCancelled]) { + return; + } + + if (self.error) { + if (failure) { + dispatch_async(self.failureCallbackQueue ? self.failureCallbackQueue : dispatch_get_main_queue(), ^{ + failure(self, self.error); + }); + } + } else { + dispatch_async(property_list_request_operation_processing_queue(), ^(void) { + id propertyList = self.responsePropertyList; + + if (self.propertyListError) { + if (failure) { + dispatch_async(self.failureCallbackQueue ? self.failureCallbackQueue : dispatch_get_main_queue(), ^{ + failure(self, self.error); + }); + } + } else { + if (success) { + dispatch_async(self.successCallbackQueue ? self.successCallbackQueue : dispatch_get_main_queue(), ^{ + success(self, propertyList); + }); + } + } + }); + } + }; +} + +@end diff --git a/AFNetworking/AFURLConnectionOperation.h b/AFNetworking/AFURLConnectionOperation.h new file mode 100644 index 0000000..0266ced --- /dev/null +++ b/AFNetworking/AFURLConnectionOperation.h @@ -0,0 +1,244 @@ +// AFURLConnectionOperation.h +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import + +/** + Indicates an error occured in AFNetworking. + + @discussion Error codes for AFNetworkingErrorDomain correspond to codes in NSURLErrorDomain. + */ +extern NSString * const AFNetworkingErrorDomain; + +/** + Posted when an operation begins executing. + */ +extern NSString * const AFNetworkingOperationDidStartNotification; + +/** + Posted when an operation finishes. + */ +extern NSString * const AFNetworkingOperationDidFinishNotification; + +/** + `AFURLConnectionOperation` is an `NSOperation` that implements NSURLConnection delegate methods. + + ## Subclassing Notes + + This is the base class of all network request operations. You may wish to create your own subclass in order to implement additional `NSURLConnection` delegate methods (see "`NSURLConnection` Delegate Methods" below), or to provide additional properties and/or class constructors. + + If you are creating a subclass that communicates over the HTTP or HTTPS protocols, you may want to consider subclassing `AFHTTPRequestOperation` instead, as it supports specifying acceptable content types or status codes. + + ## NSURLConnection Delegate Methods + + `AFURLConnectionOperation` implements the following `NSURLConnection` delegate methods: + + - `connection:didReceiveResponse:` + - `connection:didReceiveData:` + - `connectionDidFinishLoading:` + - `connection:didFailWithError:` + - `connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:` + - `connection:willCacheResponse:` + - `connection:canAuthenticateAgainstProtectionSpace:` + - `connection:didReceiveAuthenticationChallenge:` + + If any of these methods are overriden in a subclass, they _must_ call the `super` implementation first. + + ## Class Constructors + + Class constructors, or methods that return an unowned (zero retain count) instance, are the preferred way for subclasses to encapsulate any particular logic for handling the setup or parsing of response data. For instance, `AFJSONRequestOperation` provides `JSONRequestOperationWithRequest:success:failure:`, which takes block arguments, whose parameter on for a successful request is the JSON object initialized from the `response data`. + + ## Callbacks and Completion Blocks + + The built-in `completionBlock` provided by `NSOperation` allows for custom behavior to be executed after the request finishes. It is a common pattern for class constructors in subclasses to take callback block parameters, and execute them conditionally in the body of its `completionBlock`. Make sure to handle cancelled operations appropriately when setting a `completionBlock` (e.g. returning early before parsing response data). See the implementation of any of the `AFHTTPRequestOperation` subclasses for an example of this. + + @warning Subclasses are strongly discouraged from overriding `setCompletionBlock:`, as `AFURLConnectionOperation`'s implementation includes a workaround to mitigate retain cycles, and what Apple rather ominously refers to as "The Deallocation Problem" (See http://developer.apple.com/library/ios/technotes/tn2109/_index.html#//apple_ref/doc/uid/DTS40010274-CH1-SUBSECTION11) + + @warning Attempting to load a `file://` URL in iOS 4 may result in an `NSInvalidArgumentException`, caused by the connection returning `NSURLResponse` rather than `NSHTTPURLResponse`, which is the behavior as of iOS 5. + */ +@interface AFURLConnectionOperation : NSOperation + +///------------------------------- +/// @name Accessing Run Loop Modes +///------------------------------- + +/** + The run loop modes in which the operation will run on the network thread. By default, this is a single-member set containing `NSRunLoopCommonModes`. + */ +@property (nonatomic, retain) NSSet *runLoopModes; + +///----------------------------------------- +/// @name Getting URL Connection Information +///----------------------------------------- + +/** + The request used by the operation's connection. + */ +@property (readonly, nonatomic, retain) NSURLRequest *request; + +/** + The last response received by the operation's connection. + */ +@property (readonly, nonatomic, retain) NSURLResponse *response; + +/** + The error, if any, that occured in the lifecycle of the request. + */ +@property (readonly, nonatomic, retain) NSError *error; + +///---------------------------- +/// @name Getting Response Data +///---------------------------- + +/** + The data received during the request. + */ +@property (readonly, nonatomic, retain) NSData *responseData; + +/** + The string representation of the response data. + + @discussion This method uses the string encoding of the response, or if UTF-8 if not specified, to construct a string from the response data. + */ +@property (readonly, nonatomic, copy) NSString *responseString; + +///------------------------ +/// @name Accessing Streams +///------------------------ + +/** + The input stream used to read data to be sent during the request. + + @discussion This property acts as a proxy to the `HTTPBodyStream` property of `request`. + */ +@property (nonatomic, retain) NSInputStream *inputStream; + +/** + The output stream that is used to write data received until the request is finished. + + @discussion By default, data is accumulated into a buffer that is stored into `responseData` upon completion of the request. When `outputStream` is set, the data will not be accumulated into an internal buffer, and as a result, the `responseData` property of the completed request will be `nil`. The output stream will be scheduled in the network thread runloop upon being set. + */ +@property (nonatomic, retain) NSOutputStream *outputStream; + +///------------------------------------------------------ +/// @name Initializing an AFURLConnectionOperation Object +///------------------------------------------------------ + +/** + Initializes and returns a newly allocated operation object with a url connection configured with the specified url request. + + @param urlRequest The request object to be used by the operation connection. + + @discussion This is the designated initializer. + */ +- (id)initWithRequest:(NSURLRequest *)urlRequest; + +///---------------------------------- +/// @name Pausing / Resuming Requests +///---------------------------------- + +/** + Pauses the execution of the request operation. + + @discussion A paused operation returns `NO` for `-isReady`, `-isExecuting`, and `-isFinished`. As such, it will remain in an `NSOperationQueue` until it is either cancelled or resumed. Pausing a finished or cancelled operation has no effect. + */ +- (void)pause; + +/** + Whether the request operation is currently paused. + + @return `YES` if the operation is currently paused, otherwise `NO`. + */ +- (BOOL)isPaused; + +/** + Resumes the execution of the paused request operation. + + @discussion Pause/Resume behavior varies depending on the underlying implementation for the operation class. In its base implementation, resuming a paused requests restarts the original request. However, since HTTP defines a specification for how to request a specific content range, `AFHTTPRequestOperation` will resume downloading the request from where it left off, instead of restarting the original request. + */ +- (void)resume; + +///---------------------------------------------- +/// @name Configuring Backgrounding Task Behavior +///---------------------------------------------- + +/** + Specifies that the operation should continue execution after the app has entered the background, and the expiration handler for that background task. + + @param handler A handler to be called shortly before the application’s remaining background time reaches 0. The handler is wrapped in a block that cancels the operation, and cleans up and marks the end of execution, unlike the `handler` parameter in `UIApplication -beginBackgroundTaskWithExpirationHandler:`, which expects this to be done in the handler itself. The handler is called synchronously on the main thread, thus blocking the application’s suspension momentarily while the application is notified. + */ +#if __IPHONE_OS_VERSION_MIN_REQUIRED +- (void)setShouldExecuteAsBackgroundTaskWithExpirationHandler:(void (^)(void))handler; +#endif + +///--------------------------------- +/// @name Setting Progress Callbacks +///--------------------------------- + +/** + Sets a callback to be called when an undetermined number of bytes have been uploaded to the server. + + @param block A block object to be called when an undetermined number of bytes have been uploaded to the server. This block has no return value and takes three arguments: the number of bytes written since the last time the upload progress block was called, the total bytes written, and the total bytes expected to be written during the request, as initially determined by the length of the HTTP body. This block may be called multiple times. + + @see setDownloadProgressBlock + */ +- (void)setUploadProgressBlock:(void (^)(NSInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))block; + +/** + Sets a callback to be called when an undetermined number of bytes have been downloaded from the server. + + @param block A block object to be called when an undetermined number of bytes have been downloaded from the server. This block has no return value and takes three arguments: the number of bytes read since the last time the download progress block was called, the total bytes read, and the total bytes expected to be read during the request, as initially determined by the expected content size of the `NSHTTPURLResponse` object. This block may be called multiple times. + + @see setUploadProgressBlock + */ +- (void)setDownloadProgressBlock:(void (^)(NSInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead))block; + +///------------------------------------------------- +/// @name Setting NSURLConnection Delegate Callbacks +///------------------------------------------------- + +/** + Sets a block to be executed to determine whether the connection should be able to respond to a protection space's form of authentication, as handled by the `NSURLConnectionDelegate` method `connection:canAuthenticateAgainstProtectionSpace:`. + + @param block A block object to be executed to determine whether the connection should be able to respond to a protection space's form of authentication. The block has a `BOOL` return type and takes two arguments: the URL connection object, and the protection space to authenticate against. + + @discussion If `_AFNETWORKING_ALLOW_INVALID_SSL_CERTIFICATES_` is defined, `connection:canAuthenticateAgainstProtectionSpace:` will accept invalid SSL certificates, returning `YES` if the protection space authentication method is `NSURLAuthenticationMethodServerTrust`. + */ +- (void)setAuthenticationAgainstProtectionSpaceBlock:(BOOL (^)(NSURLConnection *connection, NSURLProtectionSpace *protectionSpace))block; + +/** + Sets a block to be executed when the connection must authenticate a challenge in order to download its request, as handled by the `NSURLConnectionDelegate` method `connection:didReceiveAuthenticationChallenge:`. + + @param block A block object to be executed when the connection must authenticate a challenge in order to download its request. The block has no return type and takes two arguments: the URL connection object, and the challenge that must be authenticated. + + @discussion If `_AFNETWORKING_ALLOW_INVALID_SSL_CERTIFICATES_` is defined, `connection:didReceiveAuthenticationChallenge:` will attempt to have the challenge sender use credentials with invalid SSL certificates. + */ +- (void)setAuthenticationChallengeBlock:(void (^)(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge))block; + +/** + Sets a block to be executed to modify the response a connection will cache, if any, as handled by the `NSURLConnectionDelegate` method `connection:willCacheResponse:`. + + @param block A block object to be executed to determine what response a connection will cache, if any. The block returns an `NSCachedURLResponse` object, the cached response to store in memory or `nil` to prevent the response from being cached, and takes two arguments: the URL connection object, and the cached response provided for the request. + */ +- (void)setCacheResponseBlock:(NSCachedURLResponse * (^)(NSURLConnection *connection, NSCachedURLResponse *cachedResponse))block; + +@end diff --git a/AFNetworking/AFURLConnectionOperation.m b/AFNetworking/AFURLConnectionOperation.m new file mode 100644 index 0000000..ed0d42d --- /dev/null +++ b/AFNetworking/AFURLConnectionOperation.m @@ -0,0 +1,579 @@ +// AFURLConnectionOperation.m +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "AFURLConnectionOperation.h" + +typedef enum { + AFHTTPOperationPausedState = -1, + AFHTTPOperationReadyState = 1, + AFHTTPOperationExecutingState = 2, + AFHTTPOperationFinishedState = 3, +} _AFOperationState; + +typedef signed short AFOperationState; + +#if __IPHONE_OS_VERSION_MIN_REQUIRED +typedef UIBackgroundTaskIdentifier AFBackgroundTaskIdentifier; +#else +typedef id AFBackgroundTaskIdentifier; +#endif + +static NSUInteger const kAFHTTPMinimumInitialDataCapacity = 1024; +static NSUInteger const kAFHTTPMaximumInitialDataCapacity = 1024 * 1024 * 8; + +static NSString * const kAFNetworkingLockName = @"com.alamofire.networking.operation.lock"; + +NSString * const AFNetworkingErrorDomain = @"com.alamofire.networking.error"; + +NSString * const AFNetworkingOperationDidStartNotification = @"com.alamofire.networking.operation.start"; +NSString * const AFNetworkingOperationDidFinishNotification = @"com.alamofire.networking.operation.finish"; + +typedef void (^AFURLConnectionOperationProgressBlock)(NSInteger bytes, long long totalBytes, long long totalBytesExpected); +typedef BOOL (^AFURLConnectionOperationAuthenticationAgainstProtectionSpaceBlock)(NSURLConnection *connection, NSURLProtectionSpace *protectionSpace); +typedef void (^AFURLConnectionOperationAuthenticationChallengeBlock)(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge); +typedef NSCachedURLResponse * (^AFURLConnectionOperationCacheResponseBlock)(NSURLConnection *connection, NSCachedURLResponse *cachedResponse); + +static inline NSString * AFKeyPathFromOperationState(AFOperationState state) { + switch (state) { + case AFHTTPOperationReadyState: + return @"isReady"; + case AFHTTPOperationExecutingState: + return @"isExecuting"; + case AFHTTPOperationFinishedState: + return @"isFinished"; + case AFHTTPOperationPausedState: + return @"isPaused"; + default: + return @"state"; + } +} + +static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperationState toState, BOOL isCancelled) { + switch (fromState) { + case AFHTTPOperationReadyState: + switch (toState) { + case AFHTTPOperationPausedState: + case AFHTTPOperationExecutingState: + return YES; + case AFHTTPOperationFinishedState: + return isCancelled; + default: + return NO; + } + case AFHTTPOperationExecutingState: + switch (toState) { + case AFHTTPOperationPausedState: + case AFHTTPOperationFinishedState: + return YES; + default: + return NO; + } + case AFHTTPOperationFinishedState: + return NO; + case AFHTTPOperationPausedState: + return toState == AFHTTPOperationReadyState; + default: + return YES; + } +} + +@interface AFURLConnectionOperation () +@property (readwrite, nonatomic, assign) AFOperationState state; +@property (readwrite, nonatomic, assign, getter = isCancelled) BOOL cancelled; +@property (readwrite, nonatomic, retain) NSRecursiveLock *lock; +@property (readwrite, nonatomic, retain) NSURLConnection *connection; +@property (readwrite, nonatomic, retain) NSURLRequest *request; +@property (readwrite, nonatomic, retain) NSURLResponse *response; +@property (readwrite, nonatomic, retain) NSError *error; +@property (readwrite, nonatomic, retain) NSData *responseData; +@property (readwrite, nonatomic, copy) NSString *responseString; +@property (readwrite, nonatomic, assign) long long totalBytesRead; +@property (readwrite, nonatomic, assign) AFBackgroundTaskIdentifier backgroundTaskIdentifier; +@property (readwrite, nonatomic, copy) AFURLConnectionOperationProgressBlock uploadProgress; +@property (readwrite, nonatomic, copy) AFURLConnectionOperationProgressBlock downloadProgress; +@property (readwrite, nonatomic, copy) AFURLConnectionOperationAuthenticationAgainstProtectionSpaceBlock authenticationAgainstProtectionSpace; +@property (readwrite, nonatomic, copy) AFURLConnectionOperationAuthenticationChallengeBlock authenticationChallenge; +@property (readwrite, nonatomic, copy) AFURLConnectionOperationCacheResponseBlock cacheResponse; + +- (void)operationDidStart; +- (void)finish; +@end + +@implementation AFURLConnectionOperation +@synthesize state = _state; +@synthesize cancelled = _cancelled; +@synthesize connection = _connection; +@synthesize runLoopModes = _runLoopModes; +@synthesize request = _request; +@synthesize response = _response; +@synthesize error = _error; +@synthesize responseData = _responseData; +@synthesize responseString = _responseString; +@synthesize totalBytesRead = _totalBytesRead; +@dynamic inputStream; +@synthesize outputStream = _outputStream; +@synthesize backgroundTaskIdentifier = _backgroundTaskIdentifier; +@synthesize uploadProgress = _uploadProgress; +@synthesize downloadProgress = _downloadProgress; +@synthesize authenticationAgainstProtectionSpace = _authenticationAgainstProtectionSpace; +@synthesize authenticationChallenge = _authenticationChallenge; +@synthesize cacheResponse = _cacheResponse; +@synthesize lock = _lock; + ++ (void)networkRequestThreadEntryPoint:(id)__unused object { + do { + NSAutoreleasePool *runLoopPool = [[NSAutoreleasePool alloc] init]; + [[NSRunLoop currentRunLoop] run]; + [runLoopPool drain]; + } while (YES); +} + ++ (NSThread *)networkRequestThread { + static NSThread *_networkRequestThread = nil; + static dispatch_once_t oncePredicate; + + dispatch_once(&oncePredicate, ^{ + _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil]; + [_networkRequestThread start]; + }); + + return _networkRequestThread; +} + +- (id)initWithRequest:(NSURLRequest *)urlRequest { + self = [super init]; + if (!self) { + return nil; + } + + self.lock = [[[NSRecursiveLock alloc] init] autorelease]; + self.lock.name = kAFNetworkingLockName; + + self.runLoopModes = [NSSet setWithObject:NSRunLoopCommonModes]; + + self.request = urlRequest; + + self.outputStream = [NSOutputStream outputStreamToMemory]; + + self.state = AFHTTPOperationReadyState; + + return self; +} + +- (void)dealloc { + [_lock release]; + + [_runLoopModes release]; + + [_request release]; + [_response release]; + [_error release]; + + [_responseData release]; + [_responseString release]; + + if (_outputStream) { + [_outputStream close]; + [_outputStream release]; + _outputStream = nil; + } + +#if __IPHONE_OS_VERSION_MIN_REQUIRED + if (_backgroundTaskIdentifier) { + [[UIApplication sharedApplication] endBackgroundTask:_backgroundTaskIdentifier]; + _backgroundTaskIdentifier = UIBackgroundTaskInvalid; + } +#endif + + [_uploadProgress release]; + [_downloadProgress release]; + [_authenticationChallenge release]; + [_authenticationAgainstProtectionSpace release]; + [_cacheResponse release]; + + [_connection release]; + + [super dealloc]; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p, state: %@, cancelled: %@ request: %@, response: %@>", NSStringFromClass([self class]), self, AFKeyPathFromOperationState(self.state), ([self isCancelled] ? @"YES" : @"NO"), self.request, self.response]; +} + +- (void)setCompletionBlock:(void (^)(void))block { + [self.lock lock]; + if (!block) { + [super setCompletionBlock:nil]; + } else { + __block id _blockSelf = self; + [super setCompletionBlock:^ { + block(); + [_blockSelf setCompletionBlock:nil]; + }]; + } + [self.lock unlock]; +} + +- (NSInputStream *)inputStream { + return self.request.HTTPBodyStream; +} + +- (void)setInputStream:(NSInputStream *)inputStream { + [self willChangeValueForKey:@"inputStream"]; + NSMutableURLRequest *mutableRequest = [[self.request mutableCopy] autorelease]; + mutableRequest.HTTPBodyStream = inputStream; + self.request = mutableRequest; + [self didChangeValueForKey:@"inputStream"]; +} + +- (void)setOutputStream:(NSOutputStream *)outputStream { + [self willChangeValueForKey:@"outputStream"]; + [outputStream retain]; + + if (_outputStream) { + [_outputStream close]; + [_outputStream release]; + } + _outputStream = outputStream; + [self didChangeValueForKey:@"outputStream"]; + + NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; + for (NSString *runLoopMode in self.runLoopModes) { + [self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode]; + } +} + +#if __IPHONE_OS_VERSION_MIN_REQUIRED +- (void)setShouldExecuteAsBackgroundTaskWithExpirationHandler:(void (^)(void))handler { + [self.lock lock]; + if (!self.backgroundTaskIdentifier) { + UIApplication *application = [UIApplication sharedApplication]; + self.backgroundTaskIdentifier = [application beginBackgroundTaskWithExpirationHandler:^{ + if (handler) { + handler(); + } + + [self cancel]; + + [application endBackgroundTask:self.backgroundTaskIdentifier]; + self.backgroundTaskIdentifier = UIBackgroundTaskInvalid; + }]; + } + [self.lock unlock]; +} +#endif + +- (void)setUploadProgressBlock:(void (^)(NSInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))block { + self.uploadProgress = block; +} + +- (void)setDownloadProgressBlock:(void (^)(NSInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead))block { + self.downloadProgress = block; +} + +- (void)setAuthenticationAgainstProtectionSpaceBlock:(BOOL (^)(NSURLConnection *, NSURLProtectionSpace *))block { + self.authenticationAgainstProtectionSpace = block; +} + +- (void)setAuthenticationChallengeBlock:(void (^)(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge))block { + self.authenticationChallenge = block; +} + +- (void)setCacheResponseBlock:(NSCachedURLResponse * (^)(NSURLConnection *connection, NSCachedURLResponse *cachedResponse))block { + self.cacheResponse = block; +} + +- (void)setState:(AFOperationState)state { + [self.lock lock]; + if (AFStateTransitionIsValid(self.state, state, [self isCancelled])) { + NSString *oldStateKey = AFKeyPathFromOperationState(self.state); + NSString *newStateKey = AFKeyPathFromOperationState(state); + + [self willChangeValueForKey:newStateKey]; + [self willChangeValueForKey:oldStateKey]; + _state = state; + [self didChangeValueForKey:oldStateKey]; + [self didChangeValueForKey:newStateKey]; + + switch (state) { + case AFHTTPOperationExecutingState: + [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidStartNotification object:self]; + break; + case AFHTTPOperationFinishedState: + [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidFinishNotification object:self]; + break; + default: + break; + } + } + [self.lock unlock]; +} + +- (NSString *)responseString { + [self.lock lock]; + if (!_responseString && self.response && self.responseData) { + NSStringEncoding textEncoding = NSUTF8StringEncoding; + if (self.response.textEncodingName) { + textEncoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef)self.response.textEncodingName)); + } + + self.responseString = [[[NSString alloc] initWithData:self.responseData encoding:textEncoding] autorelease]; + } + [self.lock unlock]; + + return _responseString; +} + +- (void)pause { + if ([self isPaused]) { + return; + } + + [self.lock lock]; + self.state = AFHTTPOperationPausedState; + + [self.connection performSelector:@selector(cancel) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; + [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidFinishNotification object:self]; + [self.lock unlock]; +} + +- (BOOL)isPaused { + return self.state == AFHTTPOperationPausedState; +} + +- (void)resume { + if (![self isPaused]) { + return; + } + + [self.lock lock]; + self.state = AFHTTPOperationReadyState; + + [self start]; + [self.lock unlock]; +} + +#pragma mark - NSOperation + +- (BOOL)isReady { + return self.state == AFHTTPOperationReadyState && [super isReady]; +} + +- (BOOL)isExecuting { + return self.state == AFHTTPOperationExecutingState; +} + +- (BOOL)isFinished { + return self.state == AFHTTPOperationFinishedState; +} + +- (BOOL)isConcurrent { + return YES; +} + +- (void)start { + [self.lock lock]; + if ([self isReady]) { + self.state = AFHTTPOperationExecutingState; + + [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; + } + [self.lock unlock]; +} + +- (void)operationDidStart { + [self.lock lock]; + if ([self isCancelled]) { + [self finish]; + } else { + self.connection = [[[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO] autorelease]; + + NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; + for (NSString *runLoopMode in self.runLoopModes) { + [self.connection scheduleInRunLoop:runLoop forMode:runLoopMode]; + [self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode]; + } + + [self.connection start]; + } + [self.lock unlock]; +} + +- (void)finish { + self.state = AFHTTPOperationFinishedState; +} + +- (void)cancel { + [self.lock lock]; + if (![self isFinished] && ![self isCancelled]) { + [self willChangeValueForKey:@"isCancelled"]; + _cancelled = YES; + [super cancel]; + [self didChangeValueForKey:@"isCancelled"]; + + // Cancel the connection on the thread it runs on to prevent race conditions + [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; + } + [self.lock unlock]; +} + +- (void)cancelConnection { + if (self.connection) { + [self.connection cancel]; + + // Manually send this delegate message since `[self.connection cancel]` causes the connection to never send another message to its delegate + NSDictionary *userInfo = nil; + if ([self.request URL]) { + userInfo = [NSDictionary dictionaryWithObject:[self.request URL] forKey:NSURLErrorFailingURLErrorKey]; + } + [self performSelector:@selector(connection:didFailWithError:) withObject:self.connection withObject:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCancelled userInfo:userInfo]]; + } +} + +#pragma mark - NSURLConnectionDelegate + +- (BOOL)connection:(NSURLConnection *)connection +canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace +{ +#ifdef _AFNETWORKING_ALLOW_INVALID_SSL_CERTIFICATES_ + if ([protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { + return YES; + } +#endif + + if (self.authenticationAgainstProtectionSpace) { + return self.authenticationAgainstProtectionSpace(connection, protectionSpace); + } else if ([protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust] || [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodClientCertificate]) { + return NO; + } else { + return YES; + } +} + +- (void)connection:(NSURLConnection *)connection +didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge +{ +#ifdef _AFNETWORKING_ALLOW_INVALID_SSL_CERTIFICATES_ + if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { + [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge]; + return; + } +#endif + + if (self.authenticationChallenge) { + self.authenticationChallenge(connection, challenge); + } else { + if ([challenge previousFailureCount] == 0) { + NSURLCredential *credential = nil; + + NSString *username = [(NSString *)CFURLCopyUserName((CFURLRef)[self.request URL]) autorelease]; + NSString *password = [(NSString *)CFURLCopyPassword((CFURLRef)[self.request URL]) autorelease]; + + if (username && password) { + credential = [NSURLCredential credentialWithUser:username password:password persistence:NSURLCredentialPersistenceNone]; + } else if (username) { + credential = [[[NSURLCredentialStorage sharedCredentialStorage] credentialsForProtectionSpace:[challenge protectionSpace]] objectForKey:username]; + } else { + credential = [[NSURLCredentialStorage sharedCredentialStorage] defaultCredentialForProtectionSpace:[challenge protectionSpace]]; + } + + if (credential) { + [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge]; + } else { + [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge]; + } + } else { + [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge]; + } + } +} + +- (void)connection:(NSURLConnection *)__unused connection + didSendBodyData:(NSInteger)bytesWritten + totalBytesWritten:(NSInteger)totalBytesWritten +totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite +{ + if (self.uploadProgress) { + self.uploadProgress(bytesWritten, totalBytesWritten, totalBytesExpectedToWrite); + } +} + +- (void)connection:(NSURLConnection *)__unused connection +didReceiveResponse:(NSURLResponse *)response +{ + self.response = response; + + [self.outputStream open]; +} + +- (void)connection:(NSURLConnection *)__unused connection + didReceiveData:(NSData *)data +{ + self.totalBytesRead += [data length]; + + if ([self.outputStream hasSpaceAvailable]) { + const uint8_t *dataBuffer = (uint8_t *) [data bytes]; + [self.outputStream write:&dataBuffer[0] maxLength:[data length]]; + } + + if (self.downloadProgress) { + self.downloadProgress((long long)[data length], self.totalBytesRead, self.response.expectedContentLength); + } +} + +- (void)connectionDidFinishLoading:(NSURLConnection *)__unused connection { + self.responseData = [self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey]; + + [self.outputStream close]; + + [self finish]; + + self.connection = nil; +} + +- (void)connection:(NSURLConnection *)__unused connection + didFailWithError:(NSError *)error +{ + self.error = error; + + [self.outputStream close]; + + [self finish]; + + self.connection = nil; +} + +- (NSCachedURLResponse *)connection:(NSURLConnection *)connection + willCacheResponse:(NSCachedURLResponse *)cachedResponse +{ + if (self.cacheResponse) { + return self.cacheResponse(connection, cachedResponse); + } else { + if ([self isCancelled]) { + return nil; + } + + return cachedResponse; + } +} + +@end diff --git a/AFNetworking/AFXMLRequestOperation.h b/AFNetworking/AFXMLRequestOperation.h new file mode 100644 index 0000000..0beb677 --- /dev/null +++ b/AFNetworking/AFXMLRequestOperation.h @@ -0,0 +1,89 @@ +// AFXMLRequestOperation.h +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import +#import "AFHTTPRequestOperation.h" + +#import + +/** + `AFXMLRequestOperation` is a subclass of `AFHTTPRequestOperation` for downloading and working with XML response data. + + ## Acceptable Content Types + + By default, `AFXMLRequestOperation` accepts the following MIME types, which includes the official standard, `application/xml`, as well as other commonly-used types: + + - `application/xml` + - `text/xml` + + ## Use With AFHTTPClient + + When `AFXMLRequestOperation` is registered with `AFHTTPClient`, the response object in the success callback of `HTTPRequestOperationWithRequest:success:failure:` will be an instance of `NSXMLParser`. On platforms that support `NSXMLDocument`, you have the option to ignore the response object, and simply use the `responseXMLDocument` property of the operation argument of the callback. + */ +@interface AFXMLRequestOperation : AFHTTPRequestOperation + +///---------------------------- +/// @name Getting Response Data +///---------------------------- + +/** + An `NSXMLParser` object constructed from the response data. + */ +@property (readonly, nonatomic, retain) NSXMLParser *responseXMLParser; + +#if __MAC_OS_X_VERSION_MIN_REQUIRED +/** + An `NSXMLDocument` object constructed from the response data. If an error occurs while parsing, `nil` will be returned, and the `error` property will be set to the error. + */ +@property (readonly, nonatomic, retain) NSXMLDocument *responseXMLDocument; +#endif + +/** + Creates and returns an `AFXMLRequestOperation` object and sets the specified success and failure callbacks. + + @param urlRequest The request object to be loaded asynchronously during execution of the operation + @param success A block object to be executed when the operation finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the XML parser constructed with the response data of request. + @param failure A block object to be executed when the operation finishes unsuccessfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the error describing the network error that occurred. + + @return A new XML request operation + */ ++ (AFXMLRequestOperation *)XMLParserRequestOperationWithRequest:(NSURLRequest *)urlRequest + success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSXMLParser *XMLParser))success + failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, NSXMLParser *XMLParse))failure; + + +#if __MAC_OS_X_VERSION_MIN_REQUIRED +/** + Creates and returns an `AFXMLRequestOperation` object and sets the specified success and failure callbacks. + + @param urlRequest The request object to be loaded asynchronously during execution of the operation + @param success A block object to be executed when the operation finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the XML document created from the response data of request. + @param failure A block object to be executed when the operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the resonse data as XML. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the error describing the network or parsing error that occurred. + + @return A new XML request operation + */ ++ (AFXMLRequestOperation *)XMLDocumentRequestOperationWithRequest:(NSURLRequest *)urlRequest + success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSXMLDocument *document))success + failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, NSXMLDocument *document))failure; +#endif + +@end diff --git a/AFNetworking/AFXMLRequestOperation.m b/AFNetworking/AFXMLRequestOperation.m new file mode 100644 index 0000000..f40cacb --- /dev/null +++ b/AFNetworking/AFXMLRequestOperation.m @@ -0,0 +1,177 @@ +// AFXMLRequestOperation.m +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "AFXMLRequestOperation.h" + +#include + +static dispatch_queue_t af_xml_request_operation_processing_queue; +static dispatch_queue_t xml_request_operation_processing_queue() { + if (af_xml_request_operation_processing_queue == NULL) { + af_xml_request_operation_processing_queue = dispatch_queue_create("com.alamofire.networking.xml-request.processing", 0); + } + + return af_xml_request_operation_processing_queue; +} + +@interface AFXMLRequestOperation () +@property (readwrite, nonatomic, retain) NSXMLParser *responseXMLParser; +#if __MAC_OS_X_VERSION_MIN_REQUIRED +@property (readwrite, nonatomic, retain) NSXMLDocument *responseXMLDocument; +#endif +@property (readwrite, nonatomic, retain) NSError *XMLError; +@end + +@implementation AFXMLRequestOperation +@synthesize responseXMLParser = _responseXMLParser; +#if __MAC_OS_X_VERSION_MIN_REQUIRED +@synthesize responseXMLDocument = _responseXMLDocument; +#endif +@synthesize XMLError = _XMLError; + ++ (AFXMLRequestOperation *)XMLParserRequestOperationWithRequest:(NSURLRequest *)urlRequest + success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSXMLParser *XMLParser))success + failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, NSXMLParser *XMLParser))failure +{ + AFXMLRequestOperation *requestOperation = [[[self alloc] initWithRequest:urlRequest] autorelease]; + [requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { + if (success) { + success(operation.request, operation.response, responseObject); + } + } failure:^(AFHTTPRequestOperation *operation, NSError *error) { + if (failure) { + failure(operation.request, operation.response, error, [(AFXMLRequestOperation *)operation responseXMLParser]); + } + }]; + + return requestOperation; +} + +#if __MAC_OS_X_VERSION_MIN_REQUIRED ++ (AFXMLRequestOperation *)XMLDocumentRequestOperationWithRequest:(NSURLRequest *)urlRequest + success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSXMLDocument *document))success + failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, NSXMLDocument *document))failure +{ + AFXMLRequestOperation *requestOperation = [[[self alloc] initWithRequest:urlRequest] autorelease]; + [requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, __unused id responseObject) { + if (success) { + NSXMLDocument *XMLDocument = [(AFXMLRequestOperation *)operation responseXMLDocument]; + success(operation.request, operation.response, XMLDocument); + } + } failure:^(AFHTTPRequestOperation *operation, NSError *error) { + if (failure) { + NSXMLDocument *XMLDocument = [(AFXMLRequestOperation *)operation responseXMLDocument]; + failure(operation.request, operation.response, error, XMLDocument); + } + }]; + + return requestOperation; +} +#endif + +- (void)dealloc { + [_responseXMLParser release]; + +#if __MAC_OS_X_VERSION_MIN_REQUIRED + [_responseXMLDocument release]; +#endif + + [_XMLError release]; + + [super dealloc]; +} + +- (NSXMLParser *)responseXMLParser { + if (!_responseXMLParser && [self.responseData length] > 0 && [self isFinished]) { + self.responseXMLParser = [[[NSXMLParser alloc] initWithData:self.responseData] autorelease]; + } + + return _responseXMLParser; +} + +#if __MAC_OS_X_VERSION_MIN_REQUIRED +- (NSXMLDocument *)responseXMLDocument { + if (!_responseXMLDocument && [self.responseData length] > 0 && [self isFinished]) { + NSError *error = nil; + self.responseXMLDocument = [[[NSXMLDocument alloc] initWithData:self.responseData options:0 error:&error] autorelease]; + self.XMLError = error; + } + + return _responseXMLDocument; +} +#endif + +- (NSError *)error { + if (_XMLError) { + return _XMLError; + } else { + return [super error]; + } +} + +#pragma mark - NSOperation + +- (void)cancel { + [super cancel]; + + self.responseXMLParser.delegate = nil; +} + +#pragma mark - AFHTTPRequestOperation + ++ (NSSet *)acceptableContentTypes { + return [NSSet setWithObjects:@"application/xml", @"text/xml", nil]; +} + ++ (BOOL)canProcessRequest:(NSURLRequest *)request { + return [[[request URL] pathExtension] isEqualToString:@"xml"] || [super canProcessRequest:request]; +} + +- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success + failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure +{ + self.completionBlock = ^ { + if ([self isCancelled]) { + return; + } + + dispatch_async(xml_request_operation_processing_queue(), ^(void) { + NSXMLParser *XMLParser = self.responseXMLParser; + + if (self.error) { + if (failure) { + dispatch_async(self.failureCallbackQueue ? self.failureCallbackQueue : dispatch_get_main_queue(), ^{ + failure(self, self.error); + }); + } + } else { + if (success) { + dispatch_async(self.successCallbackQueue ? self.successCallbackQueue : dispatch_get_main_queue(), ^{ + success(self, XMLParser); + }); + } + } + }); + }; +} + +@end diff --git a/BitTicker.xcodeproj/project.pbxproj b/BitTicker.xcodeproj/project.pbxproj index 4c4f136..479683d 100755 --- a/BitTicker.xcodeproj/project.pbxproj +++ b/BitTicker.xcodeproj/project.pbxproj @@ -22,17 +22,16 @@ AA4BBA431379E31C005CE351 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = AA4BBA411379E31C005CE351 /* MainMenu.xib */; }; AA6E6C4D1399DC3E003A4224 /* Credits.html in Resources */ = {isa = PBXBuildFile; fileRef = AA6E6C4C1399DC3E003A4224 /* Credits.html */; }; AA79F0A9152ADB0C002C3EEB /* GraphView.m in Sources */ = {isa = PBXBuildFile; fileRef = AA79F0A7152AD215002C3EEB /* GraphView.m */; }; - AAA1992715295E9500025EA2 /* CorePlot.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AAA1992615295E9500025EA2 /* CorePlot.framework */; }; - AAA1992C1529608E00025EA2 /* CorePlot.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = AAA1992615295E9500025EA2 /* CorePlot.framework */; }; - AAA19944152969E000025EA2 /* AFHTTPClient.m in Sources */ = {isa = PBXBuildFile; fileRef = AAA1992F152969E000025EA2 /* AFHTTPClient.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; - AAA19945152969E000025EA2 /* AFHTTPRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = AAA19931152969E000025EA2 /* AFHTTPRequestOperation.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; - AAA19946152969E000025EA2 /* AFImageCache.m in Sources */ = {isa = PBXBuildFile; fileRef = AAA19933152969E000025EA2 /* AFImageCache.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; - AAA19947152969E000025EA2 /* AFImageRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = AAA19935152969E000025EA2 /* AFImageRequestOperation.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; - AAA19948152969E000025EA2 /* AFJSONRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = AAA19937152969E000025EA2 /* AFJSONRequestOperation.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; - AAA19949152969E000025EA2 /* AFNetworkActivityIndicatorManager.m in Sources */ = {isa = PBXBuildFile; fileRef = AAA1993A152969E000025EA2 /* AFNetworkActivityIndicatorManager.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; - AAA1994A152969E000025EA2 /* AFPropertyListRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = AAA1993D152969E000025EA2 /* AFPropertyListRequestOperation.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; - AAA1994B152969E000025EA2 /* AFURLConnectionOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = AAA1993F152969E000025EA2 /* AFURLConnectionOperation.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; - AAA1994C152969E000025EA2 /* AFXMLRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = AAA19941152969E000025EA2 /* AFXMLRequestOperation.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + AAA32C6915CA3CBB00C45B89 /* CorePlot.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AAA32C6815CA3CBB00C45B89 /* CorePlot.framework */; }; + AAA32C8015CA3D0500C45B89 /* AFHTTPClient.m in Sources */ = {isa = PBXBuildFile; fileRef = AAA32C6C15CA3D0500C45B89 /* AFHTTPClient.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + AAA32C8115CA3D0500C45B89 /* AFHTTPRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = AAA32C6E15CA3D0500C45B89 /* AFHTTPRequestOperation.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + AAA32C8215CA3D0500C45B89 /* AFImageRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = AAA32C7015CA3D0500C45B89 /* AFImageRequestOperation.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + AAA32C8315CA3D0500C45B89 /* AFJSONRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = AAA32C7215CA3D0500C45B89 /* AFJSONRequestOperation.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + AAA32C8415CA3D0500C45B89 /* AFJSONUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = AAA32C7415CA3D0500C45B89 /* AFJSONUtilities.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + AAA32C8515CA3D0500C45B89 /* AFNetworkActivityIndicatorManager.m in Sources */ = {isa = PBXBuildFile; fileRef = AAA32C7615CA3D0500C45B89 /* AFNetworkActivityIndicatorManager.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + AAA32C8615CA3D0500C45B89 /* AFPropertyListRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = AAA32C7915CA3D0500C45B89 /* AFPropertyListRequestOperation.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + AAA32C8715CA3D0500C45B89 /* AFURLConnectionOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = AAA32C7B15CA3D0500C45B89 /* AFURLConnectionOperation.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + AAA32C8815CA3D0500C45B89 /* AFXMLRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = AAA32C7D15CA3D0500C45B89 /* AFXMLRequestOperation.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; AAD1726D1399BD3500B505B0 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AAD1726C1399BD3500B505B0 /* Security.framework */; }; AAD64420137B680E00589EAC /* Logo.icns in Resources */ = {isa = PBXBuildFile; fileRef = AAD6441F137B680E00589EAC /* Logo.icns */; }; /* End PBXBuildFile section */ @@ -44,7 +43,6 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - AAA1992C1529608E00025EA2 /* CorePlot.framework in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -78,27 +76,26 @@ AA79F0A6152AD215002C3EEB /* GraphView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GraphView.h; sourceTree = ""; }; AA79F0A7152AD215002C3EEB /* GraphView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GraphView.m; sourceTree = ""; }; AA79F0A8152AD664002C3EEB /* BitTicker.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = BitTicker.entitlements; sourceTree = ""; }; - AAA1992615295E9500025EA2 /* CorePlot.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CorePlot.framework; path = ../../Libraries/CorePlot_1.0/Binaries/MacOS/CorePlot.framework; sourceTree = ""; }; - AAA1992E152969E000025EA2 /* AFHTTPClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFHTTPClient.h; sourceTree = ""; }; - AAA1992F152969E000025EA2 /* AFHTTPClient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFHTTPClient.m; sourceTree = ""; }; - AAA19930152969E000025EA2 /* AFHTTPRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFHTTPRequestOperation.h; sourceTree = ""; }; - AAA19931152969E000025EA2 /* AFHTTPRequestOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFHTTPRequestOperation.m; sourceTree = ""; }; - AAA19932152969E000025EA2 /* AFImageCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFImageCache.h; sourceTree = ""; }; - AAA19933152969E000025EA2 /* AFImageCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFImageCache.m; sourceTree = ""; }; - AAA19934152969E000025EA2 /* AFImageRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFImageRequestOperation.h; sourceTree = ""; }; - AAA19935152969E000025EA2 /* AFImageRequestOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFImageRequestOperation.m; sourceTree = ""; }; - AAA19936152969E000025EA2 /* AFJSONRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFJSONRequestOperation.h; sourceTree = ""; }; - AAA19937152969E000025EA2 /* AFJSONRequestOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFJSONRequestOperation.m; sourceTree = ""; }; - AAA19938152969E000025EA2 /* AFJSONUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFJSONUtilities.h; sourceTree = ""; }; - AAA19939152969E000025EA2 /* AFNetworkActivityIndicatorManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFNetworkActivityIndicatorManager.h; sourceTree = ""; }; - AAA1993A152969E000025EA2 /* AFNetworkActivityIndicatorManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFNetworkActivityIndicatorManager.m; sourceTree = ""; }; - AAA1993B152969E000025EA2 /* AFNetworking.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFNetworking.h; sourceTree = ""; }; - AAA1993C152969E000025EA2 /* AFPropertyListRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFPropertyListRequestOperation.h; sourceTree = ""; }; - AAA1993D152969E000025EA2 /* AFPropertyListRequestOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFPropertyListRequestOperation.m; sourceTree = ""; }; - AAA1993E152969E000025EA2 /* AFURLConnectionOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFURLConnectionOperation.h; sourceTree = ""; }; - AAA1993F152969E000025EA2 /* AFURLConnectionOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFURLConnectionOperation.m; sourceTree = ""; }; - AAA19940152969E000025EA2 /* AFXMLRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFXMLRequestOperation.h; sourceTree = ""; }; - AAA19941152969E000025EA2 /* AFXMLRequestOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFXMLRequestOperation.m; sourceTree = ""; }; + AAA32C6815CA3CBB00C45B89 /* CorePlot.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = CorePlot.framework; sourceTree = ""; }; + AAA32C6B15CA3D0500C45B89 /* AFHTTPClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFHTTPClient.h; sourceTree = ""; }; + AAA32C6C15CA3D0500C45B89 /* AFHTTPClient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFHTTPClient.m; sourceTree = ""; }; + AAA32C6D15CA3D0500C45B89 /* AFHTTPRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFHTTPRequestOperation.h; sourceTree = ""; }; + AAA32C6E15CA3D0500C45B89 /* AFHTTPRequestOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFHTTPRequestOperation.m; sourceTree = ""; }; + AAA32C6F15CA3D0500C45B89 /* AFImageRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFImageRequestOperation.h; sourceTree = ""; }; + AAA32C7015CA3D0500C45B89 /* AFImageRequestOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFImageRequestOperation.m; sourceTree = ""; }; + AAA32C7115CA3D0500C45B89 /* AFJSONRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFJSONRequestOperation.h; sourceTree = ""; }; + AAA32C7215CA3D0500C45B89 /* AFJSONRequestOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFJSONRequestOperation.m; sourceTree = ""; }; + AAA32C7315CA3D0500C45B89 /* AFJSONUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFJSONUtilities.h; sourceTree = ""; }; + AAA32C7415CA3D0500C45B89 /* AFJSONUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFJSONUtilities.m; sourceTree = ""; }; + AAA32C7515CA3D0500C45B89 /* AFNetworkActivityIndicatorManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFNetworkActivityIndicatorManager.h; sourceTree = ""; }; + AAA32C7615CA3D0500C45B89 /* AFNetworkActivityIndicatorManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFNetworkActivityIndicatorManager.m; sourceTree = ""; }; + AAA32C7715CA3D0500C45B89 /* AFNetworking.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFNetworking.h; sourceTree = ""; }; + AAA32C7815CA3D0500C45B89 /* AFPropertyListRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFPropertyListRequestOperation.h; sourceTree = ""; }; + AAA32C7915CA3D0500C45B89 /* AFPropertyListRequestOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFPropertyListRequestOperation.m; sourceTree = ""; }; + AAA32C7A15CA3D0500C45B89 /* AFURLConnectionOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFURLConnectionOperation.h; sourceTree = ""; }; + AAA32C7B15CA3D0500C45B89 /* AFURLConnectionOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFURLConnectionOperation.m; sourceTree = ""; }; + AAA32C7C15CA3D0500C45B89 /* AFXMLRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFXMLRequestOperation.h; sourceTree = ""; }; + AAA32C7D15CA3D0500C45B89 /* AFXMLRequestOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFXMLRequestOperation.m; sourceTree = ""; }; AAD1726C1399BD3500B505B0 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; AAD6441F137B680E00589EAC /* Logo.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = Logo.icns; sourceTree = ""; }; /* End PBXFileReference section */ @@ -115,7 +112,7 @@ AA239F771379F17200150707 /* libz.dylib in Frameworks */, AA239F781379F17200150707 /* SystemConfiguration.framework in Frameworks */, AA4BBA2D1379E31B005CE351 /* Cocoa.framework in Frameworks */, - AAA1992715295E9500025EA2 /* CorePlot.framework in Frameworks */, + AAA32C6915CA3CBB00C45B89 /* CorePlot.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -150,10 +147,8 @@ AA4BBA2B1379E31B005CE351 /* Frameworks */ = { isa = PBXGroup; children = ( - AAA1992D152969E000025EA2 /* AFNetworking */, - AAA1992615295E9500025EA2 /* CorePlot.framework */, - AAD1726C1399BD3500B505B0 /* Security.framework */, - AA4BBA2C1379E31B005CE351 /* Cocoa.framework */, + AAA32C6A15CA3D0500C45B89 /* AFNetworking */, + AAA32C6815CA3CBB00C45B89 /* CorePlot.framework */, AA4BBA2E1379E31B005CE351 /* Other Frameworks */, ); name = Frameworks; @@ -162,6 +157,8 @@ AA4BBA2E1379E31B005CE351 /* Other Frameworks */ = { isa = PBXGroup; children = ( + AA4BBA2C1379E31B005CE351 /* Cocoa.framework */, + AAD1726C1399BD3500B505B0 /* Security.framework */, AA239F791379F1B200150707 /* Quartz.framework */, AA239F7A1379F1B200150707 /* QuartzCore.framework */, AA239F741379F17200150707 /* libz.dylib */, @@ -207,32 +204,30 @@ name = "Supporting Files"; sourceTree = ""; }; - AAA1992D152969E000025EA2 /* AFNetworking */ = { + AAA32C6A15CA3D0500C45B89 /* AFNetworking */ = { isa = PBXGroup; children = ( - AAA1992E152969E000025EA2 /* AFHTTPClient.h */, - AAA1992F152969E000025EA2 /* AFHTTPClient.m */, - AAA19930152969E000025EA2 /* AFHTTPRequestOperation.h */, - AAA19931152969E000025EA2 /* AFHTTPRequestOperation.m */, - AAA19932152969E000025EA2 /* AFImageCache.h */, - AAA19933152969E000025EA2 /* AFImageCache.m */, - AAA19934152969E000025EA2 /* AFImageRequestOperation.h */, - AAA19935152969E000025EA2 /* AFImageRequestOperation.m */, - AAA19936152969E000025EA2 /* AFJSONRequestOperation.h */, - AAA19937152969E000025EA2 /* AFJSONRequestOperation.m */, - AAA19938152969E000025EA2 /* AFJSONUtilities.h */, - AAA19939152969E000025EA2 /* AFNetworkActivityIndicatorManager.h */, - AAA1993A152969E000025EA2 /* AFNetworkActivityIndicatorManager.m */, - AAA1993B152969E000025EA2 /* AFNetworking.h */, - AAA1993C152969E000025EA2 /* AFPropertyListRequestOperation.h */, - AAA1993D152969E000025EA2 /* AFPropertyListRequestOperation.m */, - AAA1993E152969E000025EA2 /* AFURLConnectionOperation.h */, - AAA1993F152969E000025EA2 /* AFURLConnectionOperation.m */, - AAA19940152969E000025EA2 /* AFXMLRequestOperation.h */, - AAA19941152969E000025EA2 /* AFXMLRequestOperation.m */, + AAA32C6B15CA3D0500C45B89 /* AFHTTPClient.h */, + AAA32C6C15CA3D0500C45B89 /* AFHTTPClient.m */, + AAA32C6D15CA3D0500C45B89 /* AFHTTPRequestOperation.h */, + AAA32C6E15CA3D0500C45B89 /* AFHTTPRequestOperation.m */, + AAA32C6F15CA3D0500C45B89 /* AFImageRequestOperation.h */, + AAA32C7015CA3D0500C45B89 /* AFImageRequestOperation.m */, + AAA32C7115CA3D0500C45B89 /* AFJSONRequestOperation.h */, + AAA32C7215CA3D0500C45B89 /* AFJSONRequestOperation.m */, + AAA32C7315CA3D0500C45B89 /* AFJSONUtilities.h */, + AAA32C7415CA3D0500C45B89 /* AFJSONUtilities.m */, + AAA32C7515CA3D0500C45B89 /* AFNetworkActivityIndicatorManager.h */, + AAA32C7615CA3D0500C45B89 /* AFNetworkActivityIndicatorManager.m */, + AAA32C7715CA3D0500C45B89 /* AFNetworking.h */, + AAA32C7815CA3D0500C45B89 /* AFPropertyListRequestOperation.h */, + AAA32C7915CA3D0500C45B89 /* AFPropertyListRequestOperation.m */, + AAA32C7A15CA3D0500C45B89 /* AFURLConnectionOperation.h */, + AAA32C7B15CA3D0500C45B89 /* AFURLConnectionOperation.m */, + AAA32C7C15CA3D0500C45B89 /* AFXMLRequestOperation.h */, + AAA32C7D15CA3D0500C45B89 /* AFXMLRequestOperation.m */, ); - name = AFNetworking; - path = ../../Libraries/AFNetworking/AFNetworking; + path = AFNetworking; sourceTree = ""; }; /* End PBXGroup section */ @@ -307,15 +302,15 @@ AA239F511379E67300150707 /* StatusItemView.m in Sources */, 83405B3C137F6DDF0060CAD4 /* MtGox.m in Sources */, AA435C0E13A2CB550050F307 /* Dropdown.m in Sources */, - AAA19944152969E000025EA2 /* AFHTTPClient.m in Sources */, - AAA19945152969E000025EA2 /* AFHTTPRequestOperation.m in Sources */, - AAA19946152969E000025EA2 /* AFImageCache.m in Sources */, - AAA19947152969E000025EA2 /* AFImageRequestOperation.m in Sources */, - AAA19948152969E000025EA2 /* AFJSONRequestOperation.m in Sources */, - AAA19949152969E000025EA2 /* AFNetworkActivityIndicatorManager.m in Sources */, - AAA1994A152969E000025EA2 /* AFPropertyListRequestOperation.m in Sources */, - AAA1994B152969E000025EA2 /* AFURLConnectionOperation.m in Sources */, - AAA1994C152969E000025EA2 /* AFXMLRequestOperation.m in Sources */, + AAA32C8015CA3D0500C45B89 /* AFHTTPClient.m in Sources */, + AAA32C8115CA3D0500C45B89 /* AFHTTPRequestOperation.m in Sources */, + AAA32C8215CA3D0500C45B89 /* AFImageRequestOperation.m in Sources */, + AAA32C8315CA3D0500C45B89 /* AFJSONRequestOperation.m in Sources */, + AAA32C8415CA3D0500C45B89 /* AFJSONUtilities.m in Sources */, + AAA32C8515CA3D0500C45B89 /* AFNetworkActivityIndicatorManager.m in Sources */, + AAA32C8615CA3D0500C45B89 /* AFPropertyListRequestOperation.m in Sources */, + AAA32C8715CA3D0500C45B89 /* AFURLConnectionOperation.m in Sources */, + AAA32C8815CA3D0500C45B89 /* AFXMLRequestOperation.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -392,8 +387,8 @@ GCC_PREFIX_HEADER = "BitTicker/BitTicker-Prefix.pch"; HEADER_SEARCH_PATHS = "\"$(SRCROOT)/../../Libraries/CorePlot_1.0/Binaries/MacOS/CorePlot.framework/Headers/\""; INFOPLIST_FILE = "BitTicker/BitTicker-Info.plist"; + MACOSX_DEPLOYMENT_TARGET = 10.6.8; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE = ""; WRAPPER_EXTENSION = app; }; name = Debug; @@ -417,8 +412,8 @@ GCC_PREFIX_HEADER = "BitTicker/BitTicker-Prefix.pch"; HEADER_SEARCH_PATHS = "\"$(SRCROOT)/../../Libraries/CorePlot_1.0/Binaries/MacOS/CorePlot.framework/Headers/\""; INFOPLIST_FILE = "BitTicker/BitTicker-Info.plist"; + MACOSX_DEPLOYMENT_TARGET = 10.6.8; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE = ""; WRAPPER_EXTENSION = app; }; name = Release; diff --git a/BitTicker/Dropdown.h b/BitTicker/Dropdown.h index a13e73e..bc4b0a5 100755 --- a/BitTicker/Dropdown.h +++ b/BitTicker/Dropdown.h @@ -27,13 +27,13 @@ @property (copy) NSNumber *tickerValue; -@property (retain) NSString *high; -@property (retain) NSString *low; -@property (retain) NSString *vol; -@property (retain) NSString *buy; -@property (retain) NSString *sell; -@property (retain) NSString *last; - -@property (assign) IBOutlet NSView *dropdownView; +@property (strong) NSString *high; +@property (strong) NSString *low; +@property (strong) NSString *vol; +@property (strong) NSString *buy; +@property (strong) NSString *sell; +@property (strong) NSString *last; + +@property (strong) IBOutlet NSView *dropdownView; @end diff --git a/BitTicker/Dropdown.m b/BitTicker/Dropdown.m index ccac843..805d3ea 100755 --- a/BitTicker/Dropdown.m +++ b/BitTicker/Dropdown.m @@ -47,7 +47,7 @@ -(void)awakeFromNib { statusItemView = [[StatusItemView alloc] init]; statusItemView.statusItem = _statusItem; - [statusItemView setToolTip:NSLocalizedString(@"BitTicker", @"Status Item Tooltip")]; + [statusItemView setToolTip:@"BitTicker"]; [_statusItem setView:statusItemView]; @@ -64,21 +64,16 @@ -(void)awakeFromNib { } -(void)didReceiveTicker:(NSNotification *)notification { - //NSAssert([NSThread currentThread] != [NSThread mainThread],@"Running on main thread!"); + NSAssert([NSThread currentThread] == [NSThread mainThread],@"Not unning on main thread!"); NSLog(@"Dropdown got ticker"); NSDictionary *ticker = [[notification object] objectForKey:@"ticker"]; - - //dispatch_async(dispatch_get_main_queue(), ^{ - - [self setHigh:[currencyFormatter stringFromNumber:[ticker objectForKey:@"high"]]]; - [self setLow:[currencyFormatter stringFromNumber:[ticker objectForKey:@"low"]]]; - [self setBuy:[currencyFormatter stringFromNumber:[ticker objectForKey:@"buy"]]]; - [self setSell:[currencyFormatter stringFromNumber:[ticker objectForKey:@"sell"]]]; - [self setLast:[currencyFormatter stringFromNumber:[ticker objectForKey:@"last"]]]; - [self setVol:[volumeFormatter stringFromNumber:[ticker objectForKey:@"vol"]]]; - //}); - + self.high = [currencyFormatter stringFromNumber:[ticker objectForKey:@"high"]]; + self.low = [currencyFormatter stringFromNumber:[ticker objectForKey:@"low"]]; + self.buy = [currencyFormatter stringFromNumber:[ticker objectForKey:@"buy"]]; + self.sell = [currencyFormatter stringFromNumber:[ticker objectForKey:@"sell"]]; + self.last = [currencyFormatter stringFromNumber:[ticker objectForKey:@"last"]]; + self.vol = [volumeFormatter stringFromNumber:[ticker objectForKey:@"vol"]]; } diff --git a/BitTicker/GraphView.h b/BitTicker/GraphView.h index d13f640..e04d8c0 100755 --- a/BitTicker/GraphView.h +++ b/BitTicker/GraphView.h @@ -31,8 +31,8 @@ @property (nonatomic) NSInteger timeframe; -@property (retain) NSArray *graphSource; -@property (nonatomic, retain) CPTXYGraph *graph; +@property (strong) NSArray *graphSource; +@property (nonatomic, strong) CPTXYGraph *graph; @end diff --git a/BitTicker/MtGox.m b/BitTicker/MtGox.m index 1634fc1..1d1e355 100755 --- a/BitTicker/MtGox.m +++ b/BitTicker/MtGox.m @@ -44,7 +44,7 @@ - (void) loop { NSMutableDictionary *message = [NSMutableDictionary new]; NSDictionary *tickerDict = [JSON objectForKey:@"ticker"]; - // capped stack, new tickers pushed on top, store 1440 minutes of data + // capped stack, new tickers pushed on bottom, store 1440 minutes of data if ([history count] >= 1440) { [history removeLastObject]; } diff --git a/BitTicker/StatusItemView.h b/BitTicker/StatusItemView.h index 62caba6..3c86a04 100755 --- a/BitTicker/StatusItemView.h +++ b/BitTicker/StatusItemView.h @@ -20,10 +20,10 @@ NSNumberFormatter *currencyFormatter; } -@property (retain) NSStatusItem *statusItem; -@property (retain) NSNumber *tickerValue; -@property (retain) NSNumber *previousTickerValue; -@property (retain) NSTimer *colorTimer; +@property (strong) NSStatusItem *statusItem; +@property (strong) NSNumber *tickerValue; +@property (strong) NSNumber *previousTickerValue; +@property (strong) NSTimer *colorTimer; - (void)setTickerValue:(NSNumber *)value; diff --git a/CorePlot.framework/CorePlot b/CorePlot.framework/CorePlot new file mode 120000 index 0000000..3937904 --- /dev/null +++ b/CorePlot.framework/CorePlot @@ -0,0 +1 @@ +Versions/Current/CorePlot \ No newline at end of file diff --git a/CorePlot.framework/Headers b/CorePlot.framework/Headers new file mode 120000 index 0000000..a177d2a --- /dev/null +++ b/CorePlot.framework/Headers @@ -0,0 +1 @@ +Versions/Current/Headers \ No newline at end of file diff --git a/CorePlot.framework/PrivateHeaders b/CorePlot.framework/PrivateHeaders new file mode 120000 index 0000000..d8e5645 --- /dev/null +++ b/CorePlot.framework/PrivateHeaders @@ -0,0 +1 @@ +Versions/Current/PrivateHeaders \ No newline at end of file diff --git a/CorePlot.framework/Resources b/CorePlot.framework/Resources new file mode 120000 index 0000000..953ee36 --- /dev/null +++ b/CorePlot.framework/Resources @@ -0,0 +1 @@ +Versions/Current/Resources \ No newline at end of file diff --git a/CorePlot.framework/Versions/A/CorePlot b/CorePlot.framework/Versions/A/CorePlot new file mode 100755 index 0000000000000000000000000000000000000000..4ab7bf1a78c15388542857576847f9714c44fdb5 GIT binary patch literal 1422096 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�K3g6a!iDfcM`V~=rQbQ5n9EK8tf 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%0ABvcEcyEmY@eIld99<7=ThzM_}(f}Ybe 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
~oKmOlR~+<%@5)%DEu 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 Rx_vKFcNcrxVcL~a{|C!a@FxHO literal 0 HcmV?d00001 diff --git a/CorePlot.framework/Versions/A/Headers/CPTAnnotation.h b/CorePlot.framework/Versions/A/Headers/CPTAnnotation.h new file mode 100644 index 0000000..d6ae9b4 --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTAnnotation.h @@ -0,0 +1,34 @@ +#import "CPTDefinitions.h" +#import +#import + +@class CPTAnnotationHostLayer; +@class CPTLayer; + +@interface CPTAnnotation : NSObject { + @private + __cpt_weak CPTAnnotationHostLayer *annotationHostLayer; + CPTLayer *contentLayer; + CGPoint contentAnchorPoint; + CGPoint displacement; + CGFloat rotation; +} + +@property (nonatomic, readwrite, retain) CPTLayer *contentLayer; +@property (nonatomic, readwrite, cpt_weak_property) __cpt_weak CPTAnnotationHostLayer *annotationHostLayer; +@property (nonatomic, readwrite, assign) CGPoint contentAnchorPoint; +@property (nonatomic, readwrite, assign) CGPoint displacement; +@property (nonatomic, readwrite, assign) CGFloat rotation; + +@end + +#pragma mark - + +/** @category CPTAnnotation(AbstractMethods) + * @brief CPTAnnotation abstract methods—must be overridden by subclasses. + **/ +@interface CPTAnnotation(AbstractMethods) + +-(void)positionContentLayer; + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTAnnotationHostLayer.h b/CorePlot.framework/Versions/A/Headers/CPTAnnotationHostLayer.h new file mode 100644 index 0000000..841523d --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTAnnotationHostLayer.h @@ -0,0 +1,19 @@ +#import "CPTLayer.h" + +@class CPTAnnotation; + +@interface CPTAnnotationHostLayer : CPTLayer { + @private + NSMutableArray *mutableAnnotations; +} + +@property (nonatomic, readonly, retain) NSArray *annotations; + +/// @name Annotations +/// @{ +-(void)addAnnotation:(CPTAnnotation *)annotation; +-(void)removeAnnotation:(CPTAnnotation *)annotation; +-(void)removeAllAnnotations; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTAxis.h b/CorePlot.framework/Versions/A/Headers/CPTAxis.h new file mode 100644 index 0000000..fa1409f --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTAxis.h @@ -0,0 +1,265 @@ +#import "CPTDefinitions.h" +#import "CPTLayer.h" +#import "CPTTextStyle.h" +#import + +/// @file + +@class CPTAxis; +@class CPTAxisSet; +@class CPTAxisTitle; +@class CPTGridLines; +@class CPTLimitBand; +@class CPTLineCap; +@class CPTLineStyle; +@class CPTPlotSpace; +@class CPTPlotRange; +@class CPTPlotArea; +@class CPTShadow; + +/** + * @brief Enumeration of labeling policies + **/ +typedef enum _CPTAxisLabelingPolicy { + CPTAxisLabelingPolicyNone, ///< No labels provided; user sets labels and tick locations. + CPTAxisLabelingPolicyLocationsProvided, ///< User sets tick locations; axis makes labels. + CPTAxisLabelingPolicyFixedInterval, ///< Fixed interval labeling policy. + CPTAxisLabelingPolicyAutomatic, ///< Automatic labeling policy. + CPTAxisLabelingPolicyEqualDivisions ///< Divide the plot range into equal parts. +} +CPTAxisLabelingPolicy; + +#pragma mark - + +/** + * @brief Axis labeling delegate. + **/ +@protocol CPTAxisDelegate + +@optional + +/// @name Labels +/// @{ + +/** @brief (Optional) Determines if the axis should relabel itself now. + * @param axis The axis. + * @return YES if the axis should relabel now. + **/ +-(BOOL)axisShouldRelabel:(CPTAxis *)axis; + +/** @brief (Optional) The method is called after the axis is relabeled to allow the delegate to perform any + * necessary cleanup or further labeling actions. + * @param axis The axis. + **/ +-(void)axisDidRelabel:(CPTAxis *)axis; + +/** @brief (Optional) This method gives the delegate a chance to create custom labels for each tick. + * It can be used with any labeling policy. Returning NO will cause the axis not + * to update the labels. It is then the delegates responsiblity to do this. + * @param axis The axis. + * @param locations The locations of the major ticks. + * @return YES if the axis class should proceed with automatic labeling. + **/ +-(BOOL)axis:(CPTAxis *)axis shouldUpdateAxisLabelsAtLocations:(NSSet *)locations; + +/** @brief (Optional) This method gives the delegate a chance to create custom labels for each minor tick. + * It can be used with any labeling policy. Returning NO will cause the axis not + * to update the labels. It is then the delegates responsiblity to do this. + * @param axis The axis. + * @param locations The locations of the minor ticks. + * @return YES if the axis class should proceed with automatic labeling. + **/ +-(BOOL)axis:(CPTAxis *)axis shouldUpdateMinorAxisLabelsAtLocations:(NSSet *)locations; + +/// @} + +@end + +#pragma mark - + +@interface CPTAxis : CPTLayer { + @private + CPTCoordinate coordinate; + CPTPlotSpace *plotSpace; + NSSet *majorTickLocations; + NSSet *minorTickLocations; + CGFloat majorTickLength; + CGFloat minorTickLength; + CGFloat labelOffset; + CGFloat minorTickLabelOffset; + CGFloat labelRotation; + CGFloat minorTickLabelRotation; + CPTAlignment labelAlignment; + CPTAlignment minorTickLabelAlignment; + CPTLineStyle *axisLineStyle; + CPTLineStyle *majorTickLineStyle; + CPTLineStyle *minorTickLineStyle; + CPTLineStyle *majorGridLineStyle; + CPTLineStyle *minorGridLineStyle; + CPTLineCap *axisLineCapMin; + CPTLineCap *axisLineCapMax; + NSDecimal labelingOrigin; + NSDecimal majorIntervalLength; + NSUInteger minorTicksPerInterval; + NSUInteger preferredNumberOfMajorTicks; + CPTAxisLabelingPolicy labelingPolicy; + CPTTextStyle *labelTextStyle; + CPTTextStyle *minorTickLabelTextStyle; + CPTTextStyle *titleTextStyle; + NSNumberFormatter *labelFormatter; + NSNumberFormatter *minorTickLabelFormatter; + BOOL labelFormatterChanged; + BOOL minorLabelFormatterChanged; + NSSet *axisLabels; + NSSet *minorTickAxisLabels; + CPTAxisTitle *axisTitle; + NSString *title; + CGFloat titleOffset; + CGFloat titleRotation; + NSDecimal titleLocation; + CPTSign tickDirection; + BOOL needsRelabel; + NSArray *labelExclusionRanges; + CPTPlotRange *visibleRange; + CPTPlotRange *gridLinesRange; + NSArray *alternatingBandFills; + NSMutableArray *mutableBackgroundLimitBands; + BOOL separateLayers; + CPTShadow *labelShadow; + __cpt_weak CPTPlotArea *plotArea; + __cpt_weak CPTGridLines *minorGridLines; + __cpt_weak CPTGridLines *majorGridLines; +} + +/// @name Axis +/// @{ +@property (nonatomic, readwrite, copy) CPTLineStyle *axisLineStyle; +@property (nonatomic, readwrite, assign) CPTCoordinate coordinate; +@property (nonatomic, readwrite, assign) NSDecimal labelingOrigin; +@property (nonatomic, readwrite, assign) CPTSign tickDirection; +@property (nonatomic, readwrite, copy) CPTPlotRange *visibleRange; +@property (nonatomic, readwrite, copy) CPTLineCap *axisLineCapMin; +@property (nonatomic, readwrite, copy) CPTLineCap *axisLineCapMax; +/// @} + +/// @name Title +/// @{ +@property (nonatomic, readwrite, copy) CPTTextStyle *titleTextStyle; +@property (nonatomic, readwrite, retain) CPTAxisTitle *axisTitle; +@property (nonatomic, readwrite, assign) CGFloat titleOffset; +@property (nonatomic, readwrite, copy) NSString *title; +@property (nonatomic, readwrite, assign) CGFloat titleRotation; +@property (nonatomic, readwrite, assign) NSDecimal titleLocation; +@property (nonatomic, readonly, assign) NSDecimal defaultTitleLocation; +/// @} + +/// @name Labels +/// @{ +@property (nonatomic, readwrite, assign) CPTAxisLabelingPolicy labelingPolicy; +@property (nonatomic, readwrite, assign) CGFloat labelOffset; +@property (nonatomic, readwrite, assign) CGFloat minorTickLabelOffset; +@property (nonatomic, readwrite, assign) CGFloat labelRotation; +@property (nonatomic, readwrite, assign) CGFloat minorTickLabelRotation; +@property (nonatomic, readwrite, assign) CPTAlignment labelAlignment; +@property (nonatomic, readwrite, assign) CPTAlignment minorTickLabelAlignment; +@property (nonatomic, readwrite, copy) CPTTextStyle *labelTextStyle; +@property (nonatomic, readwrite, copy) CPTTextStyle *minorTickLabelTextStyle; +@property (nonatomic, readwrite, retain) NSNumberFormatter *labelFormatter; +@property (nonatomic, readwrite, retain) NSNumberFormatter *minorTickLabelFormatter; +@property (nonatomic, readwrite, retain) NSSet *axisLabels; +@property (nonatomic, readwrite, retain) NSSet *minorTickAxisLabels; +@property (nonatomic, readonly, assign) BOOL needsRelabel; +@property (nonatomic, readwrite, retain) NSArray *labelExclusionRanges; +@property (nonatomic, readwrite, retain) CPTShadow *labelShadow; +/// @} + +/// @name Major Ticks +/// @{ +@property (nonatomic, readwrite, assign) NSDecimal majorIntervalLength; +@property (nonatomic, readwrite, assign) CGFloat majorTickLength; +@property (nonatomic, readwrite, copy) CPTLineStyle *majorTickLineStyle; +@property (nonatomic, readwrite, retain) NSSet *majorTickLocations; +@property (nonatomic, readwrite, assign) NSUInteger preferredNumberOfMajorTicks; +/// @} + +/// @name Minor Ticks +/// @{ +@property (nonatomic, readwrite, assign) NSUInteger minorTicksPerInterval; +@property (nonatomic, readwrite, assign) CGFloat minorTickLength; +@property (nonatomic, readwrite, copy) CPTLineStyle *minorTickLineStyle; +@property (nonatomic, readwrite, retain) NSSet *minorTickLocations; +/// @} + +/// @name Grid Lines +/// @{ +@property (nonatomic, readwrite, copy) CPTLineStyle *majorGridLineStyle; +@property (nonatomic, readwrite, copy) CPTLineStyle *minorGridLineStyle; +@property (nonatomic, readwrite, copy) CPTPlotRange *gridLinesRange; +/// @} + +/// @name Background Bands +/// @{ +@property (nonatomic, readwrite, copy) NSArray *alternatingBandFills; +@property (nonatomic, readonly, retain) NSArray *backgroundLimitBands; +/// @} + +/// @name Plot Space +/// @{ +@property (nonatomic, readwrite, retain) CPTPlotSpace *plotSpace; +/// @} + +/// @name Layers +/// @{ +@property (nonatomic, readwrite, assign) BOOL separateLayers; +@property (nonatomic, readwrite, cpt_weak_property) __cpt_weak CPTPlotArea *plotArea; +@property (nonatomic, readonly, cpt_weak_property) __cpt_weak CPTGridLines *minorGridLines; +@property (nonatomic, readonly, cpt_weak_property) __cpt_weak CPTGridLines *majorGridLines; +@property (nonatomic, readonly, retain) CPTAxisSet *axisSet; +/// @} + +/// @name Labels +/// @{ +-(void)relabel; +-(void)setNeedsRelabel; +-(void)updateMajorTickLabels; +-(void)updateMinorTickLabels; +/// @} + +/// @name Ticks +/// @{ +-(NSSet *)filteredMajorTickLocations:(NSSet *)allLocations; +-(NSSet *)filteredMinorTickLocations:(NSSet *)allLocations; +/// @} + +/// @name Background Bands +/// @{ +-(void)addBackgroundLimitBand:(CPTLimitBand *)limitBand; +-(void)removeBackgroundLimitBand:(CPTLimitBand *)limitBand; +/// @} + +@end + +#pragma mark - + +/** @category CPTAxis(AbstractMethods) + * @brief CPTAxis abstract methods—must be overridden by subclasses + **/ +@interface CPTAxis(AbstractMethods) + +/// @name Coordinate Space Conversions +/// @{ +-(CGPoint)viewPointForCoordinateDecimalNumber:(NSDecimal)coordinateDecimalNumber; +/// @} + +/// @name Grid Lines +/// @{ +-(void)drawGridLinesInContext:(CGContextRef)context isMajor:(BOOL)major; +/// @} + +/// @name Background Bands +/// @{ +-(void)drawBackgroundBandsInContext:(CGContextRef)context; +-(void)drawBackgroundLimitsInContext:(CGContextRef)context; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTAxisLabel.h b/CorePlot.framework/Versions/A/Headers/CPTAxisLabel.h new file mode 100644 index 0000000..9c52b0c --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTAxisLabel.h @@ -0,0 +1,34 @@ +#import "CPTDefinitions.h" +#import + +@class CPTLayer; +@class CPTTextStyle; + +@interface CPTAxisLabel : NSObject { + @private + CPTLayer *contentLayer; + CGFloat offset; + CGFloat rotation; + CPTAlignment alignment; + NSDecimal tickLocation; +} + +@property (nonatomic, readwrite, retain) CPTLayer *contentLayer; +@property (nonatomic, readwrite, assign) CGFloat offset; +@property (nonatomic, readwrite, assign) CGFloat rotation; +@property (nonatomic, readwrite, assign) CPTAlignment alignment; +@property (nonatomic, readwrite) NSDecimal tickLocation; + +/// @name Initialization +/// @{ +-(id)initWithText:(NSString *)newText textStyle:(CPTTextStyle *)style; +-(id)initWithContentLayer:(CPTLayer *)layer; +/// @} + +/// @name Layout +/// @{ +-(void)positionRelativeToViewPoint:(CGPoint)point forCoordinate:(CPTCoordinate)coordinate inDirection:(CPTSign)direction; +-(void)positionBetweenViewPoint:(CGPoint)firstPoint andViewPoint:(CGPoint)secondPoint forCoordinate:(CPTCoordinate)coordinate inDirection:(CPTSign)direction; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTAxisSet.h b/CorePlot.framework/Versions/A/Headers/CPTAxisSet.h new file mode 100644 index 0000000..57d590b --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTAxisSet.h @@ -0,0 +1,20 @@ +#import "CPTLayer.h" +#import + +@class CPTLineStyle; + +@interface CPTAxisSet : CPTLayer { + @private + NSArray *axes; + CPTLineStyle *borderLineStyle; +} + +@property (nonatomic, readwrite, retain) NSArray *axes; +@property (nonatomic, readwrite, copy) CPTLineStyle *borderLineStyle; + +/// @name Labels +/// @{ +-(void)relabelAxes; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTAxisTitle.h b/CorePlot.framework/Versions/A/Headers/CPTAxisTitle.h new file mode 100644 index 0000000..81370b6 --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTAxisTitle.h @@ -0,0 +1,7 @@ +#import "CPTAxisLabel.h" +#import + +@interface CPTAxisTitle : CPTAxisLabel { +} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTBarPlot.h b/CorePlot.framework/Versions/A/Headers/CPTBarPlot.h new file mode 100644 index 0000000..b5bca37 --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTBarPlot.h @@ -0,0 +1,134 @@ +#import "CPTDefinitions.h" +#import "CPTPlot.h" +#import + +/// @file + +@class CPTLineStyle; +@class CPTMutableNumericData; +@class CPTNumericData; +@class CPTFill; +@class CPTPlotRange; +@class CPTColor; +@class CPTBarPlot; +@class CPTTextLayer; +@class CPTTextStyle; + +/// @ingroup plotBindingsBarPlot +/// @{ +extern NSString *const CPTBarPlotBindingBarLocations; +extern NSString *const CPTBarPlotBindingBarTips; +extern NSString *const CPTBarPlotBindingBarBases; +/// @} + +/** + * @brief Enumeration of bar plot data source field types + **/ +typedef enum _CPTBarPlotField { + CPTBarPlotFieldBarLocation, ///< Bar location on independent coordinate axis. + CPTBarPlotFieldBarTip, ///< Bar tip value. + CPTBarPlotFieldBarBase ///< Bar base (used only if @link CPTBarPlot::barBasesVary barBasesVary @endlink is YES). +} +CPTBarPlotField; + +#pragma mark - + +/** + * @brief A bar plot data source. + **/ +@protocol CPTBarPlotDataSource +@optional + +/// @name Bar Style +/// @{ + +/** @brief (Optional) Gets a bar fill for the given bar plot. + * @param barPlot The bar plot. + * @param index The data index of interest. + * @return The bar fill for the bar with the given index. If the data source returns nil, the default fill is used. + * If the data source returns an NSNull object, no fill is drawn. + **/ +-(CPTFill *)barFillForBarPlot:(CPTBarPlot *)barPlot recordIndex:(NSUInteger)index; + +/** @brief (Optional) Gets a bar line style for the given bar plot. + * @param barPlot The bar plot. + * @param index The data index of interest. + * @return The bar line style for the bar with the given index. If the data source returns nil, the default line style is used. + * If the data source returns an NSNull object, no line is drawn. + **/ +-(CPTLineStyle *)barLineStyleForBarPlot:(CPTBarPlot *)barPlot recordIndex:(NSUInteger)index; + +/// @} + +/// @name Legends +/// @{ + +/** @brief (Optional) Gets the legend title for the given bar plot bar. + * @param barPlot The bar plot. + * @param index The data index of interest. + * @return The title text for the legend entry for the point with the given index. + **/ +-(NSString *)legendTitleForBarPlot:(CPTBarPlot *)barPlot recordIndex:(NSUInteger)index; + +/// @} +@end + +#pragma mark - + +/** + * @brief Bar plot delegate. + **/ +@protocol CPTBarPlotDelegate + +@optional + +/// @name Point Selection +/// @{ + +/** @brief (Optional) Informs delegate that a point was touched. + * @param plot The scatter plot. + * @param index Index of touched point + **/ +-(void)barPlot:(CPTBarPlot *)plot barWasSelectedAtRecordIndex:(NSUInteger)index; + +/// @} + +@end + +#pragma mark - + +@interface CPTBarPlot : CPTPlot { + @private + CPTLineStyle *lineStyle; + CPTFill *fill; + NSDecimal barWidth; + CGFloat barWidthScale; + NSDecimal barOffset; + CGFloat barOffsetScale; + CGFloat barCornerRadius; + NSDecimal baseValue; + BOOL barsAreHorizontal; + BOOL barBasesVary; + BOOL barWidthsAreInViewCoordinates; + CPTPlotRange *plotRange; +} + +@property (nonatomic, readwrite, assign) BOOL barWidthsAreInViewCoordinates; +@property (nonatomic, readwrite, assign) NSDecimal barWidth; +@property (nonatomic, readwrite, assign) CGFloat barWidthScale; +@property (nonatomic, readwrite, assign) NSDecimal barOffset; +@property (nonatomic, readwrite, assign) CGFloat barOffsetScale; +@property (nonatomic, readwrite, assign) CGFloat barCornerRadius; +@property (nonatomic, readwrite, copy) CPTLineStyle *lineStyle; +@property (nonatomic, readwrite, copy) CPTFill *fill; +@property (nonatomic, readwrite, assign) BOOL barsAreHorizontal; +@property (nonatomic, readwrite, assign) NSDecimal baseValue; +@property (nonatomic, readwrite, assign) BOOL barBasesVary; +@property (nonatomic, readwrite, copy) CPTPlotRange *plotRange; + +/// @name Factory Methods +/// @{ ++(CPTBarPlot *)tubularBarPlotWithColor:(CPTColor *)color horizontalBars:(BOOL)horizontal; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTBorderedLayer.h b/CorePlot.framework/Versions/A/Headers/CPTBorderedLayer.h new file mode 100644 index 0000000..8b0ad67 --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTBorderedLayer.h @@ -0,0 +1,16 @@ +#import "CPTAnnotationHostLayer.h" +#import + +@class CPTLineStyle; +@class CPTFill; + +@interface CPTBorderedLayer : CPTAnnotationHostLayer { + @private + CPTLineStyle *borderLineStyle; + CPTFill *fill; +} + +@property (nonatomic, readwrite, copy) CPTLineStyle *borderLineStyle; +@property (nonatomic, readwrite, copy) CPTFill *fill; + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTColor.h b/CorePlot.framework/Versions/A/Headers/CPTColor.h new file mode 100644 index 0000000..db93fb7 --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTColor.h @@ -0,0 +1,42 @@ +#import +#import + +@interface CPTColor : NSObject { + @private + CGColorRef cgColor; +} + +@property (nonatomic, readonly, assign) CGColorRef cgColor; + +/// @name Factory Methods +/// @{ ++(CPTColor *)clearColor; ++(CPTColor *)whiteColor; ++(CPTColor *)lightGrayColor; ++(CPTColor *)grayColor; ++(CPTColor *)darkGrayColor; ++(CPTColor *)blackColor; ++(CPTColor *)redColor; ++(CPTColor *)greenColor; ++(CPTColor *)blueColor; ++(CPTColor *)cyanColor; ++(CPTColor *)yellowColor; ++(CPTColor *)magentaColor; ++(CPTColor *)orangeColor; ++(CPTColor *)purpleColor; ++(CPTColor *)brownColor; + ++(CPTColor *)colorWithCGColor:(CGColorRef)newCGColor; ++(CPTColor *)colorWithComponentRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha; ++(CPTColor *)colorWithGenericGray:(CGFloat)gray; +/// @} + +/// @name Initialization +/// @{ +-(id)initWithCGColor:(CGColorRef)cgColor; +-(id)initWithComponentRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha; + +-(CPTColor *)colorWithAlphaComponent:(CGFloat)alpha; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTColorSpace.h b/CorePlot.framework/Versions/A/Headers/CPTColorSpace.h new file mode 100644 index 0000000..ac89b30 --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTColorSpace.h @@ -0,0 +1,21 @@ +#import +#import + +@interface CPTColorSpace : NSObject { + @private + CGColorSpaceRef cgColorSpace; +} + +@property (nonatomic, readonly, assign) CGColorSpaceRef cgColorSpace; + +/// @name Factory Methods +/// @{ ++(CPTColorSpace *)genericRGBSpace; +/// @} + +/// @name Initialization +/// @{ +-(id)initWithCGColorSpace:(CGColorSpaceRef)colorSpace; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTConstraints.h b/CorePlot.framework/Versions/A/Headers/CPTConstraints.h new file mode 100644 index 0000000..e6885d7 --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTConstraints.h @@ -0,0 +1,38 @@ +#import +#import + +@interface CPTConstraints : NSObject { +} + +/// @name Factory Methods +/// @{ ++(CPTConstraints *)constraintWithLowerOffset:(CGFloat)newOffset; ++(CPTConstraints *)constraintWithUpperOffset:(CGFloat)newOffset; ++(CPTConstraints *)constraintWithRelativeOffset:(CGFloat)newOffset; +/// @} + +/// @name Initialization +/// @{ +-(id)initWithLowerOffset:(CGFloat)newOffset; +-(id)initWithUpperOffset:(CGFloat)newOffset; +-(id)initWithRelativeOffset:(CGFloat)newOffset; +/// @} + +@end + +/** @category CPTConstraints(AbstractMethods) + * @brief CPTConstraints abstract methods—must be overridden by subclasses + **/ +@interface CPTConstraints(AbstractMethods) + +/// @name Comparison +/// @{ +-(BOOL)isEqualToConstraint:(CPTConstraints *)otherConstraint; +/// @} + +/// @name Position +/// @{ +-(CGFloat)positionForLowerBound:(CGFloat)lowerBound upperBound:(CGFloat)upperBound; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTDecimalNumberValueTransformer.h b/CorePlot.framework/Versions/A/Headers/CPTDecimalNumberValueTransformer.h new file mode 100644 index 0000000..8d69f95 --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTDecimalNumberValueTransformer.h @@ -0,0 +1,6 @@ +#import + +@interface CPTDecimalNumberValueTransformer : NSValueTransformer { +} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTDefinitions.h b/CorePlot.framework/Versions/A/Headers/CPTDefinitions.h new file mode 100644 index 0000000..3881d50 --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTDefinitions.h @@ -0,0 +1,136 @@ +#import +#import + +#import +#import + +/// @file + +/** + * @def CPT_SDK_SUPPORTS_WEAK + * @brief Defined as 1 if the compiler and active SDK support weak references, 0 otherwise. + **/ + +/** + * @def __cpt_weak + * @brief A custom definition for ARC weak references that falls back to unsafe unretained values on older platforms. + **/ + +/** + * @def cpt_weak_property + * @brief A custom definition for ARC weak properties that falls back to assign on older platforms. + **/ + +// This is based on Ryan Petrich's ZWRCompatibility: https://github.com/rpetrich/ZWRCompatibility + +#if TARGET_OS_IPHONE && defined(__IPHONE_5_0) && (__IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_5_0) && __clang__ && (__clang_major__ >= 3) +#define CPT_SDK_SUPPORTS_WEAK 1 +#elif TARGET_OS_MAC && defined(__MAC_10_7) && (MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_7) && __clang__ && (__clang_major__ >= 3) +#define CPT_SDK_SUPPORTS_WEAK 1 +#else +#define CPT_SDK_SUPPORTS_WEAK 0 +#endif + +#if CPT_SDK_SUPPORTS_WEAK +#define __cpt_weak __weak +#define cpt_weak_property weak +#else +#if __clang__ && (__clang_major__ >= 3) +#define __cpt_weak __unsafe_unretained +#else +#define __cpt_weak +#endif +#define cpt_weak_property assign +#endif + +/** + * @brief Enumeration of numeric types + **/ +typedef enum _CPTNumericType { + CPTNumericTypeInteger, ///< Integer + CPTNumericTypeFloat, ///< Float + CPTNumericTypeDouble ///< Double +} +CPTNumericType; + +/** + * @brief Enumeration of error bar types + **/ +typedef enum _CPTErrorBarType { + CPTErrorBarTypeCustom, ///< Custom error bars + CPTErrorBarTypeConstantRatio, ///< Constant ratio error bars + CPTErrorBarTypeConstantValue ///< Constant value error bars +} +CPTErrorBarType; + +/** + * @brief Enumeration of axis scale types + **/ +typedef enum _CPTScaleType { + CPTScaleTypeLinear, ///< Linear axis scale + CPTScaleTypeLog, ///< Logarithmic axis scale + CPTScaleTypeAngular, ///< Angular axis scale (not implemented) + CPTScaleTypeDateTime, ///< Date/time axis scale (not implemented) + CPTScaleTypeCategory ///< Category axis scale (not implemented) +} +CPTScaleType; + +/** + * @brief Enumeration of axis coordinates + **/ +typedef enum _CPTCoordinate { + CPTCoordinateX = 0, ///< X axis + CPTCoordinateY = 1, ///< Y axis + CPTCoordinateZ = 2 ///< Z axis +} +CPTCoordinate; + +/** + * @brief RGBA color for gradients + **/ +typedef struct _CPTRGBAColor { + CGFloat red; ///< The red component (0 ≤ red ≤ 1). + CGFloat green; ///< The green component (0 ≤ green ≤ 1). + CGFloat blue; ///< The blue component (0 ≤ blue ≤ 1). + CGFloat alpha; ///< The alpha component (0 ≤ alpha ≤ 1). +} +CPTRGBAColor; + +/** + * @brief Enumeration of label positioning offset directions + **/ +typedef enum _CPTSign { + CPTSignNone = 0, ///< No offset + CPTSignPositive = +1, ///< Positive offset + CPTSignNegative = -1 ///< Negative offset +} +CPTSign; + +/** + * @brief Locations around the edge of a rectangle. + **/ +typedef enum _CPTRectAnchor { + CPTRectAnchorBottomLeft, ///< The bottom left corner + CPTRectAnchorBottom, ///< The bottom center + CPTRectAnchorBottomRight, ///< The bottom right corner + CPTRectAnchorLeft, ///< The left middle + CPTRectAnchorRight, ///< The right middle + CPTRectAnchorTopLeft, ///< The top left corner + CPTRectAnchorTop, ///< The top center + CPTRectAnchorTopRight, ///< The top right + CPTRectAnchorCenter ///< The center of the rect +} +CPTRectAnchor; + +/** + * @brief Label and constraint alignment constants. + **/ +typedef enum _CPTAlignment { + CPTAlignmentLeft, ///< Align horizontally to the left side. + CPTAlignmentCenter, ///< Align horizontally to the center. + CPTAlignmentRight, ///< Align horizontally to the right side. + CPTAlignmentTop, ///< Align vertically to the top. + CPTAlignmentMiddle, ///< Align vertically to the middle. + CPTAlignmentBottom ///< Align vertically to the bottom. +} +CPTAlignment; diff --git a/CorePlot.framework/Versions/A/Headers/CPTExceptions.h b/CorePlot.framework/Versions/A/Headers/CPTExceptions.h new file mode 100644 index 0000000..7d262d4 --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTExceptions.h @@ -0,0 +1,10 @@ +#import + +/// @file + +/// @name Custom Exception Identifiers +/// @{ +extern NSString *const CPTException; +extern NSString *const CPTDataException; +extern NSString *const CPTNumericDataException; +/// @} diff --git a/CorePlot.framework/Versions/A/Headers/CPTFill.h b/CorePlot.framework/Versions/A/Headers/CPTFill.h new file mode 100644 index 0000000..01ddbe7 --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTFill.h @@ -0,0 +1,38 @@ +#import +#import + +@class CPTGradient; +@class CPTImage; +@class CPTColor; + +@interface CPTFill : NSObject { +} + +/// @name Factory Methods +/// @{ ++(CPTFill *)fillWithColor:(CPTColor *)aColor; ++(CPTFill *)fillWithGradient:(CPTGradient *)aGradient; ++(CPTFill *)fillWithImage:(CPTImage *)anImage; +/// @} + +/// @name Initialization +/// @{ +-(id)initWithColor:(CPTColor *)aColor; +-(id)initWithGradient:(CPTGradient *)aGradient; +-(id)initWithImage:(CPTImage *)anImage; +/// @} + +@end + +/** @category CPTFill(AbstractMethods) + * @brief CPTFill abstract methods—must be overridden by subclasses + **/ +@interface CPTFill(AbstractMethods) + +/// @name Drawing +/// @{ +-(void)fillRect:(CGRect)theRect inContext:(CGContextRef)theContext; +-(void)fillPathInContext:(CGContextRef)theContext; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTGradient.h b/CorePlot.framework/Versions/A/Headers/CPTGradient.h new file mode 100644 index 0000000..981575d --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTGradient.h @@ -0,0 +1,100 @@ +// Based on CTGradient (http://blog.oofn.net/2006/01/15/gradients-in-cocoa/) +// CTGradient is in public domain (Thanks Chad Weider!) + +/// @file + +#import "CPTDefinitions.h" +#import + +/** + * @brief A structure representing one node in a linked list of RGBA colors. + **/ +typedef struct _CPTGradientElement { + CPTRGBAColor color; ///< Color + CGFloat position; ///< Gradient position (0 ≤ position ≤ 1) + + struct _CPTGradientElement *nextElement; ///< Pointer to the next CPTGradientElement in the list (last element == NULL) +} +CPTGradientElement; + +/** + * @brief Enumeration of blending modes + **/ +typedef enum _CPTBlendingMode { + CPTLinearBlendingMode, ///< Linear blending mode + CPTChromaticBlendingMode, ///< Chromatic blending mode + CPTInverseChromaticBlendingMode ///< Inverse chromatic blending mode +} +CPTGradientBlendingMode; + +/** + * @brief Enumeration of gradient types + **/ +typedef enum _CPTGradientType { + CPTGradientTypeAxial, ///< Axial gradient + CPTGradientTypeRadial ///< Radial gradient +} +CPTGradientType; + +@class CPTColorSpace; +@class CPTColor; + +@interface CPTGradient : NSObject { + @private + CPTColorSpace *colorspace; + CPTGradientElement *elementList; + CPTGradientBlendingMode blendingMode; + CGFunctionRef gradientFunction; + CGFloat angle; // angle in degrees + CPTGradientType gradientType; +} + +@property (nonatomic, readonly, assign) CPTGradientBlendingMode blendingMode; +@property (nonatomic, readwrite, assign) CGFloat angle; +@property (nonatomic, readwrite, assign) CPTGradientType gradientType; + +/// @name Factory Methods +/// @{ ++(CPTGradient *)gradientWithBeginningColor:(CPTColor *)begin endingColor:(CPTColor *)end; ++(CPTGradient *)gradientWithBeginningColor:(CPTColor *)begin endingColor:(CPTColor *)end beginningPosition:(CGFloat)beginningPosition endingPosition:(CGFloat)endingPosition; + ++(CPTGradient *)aquaSelectedGradient; ++(CPTGradient *)aquaNormalGradient; ++(CPTGradient *)aquaPressedGradient; + ++(CPTGradient *)unifiedSelectedGradient; ++(CPTGradient *)unifiedNormalGradient; ++(CPTGradient *)unifiedPressedGradient; ++(CPTGradient *)unifiedDarkGradient; + ++(CPTGradient *)sourceListSelectedGradient; ++(CPTGradient *)sourceListUnselectedGradient; + ++(CPTGradient *)rainbowGradient; ++(CPTGradient *)hydrogenSpectrumGradient; +/// @} + +/// @name Modification +/// @{ +-(CPTGradient *)gradientWithAlphaComponent:(CGFloat)alpha; +-(CPTGradient *)gradientWithBlendingMode:(CPTGradientBlendingMode)mode; + +-(CPTGradient *)addColorStop:(CPTColor *)color atPosition:(CGFloat)position; // positions given relative to [0,1] +-(CPTGradient *)removeColorStopAtIndex:(NSUInteger)index; +-(CPTGradient *)removeColorStopAtPosition:(CGFloat)position; +/// @} + +/// @name Information +/// @{ +-(CGColorRef)newColorStopAtIndex:(NSUInteger)index; +-(CGColorRef)newColorAtPosition:(CGFloat)position; +/// @} + +/// @name Drawing +/// @{ +-(void)drawSwatchInRect:(CGRect)rect inContext:(CGContextRef)context; +-(void)fillRect:(CGRect)rect inContext:(CGContextRef)context; +-(void)fillPathInContext:(CGContextRef)context; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTGraph.h b/CorePlot.framework/Versions/A/Headers/CPTGraph.h new file mode 100644 index 0000000..6d1a183 --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTGraph.h @@ -0,0 +1,136 @@ +// Abstract class +#import "CPTBorderedLayer.h" +#import "CPTDefinitions.h" + +/// @file + +@class CPTAxisSet; +@class CPTLegend; +@class CPTPlot; +@class CPTPlotAreaFrame; +@class CPTPlotSpace; +@class CPTTheme; +@class CPTTextStyle; +@class CPTLayerAnnotation; + +/// @name Graph +/// @{ + +/** @brief Notification sent by various objects to tell the graph it should redraw itself. + * @ingroup notification + **/ +extern NSString *const CPTGraphNeedsRedrawNotification; + +/// @} + +/** + * @brief Enumeration of graph layers. + **/ +typedef enum _CPTGraphLayerType { + CPTGraphLayerTypeMinorGridLines, ///< Minor grid lines. + CPTGraphLayerTypeMajorGridLines, ///< Major grid lines. + CPTGraphLayerTypeAxisLines, ///< Axis lines. + CPTGraphLayerTypePlots, ///< Plots. + CPTGraphLayerTypeAxisLabels, ///< Axis labels. + CPTGraphLayerTypeAxisTitles ///< Axis titles. +} +CPTGraphLayerType; + +#pragma mark - + +@interface CPTGraph : CPTBorderedLayer { + @private + CPTPlotAreaFrame *plotAreaFrame; + NSMutableArray *plots; + NSMutableArray *plotSpaces; + NSString *title; + CPTTextStyle *titleTextStyle; + CPTRectAnchor titlePlotAreaFrameAnchor; + CGPoint titleDisplacement; + CPTLayerAnnotation *titleAnnotation; + CPTLegend *legend; + CPTLayerAnnotation *legendAnnotation; + CPTRectAnchor legendAnchor; + CGPoint legendDisplacement; +} + +/// @name Title +/// @{ +@property (nonatomic, readwrite, copy) NSString *title; +@property (nonatomic, readwrite, copy) CPTTextStyle *titleTextStyle; +@property (nonatomic, readwrite, assign) CGPoint titleDisplacement; +@property (nonatomic, readwrite, assign) CPTRectAnchor titlePlotAreaFrameAnchor; +/// @} + +/// @name Layers +/// @{ +@property (nonatomic, readwrite, retain) CPTAxisSet *axisSet; +@property (nonatomic, readwrite, retain) CPTPlotAreaFrame *plotAreaFrame; +@property (nonatomic, readonly, retain) CPTPlotSpace *defaultPlotSpace; +@property (nonatomic, readwrite, retain) NSArray *topDownLayerOrder; +/// @} + +/// @name Legend +/// @{ +@property (nonatomic, readwrite, retain) CPTLegend *legend; +@property (nonatomic, readwrite, assign) CPTRectAnchor legendAnchor; +@property (nonatomic, readwrite, assign) CGPoint legendDisplacement; +/// @} + +/// @name Data Source +/// @{ +-(void)reloadData; +-(void)reloadDataIfNeeded; +/// @} + +/// @name Retrieving Plots +/// @{ +-(NSArray *)allPlots; +-(CPTPlot *)plotAtIndex:(NSUInteger)index; +-(CPTPlot *)plotWithIdentifier:(id)identifier; +/// @} + +/// @name Adding and Removing Plots +/// @{ +-(void)addPlot:(CPTPlot *)plot; +-(void)addPlot:(CPTPlot *)plot toPlotSpace:(CPTPlotSpace *)space; +-(void)removePlot:(CPTPlot *)plot; +-(void)removePlotWithIdentifier:(id)identifier; +-(void)insertPlot:(CPTPlot *)plot atIndex:(NSUInteger)index; +-(void)insertPlot:(CPTPlot *)plot atIndex:(NSUInteger)index intoPlotSpace:(CPTPlotSpace *)space; +/// @} + +/// @name Retrieving Plot Spaces +/// @{ +-(NSArray *)allPlotSpaces; +-(CPTPlotSpace *)plotSpaceAtIndex:(NSUInteger)index; +-(CPTPlotSpace *)plotSpaceWithIdentifier:(id)identifier; +/// @} + +/// @name Adding and Removing Plot Spaces +/// @{ +-(void)addPlotSpace:(CPTPlotSpace *)space; +-(void)removePlotSpace:(CPTPlotSpace *)plotSpace; +/// @} + +/// @name Themes +/// @{ +-(void)applyTheme:(CPTTheme *)theme; +/// @} + +@end + +#pragma mark - + +/** @category CPTGraph(AbstractFactoryMethods) + * @brief CPTGraph abstract methods—must be overridden by subclasses + **/ +@interface CPTGraph(AbstractFactoryMethods) + +/// @name Factory Methods +/// @{ +-(CPTPlotSpace *)newPlotSpace; +-(CPTAxisSet *)newAxisSet; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTGraphHostingView.h b/CorePlot.framework/Versions/A/Headers/CPTGraphHostingView.h new file mode 100644 index 0000000..869fa4b --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTGraphHostingView.h @@ -0,0 +1,12 @@ +#import + +@class CPTGraph; + +@interface CPTGraphHostingView : NSView { + @private + CPTGraph *hostedGraph; +} + +@property (nonatomic, readwrite, retain) CPTGraph *hostedGraph; + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTImage.h b/CorePlot.framework/Versions/A/Headers/CPTImage.h new file mode 100644 index 0000000..506d6a6 --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTImage.h @@ -0,0 +1,36 @@ +#import +#import + +@interface CPTImage : NSObject { + @private + CGImageRef image; + CGFloat scale; + BOOL tiled; + BOOL tileAnchoredToContext; +} + +@property (nonatomic, readwrite, assign) CGImageRef image; +@property (nonatomic, readwrite, assign) CGFloat scale; +@property (nonatomic, readwrite, assign, getter = isTiled) BOOL tiled; +@property (nonatomic, readwrite, assign) BOOL tileAnchoredToContext; + +/// @name Factory Methods +/// @{ ++(CPTImage *)imageWithCGImage:(CGImageRef)anImage scale:(CGFloat)newScale; ++(CPTImage *)imageWithCGImage:(CGImageRef)anImage; ++(CPTImage *)imageForPNGFile:(NSString *)path; +/// @} + +/// @name Initialization +/// @{ +-(id)initWithCGImage:(CGImageRef)anImage scale:(CGFloat)newScale; +-(id)initWithCGImage:(CGImageRef)anImage; +-(id)initForPNGFile:(NSString *)path; +/// @} + +/// @name Drawing +/// @{ +-(void)drawInRect:(CGRect)rect inContext:(CGContextRef)context; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTLayer.h b/CorePlot.framework/Versions/A/Headers/CPTLayer.h new file mode 100644 index 0000000..3d0d04f --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTLayer.h @@ -0,0 +1,109 @@ +#import "CPTDefinitions.h" +#import "CPTResponder.h" +#import +#import + +@class CPTGraph; +@class CPTShadow; + +@interface CPTLayer : CALayer { + @private + CGFloat paddingLeft; + CGFloat paddingTop; + CGFloat paddingRight; + CGFloat paddingBottom; + BOOL masksToBorder; + CPTShadow *shadow; + BOOL renderingRecursively; + BOOL useFastRendering; + __cpt_weak CPTGraph *graph; + CGPathRef outerBorderPath; + CGPathRef innerBorderPath; + id identifier; +} + +/// @name Graph +/// @{ +@property (nonatomic, readwrite, cpt_weak_property) __cpt_weak CPTGraph *graph; +/// @} + +/// @name Padding +/// @{ +@property (nonatomic, readwrite) CGFloat paddingLeft; +@property (nonatomic, readwrite) CGFloat paddingTop; +@property (nonatomic, readwrite) CGFloat paddingRight; +@property (nonatomic, readwrite) CGFloat paddingBottom; +/// @} + +/// @name Drawing +/// @{ +@property (readwrite, assign) CGFloat contentsScale; +@property (nonatomic, readonly, assign) BOOL useFastRendering; +@property (nonatomic, readwrite, copy) CPTShadow *shadow; +/// @} + +/// @name Masking +/// @{ +@property (nonatomic, readwrite, assign) BOOL masksToBorder; +@property (nonatomic, readwrite, assign) CGPathRef outerBorderPath; +@property (nonatomic, readwrite, assign) CGPathRef innerBorderPath; +@property (nonatomic, readonly, assign) CGPathRef maskingPath; +@property (nonatomic, readonly, assign) CGPathRef sublayerMaskingPath; +/// @} + +/// @name Identification +/// @{ +@property (nonatomic, readwrite, copy) id identifier; +/// @} + +/// @name Layout +/// @{ +@property (readonly) NSSet *sublayersExcludedFromAutomaticLayout; +/// @} + +/// @name Initialization +/// @{ +-(id)initWithFrame:(CGRect)newFrame; +/// @} + +/// @name Drawing +/// @{ +-(void)renderAsVectorInContext:(CGContextRef)context; +-(void)recursivelyRenderInContext:(CGContextRef)context; +-(void)layoutAndRenderInContext:(CGContextRef)context; +-(NSData *)dataForPDFRepresentationOfLayer; +/// @} + +/// @name Masking +/// @{ +-(void)applySublayerMaskToContext:(CGContextRef)context forSublayer:(CPTLayer *)sublayer withOffset:(CGPoint)offset; +-(void)applyMaskToContext:(CGContextRef)context; +/// @} + +/// @name Layout +/// @{ +-(void)pixelAlign; +-(void)sublayerMarginLeft:(CGFloat *)left top:(CGFloat *)top right:(CGFloat *)right bottom:(CGFloat *)bottom; +/// @} + +/// @name Information +/// @{ +-(void)logLayers; +/// @} + +@end + +/// @cond +// for MacOS 10.6 SDK compatibility +#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE +#else +#if MAC_OS_X_VERSION_MAX_ALLOWED < 1070 +@interface CALayer(CPTExtensions) + +@property (readwrite) CGFloat contentsScale; + +@end +#endif +#endif + +/// @endcond diff --git a/CorePlot.framework/Versions/A/Headers/CPTLayerAnnotation.h b/CorePlot.framework/Versions/A/Headers/CPTLayerAnnotation.h new file mode 100644 index 0000000..70d081e --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTLayerAnnotation.h @@ -0,0 +1,20 @@ +#import "CPTAnnotation.h" +#import "CPTDefinitions.h" +#import + +@class CPTConstraints; + +@interface CPTLayerAnnotation : CPTAnnotation { + @private + __cpt_weak CPTLayer *anchorLayer; + CPTConstraints *xConstraints; + CPTConstraints *yConstraints; + CPTRectAnchor rectAnchor; +} + +@property (nonatomic, readonly, cpt_weak_property) __cpt_weak CPTLayer *anchorLayer; +@property (nonatomic, readwrite, assign) CPTRectAnchor rectAnchor; + +-(id)initWithAnchorLayer:(CPTLayer *)anchorLayer; + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTLegend.h b/CorePlot.framework/Versions/A/Headers/CPTLegend.h new file mode 100644 index 0000000..fc73325 --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTLegend.h @@ -0,0 +1,135 @@ +#import "CPTBorderedLayer.h" +#import + +/// @file + +@class CPTLegend; +@class CPTPlot; +@class CPTTextStyle; + +/// @name Legend +/// @{ + +/** @brief Notification sent by plots to tell the legend it should redraw itself. + * @ingroup notification + **/ +extern NSString *const CPTLegendNeedsRedrawForPlotNotification; + +/** @brief Notification sent by plots to tell the legend it should update its layout and redraw itself. + * @ingroup notification + **/ +extern NSString *const CPTLegendNeedsLayoutForPlotNotification; + +/** @brief Notification sent by plots to tell the legend it should reload all legend entries. + * @ingroup notification + **/ +extern NSString *const CPTLegendNeedsReloadEntriesForPlotNotification; + +/// @} + +/** + * @brief Axis labeling delegate. + **/ +@protocol CPTLegendDelegate + +/// @name Drawing +/// @{ + +/** @brief (Required) This method gives the delegate a chance to draw custom swatches for each legend entry. + * + * The "swatch" is the graphical part of the legend entry, usually accompanied by a text title + * that will be drawn by the legend. Returning NO will cause the legend to not draw the default + * legend graphics. It is then the delegate's responsiblity to do this. + * @param legend The legend. + * @param index The zero-based index of the legend entry for the given plot. + * @param plot The plot. + * @param rect The bounding rectangle to use when drawing the swatch. + * @param context The graphics context to draw into. + * @return YES if the legend should draw the default swatch or NO if the delegate handled the drawing. + **/ +-(BOOL)legend:(CPTLegend *)legend shouldDrawSwatchAtIndex:(NSUInteger)index forPlot:(CPTPlot *)plot inRect:(CGRect)rect inContext:(CGContextRef)context; + +/// @} + +@end + +#pragma mark - + +@interface CPTLegend : CPTBorderedLayer { + @private + NSMutableArray *plots; + NSMutableArray *legendEntries; + BOOL layoutChanged; + CPTTextStyle *textStyle; + CGSize swatchSize; + CPTLineStyle *swatchBorderLineStyle; + CGFloat swatchCornerRadius; + CPTFill *swatchFill; + NSUInteger numberOfRows; + NSUInteger numberOfColumns; + BOOL equalRows; + BOOL equalColumns; + NSArray *rowHeights; + NSArray *rowHeightsThatFit; + NSArray *columnWidths; + NSArray *columnWidthsThatFit; + CGFloat columnMargin; + CGFloat rowMargin; + CGFloat titleOffset; +} + +/// @name Formatting +/// @{ +@property (nonatomic, readwrite, copy) CPTTextStyle *textStyle; +@property (nonatomic, readwrite, assign) CGSize swatchSize; +@property (nonatomic, readwrite, copy) CPTLineStyle *swatchBorderLineStyle; +@property (nonatomic, readwrite, assign) CGFloat swatchCornerRadius; +@property (nonatomic, readwrite, copy) CPTFill *swatchFill; +/// @} + +/// @name Layout +/// @{ +@property (nonatomic, readonly, assign) BOOL layoutChanged; +@property (nonatomic, readwrite, assign) NSUInteger numberOfRows; +@property (nonatomic, readwrite, assign) NSUInteger numberOfColumns; +@property (nonatomic, readwrite, assign) BOOL equalRows; +@property (nonatomic, readwrite, assign) BOOL equalColumns; +@property (nonatomic, readwrite, copy) NSArray *rowHeights; +@property (nonatomic, readonly, retain) NSArray *rowHeightsThatFit; +@property (nonatomic, readwrite, copy) NSArray *columnWidths; +@property (nonatomic, readonly, retain) NSArray *columnWidthsThatFit; +@property (nonatomic, readwrite, assign) CGFloat columnMargin; +@property (nonatomic, readwrite, assign) CGFloat rowMargin; +@property (nonatomic, readwrite, assign) CGFloat titleOffset; +/// @} + +/// @name Factory Methods +/// @{ ++(id)legendWithPlots:(NSArray *)newPlots; ++(id)legendWithGraph:(CPTGraph *)graph; +/// @} + +/// @name Initialization +/// @{ +-(id)initWithPlots:(NSArray *)newPlots; +-(id)initWithGraph:(CPTGraph *)graph; +/// @} + +/// @name Plots +/// @{ +-(NSArray *)allPlots; +-(CPTPlot *)plotAtIndex:(NSUInteger)index; +-(CPTPlot *)plotWithIdentifier:(id)identifier; + +-(void)addPlot:(CPTPlot *)plot; +-(void)insertPlot:(CPTPlot *)plot atIndex:(NSUInteger)index; +-(void)removePlot:(CPTPlot *)plot; +-(void)removePlotWithIdentifier:(id)identifier; +/// @} + +/// @name Layout +/// @{ +-(void)setLayoutChanged; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTLegendEntry.h b/CorePlot.framework/Versions/A/Headers/CPTLegendEntry.h new file mode 100644 index 0000000..e35a248 --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTLegendEntry.h @@ -0,0 +1,40 @@ +#import "CPTDefinitions.h" +#import +#import + +@class CPTPlot; +@class CPTTextStyle; + +@interface CPTLegendEntry : NSObject { + @private + __cpt_weak CPTPlot *plot; + NSUInteger index; + NSUInteger row; + NSUInteger column; + CPTTextStyle *textStyle; +} + +/// @name Plot Info +/// @{ +@property (nonatomic, readwrite, cpt_weak_property) __cpt_weak CPTPlot *plot; +@property (nonatomic, readwrite, assign) NSUInteger index; +/// @} + +/// @name Formatting +/// @{ +@property (nonatomic, readwrite, retain) CPTTextStyle *textStyle; +/// @} + +/// @name Layout +/// @{ +@property (nonatomic, readwrite, assign) NSUInteger row; +@property (nonatomic, readwrite, assign) NSUInteger column; +@property (nonatomic, readonly, assign) CGSize titleSize; +/// @} + +/// @name Drawing +/// @{ +-(void)drawTitleInRect:(CGRect)rect inContext:(CGContextRef)context scale:(CGFloat)scale; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTLimitBand.h b/CorePlot.framework/Versions/A/Headers/CPTLimitBand.h new file mode 100644 index 0000000..f932e9a --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTLimitBand.h @@ -0,0 +1,25 @@ +#import + +@class CPTPlotRange; +@class CPTFill; + +@interface CPTLimitBand : NSObject { + @private + CPTPlotRange *range; + CPTFill *fill; +} + +@property (nonatomic, readwrite, retain) CPTPlotRange *range; +@property (nonatomic, readwrite, retain) CPTFill *fill; + +/// @name Factory Methods +/// @{ ++(CPTLimitBand *)limitBandWithRange:(CPTPlotRange *)newRange fill:(CPTFill *)newFill; +/// @} + +/// @name Initialization +/// @{ +-(id)initWithRange:(CPTPlotRange *)newRange fill:(CPTFill *)newFill; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTLineCap.h b/CorePlot.framework/Versions/A/Headers/CPTLineCap.h new file mode 100644 index 0000000..037f133 --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTLineCap.h @@ -0,0 +1,69 @@ +#import +#import + +/// @file + +@class CPTLineStyle; +@class CPTFill; + +/** + * @brief Line cap types. + **/ +typedef enum _CPTLineCapType { + CPTLineCapTypeNone, ///< No line cap. + CPTLineCapTypeOpenArrow, ///< Open arrow line cap. + CPTLineCapTypeSolidArrow, ///< Solid arrow line cap. + CPTLineCapTypeSweptArrow, ///< Swept arrow line cap. + CPTLineCapTypeRectangle, ///< Rectangle line cap. + CPTLineCapTypeEllipse, ///< Elliptical line cap. + CPTLineCapTypeDiamond, ///< Diamond line cap. + CPTLineCapTypePentagon, ///< Pentagon line cap. + CPTLineCapTypeHexagon, ///< Hexagon line cap. + CPTLineCapTypeBar, ///< Bar line cap. + CPTLineCapTypeCross, ///< X line cap. + CPTLineCapTypeSnow, ///< Snowflake line cap. + CPTLineCapTypeCustom ///< Custom line cap. +} +CPTLineCapType; + +@interface CPTLineCap : NSObject { + @private + CGSize size; + CPTLineCapType lineCapType; + CPTLineStyle *lineStyle; + CPTFill *fill; + CGPathRef cachedLineCapPath; + CGPathRef customLineCapPath; + BOOL usesEvenOddClipRule; +} + +@property (nonatomic, readwrite, assign) CGSize size; +@property (nonatomic, readwrite, assign) CPTLineCapType lineCapType; +@property (nonatomic, readwrite, retain) CPTLineStyle *lineStyle; +@property (nonatomic, readwrite, retain) CPTFill *fill; +@property (nonatomic, readwrite, assign) CGPathRef customLineCapPath; +@property (nonatomic, readwrite, assign) BOOL usesEvenOddClipRule; + +/// @name Factory Methods +/// @{ ++(CPTLineCap *)lineCap; ++(CPTLineCap *)openArrowPlotLineCap; ++(CPTLineCap *)solidArrowPlotLineCap; ++(CPTLineCap *)sweptArrowPlotLineCap; ++(CPTLineCap *)rectanglePlotLineCap; ++(CPTLineCap *)ellipsePlotLineCap; ++(CPTLineCap *)diamondPlotLineCap; ++(CPTLineCap *)pentagonPlotLineCap; ++(CPTLineCap *)hexagonPlotLineCap; ++(CPTLineCap *)barPlotLineCap; ++(CPTLineCap *)crossPlotLineCap; ++(CPTLineCap *)snowPlotLineCap; ++(CPTLineCap *)customLineCapWithPath:(CGPathRef)aPath; +/// @} + +/// @name Drawing +/// @{ +-(void)renderAsVectorInContext:(CGContextRef)theContext atPoint:(CGPoint)center inDirection:(CGPoint)direction; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTLineStyle.h b/CorePlot.framework/Versions/A/Headers/CPTLineStyle.h new file mode 100644 index 0000000..66202fa --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTLineStyle.h @@ -0,0 +1,37 @@ +#import +#import + +@class CPTColor; + +@interface CPTLineStyle : NSObject { + @private + CGLineCap lineCap; +// CGLineDash lineDash; // We should make a struct to keep this information + CGLineJoin lineJoin; + CGFloat miterLimit; + CGFloat lineWidth; + NSArray *dashPattern; + CGFloat patternPhase; +// StrokePattern; // We should make a struct to keep this information + CPTColor *lineColor; +} + +@property (nonatomic, readonly, assign) CGLineCap lineCap; +@property (nonatomic, readonly, assign) CGLineJoin lineJoin; +@property (nonatomic, readonly, assign) CGFloat miterLimit; +@property (nonatomic, readonly, assign) CGFloat lineWidth; +@property (nonatomic, readonly, retain) NSArray *dashPattern; +@property (nonatomic, readonly, assign) CGFloat patternPhase; +@property (nonatomic, readonly, retain) CPTColor *lineColor; + +/// @name Factory Methods +/// @{ ++(id)lineStyle; +/// @} + +/// @name Drawing +/// @{ +-(void)setLineStyleInContext:(CGContextRef)theContext; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTMutableLineStyle.h b/CorePlot.framework/Versions/A/Headers/CPTMutableLineStyle.h new file mode 100644 index 0000000..a740394 --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTMutableLineStyle.h @@ -0,0 +1,17 @@ +#import "CPTLineStyle.h" +#import + +@class CPTColor; + +@interface CPTMutableLineStyle : CPTLineStyle { +} + +@property (nonatomic, readwrite, assign) CGLineCap lineCap; +@property (nonatomic, readwrite, assign) CGLineJoin lineJoin; +@property (nonatomic, readwrite, assign) CGFloat miterLimit; +@property (nonatomic, readwrite, assign) CGFloat lineWidth; +@property (nonatomic, readwrite, retain) NSArray *dashPattern; +@property (nonatomic, readwrite, assign) CGFloat patternPhase; +@property (nonatomic, readwrite, retain) CPTColor *lineColor; + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTMutableNumericData+TypeConversion.h b/CorePlot.framework/Versions/A/Headers/CPTMutableNumericData+TypeConversion.h new file mode 100644 index 0000000..f762c3e --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTMutableNumericData+TypeConversion.h @@ -0,0 +1,23 @@ +#import "CPTMutableNumericData.h" +#import "CPTNumericDataType.h" +#import + +/** @category CPTMutableNumericData(TypeConversion) + * @brief Type conversion methods for CPTMutableNumericData. + **/ +@interface CPTMutableNumericData(TypeConversion) + +/// @name Data Format +/// @{ +@property (assign, readwrite) CPTNumericDataType dataType; +@property (assign, readwrite) CPTDataTypeFormat dataTypeFormat; +@property (assign, readwrite) size_t sampleBytes; +@property (assign, readwrite) CFByteOrder byteOrder; +/// @} + +/// @name Type Conversion +/// @{ +-(void)convertToType:(CPTDataTypeFormat)newDataType sampleBytes:(size_t)newSampleBytes byteOrder:(CFByteOrder)newByteOrder; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTMutableNumericData.h b/CorePlot.framework/Versions/A/Headers/CPTMutableNumericData.h new file mode 100644 index 0000000..3e25674 --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTMutableNumericData.h @@ -0,0 +1,29 @@ +#import "CPTNumericData.h" +#import "CPTNumericDataType.h" +#import + +@interface CPTMutableNumericData : CPTNumericData { +} + +/// @name Data Buffer +/// @{ +@property (readonly) void *mutableBytes; +/// @} + +/// @name Dimensions +/// @{ +@property (copy, readwrite) NSArray *shape; +/// @} + +/// @name Factory Methods +/// @{ ++(CPTMutableNumericData *)numericDataWithData:(NSData *)newData dataType:(CPTNumericDataType)newDataType shape:(NSArray *)shapeArray; ++(CPTMutableNumericData *)numericDataWithData:(NSData *)newData dataTypeString:(NSString *)newDataTypeString shape:(NSArray *)shapeArray; +/// @} + +/// @name Initialization +/// @{ +-(id)initWithData:(NSData *)newData dataType:(CPTNumericDataType)newDataType shape:(NSArray *)shapeArray; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTMutablePlotRange.h b/CorePlot.framework/Versions/A/Headers/CPTMutablePlotRange.h new file mode 100644 index 0000000..8687af4 --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTMutablePlotRange.h @@ -0,0 +1,29 @@ +#import "CPTPlotRange.h" + +@interface CPTMutablePlotRange : CPTPlotRange { +} + +/// @name Range Limits +/// @{ +@property (nonatomic, readwrite) NSDecimal location; +@property (nonatomic, readwrite) NSDecimal length; +/// @} + +/// @name Combining Ranges +/// @{ +-(void)unionPlotRange:(CPTPlotRange *)otherRange; +-(void)intersectionPlotRange:(CPTPlotRange *)otherRange; +/// @} + +/// @name Shifting Ranges +/// @{ +-(void)shiftLocationToFitInRange:(CPTPlotRange *)otherRange; +-(void)shiftEndToFitInRange:(CPTPlotRange *)otherRange; +/// @} + +/// @name Expanding/Contracting Ranges +/// @{ +-(void)expandRangeByFactor:(NSDecimal)factor; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTMutableShadow.h b/CorePlot.framework/Versions/A/Headers/CPTMutableShadow.h new file mode 100644 index 0000000..064cb5d --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTMutableShadow.h @@ -0,0 +1,14 @@ +#import "CPTShadow.h" +#import +#import + +@class CPTColor; + +@interface CPTMutableShadow : CPTShadow { +} + +@property (nonatomic, readwrite, assign) CGSize shadowOffset; +@property (nonatomic, readwrite, assign) CGFloat shadowBlurRadius; +@property (nonatomic, readwrite, retain) CPTColor *shadowColor; + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTMutableTextStyle.h b/CorePlot.framework/Versions/A/Headers/CPTMutableTextStyle.h new file mode 100644 index 0000000..07fbdfe --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTMutableTextStyle.h @@ -0,0 +1,14 @@ +#import "CPTTextStyle.h" +#import + +@class CPTColor; + +@interface CPTMutableTextStyle : CPTTextStyle { +} + +@property (readwrite, copy, nonatomic) NSString *fontName; +@property (readwrite, assign, nonatomic) CGFloat fontSize; +@property (readwrite, copy, nonatomic) CPTColor *color; +@property (readwrite, assign, nonatomic) CPTTextAlignment textAlignment; + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTNumericData+TypeConversion.h b/CorePlot.framework/Versions/A/Headers/CPTNumericData+TypeConversion.h new file mode 100644 index 0000000..1752594 --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTNumericData+TypeConversion.h @@ -0,0 +1,23 @@ +#import "CPTNumericData.h" +#import "CPTNumericDataType.h" +#import + +/** @category CPTNumericData(TypeConversion) + * @brief Type conversion methods for CPTNumericData. + **/ +@interface CPTNumericData(TypeConversion) + +/// @name Type Conversion +/// @{ +-(CPTNumericData *)dataByConvertingToDataType:(CPTNumericDataType)newDataType; + +-(CPTNumericData *)dataByConvertingToType:(CPTDataTypeFormat)newDataType sampleBytes:(size_t)newSampleBytes byteOrder:(CFByteOrder)newByteOrder; +/// @} + +/// @name Data Conversion Utilities +/// @{ +-(void)convertData:(NSData *)sourceData dataType:(CPTNumericDataType *)sourceDataType toData:(NSMutableData *)destData dataType:(CPTNumericDataType *)destDataType; +-(void)swapByteOrderForData:(NSMutableData *)sourceData sampleSize:(size_t)sampleSize; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTNumericData.h b/CorePlot.framework/Versions/A/Headers/CPTNumericData.h new file mode 100644 index 0000000..d36b77f --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTNumericData.h @@ -0,0 +1,56 @@ +#import "CPTNumericDataType.h" +#import + +@interface CPTNumericData : NSObject { + @protected + NSData *data; + CPTNumericDataType dataType; + NSArray *shape; // array of dimension shapes (NSNumber) +} + +/// @name Data Buffer +/// @{ +@property (copy, readonly) NSData *data; +@property (readonly) const void *bytes; +@property (readonly) NSUInteger length; +/// @} + +/// @name Data Format +/// @{ +@property (assign, readonly) CPTNumericDataType dataType; +@property (readonly) CPTDataTypeFormat dataTypeFormat; +@property (readonly) size_t sampleBytes; +@property (readonly) CFByteOrder byteOrder; +/// @} + +/// @name Dimensions +/// @{ +@property (copy, readonly) NSArray *shape; +@property (readonly) NSUInteger numberOfDimensions; +@property (readonly) NSUInteger numberOfSamples; +/// @} + +/// @name Factory Methods +/// @{ ++(CPTNumericData *)numericDataWithData:(NSData *)newData dataType:(CPTNumericDataType)newDataType shape:(NSArray *)shapeArray; ++(CPTNumericData *)numericDataWithData:(NSData *)newData dataTypeString:(NSString *)newDataTypeString shape:(NSArray *)shapeArray; ++(CPTNumericData *)numericDataWithArray:(NSArray *)newData dataType:(CPTNumericDataType)newDataType shape:(NSArray *)shapeArray; ++(CPTNumericData *)numericDataWithArray:(NSArray *)newData dataTypeString:(NSString *)newDataTypeString shape:(NSArray *)shapeArray; +/// @} + +/// @name Initialization +/// @{ +-(id)initWithData:(NSData *)newData dataType:(CPTNumericDataType)newDataType shape:(NSArray *)shapeArray; +-(id)initWithData:(NSData *)newData dataTypeString:(NSString *)newDataTypeString shape:(NSArray *)shapeArray; +-(id)initWithArray:(NSArray *)newData dataType:(CPTNumericDataType)newDataType shape:(NSArray *)shapeArray; +-(id)initWithArray:(NSArray *)newData dataTypeString:(NSString *)newDataTypeString shape:(NSArray *)shapeArray; +/// @} + +/// @name Samples +/// @{ +-(void *)samplePointer:(NSUInteger)sample; +-(NSNumber *)sampleValue:(NSUInteger)sample; +-(NSArray *)sampleArray; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTNumericDataType.h b/CorePlot.framework/Versions/A/Headers/CPTNumericDataType.h new file mode 100644 index 0000000..2ef8950 --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTNumericDataType.h @@ -0,0 +1,44 @@ +#import + +/// @file + +/** + * @brief Enumeration of data formats for numeric data. + **/ +typedef enum _CPTDataTypeFormat { + CPTUndefinedDataType = 0, ///< Undefined + CPTIntegerDataType, ///< Integer + CPTUnsignedIntegerDataType, ///< Unsigned integer + CPTFloatingPointDataType, ///< Floating point + CPTComplexFloatingPointDataType, ///< Complex floating point + CPTDecimalDataType ///< NSDecimal +} +CPTDataTypeFormat; + +/** + * @brief Struct that describes the encoding of numeric data samples. + **/ +typedef struct _CPTNumericDataType { + CPTDataTypeFormat dataTypeFormat; ///< Data type format + size_t sampleBytes; ///< Number of bytes in each sample + CFByteOrder byteOrder; ///< Byte order +} +CPTNumericDataType; + +#if __cplusplus +extern "C" { +#endif + +/// @name Data Type Utilities +/// @{ +CPTNumericDataType CPTDataType(CPTDataTypeFormat format, size_t sampleBytes, CFByteOrder byteOrder); +CPTNumericDataType CPTDataTypeWithDataTypeString(NSString *dataTypeString); +NSString *CPTDataTypeStringFromDataType(CPTNumericDataType dataType); +BOOL CPTDataTypeIsSupported(CPTNumericDataType format); +BOOL CPTDataTypeEqualToDataType(CPTNumericDataType dataType1, CPTNumericDataType dataType2); + +/// @} + +#if __cplusplus +} +#endif diff --git a/CorePlot.framework/Versions/A/Headers/CPTPathExtensions.h b/CorePlot.framework/Versions/A/Headers/CPTPathExtensions.h new file mode 100644 index 0000000..3d3e328 --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTPathExtensions.h @@ -0,0 +1,15 @@ +#import +#import + +/// @file + +#if __cplusplus +extern "C" { +#endif + +CGPathRef CreateRoundedRectPath(CGRect rect, CGFloat cornerRadius); +void AddRoundedRectPath(CGContextRef context, CGRect rect, CGFloat cornerRadius); + +#if __cplusplus +} +#endif diff --git a/CorePlot.framework/Versions/A/Headers/CPTPieChart.h b/CorePlot.framework/Versions/A/Headers/CPTPieChart.h new file mode 100644 index 0000000..908b63c --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTPieChart.h @@ -0,0 +1,132 @@ +#import "CPTDefinitions.h" +#import "CPTPlot.h" +#import + +/// @file + +@class CPTColor; +@class CPTFill; +@class CPTMutableNumericData; +@class CPTNumericData; +@class CPTPieChart; +@class CPTTextLayer; +@class CPTLineStyle; + +/// @ingroup plotBindingsPieChart +/// @{ +extern NSString *const CPTPieChartBindingPieSliceWidthValues; +/// @} + +/** + * @brief Enumeration of pie chart data source field types. + **/ +typedef enum _CPTPieChartField { + CPTPieChartFieldSliceWidth, ///< Pie slice width. + CPTPieChartFieldSliceWidthNormalized, ///< Pie slice width normalized [0, 1]. + CPTPieChartFieldSliceWidthSum ///< Cumulative sum of pie slice widths. +} +CPTPieChartField; + +/** + * @brief Enumeration of pie slice drawing directions. + **/ +typedef enum _CPTPieDirection { + CPTPieDirectionClockwise, ///< Pie slices are drawn in a clockwise direction. + CPTPieDirectionCounterClockwise ///< Pie slices are drawn in a counter-clockwise direction. +} +CPTPieDirection; + +#pragma mark - + +/** + * @brief A pie chart data source. + **/ +@protocol CPTPieChartDataSource +@optional + +/// @name Slice Style +/// @{ + +/** @brief (Optional) Gets a fill for the given pie chart slice. + * @param pieChart The pie chart. + * @param index The data index of interest. + * @return The pie slice fill for the slice with the given index. + **/ +-(CPTFill *)sliceFillForPieChart:(CPTPieChart *)pieChart recordIndex:(NSUInteger)index; + +/// @} + +/// @name Slice Layout +/// @{ + +/** @brief (Optional) Offsets the slice radially from the center point. Can be used to "explode" the chart. + * @param pieChart The pie chart. + * @param index The data index of interest. + * @return The radial offset in view coordinates. Zero is no offset. + **/ +-(CGFloat)radialOffsetForPieChart:(CPTPieChart *)pieChart recordIndex:(NSUInteger)index; + +/// @{ + +/// @name Legends +/// @{ + +/** @brief (Optional) Gets the legend title for the given pie chart slice. + * @param pieChart The pie chart. + * @param index The data index of interest. + * @return The title text for the legend entry for the point with the given index. + **/ +-(NSString *)legendTitleForPieChart:(CPTPieChart *)pieChart recordIndex:(NSUInteger)index; + +/// @} +@end + +#pragma mark - + +/** + * @brief Pie chart delegate. + **/ +@protocol CPTPieChartDelegate + +@optional + +/// @name Point Selection +/// @{ + +/** @brief (Optional) Informs the delegate that a pie slice was touched or clicked. + * @param plot The pie chart. + * @param index The index of the slice that was touched or clicked. + **/ +-(void)pieChart:(CPTPieChart *)plot sliceWasSelectedAtRecordIndex:(NSUInteger)index; + +/// @} + +@end + +#pragma mark - + +@interface CPTPieChart : CPTPlot { + @private + CGFloat pieRadius; + CGFloat pieInnerRadius; + CGFloat startAngle; + CPTPieDirection sliceDirection; + CGPoint centerAnchor; + CPTLineStyle *borderLineStyle; + CPTFill *overlayFill; +} + +@property (nonatomic, readwrite) CGFloat pieRadius; +@property (nonatomic, readwrite) CGFloat pieInnerRadius; +@property (nonatomic, readwrite) CGFloat startAngle; +@property (nonatomic, readwrite) CPTPieDirection sliceDirection; +@property (nonatomic, readwrite) CGPoint centerAnchor; +@property (nonatomic, readwrite, copy) CPTLineStyle *borderLineStyle; +@property (nonatomic, readwrite, copy) CPTFill *overlayFill; + +/// @name Factory Methods +/// @{ ++(CPTColor *)defaultPieSliceColorForIndex:(NSUInteger)pieSliceIndex; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTPlatformSpecificCategories.h b/CorePlot.framework/Versions/A/Headers/CPTPlatformSpecificCategories.h new file mode 100644 index 0000000..141cca1 --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTPlatformSpecificCategories.h @@ -0,0 +1,26 @@ +#import "CPTColor.h" +#import "CPTLayer.h" +#import "CPTPlatformSpecificDefines.h" +#import +#import + +/** @category CPTLayer(CPTPlatformSpecificLayerExtensions) + * @brief Platform-specific extensions to CPTLayer. + **/ +@interface CPTLayer(CPTPlatformSpecificLayerExtensions) + +/// @name Images +/// @{ +-(CPTNativeImage *)imageOfLayer; +/// @} + +@end + +/** @category CPTColor(CPTPlatformSpecificColorExtensions) + * @brief Platform-specific extensions to CPTColor. + **/ +@interface CPTColor(CPTPlatformSpecificColorExtensions) + +@property (nonatomic, readonly, retain) NSColor *nsColor; + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTPlatformSpecificDefines.h b/CorePlot.framework/Versions/A/Headers/CPTPlatformSpecificDefines.h new file mode 100644 index 0000000..49ad5dc --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTPlatformSpecificDefines.h @@ -0,0 +1,6 @@ +#import +#import + +/// @file + +typedef NSImage CPTNativeImage; ///< Platform-native image format. diff --git a/CorePlot.framework/Versions/A/Headers/CPTPlatformSpecificFunctions.h b/CorePlot.framework/Versions/A/Headers/CPTPlatformSpecificFunctions.h new file mode 100644 index 0000000..6f2c98a --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTPlatformSpecificFunctions.h @@ -0,0 +1,33 @@ +#import "CPTDefinitions.h" +#import +#import + +/// @file + +#if __cplusplus +extern "C" { +#endif + +/// @name Graphics Context Save Stack +/// @{ +void CPTPushCGContext(CGContextRef context); +void CPTPopCGContext(void); + +/// @} + +/// @name Graphics Context +/// @{ +CGContextRef CPTGetCurrentContext(void); + +/// @} + +/// @name Color Conversion +/// @{ +CGColorRef CPTCreateCGColorFromNSColor(NSColor *nsColor); +CPTRGBAColor CPTRGBAColorFromNSColor(NSColor *nsColor); + +/// @} + +#if __cplusplus +} +#endif diff --git a/CorePlot.framework/Versions/A/Headers/CPTPlot.h b/CorePlot.framework/Versions/A/Headers/CPTPlot.h new file mode 100644 index 0000000..29d7908 --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTPlot.h @@ -0,0 +1,249 @@ +#import "CPTAnnotationHostLayer.h" +#import "CPTDefinitions.h" +#import "CPTMutableTextStyle.h" +#import "CPTNumericDataType.h" +#import "CPTPlotRange.h" + +@class CPTLegend; +@class CPTMutableNumericData; +@class CPTNumericData; +@class CPTPlot; +@class CPTPlotArea; +@class CPTPlotSpace; +@class CPTPlotSpaceAnnotation; +@class CPTPlotRange; + +/// @file + +/** + * @brief Enumeration of cache precisions. + **/ +typedef enum _CPTPlotCachePrecision { + CPTPlotCachePrecisionAuto, ///< Cache precision is determined automatically from the data. All cached data will be converted to match the last data loaded. + CPTPlotCachePrecisionDouble, ///< All cached data will be converted to double precision. + CPTPlotCachePrecisionDecimal ///< All cached data will be converted to NSDecimal. +} +CPTPlotCachePrecision; + +#pragma mark - + +/** + * @brief A plot data source. + **/ +@protocol CPTPlotDataSource + +/// @name Data Values +/// @{ + +/** @brief (Required) The number of data points for the plot. + * @param plot The plot. + * @return The number of data points for the plot. + **/ +-(NSUInteger)numberOfRecordsForPlot:(CPTPlot *)plot; + +@optional + +/** @brief (Optional) Gets a range of plot data for the given plot and field. + * Implement one and only one of the optional methods in this section. + * @param plot The plot. + * @param fieldEnum The field index. + * @param indexRange The range of the data indexes of interest. + * @return An array of data points. + **/ +-(NSArray *)numbersForPlot:(CPTPlot *)plot field:(NSUInteger)fieldEnum recordIndexRange:(NSRange)indexRange; + +/** @brief (Optional) Gets a plot data value for the given plot and field. + * Implement one and only one of the optional methods in this section. + * @param plot The plot. + * @param fieldEnum The field index. + * @param index The data index of interest. + * @return A data point. + **/ +-(NSNumber *)numberForPlot:(CPTPlot *)plot field:(NSUInteger)fieldEnum recordIndex:(NSUInteger)index; + +/** @brief (Optional) Gets a range of plot data for the given plot and field. + * Implement one and only one of the optional methods in this section. + * @param plot The plot. + * @param fieldEnum The field index. + * @param indexRange The range of the data indexes of interest. + * @return A retained C array of data points. + **/ +-(double *)doublesForPlot:(CPTPlot *)plot field:(NSUInteger)fieldEnum recordIndexRange:(NSRange)indexRange; + +/** @brief (Optional) Gets a plot data value for the given plot and field. + * Implement one and only one of the optional methods in this section. + * @param plot The plot. + * @param fieldEnum The field index. + * @param index The data index of interest. + * @return A data point. + **/ +-(double)doubleForPlot:(CPTPlot *)plot field:(NSUInteger)fieldEnum recordIndex:(NSUInteger)index; + +/** @brief (Optional) Gets a range of plot data for the given plot and field. + * Implement one and only one of the optional methods in this section. + * @param plot The plot. + * @param fieldEnum The field index. + * @param indexRange The range of the data indexes of interest. + * @return A one-dimensional array of data points. + **/ +-(CPTNumericData *)dataForPlot:(CPTPlot *)plot field:(NSUInteger)fieldEnum recordIndexRange:(NSRange)indexRange; + +/// @} + +/// @name Data Labels +/// @{ + +/** @brief (Optional) Gets a data label for the given plot. + * @param plot The plot. + * @param index The data index of interest. + * @return The data label for the point with the given index. + * If you return nil, the default data label will be used. If you return an instance of NSNull, + * no label will be shown for the index in question. + **/ +-(CPTLayer *)dataLabelForPlot:(CPTPlot *)plot recordIndex:(NSUInteger)index; + +/// @} + +@end + +#pragma mark - + +@interface CPTPlot : CPTAnnotationHostLayer { + @private + __cpt_weak id dataSource; + NSString *title; + CPTPlotSpace *plotSpace; + BOOL dataNeedsReloading; + NSMutableDictionary *cachedData; + NSUInteger cachedDataCount; + CPTPlotCachePrecision cachePrecision; + BOOL needsRelabel; + CGFloat labelOffset; + CGFloat labelRotation; + NSUInteger labelField; + CPTTextStyle *labelTextStyle; + NSNumberFormatter *labelFormatter; + NSRange labelIndexRange; + NSMutableArray *labelAnnotations; + CPTShadow *labelShadow; + BOOL alignsPointsToPixels; +} + +/// @name Data Source +/// @{ +@property (nonatomic, readwrite, cpt_weak_property) __cpt_weak id dataSource; +/// @} + +/// @name Identification +/// @{ +@property (nonatomic, readwrite, copy) NSString *title; +/// @} + +/// @name Plot Space +/// @{ +@property (nonatomic, readwrite, retain) CPTPlotSpace *plotSpace; +/// @} + +/// @name Plot Area +/// @{ +@property (nonatomic, readonly, retain) CPTPlotArea *plotArea; +/// @} + +/// @name Data Loading +/// @{ +@property (nonatomic, readonly, assign) BOOL dataNeedsReloading; +/// @} + +/// @name Data Cache +/// @{ +@property (nonatomic, readonly, assign) NSUInteger cachedDataCount; +@property (nonatomic, readonly, assign) BOOL doublePrecisionCache; +@property (nonatomic, readwrite, assign) CPTPlotCachePrecision cachePrecision; +@property (nonatomic, readonly, assign) CPTNumericDataType doubleDataType; +@property (nonatomic, readonly, assign) CPTNumericDataType decimalDataType; +/// @} + +/// @name Data Labels +/// @{ +@property (nonatomic, readonly, assign) BOOL needsRelabel; +@property (nonatomic, readwrite, assign) CGFloat labelOffset; +@property (nonatomic, readwrite, assign) CGFloat labelRotation; +@property (nonatomic, readwrite, assign) NSUInteger labelField; +@property (nonatomic, readwrite, copy) CPTTextStyle *labelTextStyle; +@property (nonatomic, readwrite, retain) NSNumberFormatter *labelFormatter; +@property (nonatomic, readwrite, retain) CPTShadow *labelShadow; +/// @} + +/// @name Drawing +/// @{ +@property (nonatomic, readwrite, assign) BOOL alignsPointsToPixels; +/// @} + +/// @name Data Labels +/// @{ +-(void)setNeedsRelabel; +-(void)relabel; +-(void)relabelIndexRange:(NSRange)indexRange; +-(void)repositionAllLabelAnnotations; +/// @} + +/// @name Data Loading +/// @{ +-(void)setDataNeedsReloading; +-(void)reloadData; +-(void)reloadDataIfNeeded; +-(void)reloadDataInIndexRange:(NSRange)indexRange; +-(void)insertDataAtIndex:(NSUInteger)index numberOfRecords:(NSUInteger)numberOfRecords; +-(void)deleteDataInIndexRange:(NSRange)indexRange; +/// @} + +/// @name Plot Data +/// @{ +-(id)numbersFromDataSourceForField:(NSUInteger)fieldEnum recordIndexRange:(NSRange)indexRange; +/// @} + +/// @name Data Cache +/// @{ +-(CPTMutableNumericData *)cachedNumbersForField:(NSUInteger)fieldEnum; +-(NSNumber *)cachedNumberForField:(NSUInteger)fieldEnum recordIndex:(NSUInteger)index; +-(double)cachedDoubleForField:(NSUInteger)fieldEnum recordIndex:(NSUInteger)index; +-(NSDecimal)cachedDecimalForField:(NSUInteger)fieldEnum recordIndex:(NSUInteger)index; +-(void)cacheNumbers:(id)numbers forField:(NSUInteger)fieldEnum; +-(void)cacheNumbers:(id)numbers forField:(NSUInteger)fieldEnum atRecordIndex:(NSUInteger)index; +/// @} + +/// @name Plot Data Ranges +/// @{ +-(CPTPlotRange *)plotRangeForField:(NSUInteger)fieldEnum; +-(CPTPlotRange *)plotRangeForCoordinate:(CPTCoordinate)coord; +/// @} + +/// @name Legends +/// @{ +-(NSUInteger)numberOfLegendEntries; +-(NSString *)titleForLegendEntryAtIndex:(NSUInteger)index; +-(void)drawSwatchForLegend:(CPTLegend *)legend atIndex:(NSUInteger)index inRect:(CGRect)rect inContext:(CGContextRef)context; +/// @} + +@end + +#pragma mark - + +/** @category CPTPlot(AbstractMethods) + * @brief CPTPlot abstract methods—must be overridden by subclasses + **/ +@interface CPTPlot(AbstractMethods) + +/// @name Fields +/// @{ +-(NSUInteger)numberOfFields; +-(NSArray *)fieldIdentifiers; +-(NSArray *)fieldIdentifiersForCoordinate:(CPTCoordinate)coord; +/// @} + +/// @name Data Labels +/// @{ +-(void)positionLabelAnnotation:(CPTPlotSpaceAnnotation *)label forIndex:(NSUInteger)index; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTPlotArea.h b/CorePlot.framework/Versions/A/Headers/CPTPlotArea.h new file mode 100644 index 0000000..d920f99 --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTPlotArea.h @@ -0,0 +1,56 @@ +#import "CPTAnnotationHostLayer.h" +#import "CPTGraph.h" +#import "CPTLayer.h" +#import + +@class CPTAxis; +@class CPTAxisLabelGroup; +@class CPTAxisSet; +@class CPTGridLineGroup; +@class CPTPlotGroup; +@class CPTLineStyle; +@class CPTFill; + +@interface CPTPlotArea : CPTAnnotationHostLayer { + @private + CPTGridLineGroup *minorGridLineGroup; + CPTGridLineGroup *majorGridLineGroup; + CPTAxisSet *axisSet; + CPTPlotGroup *plotGroup; + CPTAxisLabelGroup *axisLabelGroup; + CPTAxisLabelGroup *axisTitleGroup; + CPTFill *fill; + NSArray *topDownLayerOrder; + CPTGraphLayerType *bottomUpLayerOrder; + BOOL updatingLayers; +} + +/// @name Layers +/// @{ +@property (nonatomic, readwrite, retain) CPTGridLineGroup *minorGridLineGroup; +@property (nonatomic, readwrite, retain) CPTGridLineGroup *majorGridLineGroup; +@property (nonatomic, readwrite, retain) CPTAxisSet *axisSet; +@property (nonatomic, readwrite, retain) CPTPlotGroup *plotGroup; +@property (nonatomic, readwrite, retain) CPTAxisLabelGroup *axisLabelGroup; +@property (nonatomic, readwrite, retain) CPTAxisLabelGroup *axisTitleGroup; +/// @} + +/// @name Layer Ordering +/// @{ +@property (nonatomic, readwrite, retain) NSArray *topDownLayerOrder; +/// @} + +/// @name Decorations +/// @{ +@property (nonatomic, readwrite, copy) CPTLineStyle *borderLineStyle; +@property (nonatomic, readwrite, copy) CPTFill *fill; +/// @} + +/// @name Axis Set Layer Management +/// @{ +-(void)updateAxisSetLayersForType:(CPTGraphLayerType)layerType; +-(void)setAxisSetLayersForType:(CPTGraphLayerType)layerType; +-(unsigned)sublayerIndexForAxis:(CPTAxis *)axis layerType:(CPTGraphLayerType)layerType; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTPlotAreaFrame.h b/CorePlot.framework/Versions/A/Headers/CPTPlotAreaFrame.h new file mode 100644 index 0000000..7a2cbed --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTPlotAreaFrame.h @@ -0,0 +1,16 @@ +#import "CPTBorderedLayer.h" + +@class CPTAxisSet; +@class CPTPlotGroup; +@class CPTPlotArea; + +@interface CPTPlotAreaFrame : CPTBorderedLayer { + @private + CPTPlotArea *plotArea; +} + +@property (nonatomic, readonly, retain) CPTPlotArea *plotArea; +@property (nonatomic, readwrite, retain) CPTAxisSet *axisSet; +@property (nonatomic, readwrite, retain) CPTPlotGroup *plotGroup; + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTPlotRange.h b/CorePlot.framework/Versions/A/Headers/CPTPlotRange.h new file mode 100644 index 0000000..d787a3d --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTPlotRange.h @@ -0,0 +1,65 @@ +#import "CPTDefinitions.h" +#import + +/// @file + +/** + * @brief Enumeration of possible results of a plot range comparison. + **/ +typedef enum _CPTPlotRangeComparisonResult { + CPTPlotRangeComparisonResultNumberBelowRange, ///< Number is below the range. + CPTPlotRangeComparisonResultNumberInRange, ///< Number is in the range. + CPTPlotRangeComparisonResultNumberAboveRange ///< Number is above the range. +} +CPTPlotRangeComparisonResult; + +@interface CPTPlotRange : NSObject { + @private + NSDecimal location; + NSDecimal length; + double locationDouble; + double lengthDouble; +} + +/// @name Range Limits +/// @{ +@property (nonatomic, readonly) NSDecimal location; +@property (nonatomic, readonly) NSDecimal length; +@property (nonatomic, readonly) NSDecimal end; +@property (nonatomic, readonly) double locationDouble; +@property (nonatomic, readonly) double lengthDouble; +@property (nonatomic, readonly) double endDouble; + +@property (nonatomic, readonly) NSDecimal minLimit; +@property (nonatomic, readonly) NSDecimal midPoint; +@property (nonatomic, readonly) NSDecimal maxLimit; +@property (nonatomic, readonly) double minLimitDouble; +@property (nonatomic, readonly) double midPointDouble; +@property (nonatomic, readonly) double maxLimitDouble; +/// @} + +/// @name Factory Methods +/// @{ ++(id)plotRangeWithLocation:(NSDecimal)loc length:(NSDecimal)len; +/// @} + +/// @name Initialization +/// @{ +-(id)initWithLocation:(NSDecimal)loc length:(NSDecimal)len; +/// @} + +/// @name Checking Ranges +/// @{ +-(BOOL)contains:(NSDecimal)number; +-(BOOL)containsDouble:(double)number; +-(BOOL)isEqualToRange:(CPTPlotRange *)otherRange; +/// @} + +/// @name Range Comparison +/// @{ +-(CPTPlotRangeComparisonResult)compareToNumber:(NSNumber *)number; +-(CPTPlotRangeComparisonResult)compareToDecimal:(NSDecimal)number; +-(CPTPlotRangeComparisonResult)compareToDouble:(double)number; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTPlotSpace.h b/CorePlot.framework/Versions/A/Headers/CPTPlotSpace.h new file mode 100644 index 0000000..e84dd21 --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTPlotSpace.h @@ -0,0 +1,166 @@ +#import "CPTDefinitions.h" +#import "CPTResponder.h" + +@class CPTLayer; +@class CPTPlotRange; +@class CPTGraph; +@class CPTPlotSpace; + +/// @name Plot Space +/// @{ + +/** @brief Plot space coordinate change notification. + * + * This notification is posted to the default notification center whenever the mapping between + * the plot space coordinate system and drawing coordinates changes. + * @ingroup notification + **/ +extern NSString *const CPTPlotSpaceCoordinateMappingDidChangeNotification; + +/// @} + +/** + * @brief Plot space delegate. + **/ +@protocol CPTPlotSpaceDelegate + +@optional + +/// @name Scaling +/// @{ + +/** @brief (Optional) Informs the receiver that it should uniformly scale (e.g., in response to a pinch on iOS). + * @param space The plot space. + * @param interactionScale The scaling factor. + * @param interactionPoint The coordinates of the scaling centroid. + * @return YES should be returned if the gesture should be handled by the plot space, and NO to prevent handling. + * In either case, the delegate may choose to take extra actions, or handle the scaling itself. + **/ +-(BOOL)plotSpace:(CPTPlotSpace *)space shouldScaleBy:(CGFloat)interactionScale aboutPoint:(CGPoint)interactionPoint; + +/// @} + +/// @name Scrolling +/// @{ + +/** @brief (Optional) Notifies that plot space is going to scroll. + * @param space The plot space. + * @param proposedDisplacementVector The proposed amount by which the plot space will shift. + * @return The displacement actually applied. + **/ +-(CGPoint)plotSpace:(CPTPlotSpace *)space willDisplaceBy:(CGPoint)proposedDisplacementVector; + +/// @} + +/// @name Plot Range Changes +/// @{ + +/** @brief (Optional) Notifies that plot space is going to change a plot range. + * @param space The plot space. + * @param newRange The proposed new plot range. + * @param coordinate The coordinate of the range. + * @return The new plot range to be used. + **/ +-(CPTPlotRange *)plotSpace:(CPTPlotSpace *)space willChangePlotRangeTo:(CPTPlotRange *)newRange forCoordinate:(CPTCoordinate)coordinate; + +/** @brief (Optional) Notifies that plot space has changed a plot range. + * @param space The plot space. + * @param coordinate The coordinate of the range. + **/ +-(void)plotSpace:(CPTPlotSpace *)space didChangePlotRangeForCoordinate:(CPTCoordinate)coordinate; + +/// @} + +/// @name User Interaction +/// @{ + +/** @brief (Optional) Notifies that plot space intercepted a device down event. + * @param space The plot space. + * @param event The native event (e.g., UIEvent on iPhone) + * @param point The point in the host view. + * @return Whether the plot space should handle the event or not. + * In either case, the delegate may choose to take extra actions, or handle the scaling itself. + **/ +-(BOOL)plotSpace:(CPTPlotSpace *)space shouldHandlePointingDeviceDownEvent:(id)event atPoint:(CGPoint)point; + +/** @brief (Optional) Notifies that plot space intercepted a device dragged event. + * @param space The plot space. + * @param event The native event (e.g., UIEvent on iPhone) + * @param point The point in the host view. + * @return Whether the plot space should handle the event or not. + * In either case, the delegate may choose to take extra actions, or handle the scaling itself. + **/ +-(BOOL)plotSpace:(CPTPlotSpace *)space shouldHandlePointingDeviceDraggedEvent:(id)event atPoint:(CGPoint)point; + +/** @brief (Optional) Notifies that plot space intercepted a device cancelled event. + * @param space The plot space. + * @param event The native event (e.g., UIEvent on iPhone) + * @return Whether the plot space should handle the event or not. + * In either case, the delegate may choose to take extra actions, or handle the scaling itself. + **/ +-(BOOL)plotSpace:(CPTPlotSpace *)space shouldHandlePointingDeviceCancelledEvent:(id)event; + +/** @brief (Optional) Notifies that plot space intercepted a device up event. + * @param space The plot space. + * @param event The native event (e.g., UIEvent on iPhone) + * @param point The point in the host view. + * @return Whether the plot space should handle the event or not. + * In either case, the delegate may choose to take extra actions, or handle the scaling itself. + **/ +-(BOOL)plotSpace:(CPTPlotSpace *)space shouldHandlePointingDeviceUpEvent:(id)event atPoint:(CGPoint)point; + +/// @} + +@end + +#pragma mark - + +@interface CPTPlotSpace : NSObject { + @private + __cpt_weak CPTGraph *graph; + id identifier; + __cpt_weak id delegate; + BOOL allowsUserInteraction; +} + +@property (nonatomic, readwrite, copy) id identifier; +@property (nonatomic, readwrite, assign) BOOL allowsUserInteraction; +@property (nonatomic, readwrite, cpt_weak_property) __cpt_weak CPTGraph *graph; +@property (nonatomic, readwrite, cpt_weak_property) __cpt_weak id delegate; + +@end + +#pragma mark - + +/** @category CPTPlotSpace(AbstractMethods) + * @brief CPTPlotSpace abstract methods—must be overridden by subclasses + **/ +@interface CPTPlotSpace(AbstractMethods) + +/// @name Coordinate Space Conversions +/// @{ +-(CGPoint)plotAreaViewPointForPlotPoint:(NSDecimal *)plotPoint; +-(CGPoint)plotAreaViewPointForDoublePrecisionPlotPoint:(double *)plotPoint; +-(void)plotPoint:(NSDecimal *)plotPoint forPlotAreaViewPoint:(CGPoint)point; +-(void)doublePrecisionPlotPoint:(double *)plotPoint forPlotAreaViewPoint:(CGPoint)point; +/// @} + +/// @name Coordinate Range +/// @{ +-(void)setPlotRange:(CPTPlotRange *)newRange forCoordinate:(CPTCoordinate)coordinate; +-(CPTPlotRange *)plotRangeForCoordinate:(CPTCoordinate)coordinate; +/// @} + +/// @name Scale Types +/// @{ +-(void)setScaleType:(CPTScaleType)newType forCoordinate:(CPTCoordinate)coordinate; +-(CPTScaleType)scaleTypeForCoordinate:(CPTCoordinate)coordinate; +/// @} + +/// @name Adjusting Ranges +/// @{ +-(void)scaleToFitPlots:(NSArray *)plots; +-(void)scaleBy:(CGFloat)interactionScale aboutPoint:(CGPoint)interactionPoint; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTPlotSpaceAnnotation.h b/CorePlot.framework/Versions/A/Headers/CPTPlotSpaceAnnotation.h new file mode 100644 index 0000000..28a3c64 --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTPlotSpaceAnnotation.h @@ -0,0 +1,16 @@ +#import "CPTAnnotation.h" +#import + +@class CPTPlotSpace; + +@interface CPTPlotSpaceAnnotation : CPTAnnotation { + NSArray *anchorPlotPoint; + CPTPlotSpace *plotSpace; +} + +@property (nonatomic, readwrite, copy) NSArray *anchorPlotPoint; +@property (nonatomic, readonly, retain) CPTPlotSpace *plotSpace; + +-(id)initWithPlotSpace:(CPTPlotSpace *)space anchorPlotPoint:(NSArray *)plotPoint; + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTPlotSymbol.h b/CorePlot.framework/Versions/A/Headers/CPTPlotSymbol.h new file mode 100644 index 0000000..69cdf48 --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTPlotSymbol.h @@ -0,0 +1,74 @@ +#import +#import + +/// @file + +@class CPTLineStyle; +@class CPTFill; +@class CPTShadow; + +/** + * @brief Plot symbol types. + **/ +typedef enum _CPTPlotSymbolType { + CPTPlotSymbolTypeNone, ///< No symbol. + CPTPlotSymbolTypeRectangle, ///< Rectangle symbol. + CPTPlotSymbolTypeEllipse, ///< Elliptical symbol. + CPTPlotSymbolTypeDiamond, ///< Diamond symbol. + CPTPlotSymbolTypeTriangle, ///< Triangle symbol. + CPTPlotSymbolTypeStar, ///< 5-point star symbol. + CPTPlotSymbolTypePentagon, ///< Pentagon symbol. + CPTPlotSymbolTypeHexagon, ///< Hexagon symbol. + CPTPlotSymbolTypeCross, ///< X symbol. + CPTPlotSymbolTypePlus, ///< Plus symbol. + CPTPlotSymbolTypeDash, ///< Dash symbol. + CPTPlotSymbolTypeSnow, ///< Snowflake symbol. + CPTPlotSymbolTypeCustom ///< Custom symbol. +} +CPTPlotSymbolType; + +@interface CPTPlotSymbol : NSObject { + @private + CGSize size; + CPTPlotSymbolType symbolType; + CPTLineStyle *lineStyle; + CPTFill *fill; + CGPathRef cachedSymbolPath; + CGPathRef customSymbolPath; + BOOL usesEvenOddClipRule; + CGLayerRef cachedLayer; + CPTShadow *shadow; +} + +@property (nonatomic, readwrite, assign) CGSize size; +@property (nonatomic, readwrite, assign) CPTPlotSymbolType symbolType; +@property (nonatomic, readwrite, retain) CPTLineStyle *lineStyle; +@property (nonatomic, readwrite, retain) CPTFill *fill; +@property (nonatomic, readwrite, copy) CPTShadow *shadow; +@property (nonatomic, readwrite, assign) CGPathRef customSymbolPath; +@property (nonatomic, readwrite, assign) BOOL usesEvenOddClipRule; + +/// @name Factory Methods +/// @{ ++(CPTPlotSymbol *)plotSymbol; ++(CPTPlotSymbol *)crossPlotSymbol; ++(CPTPlotSymbol *)ellipsePlotSymbol; ++(CPTPlotSymbol *)rectanglePlotSymbol; ++(CPTPlotSymbol *)plusPlotSymbol; ++(CPTPlotSymbol *)starPlotSymbol; ++(CPTPlotSymbol *)diamondPlotSymbol; ++(CPTPlotSymbol *)trianglePlotSymbol; ++(CPTPlotSymbol *)pentagonPlotSymbol; ++(CPTPlotSymbol *)hexagonPlotSymbol; ++(CPTPlotSymbol *)dashPlotSymbol; ++(CPTPlotSymbol *)snowPlotSymbol; ++(CPTPlotSymbol *)customPlotSymbolWithPath:(CGPathRef)aPath; +/// @} + +/// @name Drawing +/// @{ +-(void)renderInContext:(CGContextRef)theContext atPoint:(CGPoint)center scale:(CGFloat)scale alignToPixels:(BOOL)alignToPixels; +-(void)renderAsVectorInContext:(CGContextRef)theContext atPoint:(CGPoint)center scale:(CGFloat)scale; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTRangePlot.h b/CorePlot.framework/Versions/A/Headers/CPTRangePlot.h new file mode 100644 index 0000000..78befa2 --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTRangePlot.h @@ -0,0 +1,50 @@ +#import "CPTDefinitions.h" +#import "CPTPlot.h" +#import + +@class CPTLineStyle; +@class CPTFill; + +/// @ingroup plotBindingsRangePlot +/// @{ +extern NSString *const CPTRangePlotBindingXValues; +extern NSString *const CPTRangePlotBindingYValues; +extern NSString *const CPTRangePlotBindingHighValues; +extern NSString *const CPTRangePlotBindingLowValues; +extern NSString *const CPTRangePlotBindingLeftValues; +extern NSString *const CPTRangePlotBindingRightValues; +/// @} + +/** + * @brief Enumeration of range plot data source field types + **/ +typedef enum _CPTRangePlotField { + CPTRangePlotFieldX, ///< X values. + CPTRangePlotFieldY, ///< Y values. + CPTRangePlotFieldHigh, ///< relative High values. + CPTRangePlotFieldLow, ///< relative Low values. + CPTRangePlotFieldLeft, ///< relative Left values. + CPTRangePlotFieldRight, ///< relative Right values. +} +CPTRangePlotField; + +@interface CPTRangePlot : CPTPlot { + CPTLineStyle *barLineStyle; + CGFloat barWidth; + CGFloat gapHeight; + CGFloat gapWidth; + CPTFill *areaFill; +} + +/// @name Bar Appearance +/// @{ +@property (nonatomic, readwrite, copy) CPTLineStyle *barLineStyle; +@property (nonatomic, readwrite) CGFloat barWidth, gapHeight, gapWidth; +/// @} + +/// @name Area Fill +/// @{ +@property (nonatomic, copy) CPTFill *areaFill; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTResponder.h b/CorePlot.framework/Versions/A/Headers/CPTResponder.h new file mode 100644 index 0000000..e5f390f --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTResponder.h @@ -0,0 +1,53 @@ +#import +#import + +/** + * @brief The basis of all event processing in Core Plot. + **/ +@protocol CPTResponder + +/// @name User Interaction +/// @{ + +/** + * @brief (Required) Informs the receiver that the user has + * @if MacOnly pressed the mouse button. @endif + * @if iOSOnly touched the screen. @endif + * @param event The OS event. + * @param interactionPoint The coordinates of the interaction. + * @return Whether the event was handled or not. + **/ +-(BOOL)pointingDeviceDownEvent:(id)event atPoint:(CGPoint)interactionPoint; + +/** + * @brief (Required) Informs the receiver that the user has + * @if MacOnly released the mouse button. @endif + * @if iOSOnly lifted their finger off the screen. @endif + * @param event The OS event. + * @param interactionPoint The coordinates of the interaction. + * @return Whether the event was handled or not. + **/ +-(BOOL)pointingDeviceUpEvent:(id)event atPoint:(CGPoint)interactionPoint; + +/** + * @brief (Required) Informs the receiver that the user has moved + * @if MacOnly the mouse with the button pressed. @endif + * @if iOSOnly their finger while touching the screen. @endif + * @param event The OS event. + * @param interactionPoint The coordinates of the interaction. + * @return Whether the event was handled or not. + **/ +-(BOOL)pointingDeviceDraggedEvent:(id)event atPoint:(CGPoint)interactionPoint; + +/** + * @brief (Required) Informs the receiver that tracking of + * @if MacOnly mouse moves @endif + * @if iOSOnly touches @endif + * has been cancelled for any reason. + * @param event The OS event. + * @return Whether the event was handled or not. + **/ +-(BOOL)pointingDeviceCancelledEvent:(id)event; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTScatterPlot.h b/CorePlot.framework/Versions/A/Headers/CPTScatterPlot.h new file mode 100644 index 0000000..f8b88b9 --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTScatterPlot.h @@ -0,0 +1,130 @@ +#import "CPTDefinitions.h" +#import "CPTPlot.h" +#import + +/// @file + +@class CPTLineStyle; +@class CPTMutableNumericData; +@class CPTNumericData; +@class CPTPlotSymbol; +@class CPTScatterPlot; +@class CPTFill; + +/// @ingroup plotBindingsScatterPlot +/// @{ +extern NSString *const CPTScatterPlotBindingXValues; +extern NSString *const CPTScatterPlotBindingYValues; +extern NSString *const CPTScatterPlotBindingPlotSymbols; +/// @} + +/** + * @brief Enumeration of scatter plot data source field types + **/ +typedef enum _CPTScatterPlotField { + CPTScatterPlotFieldX, ///< X values. + CPTScatterPlotFieldY ///< Y values. +} +CPTScatterPlotField; + +/** + * @brief Enumeration of scatter plot interpolation algorithms + **/ +typedef enum _CPTScatterPlotInterpolation { + CPTScatterPlotInterpolationLinear, ///< Linear interpolation. + CPTScatterPlotInterpolationStepped, ///< Steps beginnning at data point. + CPTScatterPlotInterpolationHistogram ///< Steps centered at data point. +} +CPTScatterPlotInterpolation; + +#pragma mark - + +/** + * @brief A scatter plot data source. + **/ +@protocol CPTScatterPlotDataSource + +@optional + +/// @name Plot Symbols +/// @{ + +/** @brief (Optional) Gets a range of plot symbols for the given scatter plot. + * @param plot The scatter plot. + * @param indexRange The range of the data indexes of interest. + * @return An array of plot symbols. + **/ +-(NSArray *)symbolsForScatterPlot:(CPTScatterPlot *)plot recordIndexRange:(NSRange)indexRange; + +/** @brief (Optional) Gets a single plot symbol for the given scatter plot. + * This method will not be called if + * @link CPTScatterPlotDataSource::symbolsForScatterPlot:recordIndexRange: -symbolsForScatterPlot:recordIndexRange: @endlink + * is also implemented in the datasource. + * @param plot The scatter plot. + * @param index The data index of interest. + * @return The plot symbol to show for the point with the given index. + **/ +-(CPTPlotSymbol *)symbolForScatterPlot:(CPTScatterPlot *)plot recordIndex:(NSUInteger)index; + +/// @} + +@end + +#pragma mark - + +/** + * @brief Scatter plot delegate. + **/ +@protocol CPTScatterPlotDelegate + +@optional + +/// @name Point Selection +/// @{ + +/** @brief (Optional) Informs delegate that a point was touched. + * @param plot The scatter plot. + * @param index Index of touched point + **/ +-(void)scatterPlot:(CPTScatterPlot *)plot plotSymbolWasSelectedAtRecordIndex:(NSUInteger)index; + +/// @} + +@end + +#pragma mark - + +@interface CPTScatterPlot : CPTPlot { + @private + CPTScatterPlotInterpolation interpolation; + CPTLineStyle *dataLineStyle; + CPTPlotSymbol *plotSymbol; + CPTFill *areaFill; + CPTFill *areaFill2; + NSDecimal areaBaseValue; + NSDecimal areaBaseValue2; + CGFloat plotSymbolMarginForHitDetection; + NSArray *plotSymbols; +} + +@property (nonatomic, readwrite, copy) CPTLineStyle *dataLineStyle; +@property (nonatomic, readwrite, copy) CPTPlotSymbol *plotSymbol; +@property (nonatomic, readwrite, copy) CPTFill *areaFill; +@property (nonatomic, readwrite, copy) CPTFill *areaFill2; +@property (nonatomic, readwrite) NSDecimal areaBaseValue; +@property (nonatomic, readwrite) NSDecimal areaBaseValue2; +@property (nonatomic, readwrite, assign) CPTScatterPlotInterpolation interpolation; +@property (nonatomic, readwrite, assign) CGFloat plotSymbolMarginForHitDetection; + +/// @name Visible Points +/// @{ +-(NSUInteger)indexOfVisiblePointClosestToPlotAreaPoint:(CGPoint)viewPoint; +-(CGPoint)plotAreaPointOfVisiblePointAtIndex:(NSUInteger)index; +/// @} + +/// @name Plot Symbols +/// @{ +-(CPTPlotSymbol *)plotSymbolForRecordIndex:(NSUInteger)index; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTShadow.h b/CorePlot.framework/Versions/A/Headers/CPTShadow.h new file mode 100644 index 0000000..76093f9 --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTShadow.h @@ -0,0 +1,27 @@ +#import +#import + +@class CPTColor; + +@interface CPTShadow : NSObject { + @private + CGSize shadowOffset; + CGFloat shadowBlurRadius; + CPTColor *shadowColor; +} + +@property (nonatomic, readonly, assign) CGSize shadowOffset; +@property (nonatomic, readonly, assign) CGFloat shadowBlurRadius; +@property (nonatomic, readonly, retain) CPTColor *shadowColor; + +/// @name Factory Methods +/// @{ ++(id)shadow; +/// @} + +/// @name Drawing +/// @{ +-(void)setShadowInContext:(CGContextRef)theContext; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTTextLayer.h b/CorePlot.framework/Versions/A/Headers/CPTTextLayer.h new file mode 100644 index 0000000..5676c97 --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTTextLayer.h @@ -0,0 +1,29 @@ +#import "CPTLayer.h" +#import "CPTTextStyle.h" + +/// @file + +extern const CGFloat kCPTTextLayerMarginWidth; ///< Margin width around the text. + +@interface CPTTextLayer : CPTLayer { + @private + NSString *text; + CPTTextStyle *textStyle; +} + +@property (readwrite, copy, nonatomic) NSString *text; +@property (readwrite, retain, nonatomic) CPTTextStyle *textStyle; + +/// @name Initialization +/// @{ +-(id)initWithText:(NSString *)newText; +-(id)initWithText:(NSString *)newText style:(CPTTextStyle *)newStyle; +/// @} + +/// @name Layout +/// @{ +-(CGSize)sizeThatFits; +-(void)sizeToFit; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTTextStyle.h b/CorePlot.framework/Versions/A/Headers/CPTTextStyle.h new file mode 100644 index 0000000..eb0b47e --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTTextStyle.h @@ -0,0 +1,53 @@ +#import +#import + +/// @file + +@class CPTColor; + +/** + * @brief Enumeration of paragraph alignments. + **/ +typedef enum _CPTTextAlignment { + CPTTextAlignmentLeft, ///< Left alignment + CPTTextAlignmentCenter, ///< Center alignment + CPTTextAlignmentRight ///< Right alignment +} +CPTTextAlignment; + +@interface CPTTextStyle : NSObject { + @protected + NSString *fontName; + CGFloat fontSize; + CPTColor *color; + CPTTextAlignment textAlignment; +} + +@property (readonly, copy, nonatomic) NSString *fontName; +@property (readonly, assign, nonatomic) CGFloat fontSize; +@property (readonly, copy, nonatomic) CPTColor *color; +@property (readonly, assign, nonatomic) CPTTextAlignment textAlignment; + +/// @name Factory Methods +/// @{ ++(id)textStyle; +/// @} + +@end + +/** @category NSString(CPTTextStyleExtensions) + * @brief NSString extensions for drawing styled text. + **/ +@interface NSString(CPTTextStyleExtensions) + +/// @name Measurement +/// @{ +-(CGSize)sizeWithTextStyle:(CPTTextStyle *)style; +/// @} + +/// @name Drawing +/// @{ +-(void)drawInRect:(CGRect)rect withTextStyle:(CPTTextStyle *)style inContext:(CGContextRef)context; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTTheme.h b/CorePlot.framework/Versions/A/Headers/CPTTheme.h new file mode 100644 index 0000000..619da67 --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTTheme.h @@ -0,0 +1,53 @@ +#import + +/// @ingroup themeNames +/// @{ +extern NSString *const kCPTDarkGradientTheme; +extern NSString *const kCPTPlainBlackTheme; +extern NSString *const kCPTPlainWhiteTheme; +extern NSString *const kCPTSlateTheme; +extern NSString *const kCPTStocksTheme; +/// @} + +@class CPTGraph; +@class CPTPlotAreaFrame; +@class CPTAxisSet; +@class CPTMutableTextStyle; + +@interface CPTTheme : NSObject { + @private + Class graphClass; +} + +@property (nonatomic, readwrite, retain) Class graphClass; + +/// @name Theme Management +/// @{ ++(void)registerTheme:(Class)themeClass; ++(NSArray *)themeClasses; ++(CPTTheme *)themeNamed:(NSString *)theme; ++(NSString *)name; +/// @} + +/// @name Theme Usage +/// @{ +-(void)applyThemeToGraph:(CPTGraph *)graph; +/// @} + +@end + +/** @category CPTTheme(AbstractMethods) + * @brief CPTTheme abstract methods—must be overridden by subclasses + **/ +@interface CPTTheme(AbstractMethods) + +/// @name Theme Usage +/// @{ +-(id)newGraph; + +-(void)applyThemeToBackground:(CPTGraph *)graph; +-(void)applyThemeToPlotArea:(CPTPlotAreaFrame *)plotAreaFrame; +-(void)applyThemeToAxisSet:(CPTAxisSet *)axisSet; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTTimeFormatter.h b/CorePlot.framework/Versions/A/Headers/CPTTimeFormatter.h new file mode 100644 index 0000000..8dacbb2 --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTTimeFormatter.h @@ -0,0 +1,19 @@ +#import + +/// @file + +@interface CPTTimeFormatter : NSNumberFormatter { + @private + NSDateFormatter *dateFormatter; + NSDate *referenceDate; +} + +@property (nonatomic, readwrite, retain) NSDateFormatter *dateFormatter; +@property (nonatomic, readwrite, copy) NSDate *referenceDate; + +/// @name Initialization +/// @{ +-(id)initWithDateFormatter:(NSDateFormatter *)aDateFormatter; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTTradingRangePlot.h b/CorePlot.framework/Versions/A/Headers/CPTTradingRangePlot.h new file mode 100644 index 0000000..085334c --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTTradingRangePlot.h @@ -0,0 +1,70 @@ +#import "CPTDefinitions.h" +#import "CPTPlot.h" +#import + +/// @file + +@class CPTLineStyle; +@class CPTMutableNumericData; +@class CPTNumericData; +@class CPTTradingRangePlot; +@class CPTFill; + +/// @ingroup plotBindingsTradingRangePlot +/// @{ +extern NSString *const CPTTradingRangePlotBindingXValues; +extern NSString *const CPTTradingRangePlotBindingOpenValues; +extern NSString *const CPTTradingRangePlotBindingHighValues; +extern NSString *const CPTTradingRangePlotBindingLowValues; +extern NSString *const CPTTradingRangePlotBindingCloseValues; +/// @} + +/** + * @brief Enumeration of Quote plot render style types. + **/ +typedef enum _CPTTradingRangePlotStyle { + CPTTradingRangePlotStyleOHLC, ///< Open-High-Low-Close (OHLC) plot. + CPTTradingRangePlotStyleCandleStick ///< Candlestick plot. +} +CPTTradingRangePlotStyle; + +/** + * @brief Enumeration of Quote plot data source field types. + **/ +typedef enum _CPTTradingRangePlotField { + CPTTradingRangePlotFieldX, ///< X values. + CPTTradingRangePlotFieldOpen, ///< Open values. + CPTTradingRangePlotFieldHigh, ///< High values. + CPTTradingRangePlotFieldLow, ///< Low values. + CPTTradingRangePlotFieldClose ///< Close values. +} +CPTTradingRangePlotField; + +#pragma mark - + +@interface CPTTradingRangePlot : CPTPlot { + @private + CPTLineStyle *lineStyle; + CPTLineStyle *increaseLineStyle; + CPTLineStyle *decreaseLineStyle; + CPTFill *increaseFill; + CPTFill *decreaseFill; + + CPTTradingRangePlotStyle plotStyle; + + CGFloat barWidth; + CGFloat stickLength; + CGFloat barCornerRadius; +} + +@property (nonatomic, readwrite, copy) CPTLineStyle *lineStyle; +@property (nonatomic, readwrite, copy) CPTLineStyle *increaseLineStyle; +@property (nonatomic, readwrite, copy) CPTLineStyle *decreaseLineStyle; +@property (nonatomic, readwrite, copy) CPTFill *increaseFill; +@property (nonatomic, readwrite, copy) CPTFill *decreaseFill; +@property (nonatomic, readwrite, assign) CPTTradingRangePlotStyle plotStyle; +@property (nonatomic, readwrite, assign) CGFloat barWidth; // In view coordinates +@property (nonatomic, readwrite, assign) CGFloat stickLength; // In view coordinates +@property (nonatomic, readwrite, assign) CGFloat barCornerRadius; + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTUtilities.h b/CorePlot.framework/Versions/A/Headers/CPTUtilities.h new file mode 100644 index 0000000..763ad8c --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTUtilities.h @@ -0,0 +1,121 @@ +#import "CPTDefinitions.h" +#import + +/// @file + +#if __cplusplus +extern "C" { +#endif + +/// @name Convert NSDecimal to Primitive Types +/// @{ +int8_t CPTDecimalCharValue(NSDecimal decimalNumber); +int16_t CPTDecimalShortValue(NSDecimal decimalNumber); +int32_t CPTDecimalLongValue(NSDecimal decimalNumber); +int64_t CPTDecimalLongLongValue(NSDecimal decimalNumber); +int CPTDecimalIntValue(NSDecimal decimalNumber); +NSInteger CPTDecimalIntegerValue(NSDecimal decimalNumber); + +uint8_t CPTDecimalUnsignedCharValue(NSDecimal decimalNumber); +uint16_t CPTDecimalUnsignedShortValue(NSDecimal decimalNumber); +uint32_t CPTDecimalUnsignedLongValue(NSDecimal decimalNumber); +uint64_t CPTDecimalUnsignedLongLongValue(NSDecimal decimalNumber); +unsigned int CPTDecimalUnsignedIntValue(NSDecimal decimalNumber); +NSUInteger CPTDecimalUnsignedIntegerValue(NSDecimal decimalNumber); + +float CPTDecimalFloatValue(NSDecimal decimalNumber); +double CPTDecimalDoubleValue(NSDecimal decimalNumber); +CGFloat CPTDecimalCGFloatValue(NSDecimal decimalNumber); + +NSString *CPTDecimalStringValue(NSDecimal decimalNumber); + +/// @} + +/// @name Convert Primitive Types to NSDecimal +/// @{ +NSDecimal CPTDecimalFromChar(int8_t i); +NSDecimal CPTDecimalFromShort(int16_t i); +NSDecimal CPTDecimalFromLong(int32_t i); +NSDecimal CPTDecimalFromLongLong(int64_t i); +NSDecimal CPTDecimalFromInt(int i); +NSDecimal CPTDecimalFromInteger(NSInteger i); + +NSDecimal CPTDecimalFromUnsignedChar(uint8_t i); +NSDecimal CPTDecimalFromUnsignedShort(uint16_t i); +NSDecimal CPTDecimalFromUnsignedLong(uint32_t i); +NSDecimal CPTDecimalFromUnsignedLongLong(uint64_t i); +NSDecimal CPTDecimalFromUnsignedInt(unsigned int i); +NSDecimal CPTDecimalFromUnsignedInteger(NSUInteger i); + +NSDecimal CPTDecimalFromFloat(float f); +NSDecimal CPTDecimalFromDouble(double d); +NSDecimal CPTDecimalFromCGFloat(CGFloat f); + +NSDecimal CPTDecimalFromString(NSString *stringRepresentation); + +/// @} + +/// @name NSDecimal Arithmetic +/// @{ +NSDecimal CPTDecimalAdd(NSDecimal leftOperand, NSDecimal rightOperand); +NSDecimal CPTDecimalSubtract(NSDecimal leftOperand, NSDecimal rightOperand); +NSDecimal CPTDecimalMultiply(NSDecimal leftOperand, NSDecimal rightOperand); +NSDecimal CPTDecimalDivide(NSDecimal numerator, NSDecimal denominator); + +/// @} + +/// @name NSDecimal Comparison +/// @{ +BOOL CPTDecimalGreaterThan(NSDecimal leftOperand, NSDecimal rightOperand); +BOOL CPTDecimalGreaterThanOrEqualTo(NSDecimal leftOperand, NSDecimal rightOperand); +BOOL CPTDecimalLessThan(NSDecimal leftOperand, NSDecimal rightOperand); +BOOL CPTDecimalLessThanOrEqualTo(NSDecimal leftOperand, NSDecimal rightOperand); +BOOL CPTDecimalEquals(NSDecimal leftOperand, NSDecimal rightOperand); + +/// @} + +/// @name NSDecimal Utilities +/// @{ +NSDecimal CPTDecimalNaN(void); + +/// @} + +/// @name Ranges +/// @{ +NSRange CPTExpandedRange(NSRange range, NSInteger expandBy); + +/// @} + +/// @name Coordinates +/// @{ +CPTCoordinate CPTOrthogonalCoordinate(CPTCoordinate coord); + +/// @} + +/// @name Gradient Colors +/// @{ +CPTRGBAColor CPTRGBAColorFromCGColor(CGColorRef color); + +/// @} + +/// @name Quartz Pixel-Alignment Functions +/// @{ +CGPoint CPTAlignPointToUserSpace(CGContextRef context, CGPoint p); +CGSize CPTAlignSizeToUserSpace(CGContextRef context, CGSize s); +CGRect CPTAlignRectToUserSpace(CGContextRef context, CGRect r); + +CGPoint CPTAlignIntegralPointToUserSpace(CGContextRef context, CGPoint p); + +/// @} + +/// @name String Formatting for Core Graphics Structs +/// @{ +NSString *CPTStringFromPoint(CGPoint p); +NSString *CPTStringFromSize(CGSize s); +NSString *CPTStringFromRect(CGRect r); + +/// @} + +#if __cplusplus +} +#endif diff --git a/CorePlot.framework/Versions/A/Headers/CPTXYAxis.h b/CorePlot.framework/Versions/A/Headers/CPTXYAxis.h new file mode 100644 index 0000000..0a7dbc1 --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTXYAxis.h @@ -0,0 +1,18 @@ +#import "CPTAxis.h" +#import + +@class CPTConstraints; + +@interface CPTXYAxis : CPTAxis { + @private + NSDecimal orthogonalCoordinateDecimal; + CPTConstraints *axisConstraints; +} + +/// @name Positioning +/// @{ +@property (nonatomic, readwrite) NSDecimal orthogonalCoordinateDecimal; +@property (nonatomic, readwrite, retain) CPTConstraints *axisConstraints; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTXYAxisSet.h b/CorePlot.framework/Versions/A/Headers/CPTXYAxisSet.h new file mode 100644 index 0000000..82c63d3 --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTXYAxisSet.h @@ -0,0 +1,12 @@ +#import "CPTAxisSet.h" +#import + +@class CPTXYAxis; + +@interface CPTXYAxisSet : CPTAxisSet { +} + +@property (nonatomic, readonly, retain) CPTXYAxis *xAxis; +@property (nonatomic, readonly, retain) CPTXYAxis *yAxis; + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTXYGraph.h b/CorePlot.framework/Versions/A/Headers/CPTXYGraph.h new file mode 100644 index 0000000..913b6ef --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTXYGraph.h @@ -0,0 +1,16 @@ +#import "CPTDefinitions.h" +#import "CPTGraph.h" +#import + +@interface CPTXYGraph : CPTGraph { + @private + CPTScaleType xScaleType; + CPTScaleType yScaleType; +} + +/// @name Initialization +/// @{ +-(id)initWithFrame:(CGRect)newFrame xScaleType:(CPTScaleType)newXScaleType yScaleType:(CPTScaleType)newYScaleType; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CPTXYPlotSpace.h b/CorePlot.framework/Versions/A/Headers/CPTXYPlotSpace.h new file mode 100644 index 0000000..7780f21 --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CPTXYPlotSpace.h @@ -0,0 +1,25 @@ +#import "CPTDefinitions.h" +#import "CPTPlotSpace.h" + +@class CPTPlotRange; + +@interface CPTXYPlotSpace : CPTPlotSpace { + @private + CPTPlotRange *xRange; + CPTPlotRange *yRange; + CPTPlotRange *globalXRange; + CPTPlotRange *globalYRange; + CPTScaleType xScaleType; + CPTScaleType yScaleType; + CGPoint lastDragPoint; + BOOL isDragging; +} + +@property (nonatomic, readwrite, copy) CPTPlotRange *xRange; +@property (nonatomic, readwrite, copy) CPTPlotRange *yRange; +@property (nonatomic, readwrite, copy) CPTPlotRange *globalXRange; +@property (nonatomic, readwrite, copy) CPTPlotRange *globalYRange; +@property (nonatomic, readwrite, assign) CPTScaleType xScaleType; +@property (nonatomic, readwrite, assign) CPTScaleType yScaleType; + +@end diff --git a/CorePlot.framework/Versions/A/Headers/CorePlot.h b/CorePlot.framework/Versions/A/Headers/CorePlot.h new file mode 100644 index 0000000..5620a4a --- /dev/null +++ b/CorePlot.framework/Versions/A/Headers/CorePlot.h @@ -0,0 +1,61 @@ +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import diff --git a/CorePlot.framework/Versions/A/PrivateHeaders/CPTAxisLabelGroup.h b/CorePlot.framework/Versions/A/PrivateHeaders/CPTAxisLabelGroup.h new file mode 100644 index 0000000..eb19f37 --- /dev/null +++ b/CorePlot.framework/Versions/A/PrivateHeaders/CPTAxisLabelGroup.h @@ -0,0 +1,7 @@ +#import "CPTLayer.h" +#import + +@interface CPTAxisLabelGroup : CPTLayer { +} + +@end diff --git a/CorePlot.framework/Versions/A/PrivateHeaders/CPTGridLineGroup.h b/CorePlot.framework/Versions/A/PrivateHeaders/CPTGridLineGroup.h new file mode 100644 index 0000000..2803687 --- /dev/null +++ b/CorePlot.framework/Versions/A/PrivateHeaders/CPTGridLineGroup.h @@ -0,0 +1,14 @@ +#import "CPTLayer.h" + +@class CPTPlotArea; + +@interface CPTGridLineGroup : CPTLayer { + @private + __cpt_weak CPTPlotArea *plotArea; + BOOL major; +} + +@property (nonatomic, readwrite, assign) __cpt_weak CPTPlotArea *plotArea; +@property (nonatomic, readwrite) BOOL major; + +@end diff --git a/CorePlot.framework/Versions/A/PrivateHeaders/CPTGridLines.h b/CorePlot.framework/Versions/A/PrivateHeaders/CPTGridLines.h new file mode 100644 index 0000000..2519491 --- /dev/null +++ b/CorePlot.framework/Versions/A/PrivateHeaders/CPTGridLines.h @@ -0,0 +1,15 @@ +#import "CPTLayer.h" +#import + +@class CPTAxis; + +@interface CPTGridLines : CPTLayer { + @private + __cpt_weak CPTAxis *axis; + BOOL major; +} + +@property (nonatomic, readwrite, assign) __cpt_weak CPTAxis *axis; +@property (nonatomic, readwrite) BOOL major; + +@end diff --git a/CorePlot.framework/Versions/A/PrivateHeaders/CPTPlotGroup.h b/CorePlot.framework/Versions/A/PrivateHeaders/CPTPlotGroup.h new file mode 100644 index 0000000..5d75489 --- /dev/null +++ b/CorePlot.framework/Versions/A/PrivateHeaders/CPTPlotGroup.h @@ -0,0 +1,14 @@ +#import "CPTLayer.h" + +@class CPTPlot; + +@interface CPTPlotGroup : CPTLayer { +} + +/// @name Adding and Removing Plots +/// @{ +-(void)addPlot:(CPTPlot *)plot; +-(void)removePlot:(CPTPlot *)plot; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/PrivateHeaders/NSCoderExtensions.h b/CorePlot.framework/Versions/A/PrivateHeaders/NSCoderExtensions.h new file mode 100644 index 0000000..c73cff1 --- /dev/null +++ b/CorePlot.framework/Versions/A/PrivateHeaders/NSCoderExtensions.h @@ -0,0 +1,37 @@ +#import +#import + +/** @category NSCoder(CPTExtensions) + * @brief Core Plot extensions to NSCoder. + **/ +@interface NSCoder(CPTExtensions) + +/// @name Encoding Data +/// @{ +-(void)encodeCGFloat:(CGFloat)number forKey:(NSString *)key; +-(void)encodeCPTPoint:(CGPoint)point forKey:(NSString *)key; +-(void)encodeCPTSize:(CGSize)size forKey:(NSString *)key; +-(void)encodeCPTRect:(CGRect)rect forKey:(NSString *)key; + +-(void)encodeCGColorSpace:(CGColorSpaceRef)colorSpace forKey:(NSString *)key; +-(void)encodeCGPath:(CGPathRef)path forKey:(NSString *)key; +-(void)encodeCGImage:(CGImageRef)image forKey:(NSString *)key; + +-(void)encodeDecimal:(NSDecimal)number forKey:(NSString *)key; +/// @} + +/// @name Decoding Data +/// @{ +-(CGFloat)decodeCGFloatForKey:(NSString *)key; +-(CGPoint)decodeCPTPointForKey:(NSString *)key; +-(CGSize)decodeCPTSizeForKey:(NSString *)key; +-(CGRect)decodeCPTRectForKey:(NSString *)key; + +-(CGColorSpaceRef)newCGColorSpaceDecodeForKey:(NSString *)key; +-(CGPathRef)newCGPathDecodeForKey:(NSString *)key; +-(CGImageRef)newCGImageDecodeForKey:(NSString *)key; + +-(NSDecimal)decodeDecimalForKey:(NSString *)key; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/PrivateHeaders/NSDecimalNumberExtensions.h b/CorePlot.framework/Versions/A/PrivateHeaders/NSDecimalNumberExtensions.h new file mode 100644 index 0000000..0ee0687 --- /dev/null +++ b/CorePlot.framework/Versions/A/PrivateHeaders/NSDecimalNumberExtensions.h @@ -0,0 +1,9 @@ +#import +#import + +/** @category NSDecimalNumber(CPTExtensions) + * @brief Core Plot extensions to NSDecimalNumber. + **/ +@interface NSDecimalNumber(CPTExtensions) + +@end diff --git a/CorePlot.framework/Versions/A/PrivateHeaders/NSNumberExtensions.h b/CorePlot.framework/Versions/A/PrivateHeaders/NSNumberExtensions.h new file mode 100644 index 0000000..fdefdfb --- /dev/null +++ b/CorePlot.framework/Versions/A/PrivateHeaders/NSNumberExtensions.h @@ -0,0 +1,16 @@ +#import +#import + +/** @category NSNumber(CPTExtensions) + * @brief Core Plot extensions to NSNumber. + **/ +@interface NSNumber(CPTExtensions) + ++(NSNumber *)numberWithCGFloat:(CGFloat)number; + +-(CGFloat)cgFloatValue; +-(id)initWithCGFloat:(CGFloat)number; + +-(NSDecimalNumber *)decimalNumber; + +@end diff --git a/CorePlot.framework/Versions/A/PrivateHeaders/_CPTConstraintsFixed.h b/CorePlot.framework/Versions/A/PrivateHeaders/_CPTConstraintsFixed.h new file mode 100644 index 0000000..ddb4eb7 --- /dev/null +++ b/CorePlot.framework/Versions/A/PrivateHeaders/_CPTConstraintsFixed.h @@ -0,0 +1,26 @@ +#import "CPTConstraints.h" +#import + +@interface _CPTConstraintsFixed : CPTConstraints { + @private + CGFloat offset; + BOOL isFixedToLower; +} + +/// @name Initialization +/// @{ +-(id)initWithLowerOffset:(CGFloat)newOffset; +-(id)initWithUpperOffset:(CGFloat)newOffset; +/// @} + +/// @name Comparison +/// @{ +-(BOOL)isEqualToConstraint:(CPTConstraints *)otherConstraint; +/// @} + +/// @name Position +/// @{ +-(CGFloat)positionForLowerBound:(CGFloat)lowerBound upperBound:(CGFloat)upperBound; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/PrivateHeaders/_CPTConstraintsRelative.h b/CorePlot.framework/Versions/A/PrivateHeaders/_CPTConstraintsRelative.h new file mode 100644 index 0000000..8191a05 --- /dev/null +++ b/CorePlot.framework/Versions/A/PrivateHeaders/_CPTConstraintsRelative.h @@ -0,0 +1,24 @@ +#import "CPTConstraints.h" +#import + +@interface _CPTConstraintsRelative : CPTConstraints { + @private + CGFloat offset; +} + +/// @name Initialization +/// @{ +-(id)initWithRelativeOffset:(CGFloat)newOffset; +/// @} + +/// @name Comparison +/// @{ +-(BOOL)isEqualToConstraint:(CPTConstraints *)otherConstraint; +/// @} + +/// @name Position +/// @{ +-(CGFloat)positionForLowerBound:(CGFloat)lowerBound upperBound:(CGFloat)upperBound; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/PrivateHeaders/_CPTDarkGradientTheme.h b/CorePlot.framework/Versions/A/PrivateHeaders/_CPTDarkGradientTheme.h new file mode 100644 index 0000000..7d6e784 --- /dev/null +++ b/CorePlot.framework/Versions/A/PrivateHeaders/_CPTDarkGradientTheme.h @@ -0,0 +1,6 @@ +#import "_CPTXYTheme.h" + +@interface _CPTDarkGradientTheme : _CPTXYTheme { +} + +@end diff --git a/CorePlot.framework/Versions/A/PrivateHeaders/_CPTFillColor.h b/CorePlot.framework/Versions/A/PrivateHeaders/_CPTFillColor.h new file mode 100644 index 0000000..b9264df --- /dev/null +++ b/CorePlot.framework/Versions/A/PrivateHeaders/_CPTFillColor.h @@ -0,0 +1,20 @@ +#import "CPTFill.h" +#import + +@interface _CPTFillColor : CPTFill { + @private + CPTColor *fillColor; +} + +/// @name Initialization +/// @{ +-(id)initWithColor:(CPTColor *)aCcolor; +/// @} + +/// @name Drawing +/// @{ +-(void)fillRect:(CGRect)theRect inContext:(CGContextRef)theContext; +-(void)fillPathInContext:(CGContextRef)theContext; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/PrivateHeaders/_CPTFillGradient.h b/CorePlot.framework/Versions/A/PrivateHeaders/_CPTFillGradient.h new file mode 100644 index 0000000..31f7263 --- /dev/null +++ b/CorePlot.framework/Versions/A/PrivateHeaders/_CPTFillGradient.h @@ -0,0 +1,22 @@ +#import "CPTFill.h" +#import + +@class CPTGradient; + +@interface _CPTFillGradient : CPTFill { + @private + CPTGradient *fillGradient; +} + +/// @name Initialization +/// @{ +-(id)initWithGradient:(CPTGradient *)aGradient; +/// @} + +/// @name Drawing +/// @{ +-(void)fillRect:(CGRect)theRect inContext:(CGContextRef)theContext; +-(void)fillPathInContext:(CGContextRef)theContext; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/PrivateHeaders/_CPTFillImage.h b/CorePlot.framework/Versions/A/PrivateHeaders/_CPTFillImage.h new file mode 100644 index 0000000..5f1ca59 --- /dev/null +++ b/CorePlot.framework/Versions/A/PrivateHeaders/_CPTFillImage.h @@ -0,0 +1,22 @@ +#import "CPTFill.h" +#import + +@class CPTImage; + +@interface _CPTFillImage : CPTFill { + @private + CPTImage *fillImage; +} + +/// @name Initialization +/// @{ +-(id)initWithImage:(CPTImage *)anImage; +/// @} + +/// @name Drawing +/// @{ +-(void)fillRect:(CGRect)theRect inContext:(CGContextRef)theContext; +-(void)fillPathInContext:(CGContextRef)theContext; +/// @} + +@end diff --git a/CorePlot.framework/Versions/A/PrivateHeaders/_CPTPlainBlackTheme.h b/CorePlot.framework/Versions/A/PrivateHeaders/_CPTPlainBlackTheme.h new file mode 100644 index 0000000..46d25c3 --- /dev/null +++ b/CorePlot.framework/Versions/A/PrivateHeaders/_CPTPlainBlackTheme.h @@ -0,0 +1,6 @@ +#import "_CPTXYTheme.h" + +@interface _CPTPlainBlackTheme : _CPTXYTheme { +} + +@end diff --git a/CorePlot.framework/Versions/A/PrivateHeaders/_CPTPlainWhiteTheme.h b/CorePlot.framework/Versions/A/PrivateHeaders/_CPTPlainWhiteTheme.h new file mode 100644 index 0000000..5375648 --- /dev/null +++ b/CorePlot.framework/Versions/A/PrivateHeaders/_CPTPlainWhiteTheme.h @@ -0,0 +1,6 @@ +#import "_CPTXYTheme.h" + +@interface _CPTPlainWhiteTheme : _CPTXYTheme { +} + +@end diff --git a/CorePlot.framework/Versions/A/PrivateHeaders/_CPTSlateTheme.h b/CorePlot.framework/Versions/A/PrivateHeaders/_CPTSlateTheme.h new file mode 100644 index 0000000..ea3acba --- /dev/null +++ b/CorePlot.framework/Versions/A/PrivateHeaders/_CPTSlateTheme.h @@ -0,0 +1,6 @@ +#import "_CPTXYTheme.h" + +@interface _CPTSlateTheme : _CPTXYTheme { +} + +@end diff --git a/CorePlot.framework/Versions/A/PrivateHeaders/_CPTStocksTheme.h b/CorePlot.framework/Versions/A/PrivateHeaders/_CPTStocksTheme.h new file mode 100644 index 0000000..298917b --- /dev/null +++ b/CorePlot.framework/Versions/A/PrivateHeaders/_CPTStocksTheme.h @@ -0,0 +1,6 @@ +#import "_CPTXYTheme.h" + +@interface _CPTStocksTheme : _CPTXYTheme { +} + +@end diff --git a/CorePlot.framework/Versions/A/PrivateHeaders/_CPTXYTheme.h b/CorePlot.framework/Versions/A/PrivateHeaders/_CPTXYTheme.h new file mode 100644 index 0000000..0ba57a9 --- /dev/null +++ b/CorePlot.framework/Versions/A/PrivateHeaders/_CPTXYTheme.h @@ -0,0 +1,6 @@ +#import "CPTTheme.h" + +@interface _CPTXYTheme : CPTTheme { +} + +@end diff --git a/CorePlot.framework/Versions/A/Resources/English.lproj/InfoPlist.strings b/CorePlot.framework/Versions/A/Resources/English.lproj/InfoPlist.strings new file mode 100644 index 0000000000000000000000000000000000000000..5e45963c382ba690b781b953a00585212b898ac5 GIT binary patch literal 92 zcmW-XQ3`+{5C!MkQ~2$No+IcIkqMDxWCV8j>LCj|yTg2Mz+o9F%uHlf9u}h9EuK`F a!Y*1dX%G66ZqL#C$|bw0ZoP5@jOGW1ArT7z literal 0 HcmV?d00001 diff --git a/CorePlot.framework/Versions/A/Resources/Info.plist b/CorePlot.framework/Versions/A/Resources/Info.plist new file mode 100644 index 0000000..fe85499 --- /dev/null +++ b/CorePlot.framework/Versions/A/Resources/Info.plist @@ -0,0 +1,38 @@ + + + + + BuildMachineOSBuild + 11D50 + CFBundleDevelopmentRegion + English + CFBundleExecutable + CorePlot + CFBundleIdentifier + org.coreplot.CorePlot + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + CorePlot + CFBundlePackageType + FMWK + CFBundleSignature + ???? + CFBundleVersion + 1.0 + DTCompiler + + DTPlatformBuild + 4D199 + DTPlatformVersion + GM + DTSDKBuild + 11C63 + DTSDKName + macosx10.7 + DTXcode + 0420 + DTXcodeBuild + 4D199 + + diff --git a/CorePlot.framework/Versions/A/Resources/License.txt b/CorePlot.framework/Versions/A/Resources/License.txt new file mode 100644 index 0000000..d2a98e2 --- /dev/null +++ b/CorePlot.framework/Versions/A/Resources/License.txt @@ -0,0 +1,9 @@ +Copyright (c) 2012, Drew McCormack, Brad Larson, Eric Skroch, Barry Wark, Dirkjan Krijnders, Rick Maddy, Vijay Kalusani, Caleb Cannon, Jeff Buck, Thomas Elstner, Jeroen Leenarts, Craig Hockenberry, Hartwig Wiesmann, Koen van der Drift. +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 Core Plot Project 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT HOLDER OR CONTRIBUTORS 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. diff --git a/CorePlot.framework/Versions/Current b/CorePlot.framework/Versions/Current new file mode 120000 index 0000000..8c7e5a6 --- /dev/null +++ b/CorePlot.framework/Versions/Current @@ -0,0 +1 @@ +A \ No newline at end of file From ed9e1313ec723d3e8a3e43259f3151df0a1fb4e8 Mon Sep 17 00:00:00 2001 From: mrsteveman1 Date: Thu, 2 Aug 2012 01:01:30 -0400 Subject: [PATCH 46/46] copy coreplot into bundle during build --- BitTicker.xcodeproj/project.pbxproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/BitTicker.xcodeproj/project.pbxproj b/BitTicker.xcodeproj/project.pbxproj index 479683d..6c68bfb 100755 --- a/BitTicker.xcodeproj/project.pbxproj +++ b/BitTicker.xcodeproj/project.pbxproj @@ -32,6 +32,7 @@ AAA32C8615CA3D0500C45B89 /* AFPropertyListRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = AAA32C7915CA3D0500C45B89 /* AFPropertyListRequestOperation.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; AAA32C8715CA3D0500C45B89 /* AFURLConnectionOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = AAA32C7B15CA3D0500C45B89 /* AFURLConnectionOperation.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; AAA32C8815CA3D0500C45B89 /* AFXMLRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = AAA32C7D15CA3D0500C45B89 /* AFXMLRequestOperation.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + AAA32C8A15CA410300C45B89 /* CorePlot.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = AAA32C6815CA3CBB00C45B89 /* CorePlot.framework */; }; AAD1726D1399BD3500B505B0 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AAD1726C1399BD3500B505B0 /* Security.framework */; }; AAD64420137B680E00589EAC /* Logo.icns in Resources */ = {isa = PBXBuildFile; fileRef = AAD6441F137B680E00589EAC /* Logo.icns */; }; /* End PBXBuildFile section */ @@ -43,6 +44,7 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( + AAA32C8A15CA410300C45B89 /* CorePlot.framework in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; };