diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..3a5cc350 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +.DS_Store +xcuserdata/ +compile/ +build/ +DerivedData/ +*.xccheckout +release/ +debug.plist +Examples/debug.html diff --git a/Aerial Test/AppDelegate.swift b/Aerial Test/AppDelegate.swift new file mode 100644 index 00000000..2cfd72c3 --- /dev/null +++ b/Aerial Test/AppDelegate.swift @@ -0,0 +1,27 @@ +// +// AppDelegate.swift +// Aerial Test +// +// Created by John Coates on 10/23/15. +// Copyright © 2015 John Coates. All rights reserved. +// + +import Cocoa + +@NSApplicationMain +class AppDelegate: NSObject, NSApplicationDelegate { + + @IBOutlet weak var window: NSWindow! + @IBOutlet var preferencesWindowController:PreferencesWindowController! + + + func applicationDidFinishLaunching(aNotification: NSNotification) { + } + + func applicationWillTerminate(aNotification: NSNotification) { + // Insert code here to tear down your application + } + + +} + diff --git a/Aerial Test/Assets.xcassets/AppIcon.appiconset/Contents.json b/Aerial Test/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..2db2b1c7 --- /dev/null +++ b/Aerial Test/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,58 @@ +{ + "images" : [ + { + "idiom" : "mac", + "size" : "16x16", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "16x16", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "32x32", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "32x32", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "128x128", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "128x128", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "256x256", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "256x256", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "512x512", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "512x512", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Aerial Test/Base.lproj/MainMenu.xib b/Aerial Test/Base.lproj/MainMenu.xib new file mode 100644 index 00000000..27cff1d2 --- /dev/null +++ b/Aerial Test/Base.lproj/MainMenu.xib @@ -0,0 +1,875 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Aerial Test/Info.plist b/Aerial Test/Info.plist new file mode 100644 index 00000000..d09bbe17 --- /dev/null +++ b/Aerial Test/Info.plist @@ -0,0 +1,39 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + Copyright © 2015 John Coates. All rights reserved. + NSMainNibFile + MainMenu + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + NSPrincipalClass + NSApplication + + diff --git a/Aerial.xcodeproj/project.pbxproj b/Aerial.xcodeproj/project.pbxproj index c86202a0..2a017000 100644 --- a/Aerial.xcodeproj/project.pbxproj +++ b/Aerial.xcodeproj/project.pbxproj @@ -7,18 +7,52 @@ objects = { /* Begin PBXBuildFile section */ - FACAF1A91BD9FC6000E539DC /* AerialView.h in Headers */ = {isa = PBXBuildFile; fileRef = FACAF1A81BD9FC6000E539DC /* AerialView.h */; }; - FACAF1AB1BD9FC6000E539DC /* AerialView.m in Sources */ = {isa = PBXBuildFile; fileRef = FACAF1AA1BD9FC6000E539DC /* AerialView.m */; }; + FA143CCB1BDA270A0041A82B /* PreferencesWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = FA143CCA1BDA270A0041A82B /* PreferencesWindow.xib */; settings = {ASSET_TAGS = (); }; }; + FA143CCD1BDA2EE00041A82B /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA143CCC1BDA2EE00041A82B /* PreferencesWindowController.swift */; settings = {ASSET_TAGS = (); }; }; + FA143CD91BDA3E880041A82B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA143CD81BDA3E880041A82B /* AppDelegate.swift */; }; + FA143CDB1BDA3E880041A82B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FA143CDA1BDA3E880041A82B /* Assets.xcassets */; }; + FA143CDE1BDA3E880041A82B /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = FA143CDC1BDA3E880041A82B /* MainMenu.xib */; }; + FA143CE31BDA3E910041A82B /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA143CCC1BDA2EE00041A82B /* PreferencesWindowController.swift */; settings = {ASSET_TAGS = (); }; }; + FA143CE61BDA3EEF0041A82B /* AVKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA143CE51BDA3EEF0041A82B /* AVKit.framework */; }; + FA143CE81BDA49BA0041A82B /* AerialVideo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA143CE71BDA49BA0041A82B /* AerialVideo.swift */; settings = {ASSET_TAGS = (); }; }; + FA143CE91BDA4A9E0041A82B /* AerialVideo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA143CE71BDA49BA0041A82B /* AerialVideo.swift */; settings = {ASSET_TAGS = (); }; }; + FA6746D01BDB7B2B00769DC6 /* CheckCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA6746CF1BDB7B2B00769DC6 /* CheckCellView.swift */; settings = {ASSET_TAGS = (); }; }; + FA6746D11BDB7B3300769DC6 /* CheckCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA6746CF1BDB7B2B00769DC6 /* CheckCellView.swift */; settings = {ASSET_TAGS = (); }; }; + FA6746D41BDB850000769DC6 /* icon-day.pdf in Resources */ = {isa = PBXBuildFile; fileRef = FA6746D21BDB850000769DC6 /* icon-day.pdf */; settings = {ASSET_TAGS = (); }; }; + FA6746D51BDB850000769DC6 /* icon-day.pdf in Resources */ = {isa = PBXBuildFile; fileRef = FA6746D21BDB850000769DC6 /* icon-day.pdf */; settings = {ASSET_TAGS = (); }; }; + FA6746D61BDB850000769DC6 /* icon-night.pdf in Resources */ = {isa = PBXBuildFile; fileRef = FA6746D31BDB850000769DC6 /* icon-night.pdf */; settings = {ASSET_TAGS = (); }; }; + FA6746D71BDB850000769DC6 /* icon-night.pdf in Resources */ = {isa = PBXBuildFile; fileRef = FA6746D31BDB850000769DC6 /* icon-night.pdf */; settings = {ASSET_TAGS = (); }; }; + FA6746D81BDB9DCE00769DC6 /* AerialView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FACAF1B31BD9FEEC00E539DC /* AerialView.swift */; settings = {ASSET_TAGS = (); }; }; + FACAF1B41BD9FEEC00E539DC /* AerialView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FACAF1B31BD9FEEC00E539DC /* AerialView.swift */; settings = {ASSET_TAGS = (); }; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + FA143CCA1BDA270A0041A82B /* PreferencesWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = PreferencesWindow.xib; path = Aerial/PreferencesWindow.xib; sourceTree = ""; }; + FA143CCC1BDA2EE00041A82B /* PreferencesWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = ""; }; + FA143CD61BDA3E880041A82B /* Aerial Test.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Aerial Test.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + FA143CD81BDA3E880041A82B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + FA143CDA1BDA3E880041A82B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + FA143CDD1BDA3E880041A82B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + FA143CDF1BDA3E880041A82B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + FA143CE51BDA3EEF0041A82B /* AVKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVKit.framework; path = System/Library/Frameworks/AVKit.framework; sourceTree = SDKROOT; }; + FA143CE71BDA49BA0041A82B /* AerialVideo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AerialVideo.swift; sourceTree = ""; }; + FA6746CF1BDB7B2B00769DC6 /* CheckCellView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckCellView.swift; sourceTree = ""; }; + FA6746D21BDB850000769DC6 /* icon-day.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; name = "icon-day.pdf"; path = "Aerial/icon-day.pdf"; sourceTree = ""; }; + FA6746D31BDB850000769DC6 /* icon-night.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; name = "icon-night.pdf"; path = "Aerial/icon-night.pdf"; sourceTree = ""; }; FACAF1A51BD9FC6000E539DC /* Aerial.saver */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Aerial.saver; sourceTree = BUILT_PRODUCTS_DIR; }; - FACAF1A81BD9FC6000E539DC /* AerialView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AerialView.h; sourceTree = ""; }; - FACAF1AA1BD9FC6000E539DC /* AerialView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AerialView.m; sourceTree = ""; }; - FACAF1AC1BD9FC6000E539DC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + FACAF1AC1BD9FC6000E539DC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Aerial/Info.plist; sourceTree = ""; }; + FACAF1B31BD9FEEC00E539DC /* AerialView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AerialView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + FA143CD31BDA3E880041A82B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + FA143CE61BDA3EEF0041A82B /* AVKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; FACAF1A11BD9FC6000E539DC /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -29,10 +63,43 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + FA143CD71BDA3E880041A82B /* Aerial Test */ = { + isa = PBXGroup; + children = ( + FA143CD81BDA3E880041A82B /* AppDelegate.swift */, + FA143CDA1BDA3E880041A82B /* Assets.xcassets */, + FA143CDC1BDA3E880041A82B /* MainMenu.xib */, + FA143CDF1BDA3E880041A82B /* Info.plist */, + ); + path = "Aerial Test"; + sourceTree = ""; + }; + FA2D7AA01BDD849E009EA54C /* Frameworks */ = { + isa = PBXGroup; + children = ( + FA143CE51BDA3EEF0041A82B /* AVKit.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + FA2D7AA11BDD84A7009EA54C /* Resources */ = { + isa = PBXGroup; + children = ( + FA143CCA1BDA270A0041A82B /* PreferencesWindow.xib */, + FA6746D21BDB850000769DC6 /* icon-day.pdf */, + FA6746D31BDB850000769DC6 /* icon-night.pdf */, + FACAF1AC1BD9FC6000E539DC /* Info.plist */, + ); + name = Resources; + sourceTree = ""; + }; FACAF19B1BD9FC6000E539DC = { isa = PBXGroup; children = ( + FA2D7AA11BDD84A7009EA54C /* Resources */, + FA2D7AA01BDD849E009EA54C /* Frameworks */, FACAF1A71BD9FC6000E539DC /* Aerial */, + FA143CD71BDA3E880041A82B /* Aerial Test */, FACAF1A61BD9FC6000E539DC /* Products */, ); sourceTree = ""; @@ -41,6 +108,7 @@ isa = PBXGroup; children = ( FACAF1A51BD9FC6000E539DC /* Aerial.saver */, + FA143CD61BDA3E880041A82B /* Aerial Test.app */, ); name = Products; sourceTree = ""; @@ -48,9 +116,10 @@ FACAF1A71BD9FC6000E539DC /* Aerial */ = { isa = PBXGroup; children = ( - FACAF1A81BD9FC6000E539DC /* AerialView.h */, - FACAF1AA1BD9FC6000E539DC /* AerialView.m */, - FACAF1AC1BD9FC6000E539DC /* Info.plist */, + FACAF1B31BD9FEEC00E539DC /* AerialView.swift */, + FA143CCC1BDA2EE00041A82B /* PreferencesWindowController.swift */, + FA143CE71BDA49BA0041A82B /* AerialVideo.swift */, + FA6746CF1BDB7B2B00769DC6 /* CheckCellView.swift */, ); path = Aerial; sourceTree = ""; @@ -62,13 +131,29 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - FACAF1A91BD9FC6000E539DC /* AerialView.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ + FA143CD51BDA3E880041A82B /* Aerial Test */ = { + isa = PBXNativeTarget; + buildConfigurationList = FA143CE01BDA3E880041A82B /* Build configuration list for PBXNativeTarget "Aerial Test" */; + buildPhases = ( + FA143CD21BDA3E880041A82B /* Sources */, + FA143CD31BDA3E880041A82B /* Frameworks */, + FA143CD41BDA3E880041A82B /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Aerial Test"; + productName = "Aerial Test"; + productReference = FA143CD61BDA3E880041A82B /* Aerial Test.app */; + productType = "com.apple.product-type.application"; + }; FACAF1A41BD9FC6000E539DC /* Aerial */ = { isa = PBXNativeTarget; buildConfigurationList = FACAF1AF1BD9FC6000E539DC /* Build configuration list for PBXNativeTarget "Aerial" */; @@ -93,9 +178,13 @@ FACAF19C1BD9FC6000E539DC /* Project object */ = { isa = PBXProject; attributes = { + LastSwiftUpdateCheck = 0700; LastUpgradeCheck = 0700; ORGANIZATIONNAME = "John Coates"; TargetAttributes = { + FA143CD51BDA3E880041A82B = { + CreatedOnToolsVersion = 7.0; + }; FACAF1A41BD9FC6000E539DC = { CreatedOnToolsVersion = 7.0; }; @@ -107,6 +196,7 @@ hasScannedForEncodings = 0; knownRegions = ( en, + Base, ); mainGroup = FACAF19B1BD9FC6000E539DC; productRefGroup = FACAF1A61BD9FC6000E539DC /* Products */; @@ -114,32 +204,102 @@ projectRoot = ""; targets = ( FACAF1A41BD9FC6000E539DC /* Aerial */, + FA143CD51BDA3E880041A82B /* Aerial Test */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + FA143CD41BDA3E880041A82B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + FA143CDB1BDA3E880041A82B /* Assets.xcassets in Resources */, + FA143CDE1BDA3E880041A82B /* MainMenu.xib in Resources */, + FA6746D71BDB850000769DC6 /* icon-night.pdf in Resources */, + FA6746D51BDB850000769DC6 /* icon-day.pdf in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; FACAF1A31BD9FC6000E539DC /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + FA143CCB1BDA270A0041A82B /* PreferencesWindow.xib in Resources */, + FA6746D61BDB850000769DC6 /* icon-night.pdf in Resources */, + FA6746D41BDB850000769DC6 /* icon-day.pdf in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + FA143CD21BDA3E880041A82B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + FA143CE31BDA3E910041A82B /* PreferencesWindowController.swift in Sources */, + FA143CD91BDA3E880041A82B /* AppDelegate.swift in Sources */, + FA143CE91BDA4A9E0041A82B /* AerialVideo.swift in Sources */, + FA6746D11BDB7B3300769DC6 /* CheckCellView.swift in Sources */, + FA6746D81BDB9DCE00769DC6 /* AerialView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; FACAF1A01BD9FC6000E539DC /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - FACAF1AB1BD9FC6000E539DC /* AerialView.m in Sources */, + FA143CCD1BDA2EE00041A82B /* PreferencesWindowController.swift in Sources */, + FACAF1B41BD9FEEC00E539DC /* AerialView.swift in Sources */, + FA143CE81BDA49BA0041A82B /* AerialVideo.swift in Sources */, + FA6746D01BDB7B2B00769DC6 /* CheckCellView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXVariantGroup section */ + FA143CDC1BDA3E880041A82B /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + FA143CDD1BDA3E880041A82B /* Base */, + ); + name = MainMenu.xib; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + /* Begin XCBuildConfiguration section */ + FA143CE11BDA3E880041A82B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "-"; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = "Aerial Test/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.10; + PRODUCT_BUNDLE_IDENTIFIER = "com.johncoates.Aerial-Test"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + FA143CE21BDA3E880041A82B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "-"; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = "Aerial Test/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.10; + PRODUCT_BUNDLE_IDENTIFIER = "com.johncoates.Aerial-Test"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; FACAF1AD1BD9FC6000E539DC /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -175,7 +335,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.10; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -211,7 +371,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.10; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; }; @@ -220,11 +380,17 @@ FACAF1B01BD9FC6000E539DC /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; INFOPLIST_FILE = Aerial/Info.plist; INSTALL_PATH = "$(HOME)/Library/Screen Savers"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.johncoates.Aerial; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Aerial/Aerial-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; WRAPPER_EXTENSION = saver; }; name = Debug; @@ -232,11 +398,16 @@ FACAF1B11BD9FC6000E539DC /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; INFOPLIST_FILE = Aerial/Info.plist; INSTALL_PATH = "$(HOME)/Library/Screen Savers"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.johncoates.Aerial; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Aerial/Aerial-Bridging-Header.h"; WRAPPER_EXTENSION = saver; }; name = Release; @@ -244,6 +415,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + FA143CE01BDA3E880041A82B /* Build configuration list for PBXNativeTarget "Aerial Test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + FA143CE11BDA3E880041A82B /* Debug */, + FA143CE21BDA3E880041A82B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; FACAF19F1BD9FC6000E539DC /* Build configuration list for PBXProject "Aerial" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -260,6 +440,7 @@ FACAF1B11BD9FC6000E539DC /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; diff --git a/Aerial.xcodeproj/xcuserdata/macbook.xcuserdatad/xcschemes/Aerial.xcscheme b/Aerial.xcodeproj/xcuserdata/macbook.xcuserdatad/xcschemes/Aerial.xcscheme index dbd16b19..904547b4 100644 --- a/Aerial.xcodeproj/xcuserdata/macbook.xcuserdatad/xcschemes/Aerial.xcscheme +++ b/Aerial.xcodeproj/xcuserdata/macbook.xcuserdatad/xcschemes/Aerial.xcscheme @@ -5,6 +5,22 @@ + + + + + + + + + + @@ -35,6 +60,15 @@ savedToolIdentifier = "" useCustomWorkingDirectory = "NO" debugDocumentVersioning = "YES"> + + + + diff --git a/Aerial.xcodeproj/xcuserdata/macbook.xcuserdatad/xcschemes/xcschememanagement.plist b/Aerial.xcodeproj/xcuserdata/macbook.xcuserdatad/xcschemes/xcschememanagement.plist index 895aea8f..b1affd9b 100644 --- a/Aerial.xcodeproj/xcuserdata/macbook.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Aerial.xcodeproj/xcuserdata/macbook.xcuserdatad/xcschemes/xcschememanagement.plist @@ -4,6 +4,11 @@ SchemeUserState + Aerial Test.xcscheme + + orderHint + 1 + Aerial.xcscheme orderHint @@ -12,6 +17,11 @@ SuppressBuildableAutocreation + FA143CD51BDA3E880041A82B + + primary + + FACAF1A41BD9FC6000E539DC primary diff --git a/Aerial/Aerial-Bridging-Header.h b/Aerial/Aerial-Bridging-Header.h new file mode 100644 index 00000000..1b2cb5d6 --- /dev/null +++ b/Aerial/Aerial-Bridging-Header.h @@ -0,0 +1,4 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + diff --git a/Aerial/AerialVideo.swift b/Aerial/AerialVideo.swift new file mode 100644 index 00000000..6fabbb1e --- /dev/null +++ b/Aerial/AerialVideo.swift @@ -0,0 +1,28 @@ +// +// AerialVideo.swift +// Aerial +// +// Created by John Coates on 10/23/15. +// Copyright © 2015 John Coates. All rights reserved. +// + +import Foundation + + +class AerialVideo { + let id:String; + let name:String; + let type:String; + let timeOfDay:String; + let url:NSURL; + var arrayPosition:Int = 1; + + + init(id:String, name:String, type:String, timeOfDay:String, url:String) { + self.id = id; + self.name = name; + self.type = type; + self.timeOfDay = timeOfDay; + self.url = NSURL(string:url)!; + } +} \ No newline at end of file diff --git a/Aerial/AerialView.h b/Aerial/AerialView.h deleted file mode 100644 index 0fc44f90..00000000 --- a/Aerial/AerialView.h +++ /dev/null @@ -1,13 +0,0 @@ -// -// AerialView.h -// Aerial -// -// Created by John Coates on 10/22/15. -// Copyright © 2015 John Coates. All rights reserved. -// - -#import - -@interface AerialView : ScreenSaverView - -@end diff --git a/Aerial/AerialView.m b/Aerial/AerialView.m deleted file mode 100644 index 0f65b63b..00000000 --- a/Aerial/AerialView.m +++ /dev/null @@ -1,52 +0,0 @@ -// -// AerialView.m -// Aerial -// -// Created by John Coates on 10/22/15. -// Copyright © 2015 John Coates. All rights reserved. -// - -#import "AerialView.h" - -@implementation AerialView - -- (instancetype)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview -{ - self = [super initWithFrame:frame isPreview:isPreview]; - if (self) { - [self setAnimationTimeInterval:1/30.0]; - } - return self; -} - -- (void)startAnimation -{ - [super startAnimation]; -} - -- (void)stopAnimation -{ - [super stopAnimation]; -} - -- (void)drawRect:(NSRect)rect -{ - [super drawRect:rect]; -} - -- (void)animateOneFrame -{ - return; -} - -- (BOOL)hasConfigureSheet -{ - return NO; -} - -- (NSWindow*)configureSheet -{ - return nil; -} - -@end diff --git a/Aerial/AerialView.swift b/Aerial/AerialView.swift new file mode 100644 index 00000000..08243667 --- /dev/null +++ b/Aerial/AerialView.swift @@ -0,0 +1,402 @@ +// +// AerialView.swift +// Aerial +// +// Created by John Coates on 10/22/15. +// Copyright © 2015 John Coates. All rights reserved. +// + +import Foundation +import ScreenSaver +import AVFoundation +import AVKit + +typealias manifestLoadCallback = ([AerialVideo]) -> (Void); + +// shuffling thanks to Nate Cook http://stackoverflow.com/questions/24026510/how-do-i-shuffle-an-array-in-swift +extension CollectionType { + /// Return a copy of `self` with its elements shuffled + func shuffle() -> [Generator.Element] { + var list = Array(self) + list.shuffleInPlace() + return list + } +} + +extension MutableCollectionType where Index == Int { + /// Shuffle the elements of `self` in-place. + mutating func shuffleInPlace() { + // empty and single-element collections don't shuffle + if count < 2 { return } + + for i in 0.. 0) { + callback(loadedManifest); + } + else { + callbacks.append(callback); + } + } + + func randomVideo() -> AerialVideo? { + + let shuffled = loadedManifest.shuffle(); + + for video in shuffled { + let possible = defaults.objectForKey(video.id); + + if let possible = possible as? NSNumber { + if possible.boolValue == false { + continue; + } + } + + return video; + } + + // nothing available??? return first thing we find + return shuffled.first; + } + + init() { + // start loading right away! + let completionHandler = { (data:NSData?, response:NSURLResponse?, error:NSError?) -> Void in + guard let data = data else { + NSLog("Couldn't load manifest!"); + return; + } + + if let error = error { + NSLog("Error! \(error)"); + return; + } + + var videos = [AerialVideo](); + + do { + let batches = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments) as! Array; + + for batch:NSDictionary in batches { + let assets = batch["assets"] as! Array; + + for item in assets { + let url = item["url"] as! String; + let name = item["accessibilityLabel"] as! String; + let timeOfDay = item["timeOfDay"] as! String; + let id = item["id"] as! String; + let type = item["type"] as! String; + + if (type != "video") { + continue; + } + + + let video = AerialVideo(id: id, name: name, type: type, timeOfDay: timeOfDay, url: url); + + videos.append(video) + } + } + + self.loadedManifest = videos; + NSLog("loaded videos: \(videos)"); + + // callbacks + for callback in self.callbacks { + callback(videos); + } + self.callbacks.removeAll() + + } + catch { + NSLog("Error retrieving content listing."); + return; + } + + + }; + let url = NSURL(string: "http://a1.phobos.apple.com/us/r1000/000/Features/atv/AutumnResources/videos/entries.json"); + let task = NSURLSession.sharedSession().dataTaskWithURL(url!, completionHandler:completionHandler); + task.resume(); + } +} + + +@objc(AerialView) class AerialView : ScreenSaverView { + var playerView: AVPlayerView! + var playerLayer:AVPlayerLayer! + var preferencesController:PreferencesWindowController? + static var players:[AVPlayer] = [AVPlayer]() + static var previewPlayer:AVPlayer? + + var player:AVPlayer? + static let defaults:NSUserDefaults = ScreenSaverDefaults(forModuleWithName: "com.JohnCoates.Aerial")! as ScreenSaverDefaults + + static var sharingPlayers:Bool { + defaults.synchronize(); + return !defaults.boolForKey("differentDisplays"); + } + + static var singlePlayerAlreadySetup:Bool = false; + class var sharedPlayer: AVPlayer { + struct Static { + static let instance: AVPlayer = AVPlayer(); + static var _player:AVPlayer?; + static var player:AVPlayer { + if let activePlayer = _player { + NSLog("returning existing player: %@", activePlayer); + return activePlayer; + } + NSLog("preview.... constructing new player!"); +// let movieURL = NSURL(string: "http://a1.phobos.apple.com/us/r1000/000/Features/atv/AutumnResources/videos/b10-2.mov"); +// let asset = AVAsset(URL: movieURL!); +// +// let item = AVPlayerItem(asset: asset); + _player = AVPlayer(); + return _player!; + } + } + + return Static.player; + } + + override init?(frame: NSRect, isPreview: Bool) { + super.init(frame: frame, isPreview: isPreview) + + self.animationTimeInterval = 1.0 / 30.0 + setup() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setup() + } + + deinit { + NSLog("deinit AerialView"); + NSNotificationCenter.defaultCenter().removeObserver(self); + + // set player item to nil if not preview player + if player != AerialView.previewPlayer { + player?.rate = 0; + player?.replaceCurrentItemWithPlayerItem(nil); + } + + guard let player = self.player else { + return; + } + + // Remove from player index + + let indexMaybe = AerialView.players.indexOf(player) + + guard let index = indexMaybe else { + return; + } + + AerialView.players.removeAtIndex(index); + } + + + func setupPlayerLayer(withPlayer player:AVPlayer) { + self.layer = CALayer() + guard let layer = self.layer else { + NSLog("Aerial Errror: Couldn't create CALayer"); + return; + } + self.wantsLayer = true + layer.backgroundColor = NSColor.blackColor().CGColor + layer.delegate = self; + layer.needsDisplayOnBoundsChange = true; + layer.frame = self.bounds + + playerLayer = AVPlayerLayer(player: player); + + playerLayer.frame = layer.bounds; + layer.addSublayer(playerLayer); + } + func setupPlayerView(withPlayer player:AVPlayer) { + playerView = AVPlayerView() + playerView.frame = self.bounds + playerView.autoresizingMask = [.ViewHeightSizable, .ViewWidthSizable] + playerView.controlsStyle = .None + playerView.player = player; + playerView.videoGravity = AVLayerVideoGravityResizeAspectFill; + self.addSubview(playerView); + } + func setup() { + + var localPlayer:AVPlayer? + + if (!self.preview) { + // check if we should share preview's player + if (AerialView.players.count == 0) { + if AerialView.previewPlayer != nil { + localPlayer = AerialView.previewPlayer; + } + } + } + + + if localPlayer == nil { + if AerialView.sharingPlayers { + if AerialView.previewPlayer != nil { + localPlayer = AerialView.previewPlayer + } + else { + localPlayer = AerialView.sharedPlayer; + } + } + else { + localPlayer = AVPlayer(); + } + } + + guard let player = localPlayer else { + NSLog("Aerial: Couldn't create AVPlayer!"); + return; + } + + self.player = player; + + if (self.preview) { + AerialView.previewPlayer = player; + } + else if (AerialView.sharingPlayers == false) { + // add to player list + AerialView.players.append(player); + } + +// setupPlayerLayer(withPlayer: player); + // view allows for video gravity + setupPlayerView(withPlayer: player); + + + if (AerialView.sharingPlayers == true && AerialView.singlePlayerAlreadySetup) { + return; + } + + AerialView.singlePlayerAlreadySetup = true; + + + ManifestLoader.instance.addCallback { (videos:[AerialVideo]) -> Void in + self.playNextVideo(player); + }; + } + + /* +public let AVPlayerItemTimeJumpedNotification: String // the item's current time has changed discontinuously +@available(OSX 10.7, *) +public let AVPlayerItemDidPlayToEndTimeNotification: String // item has played to its end time +@available(OSX 10.7, *) +public let AVPlayerItemFailedToPlayToEndTimeNotification: String // item has failed to play to its end time +@available(OSX 10.9, *) +public let AVPlayerItemPlaybackStalledNotification: String // media did not arrive in time to continue playback +@available(OSX 10.9, *) +public let AVPlayerItemNewAccessLogEntryNotification: String // a new access log entry has been added +@available(OSX 10.9, *) +public let AVPlayerItemNewErrorLogEntryNotification: String // a new error log entry has been added + +// notification userInfo key type +@available(OSX 10.7, *) +public let AVPlayerItemFailedToPlayToEndTimeErrorKey: String // NSError +*/ + func playerItemFailedtoPlayToEnd(aNotification: NSNotification) { + NSLog("AVPlayerItemFailedToPlayToEndTimeNotification \(aNotification)"); + } + + func playerItemNewErrorLogEntryNotification(aNotification: NSNotification) { + NSLog("AVPlayerItemNewErrorLogEntryNotification \(aNotification)"); + } + + func playerItemPlaybackStalledNotification(aNotification: NSNotification) { + NSLog("AVPlayerItemPlaybackStalledNotification \(aNotification)"); + } + + func playerItemDidReachEnd(aNotification: NSNotification) { + NSLog("played did reach end"); + NSLog("notification: \(aNotification)"); + guard let player = self.player else { + return; + } + + NSLog("playing next video for player \(player)"); + + // play another video + playNextVideo(player); + } + + func playNextVideo(player:AVPlayer) { + let randomVideo = ManifestLoader.instance.randomVideo(); + + guard let video = randomVideo else { + NSLog("error grabbing random video!"); + return; + } + let videoURL = video.url; +// let videoURL = NSURL(string:"http://localhost/test.mov")!; + + let asset = AVAsset(URL: videoURL); + + let item = AVPlayerItem(asset: asset); + player.replaceCurrentItemWithPlayerItem(item); + + NSLog("playing video: \(video.url)"); + if player.rate == 0 { + player.play(); + } + + guard let currentItem = player.currentItem else { + NSLog("no current item!"); + return; + } + + let notificationCenter = NSNotificationCenter.defaultCenter() + + // remove old entries + notificationCenter.removeObserver(self); + + NSLog("observing current item \(currentItem)"); + notificationCenter.addObserver(self, selector: "playerItemDidReachEnd:", name: AVPlayerItemDidPlayToEndTimeNotification, object: currentItem); + notificationCenter.addObserver(self, selector: "playerItemNewErrorLogEntryNotification:", name: AVPlayerItemNewErrorLogEntryNotification, object: currentItem); + notificationCenter.addObserver(self, selector: "playerItemFailedtoPlayToEnd:", name: AVPlayerItemFailedToPlayToEndTimeNotification, object: currentItem); + notificationCenter.addObserver(self, selector: "playerItemPlaybackStalledNotification:", name: AVPlayerItemPlaybackStalledNotification, object: currentItem); + player.actionAtItemEnd = AVPlayerActionAtItemEnd.None; + } + + override func hasConfigureSheet() -> Bool { + return true; + } + + override func configureSheet() -> NSWindow? { + if let controller = preferencesController { + return controller.window + } + + let controller = PreferencesWindowController(windowNibName: "PreferencesWindow"); + + preferencesController = controller; +// controller.loadWindow(); +// controller.window?.styleMask +// guard let window = controller.window else { +// NSLog("no controller :("); +// return nil; +// } + return controller.window; + } +} \ No newline at end of file diff --git a/Aerial/CheckCellView.swift b/Aerial/CheckCellView.swift new file mode 100644 index 00000000..c95b77a3 --- /dev/null +++ b/Aerial/CheckCellView.swift @@ -0,0 +1,44 @@ +// +// CheckCellView.swift +// Aerial +// +// Created by John Coates on 10/24/15. +// Copyright © 2015 John Coates. All rights reserved. +// + +import Cocoa + +class CheckCellView: NSTableCellView { + + @IBOutlet var checkButton:NSButton! + + var onCheck:((Bool) -> (Void))?; + + override required init(frame frameRect: NSRect) { + super.init(frame: frameRect); + } + + required init?(coder: NSCoder) { + super.init(coder:coder); + } + + override func awakeFromNib() { + checkButton.target = self; + checkButton.action = "check:"; + } + + func check(button:AnyObject?) { + NSLog("checked!"); + + guard let onCheck = self.onCheck else { + return; + } + + onCheck(checkButton.state == NSOnState); + } + + override func drawRect(dirtyRect: NSRect) { + super.drawRect(dirtyRect) + } + +} diff --git a/Aerial/Info.plist b/Aerial/Info.plist index 445ec3d6..a8fb7e75 100644 --- a/Aerial/Info.plist +++ b/Aerial/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) + com.JohnCoates.Aerial CFBundleInfoDictionaryVersion 6.0 CFBundleName @@ -20,8 +20,6 @@ ???? CFBundleVersion 1 - NSHumanReadableCopyright - Copyright © 2015 John Coates. All rights reserved. NSPrincipalClass AerialView diff --git a/Aerial/PreferencesWindow.xib b/Aerial/PreferencesWindow.xib new file mode 100644 index 00000000..a557fad2 --- /dev/null +++ b/Aerial/PreferencesWindow.xib @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Aerial/PreferencesWindowController.swift b/Aerial/PreferencesWindowController.swift new file mode 100644 index 00000000..1c8ad90e --- /dev/null +++ b/Aerial/PreferencesWindowController.swift @@ -0,0 +1,427 @@ +// +// PreferencesWindowController.swift +// Aerial +// +// Created by John Coates on 10/23/15. +// Copyright © 2015 John Coates. All rights reserved. +// + +import Cocoa +import AVKit +import AVFoundation +import ScreenSaver + +class TimeOfDay { + let title:String; + var videos:[AerialVideo] = [AerialVideo]() + + init(title:String) { + self.title = title; + } + +} + +class City { + var night:TimeOfDay = TimeOfDay(title: "night"); + var day:TimeOfDay = TimeOfDay(title: "day"); + let name:String; + + init(name:String) { + self.name = name; + } + + func addVideoForTimeOfDay(timeOfDay:String, video:AerialVideo) { + if timeOfDay.lowercaseString == "night" { + video.arrayPosition = night.videos.count; + night.videos.append(video); + } + else { + video.arrayPosition = day.videos.count; + day.videos.append(video); + } + } +} + +@objc(PreferencesWindowController) class PreferencesWindowController: NSWindowController, NSOutlineViewDataSource, NSOutlineViewDelegate { + + @IBOutlet var outlineView:NSOutlineView? + @IBOutlet var playerView:AVPlayerView! + @IBOutlet var differentAerialCheckbox:NSButton! + @IBOutlet var projectPageLink:NSButton! + + var player:AVPlayer = AVPlayer() + let defaults:NSUserDefaults = ScreenSaverDefaults(forModuleWithName: "com.JohnCoates.Aerial")! as ScreenSaverDefaults + + var videos:[AerialVideo]? + // cities -> time of day -> videos + var cities = [City](); + + static var loadedJSON:Bool = false; + + override func awakeFromNib() { + super.awakeFromNib() + + if let previewPlayer = AerialView.previewPlayer { + self.player = previewPlayer; + } + + outlineView?.floatsGroupRows = false; + loadJSON(); + + playerView.player = player; + playerView.controlsStyle = .None; + playerView.videoGravity = AVLayerVideoGravityResizeAspectFill; + defaults.synchronize(); + + if (defaults.boolForKey("differentDisplays")) { + differentAerialCheckbox.state = NSOnState; + } + + // blue link + let color = NSColor(calibratedRed:0.18, green:0.39, blue:0.76, alpha:1); + let coloredTitle = NSMutableAttributedString(attributedString: projectPageLink.attributedTitle); + let titleRange = NSMakeRange(0, coloredTitle.length); + coloredTitle.addAttribute(NSForegroundColorAttributeName, value: color, range: titleRange); + projectPageLink.attributedTitle = coloredTitle; + } + + + required init?(coder decoder: NSCoder) { + super.init(coder: decoder) + NSLog("initted!"); + } + override init(window: NSWindow?) { + super.init(window: window) + NSLog("initted!"); + + } + override func windowDidLoad() { + super.windowDidLoad() + } + + @IBAction func outlineViewSettingsClick(button:NSButton) { + let menu = NSMenu() + menu.insertItemWithTitle("Uncheck All", + action: "outlineViewUncheckAll:", + keyEquivalent: "", + atIndex: 0); + + menu.insertItemWithTitle("Check All", + action: "outlineViewCheckAll:", + keyEquivalent: "", + atIndex: 1); + + let event = NSApp.currentEvent; + NSMenu.popUpContextMenu(menu, withEvent: event!, forView: button); + } + + + func outlineViewUncheckAll(button:NSButton) { + setChecked(false); + } + + func outlineViewCheckAll(button:NSButton) { + setChecked(true); + } + + func setChecked(checked:Bool) { + guard let videos = videos else { + return; + } + + for video in videos { + self.defaults.setBool(checked, forKey: video.id); + } + self.defaults.synchronize(); + + outlineView!.reloadData() + } + + @IBAction func differentAerialsOnEachDisplayCheckClick(button:NSButton?) { + let state = differentAerialCheckbox.state; + + let onState:Bool = state == NSOnState; + + defaults.setBool(onState, forKey: "differentDisplays"); + defaults.synchronize(); + + NSLog("set differentDisplays to \(onState)"); + } + + @IBAction func pageProjectClick(button:NSButton?) { + NSWorkspace.sharedWorkspace().openURL(NSURL(string: "http://github.com/JohnCoates/Aerial")!); + } + + func loadJSON() { + if (PreferencesWindowController.loadedJSON) { + return; + } + + NSLog("loading JSON!"); + + PreferencesWindowController.loadedJSON = true; + + let completionHandler = { (data:NSData?, response:NSURLResponse?, error:NSError?) -> Void in + guard let data = data else { + return; + } + + var videos = [AerialVideo](); + var cities = [String:City](); + + do { + let batches = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments) as! Array; + + for batch:NSDictionary in batches { + let assets = batch["assets"] as! Array; + + for item in assets { + let url = item["url"] as! String; + let name = item["accessibilityLabel"] as! String; + let timeOfDay = item["timeOfDay"] as! String; + let id = item["id"] as! String; + let type = item["type"] as! String; + + if (type != "video") { + continue; + } + + // check if city name has dictionary + if cities.keys.contains(name) == false { + cities[name] = City(name: name); + } + + let city:City = cities[name]!; + + let video = AerialVideo(id: id, name: name, type: type, timeOfDay: timeOfDay, url: url); + + city.addVideoForTimeOfDay(timeOfDay, video: video); + videos.append(video) + + NSLog("id: \(id), name: \(name), time of day: \(timeOfDay), url: \(url)"); + } + } + + self.videos = videos; + // sort cities by name + let unsortedCities = cities.values; + let sortedCities = unsortedCities.sort { $0.name < $1.name }; + + self.cities = sortedCities; + dispatch_async(dispatch_get_main_queue(), { () -> Void in + self.outlineView?.reloadData(); + self.outlineView?.expandItem(nil, expandChildren: true); + }) + NSLog("reloading outline view\(self.outlineView)"); + } + catch { + NSLog("Error retrieving content listing."); + return; + } + + + }; + let url = NSURL(string: "http://a1.phobos.apple.com/us/r1000/000/Features/atv/AutumnResources/videos/entries.json"); + let task = NSURLSession.sharedSession().dataTaskWithURL(url!, completionHandler:completionHandler); + task.resume(); + } + + @IBAction func close(sender: AnyObject?) { + NSApp.mainWindow?.endSheet(window!); + } + + + // MARK: - Outline View Delegate & Data Source + + + func outlineView(outlineView: NSOutlineView, numberOfChildrenOfItem item: AnyObject?) -> Int { + if item == nil { + NSLog("count: \(cities.count)"); + return cities.count; + } + + switch item { + case is TimeOfDay: + let timeOfDay = item as! TimeOfDay; + return timeOfDay.videos.count; + case is City: + let city = item as! City; + + var count = 0; + + if city.night.videos.count > 0 { + count++; + } + + if city.day.videos.count > 0 { + count++; + } + + return count; + default: + return 0; + } + + } + + func outlineView(outlineView: NSOutlineView, isItemExpandable item: AnyObject) -> Bool { + switch item { + case is TimeOfDay: + return true; + case is City: // cities + return true; + default: + return false; + } + } + + func outlineView(outlineView: NSOutlineView, child index: Int, ofItem item: AnyObject?) -> AnyObject { + if item == nil { + return cities[index]; + } + + switch item { + case is City: + let city = item as! City; + + if (index == 0 && city.day.videos.count > 0) { + return city.day; + } + else { + return city.night; + } + + case is TimeOfDay: + let timeOfDay = item as! TimeOfDay; + return timeOfDay.videos[index]; + + default: + return false; + } + } + + func outlineView(outlineView: NSOutlineView, objectValueForTableColumn tableColumn: NSTableColumn?, byItem item: AnyObject?) -> AnyObject? { + switch item { + case is City: + let city = item as! City; + return city.name; + case is TimeOfDay: + let timeOfDay = item as! TimeOfDay; + return timeOfDay.title; + + default: + return "untitled"; + } + } + + func outlineView(outlineView: NSOutlineView, shouldEditTableColumn tableColumn: NSTableColumn?, item: AnyObject) -> Bool { + return false; + } + + func outlineView(outlineView: NSOutlineView, dataCellForTableColumn tableColumn: NSTableColumn?, item: AnyObject) -> NSCell? { + let row = outlineView.rowForItem(item); + return tableColumn!.dataCellForRow(row) as? NSCell; + } + + func outlineView(outlineView: NSOutlineView, isGroupItem item: AnyObject) -> Bool { + switch item { + case is TimeOfDay: + return true; + case is City: + return true; + default: + return false; + } + } + + func outlineView(outlineView: NSOutlineView, viewForTableColumn tableColumn: NSTableColumn?, item: AnyObject) -> NSView? { + switch item { + case is City: + + let city = item as! City; + let view = outlineView.makeViewWithIdentifier("HeaderCell", owner: self) as? NSTableCellView + + view?.textField?.stringValue = city.name; + + return view; + case is TimeOfDay: + let view = outlineView.makeViewWithIdentifier("DataCell", owner: self) as? NSTableCellView + + let timeOfDay = item as! TimeOfDay; + view?.textField?.stringValue = timeOfDay.title.capitalizedString; + let bundle = NSBundle(forClass: PreferencesWindowController.self); + let imagePath = bundle.pathForResource("icon-\(timeOfDay.title)", ofType:"pdf"); + let image = NSImage(contentsOfFile: imagePath!); + view?.imageView?.image = image; + return view; + case is AerialVideo: + let view = outlineView.makeViewWithIdentifier("CheckCell", owner: self) as? CheckCellView + + + let video = item as! AerialVideo; + let number = video.arrayPosition + 1; + let numberFormatter = NSNumberFormatter(); + + numberFormatter.numberStyle = NSNumberFormatterStyle.SpellOutStyle; + let numberString = numberFormatter.stringFromNumber(number); + view?.textField?.stringValue = numberString!.capitalizedString; + + let settingValue = defaults.objectForKey(video.id); + + if let settingValue = settingValue as? NSNumber { + if settingValue.boolValue == false { + view?.checkButton.state = NSOffState; + } + else { + view?.checkButton.state = NSOnState; + } + } + else { + view?.checkButton.state = NSOnState; + } + + + view?.onCheck = { (checked:Bool) in + self.defaults.setBool(checked, forKey: video.id); + self.defaults.synchronize(); + }; + + return view; + default: + return nil; + } + } + + func outlineView(outlineView: NSOutlineView, shouldSelectItem item: AnyObject) -> Bool { + switch item { + case is AerialVideo: + let video = item as! AerialVideo; + + let asset = AVAsset(URL: video.url); + + let item = AVPlayerItem(asset: asset); + player.replaceCurrentItemWithPlayerItem(item); + player.play(); + + return true; + case is TimeOfDay: + return false; + default: + return false; + } + } + + func outlineView(outlineView: NSOutlineView, heightOfRowByItem item: AnyObject) -> CGFloat { + switch item { + case is AerialVideo: + return 18; + case is TimeOfDay: + return 18; + case is City: + return 18; + default: + return 0; + } + } + +} diff --git a/Aerial/icon-day.pdf b/Aerial/icon-day.pdf new file mode 100644 index 00000000..27c1445d Binary files /dev/null and b/Aerial/icon-day.pdf differ diff --git a/Aerial/icon-night.pdf b/Aerial/icon-night.pdf new file mode 100644 index 00000000..3a2e1fee Binary files /dev/null and b/Aerial/icon-night.pdf differ diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..e3882312 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 John Coates + +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. diff --git a/Readme.md b/Readme.md new file mode 100644 index 00000000..b0bea4fe --- /dev/null +++ b/Readme.md @@ -0,0 +1,44 @@ +![Screencast](https://raw.githubusercontent.com/JohnCoates/Aerial/master/screencast.gif) + +## Aerial - Apple TV Aerial Views Screensaver +Aerial is a OS X screensaver based on the new Apple TV screensaver that displays the aerial movies Apple shot over New York, San Francisco, Hawaii, China, etc. + +Aerial is completely open source, so feel free to contribute to its development! + +## Download +Download from [Github](https://github.com/JohnCoates/Aerial/archive/master.zip) + +Two ways to install: + +**Option A:** Open Aerial.saver and OS X will ask if you'd like it installed. + +**Option B:** Place Aerial.saver in ~/Library/Screen Savers + +## Using Aerial + +* To enable Aerial open System Preferences -> Desktop & Screen Saver -> Screen Saver +Choose Aerial and click on Screen Saver Options to select your settings. + + +## Features +* **Auto Load Latest Aerials:** Aerials are loaded directly from Apple, so you're never out of date. +* **Play Different Aerial On Each Display:** If you've got multiple monitors, this setting loads a different aerial for each of your displays. +* **Favorites:** You can choose to only have certain aerials play. +* **Preview:** Clicking on an aerial in the screen saver options previews that aerial for you. + +## Community +- **Find a bug?** [Open an issue](https://github.com/JohnCoates/Aerial/issues/new). Try to be as specific as possible. +- **Have a feature request** [Open an issue](https://github.com/JohnCoates/Aerial/issues/new). Tell me why this feature would be useful, and why you and others would want it. + +## Contribute +I appreciate all pull requests. Caching hasn't been added yet. + +## Changelog + +- October 26th, 2015 - 1.0: first release + +## License +[MIT License](https://raw.githubusercontent.com/JohnCoates/Aerial/master/LICENSE) + +## Author +Maintained and created by John Coates [@punksomething](http://twitter.com/punksomething) diff --git a/screencast.gif b/screencast.gif new file mode 100644 index 00000000..1be8a8f7 Binary files /dev/null and b/screencast.gif differ