diff --git a/CTCDefaults.h b/CTCDefaults.h index 9d7c146..35c5824 100644 --- a/CTCDefaults.h +++ b/CTCDefaults.h @@ -21,6 +21,8 @@ extern NSString * const kCTCDefaultsServiceFeedURLPrefix; + (void)refreshLoginItemStatus; + (BOOL)isConfigurationValid; ++ (BOOL)isFeedURLValid; ++ (BOOL)isTorrentsSavePathValid; + (NSString *)appName; diff --git a/CTCDefaults.m b/CTCDefaults.m index 6c89888..68f6d21 100644 --- a/CTCDefaults.m +++ b/CTCDefaults.m @@ -91,23 +91,8 @@ + (void)refreshLoginItemStatus { [CTCLoginItems toggleRegisteredAsLoginItem:[NSUserDefaults.standardUserDefaults boolForKey:kCTCDefaultsOpenAtLoginKey]]; } -+ (BOOL)isConfigurationValid { - // Validate torrent folder. This should never fail! - NSString *torrentFolder = self.torrentsSavePath; - - if (!torrentFolder) return NO; - - BOOL isDirectory = NO; - if ([NSFileManager.defaultManager fileExistsAtPath:torrentFolder - isDirectory:&isDirectory]) { - if (!isDirectory) return NO; - } - else { - return NO; - } - - // Most importantly, validate feed URL - NSString *feedURL = CTCDefaults.feedURL; ++ (BOOL)isFeedURLValid { + NSString *feedURL = CTCDefaults.feedURL; if (![feedURL hasPrefix:kCTCDefaultsServiceFeedURLPrefix]) { // The URL should start with the prefix! NSLog(@"Feed URL (%@) does not start with expected prefix (%@)", feedURL, kCTCDefaultsServiceFeedURLPrefix); @@ -122,6 +107,27 @@ + (BOOL)isConfigurationValid { return YES; } ++ (BOOL)isTorrentsSavePathValid { + NSString *torrentFolder = self.torrentsSavePath; + + if (!torrentFolder) return NO; + + BOOL isDirectory = NO; + if ([NSFileManager.defaultManager fileExistsAtPath:torrentFolder + isDirectory:&isDirectory]) { + if (!isDirectory) return NO; + } + else { + return NO; + } + + return YES; +} + ++ (BOOL)isConfigurationValid { + return self.isTorrentsSavePathValid && self.isFeedURLValid; +} + + (NSString *)infoStringForKey:(NSString *)key { return [NSBundle.mainBundle objectForInfoDictionaryKey:key]; } diff --git a/CTCPreferencesController.m b/CTCPreferencesController.m index f2d848c..16b6a44 100644 --- a/CTCPreferencesController.m +++ b/CTCPreferencesController.m @@ -1,6 +1,7 @@ #import "CTCPreferencesController.h" #import "CTCDefaults.h" #import "CTCScheduler.h" +#import "NSWindow+ShakeAnimation.h" @implementation CTCPreferencesController @@ -12,7 +13,7 @@ - (void)awakeFromNib { if (!CTCDefaults.isConfigurationValid) [self showWindow:self]; } -- (void)showWindow:(id)sender { +- (IBAction)showWindow:(id)sender { [NSApp activateIgnoringOtherApps:YES]; [super showWindow:sender]; } @@ -20,17 +21,22 @@ - (void)showWindow:(id)sender { - (IBAction)savePreferences:(id)sender { [CTCDefaults save]; - // If the feed URL is invalid, just warn user - if (!CTCDefaults.isConfigurationValid) { - [self showBadURLAlert]; - return; + if (CTCDefaults.isConfigurationValid) { + // Hide the Preferences window + [self.window close]; + + // Also force check + [CTCScheduler.sharedScheduler forceCheck]; + } + else { + // Show the Feeds tab because all possible invalid inputs are currently there + [self showFeeds:self]; + + // Shake the window to signal invalid input + [self.window performShakeAnimation]; + + //[self showBadURLAlert]; } - - // Hide the Preferences window - [self.window close]; - - // Also force check - [CTCScheduler.sharedScheduler forceCheck]; } - (IBAction)showFeeds:(id)sender { @@ -44,8 +50,6 @@ - (IBAction)showTweaks:(id)sender { } - (void)showBadURLAlert { - [self showFeeds:self]; - // Show an alert warning the user: the feed URL is invalid NSAlert *badURLAlert = NSAlert.new; badURLAlert.messageText = NSLocalizedString(@"badurl", @"Message for bad feed URL in preferences"); diff --git a/Catch.xcodeproj/project.pbxproj b/Catch.xcodeproj/project.pbxproj index fed8116..c3fd473 100644 --- a/Catch.xcodeproj/project.pbxproj +++ b/Catch.xcodeproj/project.pbxproj @@ -35,6 +35,8 @@ 44717ADD1913AF3400580054 /* CTCFileUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 44717AA91913173600580054 /* CTCFileUtils.m */; }; 448CC7E817FE1F840063D3FD /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 448CC7E617FE1F840063D3FD /* Sparkle.framework */; }; 44EAD12E1916682D002C7443 /* CTCApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = 44EAD12D1916682D002C7443 /* CTCApplication.m */; }; + 44FBC67B196312A400434B01 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 44FBC67A196312A400434B01 /* QuartzCore.framework */; }; + 44FBC67E196316E900434B01 /* NSWindow+ShakeAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 44FBC67D196316E900434B01 /* NSWindow+ShakeAnimation.m */; }; 8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; }; 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; }; /* End PBXBuildFile section */ @@ -147,6 +149,9 @@ 448CECE219157BDF0075ADD9 /* zh-Hant */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/UI.strings"; sourceTree = ""; }; 44EAD12C1916682D002C7443 /* CTCApplication.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CTCApplication.h; sourceTree = ""; }; 44EAD12D1916682D002C7443 /* CTCApplication.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CTCApplication.m; sourceTree = ""; }; + 44FBC67A196312A400434B01 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; + 44FBC67C196316E900434B01 /* NSWindow+ShakeAnimation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSWindow+ShakeAnimation.h"; sourceTree = ""; }; + 44FBC67D196316E900434B01 /* NSWindow+ShakeAnimation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSWindow+ShakeAnimation.m"; sourceTree = ""; }; 8D1107310486CEB800E47090 /* Catch-Info.plist */ = {isa = PBXFileReference; explicitFileType = text.plist.xml; fileEncoding = 4; path = "Catch-Info.plist"; sourceTree = ""; }; 8D1107320486CEB800E47090 /* Catch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Catch.app; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -172,6 +177,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 44FBC67B196312A400434B01 /* QuartzCore.framework in Frameworks */, 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */, 38F9B09111C98E9E0021C540 /* Automator.framework in Frameworks */, 448CC7E817FE1F840063D3FD /* Sparkle.framework in Frameworks */, @@ -242,6 +248,7 @@ 29B97323FDCFA39411CA2CEA /* Frameworks */ = { isa = PBXGroup; children = ( + 44FBC67A196312A400434B01 /* QuartzCore.framework */, 29B97325FDCFA39411CA2CEA /* Foundation.framework */, 29B97324FDCFA39411CA2CEA /* AppKit.framework */, 38F9B09011C98E9E0021C540 /* Automator.framework */, @@ -257,6 +264,8 @@ children = ( 4456EE5F1916D20000B3FF1A /* NSDate+TimeOfDayMath.h */, 4456EE601916D20000B3FF1A /* NSDate+TimeOfDayMath.m */, + 44FBC67C196316E900434B01 /* NSWindow+ShakeAnimation.h */, + 44FBC67D196316E900434B01 /* NSWindow+ShakeAnimation.m */, ); name = Categories; sourceTree = ""; @@ -529,6 +538,7 @@ 38D39B6011C3A6E100C17C80 /* CTCMenuController.m in Sources */, 3803C7AE11C3DA9300FC08DB /* CTCAppDelegate.m in Sources */, 446298EB191655080045EC72 /* CTCPreferencesController.m in Sources */, + 44FBC67E196316E900434B01 /* NSWindow+ShakeAnimation.m in Sources */, 44717AA71913072200580054 /* CTCLoginItems.m in Sources */, 3803C7EC11C3E7AD00FC08DB /* CTCScheduler.m in Sources */, 44717AAA1913173600580054 /* CTCFileUtils.m in Sources */, diff --git a/NSWindow+ShakeAnimation.h b/NSWindow+ShakeAnimation.h new file mode 100644 index 0000000..66d2788 --- /dev/null +++ b/NSWindow+ShakeAnimation.h @@ -0,0 +1,8 @@ +#import + + +@interface NSWindow (ShakeAnimation) + +- (void)performShakeAnimation; + +@end diff --git a/NSWindow+ShakeAnimation.m b/NSWindow+ShakeAnimation.m new file mode 100644 index 0000000..bc04287 --- /dev/null +++ b/NSWindow+ShakeAnimation.m @@ -0,0 +1,30 @@ +#import +#import "NSWindow+ShakeAnimation.h" + + +@implementation NSWindow (ShakeAnimation) + +- (void)performShakeAnimation { + // Stolen from: http://stackoverflow.com/questions/10517386 + static int numberOfShakes = 2; + static float durationOfShake = 0.3f; + static float vigourOfShake = 0.015f; + + CGRect frame = self.frame; + CAKeyframeAnimation *shakeAnimation = CAKeyframeAnimation.animation; + + CGMutablePathRef shakePath = CGPathCreateMutable(); + CGPathMoveToPoint(shakePath, NULL, NSMinX(frame), NSMinY(frame)); + for (NSInteger index = 0; index < numberOfShakes; index++){ + CGPathAddLineToPoint(shakePath, NULL, NSMinX(frame) - frame.size.width * vigourOfShake, NSMinY(frame)); + CGPathAddLineToPoint(shakePath, NULL, NSMinX(frame) + frame.size.width * vigourOfShake, NSMinY(frame)); + } + CGPathCloseSubpath(shakePath); + shakeAnimation.path = shakePath; + shakeAnimation.duration = durationOfShake; + + self.animations = @{@"frameOrigin": shakeAnimation}; + [self.animator setFrameOrigin:self.frame.origin]; +} + +@end