├── .gitignore ├── INPopoverController.podspec ├── INPopoverController.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── INPopoverController ├── INPopoverController-Info.plist ├── INPopoverController.h ├── INPopoverController.m ├── INPopoverControllerDefines.h ├── INPopoverParentWindow.h ├── INPopoverParentWindow.m ├── INPopoverPrefixHeader.pch ├── INPopoverWindow.h ├── INPopoverWindow.m ├── INPopoverWindowFrame.h └── INPopoverWindowFrame.m ├── LICENSE ├── PopoverSampleApp ├── PopoverSampleApp.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcuserdata │ │ └── Indragie.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints.xcbkptlist │ │ └── xcschemes │ │ ├── PopoverSampleApp.xcscheme │ │ └── xcschememanagement.plist └── PopoverSampleApp │ ├── AlphaColorWell.h │ ├── AlphaColorWell.m │ ├── ContentViewController.h │ ├── ContentViewController.m │ ├── ContentViewController.xib │ ├── PopoverSampleApp-Info.plist │ ├── PopoverSampleApp-Prefix.pch │ ├── PopoverSampleAppAppDelegate.h │ ├── PopoverSampleAppAppDelegate.m │ ├── en.lproj │ ├── Credits.rtf │ ├── InfoPlist.strings │ └── MainMenu.xib │ └── main.m ├── README.md └── screenshot.png /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | # CocoaPods 23 | Pods 24 | 25 | # AppCode 26 | .idea/ -------------------------------------------------------------------------------- /INPopoverController.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'INPopoverController' 3 | s.version = '0.0.1' 4 | s.summary = 'A customizable popover controller for Mac OS X' 5 | s.homepage = 'https://github.com/indragiek/INPopoverController' 6 | s.author = { 'Indragie Karunaratne' => 'i@indragie.com' } 7 | s.source = { :git => 'https://github.com/indragiek/INPopoverController.git' } 8 | s.source_files = 'INPopoverController/*.{h,m}' 9 | s.public_header_files = 'INPopoverController/*.h' 10 | s.platform = :osx 11 | s.requires_arc = true 12 | s.license = 'BSD' 13 | s.frameworks = 'QuartzCore' 14 | end 15 | -------------------------------------------------------------------------------- /INPopoverController.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 7619A0908F1873F212A86D92 /* INPopoverController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7619A090EED3A0874FFB0542 /* INPopoverController.m */; }; 11 | 7619A507692D41F9A4EA0EB1 /* INPopoverController.h in Headers */ = {isa = PBXBuildFile; fileRef = 7619AF3F1F908EC06616FBBA /* INPopoverController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 12 | 7619A5078560AD04EB09398F /* INPopoverWindowFrame.m in Sources */ = {isa = PBXBuildFile; fileRef = 7619AFDD9CA28B629C79B9B7 /* INPopoverWindowFrame.m */; }; 13 | 7619A72138650AA792042726 /* INPopoverParentWindow.h in Headers */ = {isa = PBXBuildFile; fileRef = 7619A3274F11C96F76F9A2AA /* INPopoverParentWindow.h */; }; 14 | 7619A73868BE7D04215703FA /* INPopoverControllerDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = 7619A49B8AE793DFA2663E95 /* INPopoverControllerDefines.h */; settings = {ATTRIBUTES = (Public, ); }; }; 15 | 7619A73A585AD478336FBD44 /* INPopoverParentWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 7619A840E914A3391A33BCB2 /* INPopoverParentWindow.m */; }; 16 | 7619A7C39101BC5170F2CF23 /* INPopoverWindow.h in Headers */ = {isa = PBXBuildFile; fileRef = 7619AEB4110920D49EB68147 /* INPopoverWindow.h */; }; 17 | 7619AFCF3AC75534C6582B37 /* INPopoverWindowFrame.h in Headers */ = {isa = PBXBuildFile; fileRef = 7619ABC89C19022487731625 /* INPopoverWindowFrame.h */; }; 18 | 7619AFCFD4611DEE23332859 /* INPopoverWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 7619A46EB85527945376718B /* INPopoverWindow.m */; }; 19 | 98BA893B143EA04A006F744E /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 98BA893A143EA04A006F744E /* Cocoa.framework */; }; 20 | 98BA8983143EA1E9006F744E /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 98BA8982143EA1E9006F744E /* QuartzCore.framework */; }; 21 | 98BA8985143EA28E006F744E /* INPopoverPrefixHeader.pch in Headers */ = {isa = PBXBuildFile; fileRef = 98BA8984143EA28E006F744E /* INPopoverPrefixHeader.pch */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXFileReference section */ 25 | 7619A090EED3A0874FFB0542 /* INPopoverController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = INPopoverController.m; sourceTree = ""; }; 26 | 7619A3274F11C96F76F9A2AA /* INPopoverParentWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = INPopoverParentWindow.h; sourceTree = ""; }; 27 | 7619A46EB85527945376718B /* INPopoverWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = INPopoverWindow.m; sourceTree = ""; }; 28 | 7619A49B8AE793DFA2663E95 /* INPopoverControllerDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = INPopoverControllerDefines.h; sourceTree = ""; }; 29 | 7619A840E914A3391A33BCB2 /* INPopoverParentWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = INPopoverParentWindow.m; sourceTree = ""; }; 30 | 7619ABC89C19022487731625 /* INPopoverWindowFrame.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = INPopoverWindowFrame.h; sourceTree = ""; }; 31 | 7619AEB4110920D49EB68147 /* INPopoverWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = INPopoverWindow.h; sourceTree = ""; }; 32 | 7619AF3F1F908EC06616FBBA /* INPopoverController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = INPopoverController.h; sourceTree = ""; }; 33 | 7619AFDD9CA28B629C79B9B7 /* INPopoverWindowFrame.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = INPopoverWindowFrame.m; sourceTree = ""; }; 34 | 98BA8937143EA04A006F744E /* INPopoverController.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = INPopoverController.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 35 | 98BA893A143EA04A006F744E /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 36 | 98BA893D143EA04A006F744E /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 37 | 98BA893E143EA04A006F744E /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 38 | 98BA893F143EA04A006F744E /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 39 | 98BA8942143EA04A006F744E /* INPopoverController-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "INPopoverController-Info.plist"; sourceTree = ""; }; 40 | 98BA8982143EA1E9006F744E /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = SDKs/MacOSX10.6.sdk/System/Library/Frameworks/QuartzCore.framework; sourceTree = DEVELOPER_DIR; }; 41 | 98BA8984143EA28E006F744E /* INPopoverPrefixHeader.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = INPopoverPrefixHeader.pch; sourceTree = ""; }; 42 | /* End PBXFileReference section */ 43 | 44 | /* Begin PBXFrameworksBuildPhase section */ 45 | 98BA8933143EA04A006F744E /* Frameworks */ = { 46 | isa = PBXFrameworksBuildPhase; 47 | buildActionMask = 2147483647; 48 | files = ( 49 | 98BA893B143EA04A006F744E /* Cocoa.framework in Frameworks */, 50 | 98BA8983143EA1E9006F744E /* QuartzCore.framework in Frameworks */, 51 | ); 52 | runOnlyForDeploymentPostprocessing = 0; 53 | }; 54 | /* End PBXFrameworksBuildPhase section */ 55 | 56 | /* Begin PBXGroup section */ 57 | 98BA892B143EA049006F744E = { 58 | isa = PBXGroup; 59 | children = ( 60 | 98BA8940143EA04A006F744E /* INPopoverController */, 61 | 98BA8939143EA04A006F744E /* Frameworks */, 62 | 98BA8938143EA04A006F744E /* Products */, 63 | ); 64 | sourceTree = ""; 65 | }; 66 | 98BA8938143EA04A006F744E /* Products */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | 98BA8937143EA04A006F744E /* INPopoverController.framework */, 70 | ); 71 | name = Products; 72 | sourceTree = ""; 73 | }; 74 | 98BA8939143EA04A006F744E /* Frameworks */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | 98BA8982143EA1E9006F744E /* QuartzCore.framework */, 78 | 98BA893A143EA04A006F744E /* Cocoa.framework */, 79 | 98BA893C143EA04A006F744E /* Other Frameworks */, 80 | ); 81 | name = Frameworks; 82 | sourceTree = ""; 83 | }; 84 | 98BA893C143EA04A006F744E /* Other Frameworks */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | 98BA893D143EA04A006F744E /* AppKit.framework */, 88 | 98BA893E143EA04A006F744E /* CoreData.framework */, 89 | 98BA893F143EA04A006F744E /* Foundation.framework */, 90 | ); 91 | name = "Other Frameworks"; 92 | sourceTree = ""; 93 | }; 94 | 98BA8940143EA04A006F744E /* INPopoverController */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | 98BA8941143EA04A006F744E /* Supporting Files */, 98 | 7619A49B8AE793DFA2663E95 /* INPopoverControllerDefines.h */, 99 | 7619AF3F1F908EC06616FBBA /* INPopoverController.h */, 100 | 7619A090EED3A0874FFB0542 /* INPopoverController.m */, 101 | 7619ABC89C19022487731625 /* INPopoverWindowFrame.h */, 102 | 7619AFDD9CA28B629C79B9B7 /* INPopoverWindowFrame.m */, 103 | 7619AEB4110920D49EB68147 /* INPopoverWindow.h */, 104 | 7619A46EB85527945376718B /* INPopoverWindow.m */, 105 | 7619A3274F11C96F76F9A2AA /* INPopoverParentWindow.h */, 106 | 7619A840E914A3391A33BCB2 /* INPopoverParentWindow.m */, 107 | ); 108 | path = INPopoverController; 109 | sourceTree = ""; 110 | }; 111 | 98BA8941143EA04A006F744E /* Supporting Files */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | 98BA8984143EA28E006F744E /* INPopoverPrefixHeader.pch */, 115 | 98BA8942143EA04A006F744E /* INPopoverController-Info.plist */, 116 | ); 117 | name = "Supporting Files"; 118 | sourceTree = ""; 119 | }; 120 | /* End PBXGroup section */ 121 | 122 | /* Begin PBXHeadersBuildPhase section */ 123 | 98BA8934143EA04A006F744E /* Headers */ = { 124 | isa = PBXHeadersBuildPhase; 125 | buildActionMask = 2147483647; 126 | files = ( 127 | 98BA8985143EA28E006F744E /* INPopoverPrefixHeader.pch in Headers */, 128 | 7619A507692D41F9A4EA0EB1 /* INPopoverController.h in Headers */, 129 | 7619A72138650AA792042726 /* INPopoverParentWindow.h in Headers */, 130 | 7619A73868BE7D04215703FA /* INPopoverControllerDefines.h in Headers */, 131 | 7619A7C39101BC5170F2CF23 /* INPopoverWindow.h in Headers */, 132 | 7619AFCF3AC75534C6582B37 /* INPopoverWindowFrame.h in Headers */, 133 | ); 134 | runOnlyForDeploymentPostprocessing = 0; 135 | }; 136 | /* End PBXHeadersBuildPhase section */ 137 | 138 | /* Begin PBXNativeTarget section */ 139 | 98BA8936143EA04A006F744E /* INPopoverController */ = { 140 | isa = PBXNativeTarget; 141 | buildConfigurationList = 98BA8962143EA04B006F744E /* Build configuration list for PBXNativeTarget "INPopoverController" */; 142 | buildPhases = ( 143 | 98BA8932143EA04A006F744E /* Sources */, 144 | 98BA8933143EA04A006F744E /* Frameworks */, 145 | 98BA8934143EA04A006F744E /* Headers */, 146 | 98BA8935143EA04A006F744E /* Resources */, 147 | ); 148 | buildRules = ( 149 | ); 150 | dependencies = ( 151 | ); 152 | name = INPopoverController; 153 | productName = INPopoverController; 154 | productReference = 98BA8937143EA04A006F744E /* INPopoverController.framework */; 155 | productType = "com.apple.product-type.framework"; 156 | }; 157 | /* End PBXNativeTarget section */ 158 | 159 | /* Begin PBXProject section */ 160 | 98BA892D143EA049006F744E /* Project object */ = { 161 | isa = PBXProject; 162 | attributes = { 163 | LastUpgradeCheck = 0500; 164 | }; 165 | buildConfigurationList = 98BA8930143EA049006F744E /* Build configuration list for PBXProject "INPopoverController" */; 166 | compatibilityVersion = "Xcode 3.2"; 167 | developmentRegion = English; 168 | hasScannedForEncodings = 0; 169 | knownRegions = ( 170 | en, 171 | ); 172 | mainGroup = 98BA892B143EA049006F744E; 173 | productRefGroup = 98BA8938143EA04A006F744E /* Products */; 174 | projectDirPath = ""; 175 | projectRoot = ""; 176 | targets = ( 177 | 98BA8936143EA04A006F744E /* INPopoverController */, 178 | ); 179 | }; 180 | /* End PBXProject section */ 181 | 182 | /* Begin PBXResourcesBuildPhase section */ 183 | 98BA8935143EA04A006F744E /* Resources */ = { 184 | isa = PBXResourcesBuildPhase; 185 | buildActionMask = 2147483647; 186 | files = ( 187 | ); 188 | runOnlyForDeploymentPostprocessing = 0; 189 | }; 190 | /* End PBXResourcesBuildPhase section */ 191 | 192 | /* Begin PBXSourcesBuildPhase section */ 193 | 98BA8932143EA04A006F744E /* Sources */ = { 194 | isa = PBXSourcesBuildPhase; 195 | buildActionMask = 2147483647; 196 | files = ( 197 | 7619A0908F1873F212A86D92 /* INPopoverController.m in Sources */, 198 | 7619A5078560AD04EB09398F /* INPopoverWindowFrame.m in Sources */, 199 | 7619AFCFD4611DEE23332859 /* INPopoverWindow.m in Sources */, 200 | 7619A73A585AD478336FBD44 /* INPopoverParentWindow.m in Sources */, 201 | ); 202 | runOnlyForDeploymentPostprocessing = 0; 203 | }; 204 | /* End PBXSourcesBuildPhase section */ 205 | 206 | /* Begin XCBuildConfiguration section */ 207 | 98BA8960143EA04B006F744E /* Debug */ = { 208 | isa = XCBuildConfiguration; 209 | buildSettings = { 210 | ALWAYS_SEARCH_USER_PATHS = NO; 211 | CLANG_ENABLE_OBJC_ARC = YES; 212 | CLANG_WARN_BOOL_CONVERSION = YES; 213 | CLANG_WARN_CONSTANT_CONVERSION = YES; 214 | CLANG_WARN_EMPTY_BODY = YES; 215 | CLANG_WARN_ENUM_CONVERSION = YES; 216 | CLANG_WARN_INT_CONVERSION = YES; 217 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 218 | COPY_PHASE_STRIP = NO; 219 | GCC_C_LANGUAGE_STANDARD = gnu99; 220 | GCC_DYNAMIC_NO_PIC = NO; 221 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 222 | GCC_OPTIMIZATION_LEVEL = 0; 223 | GCC_PREPROCESSOR_DEFINITIONS = ( 224 | "DEBUG=1", 225 | "$(inherited)", 226 | ); 227 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 228 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 229 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 230 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 231 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 232 | GCC_WARN_UNDECLARED_SELECTOR = YES; 233 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 234 | GCC_WARN_UNUSED_FUNCTION = YES; 235 | GCC_WARN_UNUSED_VARIABLE = YES; 236 | MACOSX_DEPLOYMENT_TARGET = 10.6; 237 | ONLY_ACTIVE_ARCH = YES; 238 | SDKROOT = macosx; 239 | }; 240 | name = Debug; 241 | }; 242 | 98BA8961143EA04B006F744E /* Release */ = { 243 | isa = XCBuildConfiguration; 244 | buildSettings = { 245 | ALWAYS_SEARCH_USER_PATHS = NO; 246 | CLANG_ENABLE_OBJC_ARC = YES; 247 | CLANG_WARN_BOOL_CONVERSION = YES; 248 | CLANG_WARN_CONSTANT_CONVERSION = YES; 249 | CLANG_WARN_EMPTY_BODY = YES; 250 | CLANG_WARN_ENUM_CONVERSION = YES; 251 | CLANG_WARN_INT_CONVERSION = YES; 252 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 253 | COPY_PHASE_STRIP = YES; 254 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 255 | GCC_C_LANGUAGE_STANDARD = gnu99; 256 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 257 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 258 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 259 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 260 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 261 | GCC_WARN_UNDECLARED_SELECTOR = YES; 262 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 263 | GCC_WARN_UNUSED_FUNCTION = YES; 264 | GCC_WARN_UNUSED_VARIABLE = YES; 265 | MACOSX_DEPLOYMENT_TARGET = 10.6; 266 | SDKROOT = macosx; 267 | }; 268 | name = Release; 269 | }; 270 | 98BA8963143EA04B006F744E /* Debug */ = { 271 | isa = XCBuildConfiguration; 272 | buildSettings = { 273 | CLANG_ENABLE_OBJC_ARC = YES; 274 | COMBINE_HIDPI_IMAGES = YES; 275 | DYLIB_COMPATIBILITY_VERSION = 1; 276 | DYLIB_CURRENT_VERSION = 1; 277 | FRAMEWORK_SEARCH_PATHS = ( 278 | "$(inherited)", 279 | "\"$(DEVELOPER_DIR)/SDKs/MacOSX10.6.sdk/System/Library/Frameworks\"", 280 | ); 281 | FRAMEWORK_VERSION = A; 282 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 283 | GCC_PREFIX_HEADER = INPopoverController/INPopoverPrefixHeader.pch; 284 | GCC_VERSION = ""; 285 | GCC_WARN_64_TO_32_BIT_CONVERSION = NO; 286 | INFOPLIST_FILE = "INPopoverController/INPopoverController-Info.plist"; 287 | INSTALL_PATH = "@rpath"; 288 | PRODUCT_NAME = "$(TARGET_NAME)"; 289 | SDKROOT = macosx; 290 | WRAPPER_EXTENSION = framework; 291 | }; 292 | name = Debug; 293 | }; 294 | 98BA8964143EA04B006F744E /* Release */ = { 295 | isa = XCBuildConfiguration; 296 | buildSettings = { 297 | CLANG_ENABLE_OBJC_ARC = YES; 298 | COMBINE_HIDPI_IMAGES = YES; 299 | DYLIB_COMPATIBILITY_VERSION = 1; 300 | DYLIB_CURRENT_VERSION = 1; 301 | FRAMEWORK_SEARCH_PATHS = ( 302 | "$(inherited)", 303 | "\"$(DEVELOPER_DIR)/SDKs/MacOSX10.6.sdk/System/Library/Frameworks\"", 304 | ); 305 | FRAMEWORK_VERSION = A; 306 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 307 | GCC_PREFIX_HEADER = INPopoverController/INPopoverPrefixHeader.pch; 308 | GCC_VERSION = ""; 309 | GCC_WARN_64_TO_32_BIT_CONVERSION = NO; 310 | INFOPLIST_FILE = "INPopoverController/INPopoverController-Info.plist"; 311 | INSTALL_PATH = "@rpath"; 312 | PRODUCT_NAME = "$(TARGET_NAME)"; 313 | SDKROOT = macosx; 314 | WRAPPER_EXTENSION = framework; 315 | }; 316 | name = Release; 317 | }; 318 | /* End XCBuildConfiguration section */ 319 | 320 | /* Begin XCConfigurationList section */ 321 | 98BA8930143EA049006F744E /* Build configuration list for PBXProject "INPopoverController" */ = { 322 | isa = XCConfigurationList; 323 | buildConfigurations = ( 324 | 98BA8960143EA04B006F744E /* Debug */, 325 | 98BA8961143EA04B006F744E /* Release */, 326 | ); 327 | defaultConfigurationIsVisible = 0; 328 | defaultConfigurationName = Release; 329 | }; 330 | 98BA8962143EA04B006F744E /* Build configuration list for PBXNativeTarget "INPopoverController" */ = { 331 | isa = XCConfigurationList; 332 | buildConfigurations = ( 333 | 98BA8963143EA04B006F744E /* Debug */, 334 | 98BA8964143EA04B006F744E /* Release */, 335 | ); 336 | defaultConfigurationIsVisible = 0; 337 | defaultConfigurationName = Release; 338 | }; 339 | /* End XCConfigurationList section */ 340 | }; 341 | rootObject = 98BA892D143EA049006F744E /* Project object */; 342 | } 343 | -------------------------------------------------------------------------------- /INPopoverController.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /INPopoverController/INPopoverController-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | com.indragie.INPopoverController.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | NSHumanReadableCopyright 26 | Copyright © 2011 Indragie Karunaratne. All rights reserved. 27 | NSPrincipalClass 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /INPopoverController/INPopoverController.h: -------------------------------------------------------------------------------- 1 | // 2 | // INPopoverController.h 3 | // Copyright 2011-2014 Indragie Karunaratne. All rights reserved. 4 | // 5 | 6 | #import 7 | #import "INPopoverControllerDefines.h" 8 | 9 | @protocol INPopoverControllerDelegate; 10 | 11 | @interface INPopoverController : NSObject 12 | 13 | #pragma mark - 14 | #pragma mark Properties 15 | 16 | /** The delegate of the INPopoverController object (should conform to the INPopoverControllerDelegate protocol) **/ 17 | @property (nonatomic, assign) id delegate; 18 | 19 | /** The background color of the popover. Default value is [NSColor blackColor] with an alpha value of 0.8. Changes to this value are not animated. **/ 20 | @property (nonatomic, strong) NSColor *color; 21 | 22 | /** Border color to use when drawing a border. Default value: [NSColor blackColor]. Changes to this value are not animated. **/ 23 | @property (nonatomic, strong) NSColor *borderColor; 24 | 25 | /** Color to use for drawing a 1px highlight just below the top. Can be nil. Changes to this value are not animated. **/ 26 | @property (nonatomic, strong) NSColor *topHighlightColor; 27 | 28 | /** The width of the popover border, drawn using borderColor. Default value: 0.0 (no border). Changes to this value are not animated. **/ 29 | @property (nonatomic, assign) CGFloat borderWidth; 30 | 31 | /** Corner radius of the popover window. Default value: 4. Changes to this value are not animated. **/ 32 | @property (nonatomic, assign) CGFloat cornerRadius; 33 | 34 | /** The size of the popover arrow. Default value: {23, 12}. Changes to this value are not animated. **/ 35 | @property (nonatomic, assign) NSSize arrowSize; 36 | 37 | /** The current arrow direction of the popover. If the popover has never been displayed, then this will return INPopoverArrowDirectionUndefined */ 38 | @property (nonatomic, assign, readonly) INPopoverArrowDirection arrowDirection; 39 | 40 | /** The size of the content of the popover. This is automatically set to contentViewController's size when the view controller is set, but can be modified. Changes to this value are animated when animates is set to YES **/ 41 | @property (nonatomic, assign) NSSize contentSize; 42 | 43 | /** Whether the popover closes when user presses escape key. Default value: YES */ 44 | @property (nonatomic, assign) BOOL closesWhenEscapeKeyPressed; 45 | 46 | /** Whether the popover closes when the popover window resigns its key status. Default value: YES **/ 47 | @property (nonatomic, assign) BOOL closesWhenPopoverResignsKey; 48 | 49 | /** Whether the popover closes when the application becomes inactive. Default value: NO **/ 50 | @property (nonatomic, assign) BOOL closesWhenApplicationBecomesInactive; 51 | 52 | /** Enable or disable animation when showing/closing the popover and changing the content size. Default value: YES */ 53 | @property (nonatomic, assign) BOOL animates; 54 | 55 | /* If `animates` is `YES`, this is the animation type to use when showing/closing the popover. 56 | Default value: `INPopoverAnimationTypePop` **/ 57 | @property (nonatomic, assign) INPopoverAnimationType animationType; 58 | 59 | /** The content view controller from which content is displayed in the popover **/ 60 | @property (nonatomic, strong) NSViewController *contentViewController; 61 | 62 | /** The view that the currently displayed popover is positioned relative to. If there is no popover being displayed, this returns nil. **/ 63 | @property (nonatomic, strong, readonly) NSView *positionView; 64 | 65 | /** The window of the popover **/ 66 | @property (nonatomic, strong, readonly) NSWindow *popoverWindow; 67 | 68 | /** Whether the popover is currently visible or not **/ 69 | @property (nonatomic, assign, readonly) BOOL popoverIsVisible; 70 | 71 | #pragma mark - 72 | #pragma mark Methods 73 | 74 | /** 75 | Initializes the popover with a content view already set. 76 | @param viewController the content view controller 77 | @returns a new instance of INPopoverController 78 | */ 79 | - (id)initWithContentViewController:(NSViewController *)viewController; 80 | 81 | /** 82 | Displays the popover. 83 | @param rect the rect in the positionView from which to display the popover 84 | @param positionView the view that the popover is positioned relative to 85 | @param direction the prefered direction at which the arrow will point. There is no guarantee that this will be the actual arrow direction, depending on whether the screen is able to accomodate the popover in that position. 86 | @param anchors Whether the popover binds to the frame of the positionView. This means that if the positionView is resized or moved, the popover will be repositioned according to the point at which it was originally placed. This also means that if the positionView goes off screen, the popover will be automatically closed. **/ 87 | 88 | - (void)presentPopoverFromRect:(NSRect)rect inView:(NSView *)positionView preferredArrowDirection:(INPopoverArrowDirection)direction anchorsToPositionView:(BOOL)anchors; 89 | 90 | /** 91 | Recalculates the best arrow direction for the current window position and resets the arrow direction. The change will not be animated. **/ 92 | - (void)recalculateAndResetArrowDirection; 93 | 94 | /** 95 | Closes the popover unless NO is returned for the -popoverShouldClose: delegate method 96 | @param sender the object that sent this message 97 | */ 98 | - (IBAction)closePopover:(id)sender; 99 | 100 | /** 101 | Closes the popover regardless of what the delegate returns 102 | @param sender the object that sent this message 103 | */ 104 | - (IBAction)forceClosePopover:(id)sender; 105 | 106 | /** 107 | Returns the frame for a popop window with a given size depending on the arrow direction. 108 | @param contentSize the popover window content size 109 | @param direction the arrow direction 110 | */ 111 | - (NSRect)popoverFrameWithSize:(NSSize)contentSize andArrowDirection:(INPopoverArrowDirection)direction; 112 | 113 | @end 114 | 115 | @protocol INPopoverControllerDelegate 116 | @optional 117 | /** 118 | When the -closePopover: method is invoked, this method is called to give a change for the delegate to prevent it from closing. Returning NO for this delegate method will prevent the popover being closed. This delegate method does not apply to the -forceClosePopover: method, which will close the popover regardless of what the delegate returns. 119 | @param popover the @class INPopoverController object that is controlling the popover 120 | @returns whether the popover should close or not 121 | */ 122 | - (BOOL)popoverShouldClose:(INPopoverController *)popover; 123 | 124 | /** 125 | Invoked right before the popover shows on screen 126 | @param popover the @class INPopoverController object that is controlling the popover 127 | */ 128 | - (void)popoverWillShow:(INPopoverController *)popover; 129 | 130 | /** 131 | Invoked right after the popover shows on screen 132 | @param popover the @class INPopoverController object that is controlling the popover 133 | */ 134 | - (void)popoverDidShow:(INPopoverController *)popover; 135 | 136 | /** 137 | Invoked right before the popover closes 138 | @param popover the @class INPopoverController object that is controlling the popover 139 | */ 140 | - (void)popoverWillClose:(INPopoverController *)popover; 141 | 142 | /** 143 | Invoked right after the popover closes 144 | @param popover the @class INPopoverController object that is controlling the popover 145 | */ 146 | - (void)popoverDidClose:(INPopoverController *)popover; 147 | @end 148 | -------------------------------------------------------------------------------- /INPopoverController/INPopoverController.m: -------------------------------------------------------------------------------- 1 | // 2 | // INPopoverController.m 3 | // Copyright 2011-2014 Indragie Karunaratne. All rights reserved. 4 | // 5 | 6 | #import "INPopoverController.h" 7 | #import "INPopoverWindow.h" 8 | #import "INPopoverWindowFrame.h" 9 | #import "INPopoverParentWindow.h" 10 | #include 11 | 12 | @implementation INPopoverController { 13 | INPopoverWindow *_popoverWindow; 14 | NSRect _screenRect; 15 | NSRect _viewRect; 16 | } 17 | 18 | #pragma mark - 19 | #pragma mark Initialization 20 | 21 | - (id)init 22 | { 23 | if ((self = [super init])) { 24 | [self _setInitialPropertyValues]; 25 | } 26 | return self; 27 | } 28 | 29 | - (void)awakeFromNib 30 | { 31 | [super awakeFromNib]; 32 | [self _setInitialPropertyValues]; 33 | } 34 | 35 | #pragma mark - 36 | #pragma mark - Memory Management 37 | 38 | - (void)dealloc 39 | { 40 | _popoverWindow.popoverController = nil; 41 | } 42 | 43 | #pragma mark - 44 | #pragma mark Public Methods 45 | 46 | - (id)initWithContentViewController:(NSViewController *)viewController 47 | { 48 | if ((self = [super init])) { 49 | [self _setInitialPropertyValues]; 50 | self.contentViewController = viewController; 51 | } 52 | return self; 53 | } 54 | 55 | - (void)presentPopoverFromRect:(NSRect)rect inView:(NSView *)positionView preferredArrowDirection:(INPopoverArrowDirection)direction anchorsToPositionView:(BOOL)anchors 56 | { 57 | if (self.popoverIsVisible) {return;} // If it's already visible, do nothing 58 | NSWindow *mainWindow = [positionView window]; 59 | _positionView = positionView; 60 | _viewRect = rect; 61 | _screenRect = [positionView convertRect:rect toView:nil]; // Convert the rect to window coordinates 62 | _screenRect.origin = [mainWindow convertBaseToScreen:_screenRect.origin]; // Convert window coordinates to screen coordinates 63 | INPopoverArrowDirection calculatedDirection = [self _arrowDirectionWithPreferredArrowDirection:direction]; // Calculate the best arrow direction 64 | [self _setArrowDirection:calculatedDirection]; // Change the arrow direction of the popover 65 | NSRect windowFrame = [self popoverFrameWithSize:self.contentSize andArrowDirection:calculatedDirection]; // Calculate the window frame based on the arrow direction 66 | [_popoverWindow setFrame:windowFrame display:YES]; // Se the frame of the window 67 | [[_popoverWindow animationForKey:@"alphaValue"] setDelegate:self]; 68 | 69 | // Show the popover 70 | [self _callDelegateMethod:@selector(popoverWillShow:)]; // Call the delegate 71 | if (self.animates && self.animationType != INPopoverAnimationTypeFadeOut) { 72 | // Animate the popover in 73 | [_popoverWindow presentAnimated]; 74 | } else { 75 | [_popoverWindow setAlphaValue:1.0]; 76 | [mainWindow addChildWindow:_popoverWindow ordered:NSWindowAbove]; // Add the popover as a child window of the main window 77 | [_popoverWindow makeKeyAndOrderFront:nil]; // Show the popover 78 | [self _callDelegateMethod:@selector(popoverDidShow:)]; // Call the delegate 79 | } 80 | 81 | NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; 82 | if (anchors) { // If the anchors option is enabled, register for frame change notifications 83 | [nc addObserver:self selector:@selector(_positionViewFrameChanged:) name:NSViewFrameDidChangeNotification object:self.positionView]; 84 | } 85 | // When -closesWhenPopoverResignsKey is set to YES, the popover will automatically close when the popover loses its key status 86 | if (self.closesWhenPopoverResignsKey) { 87 | [nc addObserver:self selector:@selector(closePopover:) name:NSWindowDidResignKeyNotification object:_popoverWindow]; 88 | if (!self.closesWhenApplicationBecomesInactive) { 89 | [nc addObserver:self selector:@selector(applicationDidBecomeActive:) name:NSApplicationDidBecomeActiveNotification object:nil]; 90 | } 91 | } else if (self.closesWhenApplicationBecomesInactive) { 92 | // this is only needed if closesWhenPopoverResignsKey is NO, otherwise we already get a "resign key" notification when resigning active 93 | [nc addObserver:self selector:@selector(closePopover:) name:NSApplicationDidResignActiveNotification object:nil]; 94 | } 95 | } 96 | 97 | - (void)recalculateAndResetArrowDirection 98 | { 99 | INPopoverArrowDirection direction = [self _arrowDirectionWithPreferredArrowDirection:self.arrowDirection]; 100 | [self _setArrowDirection:direction]; 101 | } 102 | 103 | - (IBAction)closePopover:(id)sender 104 | { 105 | if (![_popoverWindow isVisible]) {return;} 106 | if ([sender isKindOfClass:[NSNotification class]] && [[(NSNotification *) sender name] isEqualToString:NSWindowDidResignKeyNotification]) { 107 | // ignore "resign key" notification sent when app becomes inactive unless closesWhenApplicationBecomesInactive is enabled 108 | if (!self.closesWhenApplicationBecomesInactive && ![NSApp isActive]) 109 | return; 110 | } 111 | BOOL close = YES; 112 | // Check to see if the delegate has implemented the -popoverShouldClose: method 113 | if ([self.delegate respondsToSelector:@selector(popoverShouldClose:)]) { 114 | close = [self.delegate popoverShouldClose:self]; 115 | } 116 | if (close) {[self forceClosePopover:nil];} 117 | } 118 | 119 | - (IBAction)forceClosePopover:(id)sender 120 | { 121 | if (![_popoverWindow isVisible]) {return;} 122 | [self _callDelegateMethod:@selector(popoverWillClose:)]; // Call delegate 123 | if (self.animates && self.animationType != INPopoverAnimationTypeFadeIn) { 124 | [_popoverWindow dismissAnimated]; 125 | } else { 126 | [self _closePopoverAndResetVariables]; 127 | } 128 | } 129 | 130 | // Calculate the frame of the window depending on the arrow direction 131 | - (NSRect)popoverFrameWithSize:(NSSize)contentSize andArrowDirection:(INPopoverArrowDirection)direction 132 | { 133 | NSRect contentRect = NSZeroRect; 134 | contentRect.size = contentSize; 135 | NSRect windowFrame = [_popoverWindow frameRectForContentRect:contentRect]; 136 | if (direction == INPopoverArrowDirectionUp) { 137 | CGFloat xOrigin = NSMidX(_screenRect) - floor(windowFrame.size.width / 2.0); 138 | CGFloat yOrigin = NSMinY(_screenRect) - windowFrame.size.height; 139 | windowFrame.origin = NSMakePoint(xOrigin, yOrigin); 140 | } else if (direction == INPopoverArrowDirectionDown) { 141 | CGFloat xOrigin = NSMidX(_screenRect) - floor(windowFrame.size.width / 2.0); 142 | windowFrame.origin = NSMakePoint(xOrigin, NSMaxY(_screenRect)); 143 | } else if (direction == INPopoverArrowDirectionLeft) { 144 | CGFloat yOrigin = NSMidY(_screenRect) - floor(windowFrame.size.height / 2.0); 145 | windowFrame.origin = NSMakePoint(NSMaxX(_screenRect), yOrigin); 146 | } else if (direction == INPopoverArrowDirectionRight) { 147 | CGFloat xOrigin = NSMinX(_screenRect) - windowFrame.size.width; 148 | CGFloat yOrigin = NSMidY(_screenRect) - floor(windowFrame.size.height / 2.0); 149 | windowFrame.origin = NSMakePoint(xOrigin, yOrigin); 150 | } else { 151 | // If no arrow direction is specified, just return an empty rect 152 | windowFrame = NSZeroRect; 153 | } 154 | return windowFrame; 155 | } 156 | 157 | - (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)flag 158 | { 159 | #pragma unused(animation) 160 | #pragma unused(flag) 161 | // Detect the end of fade out and close the window 162 | if (0.0 == [_popoverWindow alphaValue]) 163 | [self _closePopoverAndResetVariables]; 164 | else if (1.0 == [_popoverWindow alphaValue]) { 165 | [[_positionView window] addChildWindow:_popoverWindow ordered:NSWindowAbove]; 166 | [self _callDelegateMethod:@selector(popoverDidShow:)]; 167 | } 168 | } 169 | 170 | - (void)applicationDidBecomeActive:(NSNotification *)notification 171 | { 172 | // when the user clicks in the parent window for activating the app, the parent window becomes key which prevents 173 | if ([_popoverWindow isVisible]) 174 | [self performSelector:@selector(checkPopoverKeyWindowStatus) withObject:nil afterDelay:0]; 175 | } 176 | 177 | - (void)checkPopoverKeyWindowStatus 178 | { 179 | id parentWindow = [_positionView window]; // could be INPopoverParentWindow 180 | BOOL isKey = [parentWindow respondsToSelector:@selector(isReallyKeyWindow)] ? [parentWindow isReallyKeyWindow] : [parentWindow isKeyWindow]; 181 | if (isKey) 182 | [_popoverWindow makeKeyWindow]; 183 | } 184 | 185 | #pragma mark - 186 | #pragma mark Getters 187 | 188 | - (NSColor *)color 189 | { 190 | return _popoverWindow.frameView.color; 191 | } 192 | 193 | - (CGFloat)borderWidth 194 | { 195 | return _popoverWindow.frameView.borderWidth; 196 | } 197 | 198 | - (NSColor *)borderColor 199 | { 200 | return _popoverWindow.frameView.borderColor; 201 | } 202 | 203 | - (NSColor *)topHighlightColor 204 | { 205 | return _popoverWindow.frameView.topHighlightColor; 206 | } 207 | 208 | - (CGFloat)cornerRadius 209 | { 210 | return _popoverWindow.frameView.cornerRadius; 211 | } 212 | 213 | - (NSSize)arrowSize 214 | { 215 | return _popoverWindow.frameView.arrowSize; 216 | } 217 | 218 | - (INPopoverArrowDirection)arrowDirection 219 | { 220 | return _popoverWindow.frameView.arrowDirection; 221 | } 222 | 223 | - (NSView *)contentView 224 | { 225 | return [_popoverWindow popoverContentView]; 226 | } 227 | 228 | - (BOOL)popoverIsVisible 229 | { 230 | return [_popoverWindow isVisible]; 231 | } 232 | 233 | #pragma mark - 234 | #pragma mark Setters 235 | 236 | - (void)setColor:(NSColor *)newColor 237 | { 238 | _popoverWindow.frameView.color = newColor; 239 | } 240 | 241 | - (void)setBorderWidth:(CGFloat)newBorderWidth 242 | { 243 | _popoverWindow.frameView.borderWidth = newBorderWidth; 244 | } 245 | 246 | - (void)setBorderColor:(NSColor *)newBorderColor 247 | { 248 | _popoverWindow.frameView.borderColor = newBorderColor; 249 | } 250 | 251 | - (void)setTopHighlightColor:(NSColor *)newTopHighlightColor 252 | { 253 | _popoverWindow.frameView.topHighlightColor = newTopHighlightColor; 254 | } 255 | 256 | - (void)setCornerRadius:(CGFloat)cornerRadius 257 | { 258 | _popoverWindow.frameView.cornerRadius = cornerRadius; 259 | } 260 | 261 | - (void)setArrowSize:(NSSize)arrowSize 262 | { 263 | _popoverWindow.frameView.arrowSize = arrowSize; 264 | } 265 | 266 | - (void)setContentViewController:(NSViewController *)newContentViewController 267 | { 268 | if (_contentViewController != newContentViewController) { 269 | [_popoverWindow setPopoverContentView:nil]; // Clear the content view 270 | _contentViewController = newContentViewController; 271 | NSView *contentView = [_contentViewController view]; 272 | self.contentSize = [contentView frame].size; 273 | [_popoverWindow setPopoverContentView:contentView]; 274 | } 275 | } 276 | 277 | - (void)setContentSize:(NSSize)newContentSize 278 | { 279 | // We use -frameRectForContentRect: just to get the frame size because the origin it returns is not the one we want to use. Instead, -windowFrameWithSize:andArrowDirection: is used to complete the frame 280 | _contentSize = newContentSize; 281 | NSRect adjustedRect = [self popoverFrameWithSize:newContentSize andArrowDirection:self.arrowDirection]; 282 | [_popoverWindow setFrame:adjustedRect display:YES animate:self.animates]; 283 | } 284 | 285 | - (void)_setArrowDirection:(INPopoverArrowDirection)direction 286 | { 287 | _popoverWindow.frameView.arrowDirection = direction; 288 | } 289 | 290 | #pragma mark - 291 | #pragma mark Private 292 | 293 | // Set the default values for all the properties as described in the header documentation 294 | - (void)_setInitialPropertyValues 295 | { 296 | // Create an empty popover window 297 | _popoverWindow = [[INPopoverWindow alloc] initWithContentRect:NSZeroRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]; 298 | _popoverWindow.popoverController = self; 299 | 300 | // set defaults like iCal popover 301 | self.color = [NSColor colorWithCalibratedWhite:0.94 alpha:0.92]; 302 | self.borderColor = [NSColor colorWithCalibratedWhite:1.0 alpha:0.92]; 303 | self.borderWidth = 1.0; 304 | self.closesWhenEscapeKeyPressed = YES; 305 | self.closesWhenPopoverResignsKey = YES; 306 | self.closesWhenApplicationBecomesInactive = NO; 307 | self.animates = YES; 308 | self.animationType = INPopoverAnimationTypePop; 309 | 310 | // create animation to get callback - delegate is set when opening popover to avoid memory cycles 311 | CAAnimation *animation = [CABasicAnimation animation]; 312 | [_popoverWindow setAnimations:[NSDictionary dictionaryWithObject:animation forKey:@"alphaValue"]]; 313 | } 314 | 315 | // Figure out which direction best stays in screen bounds 316 | - (INPopoverArrowDirection)_arrowDirectionWithPreferredArrowDirection:(INPopoverArrowDirection)direction 317 | { 318 | NSRect screenFrame = [[[_positionView window] screen] frame]; 319 | // If the window with the preferred arrow direction already falls within the screen bounds then no need to go any further 320 | NSRect windowFrame = [self popoverFrameWithSize:self.contentSize andArrowDirection:direction]; 321 | if (NSContainsRect(screenFrame, windowFrame)) { 322 | return direction; 323 | } 324 | // First thing to try is making the popover go opposite of its current direction 325 | INPopoverArrowDirection newDirection = INPopoverArrowDirectionUndefined; 326 | switch (direction) { 327 | case INPopoverArrowDirectionUp: 328 | newDirection = INPopoverArrowDirectionDown; 329 | break; 330 | case INPopoverArrowDirectionDown: 331 | newDirection = INPopoverArrowDirectionUp; 332 | break; 333 | case INPopoverArrowDirectionLeft: 334 | newDirection = INPopoverArrowDirectionRight; 335 | break; 336 | case INPopoverArrowDirectionRight: 337 | newDirection = INPopoverArrowDirectionLeft; 338 | break; 339 | default: 340 | break; 341 | } 342 | // If the popover now fits within bounds, then return the newly adjusted direction 343 | windowFrame = [self popoverFrameWithSize:self.contentSize andArrowDirection:newDirection]; 344 | if (NSContainsRect(screenFrame, windowFrame)) { 345 | return newDirection; 346 | } 347 | // Calculate the remaining space on each side and figure out which would be the best to try next 348 | CGFloat left = NSMinX(_screenRect); 349 | CGFloat right = screenFrame.size.width - NSMaxX(_screenRect); 350 | CGFloat up = screenFrame.size.height - NSMaxY(_screenRect); 351 | CGFloat down = NSMinY(_screenRect); 352 | BOOL arrowLeft = (right > left); 353 | BOOL arrowUp = (down > up); 354 | // Now the next thing to try is the direction with the most space 355 | switch (direction) { 356 | case INPopoverArrowDirectionUp: 357 | case INPopoverArrowDirectionDown: 358 | newDirection = arrowLeft ? INPopoverArrowDirectionLeft : INPopoverArrowDirectionRight; 359 | case INPopoverArrowDirectionLeft: 360 | case INPopoverArrowDirectionRight: 361 | newDirection = arrowUp ? INPopoverArrowDirectionUp : INPopoverArrowDirectionDown; 362 | break; 363 | default: 364 | break; 365 | } 366 | // If the popover now fits within bounds, then return the newly adjusted direction 367 | windowFrame = [self popoverFrameWithSize:self.contentSize andArrowDirection:newDirection]; 368 | if (NSContainsRect(screenFrame, windowFrame)) { 369 | return newDirection; 370 | } 371 | // If that didn't fit, then that means that it will be out of bounds on every side so just return the original direction 372 | return direction; 373 | } 374 | 375 | - (void)_positionViewFrameChanged:(NSNotification *)notification 376 | { 377 | NSRect superviewBounds = [[self.positionView superview] bounds]; 378 | if (!(NSContainsRect(superviewBounds, [self.positionView frame]))) { 379 | [self forceClosePopover:nil]; // If the position view goes off screen then close the popover 380 | return; 381 | } 382 | NSRect newFrame = [_popoverWindow frame]; 383 | _screenRect = [self.positionView convertRect:_viewRect toView:nil]; // Convert the rect to window coordinates 384 | _screenRect.origin = [[self.positionView window] convertBaseToScreen:_screenRect.origin]; // Convert window coordinates to screen coordinates 385 | NSRect calculatedFrame = [self popoverFrameWithSize:self.contentSize andArrowDirection:self.arrowDirection]; // Calculate the window frame based on the arrow direction 386 | newFrame.origin = calculatedFrame.origin; 387 | [_popoverWindow setFrame:newFrame display:YES animate:NO]; // Set the frame of the window 388 | } 389 | 390 | - (void)_closePopoverAndResetVariables 391 | { 392 | NSWindow *positionWindow = [self.positionView window]; 393 | [_popoverWindow orderOut:nil]; // Close the window 394 | [self _callDelegateMethod:@selector(popoverDidClose:)]; // Call the delegate to inform that the popover has closed 395 | [positionWindow removeChildWindow:_popoverWindow]; // Remove it as a child window 396 | [positionWindow makeKeyAndOrderFront:nil]; 397 | // Clear all the ivars 398 | [self _setArrowDirection:INPopoverArrowDirectionUndefined]; 399 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 400 | _positionView = nil; 401 | _screenRect = NSZeroRect; 402 | _viewRect = NSZeroRect; 403 | 404 | // When using ARC and no animation, there is a "message sent to deallocated instance" crash if setDelegate: is not performed at the end of the event. 405 | [[_popoverWindow animationForKey:@"alphaValue"] performSelector:@selector(setDelegate:) withObject:nil afterDelay:0]; 406 | } 407 | 408 | - (void)_callDelegateMethod:(SEL)selector 409 | { 410 | if ([self.delegate respondsToSelector:selector]) { 411 | #pragma clang diagnostic push 412 | #pragma clang diagnostic ignored "-Warc-performSelector-leaks" 413 | [self.delegate performSelector:selector withObject:self]; 414 | #pragma clang diagnostic pop 415 | } 416 | } 417 | 418 | @end 419 | -------------------------------------------------------------------------------- /INPopoverController/INPopoverControllerDefines.h: -------------------------------------------------------------------------------- 1 | // 2 | // INPopoverControllerDefines.h 3 | // Copyright 2011-2014 Indragie Karunaratne. All rights reserved. 4 | // 5 | 6 | typedef NS_ENUM(NSUInteger, INPopoverArrowDirection) { 7 | INPopoverArrowDirectionUndefined = 0, 8 | INPopoverArrowDirectionLeft, 9 | INPopoverArrowDirectionRight, 10 | INPopoverArrowDirectionUp, 11 | INPopoverArrowDirectionDown 12 | }; 13 | 14 | typedef NS_ENUM(NSInteger, INPopoverAnimationType) { 15 | INPopoverAnimationTypePop = 0, // Pop animation similar to NSPopover 16 | INPopoverAnimationTypeFadeIn, // Fade in only, no fade out 17 | INPopoverAnimationTypeFadeOut, // Fade out only, no fade in 18 | INPopoverAnimationTypeFadeInOut // Fade in and out 19 | }; -------------------------------------------------------------------------------- /INPopoverController/INPopoverParentWindow.h: -------------------------------------------------------------------------------- 1 | // 2 | // INPopoverParentWindow.h 3 | // Copyright 2011-2014 Indragie Karunaratne. All rights reserved. 4 | // 5 | 6 | #import 7 | 8 | @interface INPopoverParentWindow : NSWindow 9 | 10 | - (BOOL)isReallyKeyWindow; 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /INPopoverController/INPopoverParentWindow.m: -------------------------------------------------------------------------------- 1 | // 2 | // INAlwaysKeyWindow.m 3 | // Copyright 2011-2014 Indragie Karunaratne. All rights reserved. 4 | // 5 | 6 | #import "INPopoverParentWindow.h" 7 | #import "INPopoverWindow.h" 8 | 9 | @implementation INPopoverParentWindow 10 | 11 | - (BOOL)isKeyWindow 12 | { 13 | BOOL isKey = [super isKeyWindow]; 14 | if (!isKey) { 15 | for (NSWindow *childWindow in [self childWindows]) { 16 | if ([childWindow isKindOfClass:[INPopoverWindow class]]) { 17 | // if we have popover attached, window is key if app is active 18 | isKey = [NSApp isActive]; 19 | break; 20 | } 21 | } 22 | } 23 | return isKey; 24 | } 25 | 26 | - (BOOL)isReallyKeyWindow 27 | { 28 | return [super isKeyWindow]; 29 | } 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /INPopoverController/INPopoverPrefixHeader.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | 3 | #import 4 | 5 | #endif 6 | -------------------------------------------------------------------------------- /INPopoverController/INPopoverWindow.h: -------------------------------------------------------------------------------- 1 | // 2 | // INPopoverWindow.h 3 | // Copyright 2011-2014 Indragie Karunaratne. All rights reserved. 4 | // 5 | 6 | #import 7 | #import "INPopoverControllerDefines.h" 8 | 9 | /** 10 | @class INPopoverWindow 11 | An NSWindow subclass used to draw a custom window frame (@class INPopoverWindowFrame) 12 | **/ 13 | @class INPopoverWindowFrame; 14 | @class INPopoverController; 15 | 16 | @interface INPopoverWindow : NSPanel 17 | @property (nonatomic, readonly) INPopoverWindowFrame *frameView; // Equivalent to contentView 18 | @property (nonatomic, assign) INPopoverController *popoverController; 19 | @property (nonatomic, strong) NSView *popoverContentView; 20 | 21 | - (void)presentAnimated; 22 | - (void)dismissAnimated; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /INPopoverController/INPopoverWindow.m: -------------------------------------------------------------------------------- 1 | // 2 | // INPopoverWindow.m 3 | // Copyright 2011-2014 Indragie Karunaratne. All rights reserved. 4 | // 5 | 6 | #import "INPopoverWindow.h" 7 | #import "INPopoverControllerDefines.h" 8 | #import "INPopoverWindowFrame.h" 9 | #import "INPopoverController.h" 10 | #import 11 | 12 | #define START_SIZE NSMakeSize(20, 20) 13 | #define OVERSHOOT_FACTOR 1.2 14 | 15 | // A lot of this code was adapted from the following article: 16 | // 17 | 18 | @implementation INPopoverWindow { 19 | NSView *_popoverContentView; 20 | NSWindow *_zoomWindow; 21 | } 22 | 23 | // Borderless, transparent window 24 | - (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)windowStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation 25 | { 26 | if ((self = [super initWithContentRect:contentRect styleMask:NSNonactivatingPanelMask backing:bufferingType defer:deferCreation])) { 27 | [self setOpaque:NO]; 28 | [self setBackgroundColor:[NSColor clearColor]]; 29 | [self setHasShadow:YES]; 30 | } 31 | return self; 32 | } 33 | 34 | // Leave some space around the content for drawing the arrow 35 | - (NSRect)contentRectForFrameRect:(NSRect)windowFrame 36 | { 37 | windowFrame.origin = NSZeroPoint; 38 | const CGFloat arrowHeight = self.frameView.arrowSize.height; 39 | return NSInsetRect(windowFrame, arrowHeight, arrowHeight); 40 | } 41 | 42 | - (NSRect)frameRectForContentRect:(NSRect)contentRect 43 | { 44 | const CGFloat arrowHeight = self.frameView.arrowSize.height; 45 | return NSInsetRect(contentRect, -arrowHeight, -arrowHeight); 46 | } 47 | 48 | // Allow the popover to become the key window 49 | - (BOOL)canBecomeKeyWindow 50 | { 51 | return YES; 52 | } 53 | 54 | - (BOOL)canBecomeMainWindow 55 | { 56 | return NO; 57 | } 58 | 59 | - (BOOL)isVisible 60 | { 61 | return [super isVisible] || [_zoomWindow isVisible]; 62 | } 63 | 64 | - (INPopoverWindowFrame *)frameView 65 | { 66 | return (INPopoverWindowFrame *) [self contentView]; 67 | } 68 | 69 | - (void)setContentView:(NSView *)aView 70 | { 71 | [self setPopoverContentView:aView]; 72 | } 73 | 74 | - (void)setPopoverContentView:(NSView *)aView 75 | { 76 | if ([_popoverContentView isEqualTo:aView]) {return;} 77 | NSRect bounds = [self frame]; 78 | bounds.origin = NSZeroPoint; 79 | INPopoverWindowFrame *frameView = [self frameView]; 80 | if (!frameView) { 81 | frameView = [[INPopoverWindowFrame alloc] initWithFrame:bounds]; 82 | [super setContentView:frameView]; // Call on super or there will be infinite loop 83 | } 84 | if (_popoverContentView) { 85 | [_popoverContentView removeFromSuperview]; 86 | } 87 | _popoverContentView = aView; 88 | [_popoverContentView setFrame:[self contentRectForFrameRect:bounds]]; 89 | [_popoverContentView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; 90 | [frameView addSubview:_popoverContentView]; 91 | } 92 | 93 | - (void)presentAnimated 94 | { 95 | if ([self isVisible]) 96 | return; 97 | 98 | switch (self.popoverController.animationType) { 99 | case INPopoverAnimationTypePop: 100 | [self presentWithPopAnimation]; 101 | break; 102 | case INPopoverAnimationTypeFadeIn: 103 | case INPopoverAnimationTypeFadeInOut: 104 | [self presentWithFadeAnimation]; 105 | break; 106 | default: 107 | break; 108 | } 109 | } 110 | 111 | - (void)presentWithPopAnimation 112 | { 113 | NSRect endFrame = [self frame]; 114 | NSRect startFrame = [self.popoverController popoverFrameWithSize:START_SIZE andArrowDirection:self.frameView.arrowDirection]; 115 | NSRect overshootFrame = [self.popoverController popoverFrameWithSize:NSMakeSize(endFrame.size.width * OVERSHOOT_FACTOR, endFrame.size.height * OVERSHOOT_FACTOR) andArrowDirection:self.frameView.arrowDirection]; 116 | 117 | _zoomWindow = [self _zoomWindowWithRect:startFrame]; 118 | [_zoomWindow setAlphaValue:0.0]; 119 | [_zoomWindow orderFront:self]; 120 | 121 | // configure bounce-out animation 122 | CAKeyframeAnimation *anim = [CAKeyframeAnimation animation]; 123 | [anim setDelegate:self]; 124 | [anim setValues:[NSArray arrayWithObjects:[NSValue valueWithRect:startFrame], [NSValue valueWithRect:overshootFrame], [NSValue valueWithRect:endFrame], nil]]; 125 | [_zoomWindow setAnimations:[NSDictionary dictionaryWithObjectsAndKeys:anim, @"frame", nil]]; 126 | 127 | [NSAnimationContext beginGrouping]; 128 | [[_zoomWindow animator] setAlphaValue:1.0]; 129 | [[_zoomWindow animator] setFrame:endFrame display:YES]; 130 | [NSAnimationContext endGrouping]; 131 | } 132 | 133 | - (void)presentWithFadeAnimation 134 | { 135 | [self setAlphaValue:0.0]; 136 | [self makeKeyAndOrderFront:nil]; 137 | [[self animator] setAlphaValue:1.0]; 138 | } 139 | 140 | - (void)dismissAnimated 141 | { 142 | [[_zoomWindow animator] setAlphaValue:0.0]; // in case zoom window is currently animating 143 | [[self animator] setAlphaValue:0.0]; 144 | } 145 | 146 | - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag 147 | { 148 | [self setAlphaValue:1.0]; 149 | [self makeKeyAndOrderFront:self]; 150 | [_zoomWindow close]; 151 | _zoomWindow = nil; 152 | 153 | // call the animation delegate of the "real" window 154 | CAAnimation *windowAnimation = [self animationForKey:@"alphaValue"]; 155 | [[windowAnimation delegate] animationDidStop:anim finished:flag]; 156 | } 157 | 158 | - (void)cancelOperation:(id)sender 159 | { 160 | if (self.popoverController.closesWhenEscapeKeyPressed) [self.popoverController closePopover:nil]; 161 | } 162 | 163 | #pragma mark - 164 | #pragma mark Private 165 | 166 | // The following method is adapted from the following class: 167 | // 168 | // Copyright 2007-2009 Noodlesoft, LLC. All rights reserved. 169 | - (NSWindow *)_zoomWindowWithRect:(NSRect)rect 170 | { 171 | BOOL isOneShot = [self isOneShot]; 172 | if (isOneShot) 173 | [self setOneShot:NO]; 174 | 175 | if ([self windowNumber] <= 0) { 176 | // force creation of window device by putting it on-screen. We make it transparent to minimize the chance of visible flicker 177 | CGFloat alpha = [self alphaValue]; 178 | [self setAlphaValue:0.0]; 179 | [self orderBack:self]; 180 | [self orderOut:self]; 181 | [self setAlphaValue:alpha]; 182 | } 183 | 184 | // get window content as image 185 | NSRect frame = [self frame]; 186 | NSImage *image = [[NSImage alloc] initWithSize:frame.size]; 187 | [self displayIfNeeded]; // refresh view 188 | NSView *view = self.contentView; 189 | NSBitmapImageRep *imageRep = [view bitmapImageRepForCachingDisplayInRect:view.bounds]; 190 | [view cacheDisplayInRect:view.bounds toBitmapImageRep:imageRep]; 191 | [image addRepresentation:imageRep]; 192 | 193 | // create zoom window 194 | NSWindow *zoomWindow = [[NSWindow alloc] initWithContentRect:rect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]; 195 | [zoomWindow setBackgroundColor:[NSColor clearColor]]; 196 | [zoomWindow setHasShadow:[self hasShadow]]; 197 | [zoomWindow setLevel:[self level]]; 198 | [zoomWindow setOpaque:NO]; 199 | [zoomWindow setReleasedWhenClosed:NO]; 200 | [zoomWindow useOptimizedDrawing:YES]; 201 | 202 | NSImageView *imageView = [[NSImageView alloc] initWithFrame:[zoomWindow contentRectForFrameRect:frame]]; 203 | [imageView setImage:image]; 204 | [imageView setImageFrameStyle:NSImageFrameNone]; 205 | [imageView setImageScaling:NSScaleToFit]; 206 | [imageView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; 207 | 208 | [zoomWindow setContentView:imageView]; 209 | 210 | // reset one shot flag 211 | [self setOneShot:isOneShot]; 212 | 213 | return zoomWindow; 214 | } 215 | 216 | @end 217 | -------------------------------------------------------------------------------- /INPopoverController/INPopoverWindowFrame.h: -------------------------------------------------------------------------------- 1 | // 2 | // INPopoverWindowFrame.h 3 | // Copyright 2011-2014 Indragie Karunaratne. All rights reserved. 4 | // 5 | 6 | #import 7 | #import "INPopoverControllerDefines.h" 8 | 9 | /** 10 | @class INPopoverWindowFrame 11 | The NSView subclass responsible for drawing the frame of the popover 12 | */ 13 | @interface INPopoverWindowFrame : NSView 14 | @property (nonatomic, strong) NSColor *color; 15 | @property (nonatomic, strong) NSColor *borderColor; 16 | @property (nonatomic, strong) NSColor *topHighlightColor; 17 | @property (nonatomic, assign) CGFloat borderWidth; 18 | @property (nonatomic, assign) CGFloat cornerRadius; 19 | @property (nonatomic, assign) NSSize arrowSize; 20 | @property (nonatomic, assign) INPopoverArrowDirection arrowDirection; 21 | @end 22 | -------------------------------------------------------------------------------- /INPopoverController/INPopoverWindowFrame.m: -------------------------------------------------------------------------------- 1 | // 2 | // INPopoverWindowFrame.m 3 | // Copyright 2011-2014 Indragie Karunaratne. All rights reserved. 4 | // 5 | 6 | #import "INPopoverWindowFrame.h" 7 | 8 | @implementation INPopoverWindowFrame 9 | 10 | - (id)initWithFrame:(NSRect)frame 11 | { 12 | if ((self = [super initWithFrame:frame])) { 13 | _color = [NSColor colorWithCalibratedWhite:0.0 alpha:0.8]; 14 | _cornerRadius = 4.0; 15 | _arrowSize = NSMakeSize(23.0, 12.0); 16 | _arrowDirection = INPopoverArrowDirectionLeft; 17 | } 18 | return self; 19 | } 20 | 21 | - (void)drawRect:(NSRect)dirtyRect 22 | { 23 | NSRect bounds = [self bounds]; 24 | if (((NSUInteger) self.borderWidth % 2) == 1) { // Remove draw glitch on odd border width 25 | bounds = NSInsetRect(bounds, 0.5, 0.5); 26 | } 27 | 28 | NSBezierPath *path = [self _popoverBezierPathWithRect:bounds]; 29 | if (self.color) { 30 | [self.color set]; 31 | [path fill]; 32 | } 33 | if (self.borderWidth > 0) { 34 | [path setLineWidth:self.borderWidth]; 35 | [self.borderColor set]; 36 | [path stroke]; 37 | } 38 | 39 | const CGFloat arrowWidth = self.arrowSize.width; 40 | const CGFloat arrowHeight = self.arrowSize.height; 41 | const CGFloat radius = self.cornerRadius; 42 | 43 | if (self.topHighlightColor) { 44 | [self.topHighlightColor set]; 45 | NSRect bounds = NSInsetRect([self bounds], arrowHeight, arrowHeight); 46 | NSRect lineRect = NSMakeRect(floor(NSMinX(bounds) + (radius / 2.0)), NSMaxY(bounds) - self.borderWidth - 1, NSWidth(bounds) - radius, 1.0); 47 | 48 | if (self.arrowDirection == INPopoverArrowDirectionUp) { 49 | CGFloat width = floor((lineRect.size.width / 2.0) - (arrowWidth / 2.0)); 50 | NSRectFill(NSMakeRect(lineRect.origin.x, lineRect.origin.y, width, lineRect.size.height)); 51 | NSRectFill(NSMakeRect(floor(lineRect.origin.x + (lineRect.size.width / 2.0) + (arrowWidth / 2.0)), lineRect.origin.y, width, lineRect.size.height)); 52 | } else { 53 | NSRectFill(lineRect); 54 | } 55 | } 56 | } 57 | 58 | #pragma mark - 59 | #pragma mark Private 60 | 61 | - (NSBezierPath *)_popoverBezierPathWithRect:(NSRect)aRect 62 | { 63 | const CGFloat radius = self.cornerRadius; 64 | const CGFloat arrowWidth = self.arrowSize.width; 65 | const CGFloat arrowHeight = self.arrowSize.height; 66 | const CGFloat inset = radius + arrowHeight; 67 | const NSRect drawingRect = NSInsetRect(aRect, inset, inset); 68 | const CGFloat minX = NSMinX(drawingRect); 69 | const CGFloat maxX = NSMaxX(drawingRect); 70 | const CGFloat minY = NSMinY(drawingRect); 71 | const CGFloat maxY = NSMaxY(drawingRect); 72 | 73 | NSBezierPath *path = [NSBezierPath bezierPath]; 74 | [path setLineJoinStyle:NSRoundLineJoinStyle]; 75 | 76 | // Bottom left corner 77 | [path appendBezierPathWithArcWithCenter:NSMakePoint(minX, minY) radius:radius startAngle:180.0 endAngle:270.0]; 78 | if (self.arrowDirection == INPopoverArrowDirectionDown) { 79 | CGFloat midX = NSMidX(drawingRect); 80 | NSPoint points[3]; 81 | points[0] = NSMakePoint(floor(midX - (arrowWidth / 2.0)), minY - radius); // Starting point 82 | points[1] = NSMakePoint(floor(midX), points[0].y - arrowHeight); // Arrow tip 83 | points[2] = NSMakePoint(floor(midX + (arrowWidth / 2.0)), points[0].y); // Ending point 84 | [path appendBezierPathWithPoints:points count:3]; 85 | } 86 | // Bottom right corner 87 | [path appendBezierPathWithArcWithCenter:NSMakePoint(maxX, minY) radius:radius startAngle:270.0 endAngle:360.0]; 88 | if (self.arrowDirection == INPopoverArrowDirectionRight) { 89 | CGFloat midY = NSMidY(drawingRect); 90 | NSPoint points[3]; 91 | points[0] = NSMakePoint(maxX + radius, floor(midY - (arrowWidth / 2.0))); 92 | points[1] = NSMakePoint(points[0].x + arrowHeight, floor(midY)); 93 | points[2] = NSMakePoint(points[0].x, floor(midY + (arrowWidth / 2.0))); 94 | [path appendBezierPathWithPoints:points count:3]; 95 | } 96 | // Top right corner 97 | [path appendBezierPathWithArcWithCenter:NSMakePoint(maxX, maxY) radius:radius startAngle:0.0 endAngle:90.0]; 98 | if (self.arrowDirection == INPopoverArrowDirectionUp) { 99 | CGFloat midX = NSMidX(drawingRect); 100 | NSPoint points[3]; 101 | points[0] = NSMakePoint(floor(midX + (arrowWidth / 2.0)), maxY + radius); 102 | points[1] = NSMakePoint(floor(midX), points[0].y + arrowHeight); 103 | points[2] = NSMakePoint(floor(midX - (arrowWidth / 2.0)), points[0].y); 104 | [path appendBezierPathWithPoints:points count:3]; 105 | } 106 | // Top left corner 107 | [path appendBezierPathWithArcWithCenter:NSMakePoint(minX, maxY) radius:radius startAngle:90.0 endAngle:180.0]; 108 | if (self.arrowDirection == INPopoverArrowDirectionLeft) { 109 | CGFloat midY = NSMidY(drawingRect); 110 | NSPoint points[3]; 111 | points[0] = NSMakePoint(minX - radius, floor(midY + (arrowWidth / 2.0))); 112 | points[1] = NSMakePoint(points[0].x - arrowHeight, floor(midY)); 113 | points[2] = NSMakePoint(points[0].x, floor(midY - (arrowWidth / 2.0))); 114 | [path appendBezierPathWithPoints:points count:3]; 115 | } 116 | [path closePath]; 117 | 118 | return path; 119 | } 120 | 121 | #pragma mark - 122 | #pragma mark Accessors 123 | 124 | // Redraw the frame every time a property is changed 125 | - (void)setColor:(NSColor *)newColor 126 | { 127 | if (_color != newColor) { 128 | _color = newColor; 129 | [self setNeedsDisplay:YES]; 130 | } 131 | } 132 | 133 | - (void)setBorderColor:(NSColor *)newBorderColor 134 | { 135 | if (_borderColor != newBorderColor) { 136 | _borderColor = newBorderColor; 137 | [self setNeedsDisplay:YES]; 138 | } 139 | } 140 | 141 | 142 | - (void)setBorderWidth:(CGFloat)newBorderWidth 143 | { 144 | if (_borderWidth != newBorderWidth) { 145 | _borderWidth = newBorderWidth; 146 | [self setNeedsDisplay:YES]; 147 | } 148 | } 149 | 150 | - (void)setCornerRadius:(CGFloat)cornerRadius 151 | { 152 | if (_cornerRadius != cornerRadius) { 153 | _cornerRadius = cornerRadius; 154 | [self setNeedsDisplay:YES]; 155 | } 156 | } 157 | 158 | - (void)setArrowSize:(NSSize)arrowSize 159 | { 160 | if (!NSEqualSizes(_arrowSize, arrowSize)) { 161 | _arrowSize = arrowSize; 162 | [self setNeedsDisplay:YES]; 163 | } 164 | } 165 | 166 | - (void)setArrowDirection:(INPopoverArrowDirection)newArrowDirection 167 | { 168 | if (_arrowDirection != newArrowDirection) { 169 | _arrowDirection = newArrowDirection; 170 | [self setNeedsDisplay:YES]; 171 | } 172 | } 173 | 174 | @end 175 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011-2014, Indragie Karunaratne 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. 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. 9 | 10 | 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. 11 | -------------------------------------------------------------------------------- /PopoverSampleApp/PopoverSampleApp.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 03657F2B187A09BE00C969BA /* INPopoverController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03657F28187A09A000C969BA /* INPopoverController.framework */; }; 11 | 03657F2D187A09C900C969BA /* INPopoverController.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 03657F28187A09A000C969BA /* INPopoverController.framework */; }; 12 | 0391A4FF18822FAA007D32D5 /* AlphaColorWell.m in Sources */ = {isa = PBXBuildFile; fileRef = 0391A4FE18822FAA007D32D5 /* AlphaColorWell.m */; }; 13 | 0393272D131F613D0007496E /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0393272C131F613D0007496E /* Cocoa.framework */; }; 14 | 03932737131F613D0007496E /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 03932735131F613D0007496E /* InfoPlist.strings */; }; 15 | 0393273A131F613D0007496E /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 03932739131F613D0007496E /* main.m */; }; 16 | 0393273D131F613D0007496E /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 0393273B131F613D0007496E /* Credits.rtf */; }; 17 | 03932740131F613D0007496E /* PopoverSampleAppAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 0393273F131F613D0007496E /* PopoverSampleAppAppDelegate.m */; }; 18 | 03932743131F613D0007496E /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 03932741131F613D0007496E /* MainMenu.xib */; }; 19 | 0393274C131F61590007496E /* ContentViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0393274A131F61590007496E /* ContentViewController.m */; }; 20 | 0393274D131F61590007496E /* ContentViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0393274B131F61590007496E /* ContentViewController.xib */; }; 21 | BFF06C9116CAAEC100C3D751 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BFF06C9016CAAEC100C3D751 /* QuartzCore.framework */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXContainerItemProxy section */ 25 | 03657F27187A09A000C969BA /* PBXContainerItemProxy */ = { 26 | isa = PBXContainerItemProxy; 27 | containerPortal = 03657F23187A09A000C969BA /* INPopoverController.xcodeproj */; 28 | proxyType = 2; 29 | remoteGlobalIDString = 98BA8937143EA04A006F744E; 30 | remoteInfo = INPopoverController; 31 | }; 32 | 03657F29187A09BA00C969BA /* PBXContainerItemProxy */ = { 33 | isa = PBXContainerItemProxy; 34 | containerPortal = 03657F23187A09A000C969BA /* INPopoverController.xcodeproj */; 35 | proxyType = 1; 36 | remoteGlobalIDString = 98BA8936143EA04A006F744E; 37 | remoteInfo = INPopoverController; 38 | }; 39 | /* End PBXContainerItemProxy section */ 40 | 41 | /* Begin PBXCopyFilesBuildPhase section */ 42 | 03657F2C187A09C300C969BA /* CopyFiles */ = { 43 | isa = PBXCopyFilesBuildPhase; 44 | buildActionMask = 2147483647; 45 | dstPath = ""; 46 | dstSubfolderSpec = 10; 47 | files = ( 48 | 03657F2D187A09C900C969BA /* INPopoverController.framework in CopyFiles */, 49 | ); 50 | runOnlyForDeploymentPostprocessing = 0; 51 | }; 52 | /* End PBXCopyFilesBuildPhase section */ 53 | 54 | /* Begin PBXFileReference section */ 55 | 03657F23187A09A000C969BA /* INPopoverController.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = INPopoverController.xcodeproj; path = ../../INPopoverController.xcodeproj; sourceTree = ""; }; 56 | 0391A4FD18822FAA007D32D5 /* AlphaColorWell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AlphaColorWell.h; sourceTree = ""; }; 57 | 0391A4FE18822FAA007D32D5 /* AlphaColorWell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AlphaColorWell.m; sourceTree = ""; }; 58 | 03932728131F613D0007496E /* PopoverSampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PopoverSampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 59 | 0393272C131F613D0007496E /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 60 | 0393272F131F613D0007496E /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 61 | 03932730131F613D0007496E /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 62 | 03932731131F613D0007496E /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 63 | 03932734131F613D0007496E /* PopoverSampleApp-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "PopoverSampleApp-Info.plist"; sourceTree = ""; }; 64 | 03932736131F613D0007496E /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 65 | 03932738131F613D0007496E /* PopoverSampleApp-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "PopoverSampleApp-Prefix.pch"; sourceTree = ""; }; 66 | 03932739131F613D0007496E /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 67 | 0393273C131F613D0007496E /* en */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = en; path = en.lproj/Credits.rtf; sourceTree = ""; }; 68 | 0393273E131F613D0007496E /* PopoverSampleAppAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PopoverSampleAppAppDelegate.h; sourceTree = ""; }; 69 | 0393273F131F613D0007496E /* PopoverSampleAppAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PopoverSampleAppAppDelegate.m; sourceTree = ""; }; 70 | 03932742131F613D0007496E /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/MainMenu.xib; sourceTree = ""; }; 71 | 03932749131F61590007496E /* ContentViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ContentViewController.h; sourceTree = ""; }; 72 | 0393274A131F61590007496E /* ContentViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ContentViewController.m; sourceTree = ""; }; 73 | 0393274B131F61590007496E /* ContentViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ContentViewController.xib; sourceTree = ""; }; 74 | BFF06C9016CAAEC100C3D751 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 75 | /* End PBXFileReference section */ 76 | 77 | /* Begin PBXFrameworksBuildPhase section */ 78 | 03932725131F613D0007496E /* Frameworks */ = { 79 | isa = PBXFrameworksBuildPhase; 80 | buildActionMask = 2147483647; 81 | files = ( 82 | 03657F2B187A09BE00C969BA /* INPopoverController.framework in Frameworks */, 83 | BFF06C9116CAAEC100C3D751 /* QuartzCore.framework in Frameworks */, 84 | 0393272D131F613D0007496E /* Cocoa.framework in Frameworks */, 85 | ); 86 | runOnlyForDeploymentPostprocessing = 0; 87 | }; 88 | /* End PBXFrameworksBuildPhase section */ 89 | 90 | /* Begin PBXGroup section */ 91 | 03657F24187A09A000C969BA /* Products */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | 03657F28187A09A000C969BA /* INPopoverController.framework */, 95 | ); 96 | name = Products; 97 | sourceTree = ""; 98 | }; 99 | 0393271D131F613D0007496E = { 100 | isa = PBXGroup; 101 | children = ( 102 | 03932732131F613D0007496E /* PopoverSampleApp */, 103 | 0393272B131F613D0007496E /* Frameworks */, 104 | 03932729131F613D0007496E /* Products */, 105 | ); 106 | sourceTree = ""; 107 | }; 108 | 03932729131F613D0007496E /* Products */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | 03932728131F613D0007496E /* PopoverSampleApp.app */, 112 | ); 113 | name = Products; 114 | sourceTree = ""; 115 | }; 116 | 0393272B131F613D0007496E /* Frameworks */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | BFF06C9016CAAEC100C3D751 /* QuartzCore.framework */, 120 | 0393272C131F613D0007496E /* Cocoa.framework */, 121 | 0393272E131F613D0007496E /* Other Frameworks */, 122 | ); 123 | name = Frameworks; 124 | sourceTree = ""; 125 | }; 126 | 0393272E131F613D0007496E /* Other Frameworks */ = { 127 | isa = PBXGroup; 128 | children = ( 129 | 0393272F131F613D0007496E /* AppKit.framework */, 130 | 03932730131F613D0007496E /* CoreData.framework */, 131 | 03932731131F613D0007496E /* Foundation.framework */, 132 | ); 133 | name = "Other Frameworks"; 134 | sourceTree = ""; 135 | }; 136 | 03932732131F613D0007496E /* PopoverSampleApp */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | 03657F23187A09A000C969BA /* INPopoverController.xcodeproj */, 140 | 0393273E131F613D0007496E /* PopoverSampleAppAppDelegate.h */, 141 | 0393273F131F613D0007496E /* PopoverSampleAppAppDelegate.m */, 142 | 03932749131F61590007496E /* ContentViewController.h */, 143 | 0393274A131F61590007496E /* ContentViewController.m */, 144 | 0391A4FD18822FAA007D32D5 /* AlphaColorWell.h */, 145 | 0391A4FE18822FAA007D32D5 /* AlphaColorWell.m */, 146 | 03932741131F613D0007496E /* MainMenu.xib */, 147 | 0393274B131F61590007496E /* ContentViewController.xib */, 148 | 03932733131F613D0007496E /* Supporting Files */, 149 | ); 150 | path = PopoverSampleApp; 151 | sourceTree = ""; 152 | }; 153 | 03932733131F613D0007496E /* Supporting Files */ = { 154 | isa = PBXGroup; 155 | children = ( 156 | 03932734131F613D0007496E /* PopoverSampleApp-Info.plist */, 157 | 03932735131F613D0007496E /* InfoPlist.strings */, 158 | 03932738131F613D0007496E /* PopoverSampleApp-Prefix.pch */, 159 | 03932739131F613D0007496E /* main.m */, 160 | 0393273B131F613D0007496E /* Credits.rtf */, 161 | ); 162 | name = "Supporting Files"; 163 | sourceTree = ""; 164 | }; 165 | /* End PBXGroup section */ 166 | 167 | /* Begin PBXNativeTarget section */ 168 | 03932727131F613D0007496E /* PopoverSampleApp */ = { 169 | isa = PBXNativeTarget; 170 | buildConfigurationList = 03932746131F613D0007496E /* Build configuration list for PBXNativeTarget "PopoverSampleApp" */; 171 | buildPhases = ( 172 | 03932724131F613D0007496E /* Sources */, 173 | 03932725131F613D0007496E /* Frameworks */, 174 | 03932726131F613D0007496E /* Resources */, 175 | 03657F2C187A09C300C969BA /* CopyFiles */, 176 | ); 177 | buildRules = ( 178 | ); 179 | dependencies = ( 180 | 03657F2A187A09BA00C969BA /* PBXTargetDependency */, 181 | ); 182 | name = PopoverSampleApp; 183 | productName = PopoverSampleApp; 184 | productReference = 03932728131F613D0007496E /* PopoverSampleApp.app */; 185 | productType = "com.apple.product-type.application"; 186 | }; 187 | /* End PBXNativeTarget section */ 188 | 189 | /* Begin PBXProject section */ 190 | 0393271F131F613D0007496E /* Project object */ = { 191 | isa = PBXProject; 192 | attributes = { 193 | ORGANIZATIONNAME = "Indragie Karunaratne"; 194 | }; 195 | buildConfigurationList = 03932722131F613D0007496E /* Build configuration list for PBXProject "PopoverSampleApp" */; 196 | compatibilityVersion = "Xcode 3.2"; 197 | developmentRegion = English; 198 | hasScannedForEncodings = 0; 199 | knownRegions = ( 200 | en, 201 | ); 202 | mainGroup = 0393271D131F613D0007496E; 203 | productRefGroup = 03932729131F613D0007496E /* Products */; 204 | projectDirPath = ""; 205 | projectReferences = ( 206 | { 207 | ProductGroup = 03657F24187A09A000C969BA /* Products */; 208 | ProjectRef = 03657F23187A09A000C969BA /* INPopoverController.xcodeproj */; 209 | }, 210 | ); 211 | projectRoot = ""; 212 | targets = ( 213 | 03932727131F613D0007496E /* PopoverSampleApp */, 214 | ); 215 | }; 216 | /* End PBXProject section */ 217 | 218 | /* Begin PBXReferenceProxy section */ 219 | 03657F28187A09A000C969BA /* INPopoverController.framework */ = { 220 | isa = PBXReferenceProxy; 221 | fileType = wrapper.framework; 222 | path = INPopoverController.framework; 223 | remoteRef = 03657F27187A09A000C969BA /* PBXContainerItemProxy */; 224 | sourceTree = BUILT_PRODUCTS_DIR; 225 | }; 226 | /* End PBXReferenceProxy section */ 227 | 228 | /* Begin PBXResourcesBuildPhase section */ 229 | 03932726131F613D0007496E /* Resources */ = { 230 | isa = PBXResourcesBuildPhase; 231 | buildActionMask = 2147483647; 232 | files = ( 233 | 03932737131F613D0007496E /* InfoPlist.strings in Resources */, 234 | 0393273D131F613D0007496E /* Credits.rtf in Resources */, 235 | 03932743131F613D0007496E /* MainMenu.xib in Resources */, 236 | 0393274D131F61590007496E /* ContentViewController.xib in Resources */, 237 | ); 238 | runOnlyForDeploymentPostprocessing = 0; 239 | }; 240 | /* End PBXResourcesBuildPhase section */ 241 | 242 | /* Begin PBXSourcesBuildPhase section */ 243 | 03932724131F613D0007496E /* Sources */ = { 244 | isa = PBXSourcesBuildPhase; 245 | buildActionMask = 2147483647; 246 | files = ( 247 | 0391A4FF18822FAA007D32D5 /* AlphaColorWell.m in Sources */, 248 | 0393273A131F613D0007496E /* main.m in Sources */, 249 | 03932740131F613D0007496E /* PopoverSampleAppAppDelegate.m in Sources */, 250 | 0393274C131F61590007496E /* ContentViewController.m in Sources */, 251 | ); 252 | runOnlyForDeploymentPostprocessing = 0; 253 | }; 254 | /* End PBXSourcesBuildPhase section */ 255 | 256 | /* Begin PBXTargetDependency section */ 257 | 03657F2A187A09BA00C969BA /* PBXTargetDependency */ = { 258 | isa = PBXTargetDependency; 259 | name = INPopoverController; 260 | targetProxy = 03657F29187A09BA00C969BA /* PBXContainerItemProxy */; 261 | }; 262 | /* End PBXTargetDependency section */ 263 | 264 | /* Begin PBXVariantGroup section */ 265 | 03932735131F613D0007496E /* InfoPlist.strings */ = { 266 | isa = PBXVariantGroup; 267 | children = ( 268 | 03932736131F613D0007496E /* en */, 269 | ); 270 | name = InfoPlist.strings; 271 | sourceTree = ""; 272 | }; 273 | 0393273B131F613D0007496E /* Credits.rtf */ = { 274 | isa = PBXVariantGroup; 275 | children = ( 276 | 0393273C131F613D0007496E /* en */, 277 | ); 278 | name = Credits.rtf; 279 | sourceTree = ""; 280 | }; 281 | 03932741131F613D0007496E /* MainMenu.xib */ = { 282 | isa = PBXVariantGroup; 283 | children = ( 284 | 03932742131F613D0007496E /* en */, 285 | ); 286 | name = MainMenu.xib; 287 | sourceTree = ""; 288 | }; 289 | /* End PBXVariantGroup section */ 290 | 291 | /* Begin XCBuildConfiguration section */ 292 | 03932744131F613D0007496E /* Debug */ = { 293 | isa = XCBuildConfiguration; 294 | buildSettings = { 295 | CLANG_ENABLE_OBJC_ARC = YES; 296 | GCC_C_LANGUAGE_STANDARD = gnu99; 297 | GCC_OPTIMIZATION_LEVEL = 0; 298 | GCC_PREPROCESSOR_DEFINITIONS = DEBUG; 299 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 300 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 301 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 302 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 303 | GCC_WARN_UNUSED_VARIABLE = YES; 304 | MACOSX_DEPLOYMENT_TARGET = 10.7; 305 | ONLY_ACTIVE_ARCH = YES; 306 | SDKROOT = macosx; 307 | }; 308 | name = Debug; 309 | }; 310 | 03932745131F613D0007496E /* Release */ = { 311 | isa = XCBuildConfiguration; 312 | buildSettings = { 313 | CLANG_ENABLE_OBJC_ARC = YES; 314 | GCC_C_LANGUAGE_STANDARD = gnu99; 315 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 316 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 317 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 318 | GCC_WARN_UNUSED_VARIABLE = YES; 319 | MACOSX_DEPLOYMENT_TARGET = 10.7; 320 | SDKROOT = macosx; 321 | }; 322 | name = Release; 323 | }; 324 | 03932747131F613D0007496E /* Debug */ = { 325 | isa = XCBuildConfiguration; 326 | buildSettings = { 327 | ALWAYS_SEARCH_USER_PATHS = NO; 328 | ARCHS = "$(ARCHS_STANDARD_64_BIT)"; 329 | CLANG_ENABLE_OBJC_ARC = YES; 330 | COPY_PHASE_STRIP = NO; 331 | GCC_DYNAMIC_NO_PIC = NO; 332 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 333 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 334 | GCC_PREFIX_HEADER = "PopoverSampleApp/PopoverSampleApp-Prefix.pch"; 335 | INFOPLIST_FILE = "PopoverSampleApp/PopoverSampleApp-Info.plist"; 336 | LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; 337 | PRODUCT_NAME = "$(TARGET_NAME)"; 338 | WRAPPER_EXTENSION = app; 339 | }; 340 | name = Debug; 341 | }; 342 | 03932748131F613D0007496E /* Release */ = { 343 | isa = XCBuildConfiguration; 344 | buildSettings = { 345 | ALWAYS_SEARCH_USER_PATHS = NO; 346 | ARCHS = "$(ARCHS_STANDARD_64_BIT)"; 347 | CLANG_ENABLE_OBJC_ARC = YES; 348 | COPY_PHASE_STRIP = YES; 349 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 350 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 351 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 352 | GCC_PREFIX_HEADER = "PopoverSampleApp/PopoverSampleApp-Prefix.pch"; 353 | INFOPLIST_FILE = "PopoverSampleApp/PopoverSampleApp-Info.plist"; 354 | LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; 355 | PRODUCT_NAME = "$(TARGET_NAME)"; 356 | WRAPPER_EXTENSION = app; 357 | }; 358 | name = Release; 359 | }; 360 | /* End XCBuildConfiguration section */ 361 | 362 | /* Begin XCConfigurationList section */ 363 | 03932722131F613D0007496E /* Build configuration list for PBXProject "PopoverSampleApp" */ = { 364 | isa = XCConfigurationList; 365 | buildConfigurations = ( 366 | 03932744131F613D0007496E /* Debug */, 367 | 03932745131F613D0007496E /* Release */, 368 | ); 369 | defaultConfigurationIsVisible = 0; 370 | defaultConfigurationName = Release; 371 | }; 372 | 03932746131F613D0007496E /* Build configuration list for PBXNativeTarget "PopoverSampleApp" */ = { 373 | isa = XCConfigurationList; 374 | buildConfigurations = ( 375 | 03932747131F613D0007496E /* Debug */, 376 | 03932748131F613D0007496E /* Release */, 377 | ); 378 | defaultConfigurationIsVisible = 0; 379 | defaultConfigurationName = Release; 380 | }; 381 | /* End XCConfigurationList section */ 382 | }; 383 | rootObject = 0393271F131F613D0007496E /* Project object */; 384 | } 385 | -------------------------------------------------------------------------------- /PopoverSampleApp/PopoverSampleApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PopoverSampleApp/PopoverSampleApp.xcodeproj/xcuserdata/Indragie.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /PopoverSampleApp/PopoverSampleApp.xcodeproj/xcuserdata/Indragie.xcuserdatad/xcschemes/PopoverSampleApp.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 44 | 45 | 51 | 52 | 53 | 54 | 55 | 56 | 64 | 65 | 71 | 72 | 73 | 74 | 76 | 77 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /PopoverSampleApp/PopoverSampleApp.xcodeproj/xcuserdata/Indragie.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | PopoverSampleApp.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 03932727131F613D0007496E 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /PopoverSampleApp/PopoverSampleApp/AlphaColorWell.h: -------------------------------------------------------------------------------- 1 | // 2 | // AlphaColorWell.h 3 | // PopoverSampleApp 4 | // 5 | // Created by Indragie Karunaratne on 1/11/2014. 6 | // 7 | 8 | #import 9 | 10 | @interface AlphaColorWell : NSColorWell 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /PopoverSampleApp/PopoverSampleApp/AlphaColorWell.m: -------------------------------------------------------------------------------- 1 | // 2 | // AlphaColorWell.m 3 | // PopoverSampleApp 4 | // 5 | // Created by Indragie Karunaratne on 1/11/2014. 6 | // 7 | 8 | #import "AlphaColorWell.h" 9 | 10 | @implementation AlphaColorWell 11 | 12 | - (void)activate:(BOOL)exclusive 13 | { 14 | [[NSColorPanel sharedColorPanel] setShowsAlpha:YES]; 15 | [super activate:exclusive]; 16 | } 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /PopoverSampleApp/PopoverSampleApp/ContentViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ContentViewController.h 3 | // Copyright 2011-2014 Indragie Karunaratne. All rights reserved. 4 | // 5 | 6 | #import 7 | 8 | 9 | @interface ContentViewController : NSViewController { 10 | @private 11 | 12 | } 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /PopoverSampleApp/PopoverSampleApp/ContentViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ContentViewController.m 3 | // Copyright 2011-2014 Indragie Karunaratne. All rights reserved. 4 | // 5 | 6 | #import "ContentViewController.h" 7 | 8 | 9 | @implementation ContentViewController 10 | 11 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 12 | { 13 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 14 | if (self) { 15 | // Initialization code here. 16 | } 17 | 18 | return self; 19 | } 20 | 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /PopoverSampleApp/PopoverSampleApp/ContentViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1070 5 | 11A390 6 | 1510 7 | 1110.91 8 | 544.00 9 | 10 | com.apple.InterfaceBuilder.CocoaPlugin 11 | 1510 12 | 13 | 14 | NSCustomView 15 | NSTextField 16 | NSTextFieldCell 17 | NSCustomObject 18 | 19 | 20 | com.apple.InterfaceBuilder.CocoaPlugin 21 | 22 | 23 | 24 | 25 | ContentViewController 26 | 27 | 28 | FirstResponder 29 | 30 | 31 | NSApplication 32 | 33 | 34 | 35 | 268 36 | 37 | 38 | 39 | 268 40 | {{68, 83}, {215, 39}} 41 | 42 | 43 | _NS:3583 44 | YES 45 | 46 | 67239424 47 | 272629760 48 | This is view content loaded from ContentViewController.xib 49 | 50 | LucidaGrande 51 | 13 52 | 16 53 | 54 | 55 | 56 | 6 57 | System 58 | controlColor 59 | 60 | 3 61 | MC42NjY2NjY2NjY3AA 62 | 63 | 64 | 65 | 1 66 | MCAwIDAAA 67 | 68 | 69 | 70 | 71 | {351, 204} 72 | 73 | 74 | NSView 75 | 76 | 77 | 78 | 79 | 80 | 81 | view 82 | 83 | 84 | 85 | 3 86 | 87 | 88 | 89 | 90 | 91 | 0 92 | 93 | 94 | 95 | 96 | 97 | -2 98 | 99 | 100 | File's Owner 101 | 102 | 103 | -1 104 | 105 | 106 | First Responder 107 | 108 | 109 | -3 110 | 111 | 112 | Application 113 | 114 | 115 | 1 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 6 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 7 132 | 133 | 134 | 135 | 136 | 137 | 138 | com.apple.InterfaceBuilder.CocoaPlugin 139 | com.apple.InterfaceBuilder.CocoaPlugin 140 | com.apple.InterfaceBuilder.CocoaPlugin 141 | {{633, 728}, {480, 272}} 142 | com.apple.InterfaceBuilder.CocoaPlugin 143 | {628, 654} 144 | {{357, 416}, {480, 272}} 145 | com.apple.InterfaceBuilder.CocoaPlugin 146 | com.apple.InterfaceBuilder.CocoaPlugin 147 | 148 | 149 | 150 | 151 | 152 | 7 153 | 154 | 155 | 156 | 157 | ContentViewController 158 | NSViewController 159 | 160 | IBProjectSource 161 | ./Classes/ContentViewController.h 162 | 163 | 164 | 165 | 166 | 0 167 | IBCocoaFramework 168 | YES 169 | 3 170 | 171 | 172 | -------------------------------------------------------------------------------- /PopoverSampleApp/PopoverSampleApp/PopoverSampleApp-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | com.indragie.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSMinimumSystemVersion 26 | ${MACOSX_DEPLOYMENT_TARGET} 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /PopoverSampleApp/PopoverSampleApp/PopoverSampleApp-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'PopoverSampleApp' target in the 'PopoverSampleApp' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /PopoverSampleApp/PopoverSampleApp/PopoverSampleAppAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // PopoverSampleAppAppDelegate.h 3 | // Copyright 2011-2014 Indragie Karunaratne. All rights reserved. 4 | // 5 | 6 | #import 7 | 8 | @class INPopoverController; 9 | @interface PopoverSampleAppAppDelegate : NSObject { 10 | @private 11 | NSWindow *__weak window; 12 | INPopoverController *popoverController; 13 | } 14 | @property (nonatomic, strong) INPopoverController *popoverController; 15 | @property (weak) IBOutlet NSWindow *window; 16 | - (IBAction)togglePopover:(id)sender; 17 | @end 18 | -------------------------------------------------------------------------------- /PopoverSampleApp/PopoverSampleApp/PopoverSampleAppAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // PopoverSampleAppAppDelegate.m 3 | // Copyright 2011-2014 Indragie Karunaratne. All rights reserved. 4 | // 5 | 6 | #import "PopoverSampleAppAppDelegate.h" 7 | #import "ContentViewController.h" 8 | #import 9 | 10 | @implementation PopoverSampleAppAppDelegate 11 | @synthesize window, popoverController; 12 | 13 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification 14 | { 15 | ContentViewController *viewController = [[ContentViewController alloc] initWithNibName:@"ContentViewController" bundle:nil]; 16 | self.popoverController = [[INPopoverController alloc] initWithContentViewController:viewController]; 17 | } 18 | 19 | - (IBAction)togglePopover:(id)sender 20 | { 21 | if (self.popoverController.popoverIsVisible) { 22 | [self.popoverController closePopover:nil]; 23 | } else { 24 | [self.popoverController presentPopoverFromRect:[sender bounds] inView:sender preferredArrowDirection:INPopoverArrowDirectionLeft anchorsToPositionView:YES]; 25 | } 26 | } 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /PopoverSampleApp/PopoverSampleApp/en.lproj/Credits.rtf: -------------------------------------------------------------------------------- 1 | {\rtf0\ansi{\fonttbl\f0\fswiss Helvetica;} 2 | {\colortbl;\red255\green255\blue255;} 3 | \paperw9840\paperh8400 4 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural 5 | 6 | \f0\b\fs24 \cf0 Engineering: 7 | \b0 \ 8 | Some people\ 9 | \ 10 | 11 | \b Human Interface Design: 12 | \b0 \ 13 | Some other people\ 14 | \ 15 | 16 | \b Testing: 17 | \b0 \ 18 | Hopefully not nobody\ 19 | \ 20 | 21 | \b Documentation: 22 | \b0 \ 23 | Whoever\ 24 | \ 25 | 26 | \b With special thanks to: 27 | \b0 \ 28 | Mom\ 29 | } 30 | -------------------------------------------------------------------------------- /PopoverSampleApp/PopoverSampleApp/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /PopoverSampleApp/PopoverSampleApp/en.lproj/MainMenu.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | Default 523 | 524 | 525 | 526 | 527 | 528 | 529 | Left to Right 530 | 531 | 532 | 533 | 534 | 535 | 536 | Right to Left 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | Default 548 | 549 | 550 | 551 | 552 | 553 | 554 | Left to Right 555 | 556 | 557 | 558 | 559 | 560 | 561 | Right to Left 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 731 | 742 | 753 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 774 | -------------------------------------------------------------------------------- /PopoverSampleApp/PopoverSampleApp/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // PopoverSampleApp 4 | // 5 | // Created by Indragie Karunaratne on 11-03-02. 6 | // Copyright 2011-2014 PCWiz Computer. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, char *argv[]) 12 | { 13 | return NSApplicationMain(argc, (const char **)argv); 14 | } 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## INPopoverController 2 | ### Open source OS X popover implementation 3 | 4 | OS X 10.7 introduced the `NSPopover` class for displaying popover windows. That said, developers who want to support older versions of OS X (like me) are unable to use that API without breaking backward compatibility. So I developed this class that will allow developers to easily add popovers into their applications and also have it be compatible with older versions of OS X. I've included a sample app to demonstrate how to use INPopoverController: 5 | 6 | ![INPopoverController](https://raw.github.com/indragiek/INPopoverController/master/screenshot.png) 7 | 8 | **Features:** 9 | 10 | - Customizable color, border color, border width, arrow size, and corner radius. 11 | - Automatically calculates the best arrow direction depending on screen space and popover position 12 | - Displays content from a regular `NSViewController` (can be loaded from a NIB) 13 | - Animation for when the popover appears/disappears and when the content size is changed 14 | - Popover can anchor to a view 15 | - Customizable popover behaviour (close when key status is lost, when application resigns active) 16 | 17 | ### How to use it 18 | 19 | The headers are well documented (and I've also included a sample app) so it should be simple to figure out how to use it. There are `color`, `borderColor`, and `borderWidth` properties to customize the appearance of the popover. There are also some hard-coded defines in the `INPopoverControllerDefines.h` file which can be changed to further customize the appearance. The `closesWhenPopoverResignsKey` and `closesWhenApplicationBecomesInactive` properties can be used to control the behaviour of the popover. Everything else should be pretty much self explanatory. 20 | 21 | If you want to completely customize the drawing of the popover, you can edit the `INPopoverWindowFrame.m` file to run your own drawing code instead of the default. Make sure that you're taking the `arrowDirection` property into account when drawing. 22 | 23 | **Create a new issue if you have trouble getting it working, or if you want to request new features** 24 | 25 | ### Contact 26 | 27 | * Indragie Karunaratne 28 | * [@indragie](http://twitter.com/indragie) 29 | * [http://indragie.com](http://indragie.com) 30 | 31 | ### Licensing 32 | 33 | INPopoverController is licensed under the [BSD license](http://www.opensource.org/licenses/bsd-license.php). -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/INPopoverController/ba272ac695dfca3880990a3df62174b3b1e7c44b/screenshot.png --------------------------------------------------------------------------------