├── .gitignore ├── ChromeMenu.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── ChromeMenu.xccheckout │ └── xcuserdata │ │ └── objective.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── objective.xcuserdatad │ └── xcschemes │ ├── ChromeMenu.xcscheme │ └── xcschememanagement.plist ├── ChromeMenu ├── ChromeMenu-Info.plist ├── ChromeMenu-Prefix.pch ├── en.lproj │ ├── CMMenuItemIconView.xib │ ├── CMMenuItemSeparatorView.xib │ ├── CMMenuItemView.xib │ └── InfoPlist.strings └── src │ ├── CMDebug.h │ ├── CMMenu+InternalMethods.h │ ├── CMMenu.h │ ├── CMMenu.m │ ├── CMMenuItem+InternalMethods.h │ ├── CMMenuItem.h │ ├── CMMenuItem.m │ ├── CMMenuItemSeparatorView.h │ ├── CMMenuItemSeparatorView.m │ ├── CMMenuItemView+InternalMethods.h │ ├── CMMenuItemView.h │ ├── CMMenuItemView.m │ ├── CMMenuKeyEventInterpreter.h │ ├── CMMenuKeyEventInterpreter.m │ ├── CMMenuScroller.h │ ├── CMMenuScroller.m │ ├── CMScrollDocumentView.h │ ├── CMScrollDocumentView.m │ ├── CMScrollView.h │ ├── CMScrollView.m │ ├── CMUnderlyingView.h │ ├── CMUnderlyingView.m │ ├── CMUnderlyingWindow.h │ ├── CMUnderlyingWindow.m │ ├── CMWindowController.h │ ├── CMWindowController.m │ ├── ChromeMenu.h │ ├── NSImage+CMMenuImageRepAdditions.h │ └── NSImage+CMMenuImageRepAdditions.m ├── ChromeMenuTests ├── ChromeMenuTests-Info.plist ├── ChromeMenuTests.m └── en.lproj │ └── InfoPlist.strings ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # OS generated files # 2 | ###################### 3 | .DS_Store 4 | .DS_Store? 5 | ._* 6 | .Spotlight-V100 7 | .Trashes 8 | ehthumbs.db 9 | Thumbs.db 10 | -------------------------------------------------------------------------------- /ChromeMenu.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | A04489C1183B65840056818A /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A04489C0183B65840056818A /* Cocoa.framework */; }; 11 | A04489CB183B65840056818A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = A04489C9183B65840056818A /* InfoPlist.strings */; }; 12 | A04489D6183B65850056818A /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A04489D5183B65850056818A /* XCTest.framework */; }; 13 | A04489D7183B65850056818A /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A04489C0183B65840056818A /* Cocoa.framework */; }; 14 | A04489DA183B65850056818A /* ChromeMenu.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A04489BD183B65840056818A /* ChromeMenu.framework */; }; 15 | A04489E0183B65850056818A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = A04489DE183B65850056818A /* InfoPlist.strings */; }; 16 | A04489E2183B65850056818A /* ChromeMenuTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A04489E1183B65850056818A /* ChromeMenuTests.m */; }; 17 | A0448A09183B6D3E0056818A /* ChromeMenu.h in Headers */ = {isa = PBXBuildFile; fileRef = A04489EC183B6D3E0056818A /* ChromeMenu.h */; settings = {ATTRIBUTES = (Public, ); }; }; 18 | A0448A0A183B6D3E0056818A /* CMUnderlyingView.h in Headers */ = {isa = PBXBuildFile; fileRef = A04489ED183B6D3E0056818A /* CMUnderlyingView.h */; }; 19 | A0448A0B183B6D3E0056818A /* CMUnderlyingView.m in Sources */ = {isa = PBXBuildFile; fileRef = A04489EE183B6D3E0056818A /* CMUnderlyingView.m */; }; 20 | A0448A0C183B6D3E0056818A /* CMUnderlyingWindow.h in Headers */ = {isa = PBXBuildFile; fileRef = A04489EF183B6D3E0056818A /* CMUnderlyingWindow.h */; }; 21 | A0448A0D183B6D3E0056818A /* CMUnderlyingWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = A04489F0183B6D3E0056818A /* CMUnderlyingWindow.m */; }; 22 | A0448A0E183B6D3E0056818A /* CMDebug.h in Headers */ = {isa = PBXBuildFile; fileRef = A04489F1183B6D3E0056818A /* CMDebug.h */; }; 23 | A0448A0F183B6D3E0056818A /* CMMenu.h in Headers */ = {isa = PBXBuildFile; fileRef = A04489F2183B6D3E0056818A /* CMMenu.h */; settings = {ATTRIBUTES = (Public, ); }; }; 24 | A0448A10183B6D3E0056818A /* CMMenu.m in Sources */ = {isa = PBXBuildFile; fileRef = A04489F3183B6D3E0056818A /* CMMenu.m */; }; 25 | A0448A11183B6D3E0056818A /* CMMenu+InternalMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = A04489F4183B6D3E0056818A /* CMMenu+InternalMethods.h */; }; 26 | A0448A12183B6D3E0056818A /* CMMenuItem.h in Headers */ = {isa = PBXBuildFile; fileRef = A04489F5183B6D3E0056818A /* CMMenuItem.h */; settings = {ATTRIBUTES = (Public, ); }; }; 27 | A0448A13183B6D3E0056818A /* CMMenuItem.m in Sources */ = {isa = PBXBuildFile; fileRef = A04489F6183B6D3E0056818A /* CMMenuItem.m */; }; 28 | A0448A14183B6D3E0056818A /* CMMenuItem+InternalMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = A04489F7183B6D3E0056818A /* CMMenuItem+InternalMethods.h */; }; 29 | A0448A15183B6D3E0056818A /* CMMenuItemSeparatorView.h in Headers */ = {isa = PBXBuildFile; fileRef = A04489F8183B6D3E0056818A /* CMMenuItemSeparatorView.h */; }; 30 | A0448A16183B6D3E0056818A /* CMMenuItemSeparatorView.m in Sources */ = {isa = PBXBuildFile; fileRef = A04489F9183B6D3E0056818A /* CMMenuItemSeparatorView.m */; }; 31 | A0448A17183B6D3E0056818A /* CMMenuItemView.h in Headers */ = {isa = PBXBuildFile; fileRef = A04489FA183B6D3E0056818A /* CMMenuItemView.h */; }; 32 | A0448A18183B6D3E0056818A /* CMMenuItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = A04489FB183B6D3E0056818A /* CMMenuItemView.m */; }; 33 | A0448A19183B6D3E0056818A /* CMMenuItemView+InternalMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = A04489FC183B6D3E0056818A /* CMMenuItemView+InternalMethods.h */; }; 34 | A0448A1A183B6D3E0056818A /* CMMenuKeyEventInterpreter.h in Headers */ = {isa = PBXBuildFile; fileRef = A04489FD183B6D3E0056818A /* CMMenuKeyEventInterpreter.h */; }; 35 | A0448A1B183B6D3E0056818A /* CMMenuKeyEventInterpreter.m in Sources */ = {isa = PBXBuildFile; fileRef = A04489FE183B6D3E0056818A /* CMMenuKeyEventInterpreter.m */; }; 36 | A0448A1C183B6D3E0056818A /* CMMenuScroller.h in Headers */ = {isa = PBXBuildFile; fileRef = A04489FF183B6D3E0056818A /* CMMenuScroller.h */; }; 37 | A0448A1D183B6D3E0056818A /* CMMenuScroller.m in Sources */ = {isa = PBXBuildFile; fileRef = A0448A00183B6D3E0056818A /* CMMenuScroller.m */; }; 38 | A0448A1E183B6D3E0056818A /* CMScrollDocumentView.h in Headers */ = {isa = PBXBuildFile; fileRef = A0448A01183B6D3E0056818A /* CMScrollDocumentView.h */; }; 39 | A0448A1F183B6D3E0056818A /* CMScrollDocumentView.m in Sources */ = {isa = PBXBuildFile; fileRef = A0448A02183B6D3E0056818A /* CMScrollDocumentView.m */; }; 40 | A0448A20183B6D3E0056818A /* CMScrollView.h in Headers */ = {isa = PBXBuildFile; fileRef = A0448A03183B6D3E0056818A /* CMScrollView.h */; }; 41 | A0448A21183B6D3E0056818A /* CMScrollView.m in Sources */ = {isa = PBXBuildFile; fileRef = A0448A04183B6D3E0056818A /* CMScrollView.m */; }; 42 | A0448A22183B6D3E0056818A /* CMWindowController.h in Headers */ = {isa = PBXBuildFile; fileRef = A0448A05183B6D3E0056818A /* CMWindowController.h */; }; 43 | A0448A23183B6D3E0056818A /* CMWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = A0448A06183B6D3E0056818A /* CMWindowController.m */; }; 44 | A0448A24183B6D3E0056818A /* NSImage+CMMenuImageRepAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = A0448A07183B6D3E0056818A /* NSImage+CMMenuImageRepAdditions.h */; }; 45 | A0448A25183B6D3E0056818A /* NSImage+CMMenuImageRepAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = A0448A08183B6D3E0056818A /* NSImage+CMMenuImageRepAdditions.m */; }; 46 | A0448A2C183B6D5D0056818A /* CMMenuItemIconView.xib in Resources */ = {isa = PBXBuildFile; fileRef = A0448A26183B6D5D0056818A /* CMMenuItemIconView.xib */; }; 47 | A0448A2D183B6D5D0056818A /* CMMenuItemSeparatorView.xib in Resources */ = {isa = PBXBuildFile; fileRef = A0448A28183B6D5D0056818A /* CMMenuItemSeparatorView.xib */; }; 48 | A0448A2E183B6D5D0056818A /* CMMenuItemView.xib in Resources */ = {isa = PBXBuildFile; fileRef = A0448A2A183B6D5D0056818A /* CMMenuItemView.xib */; }; 49 | A0448A30183B6F1D0056818A /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A0448A2F183B6F1D0056818A /* QuartzCore.framework */; }; 50 | /* End PBXBuildFile section */ 51 | 52 | /* Begin PBXContainerItemProxy section */ 53 | A04489D8183B65850056818A /* PBXContainerItemProxy */ = { 54 | isa = PBXContainerItemProxy; 55 | containerPortal = A04489B4183B65840056818A /* Project object */; 56 | proxyType = 1; 57 | remoteGlobalIDString = A04489BC183B65840056818A; 58 | remoteInfo = ChromeMenu; 59 | }; 60 | /* End PBXContainerItemProxy section */ 61 | 62 | /* Begin PBXFileReference section */ 63 | A04489BD183B65840056818A /* ChromeMenu.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ChromeMenu.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 64 | A04489C0183B65840056818A /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 65 | A04489C3183B65840056818A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 66 | A04489C4183B65840056818A /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 67 | A04489C5183B65840056818A /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 68 | A04489C8183B65840056818A /* ChromeMenu-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "ChromeMenu-Info.plist"; sourceTree = ""; }; 69 | A04489CA183B65840056818A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 70 | A04489CC183B65840056818A /* ChromeMenu-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ChromeMenu-Prefix.pch"; sourceTree = ""; }; 71 | A04489D4183B65850056818A /* ChromeMenuTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ChromeMenuTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 72 | A04489D5183B65850056818A /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 73 | A04489DD183B65850056818A /* ChromeMenuTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "ChromeMenuTests-Info.plist"; sourceTree = ""; }; 74 | A04489DF183B65850056818A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 75 | A04489E1183B65850056818A /* ChromeMenuTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ChromeMenuTests.m; sourceTree = ""; }; 76 | A04489EC183B6D3E0056818A /* ChromeMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ChromeMenu.h; path = src/ChromeMenu.h; sourceTree = ""; }; 77 | A04489ED183B6D3E0056818A /* CMUnderlyingView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CMUnderlyingView.h; path = src/CMUnderlyingView.h; sourceTree = ""; }; 78 | A04489EE183B6D3E0056818A /* CMUnderlyingView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CMUnderlyingView.m; path = src/CMUnderlyingView.m; sourceTree = ""; }; 79 | A04489EF183B6D3E0056818A /* CMUnderlyingWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CMUnderlyingWindow.h; path = src/CMUnderlyingWindow.h; sourceTree = ""; }; 80 | A04489F0183B6D3E0056818A /* CMUnderlyingWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CMUnderlyingWindow.m; path = src/CMUnderlyingWindow.m; sourceTree = ""; }; 81 | A04489F1183B6D3E0056818A /* CMDebug.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CMDebug.h; path = src/CMDebug.h; sourceTree = ""; }; 82 | A04489F2183B6D3E0056818A /* CMMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CMMenu.h; path = src/CMMenu.h; sourceTree = ""; }; 83 | A04489F3183B6D3E0056818A /* CMMenu.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CMMenu.m; path = src/CMMenu.m; sourceTree = ""; }; 84 | A04489F4183B6D3E0056818A /* CMMenu+InternalMethods.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "CMMenu+InternalMethods.h"; path = "src/CMMenu+InternalMethods.h"; sourceTree = ""; }; 85 | A04489F5183B6D3E0056818A /* CMMenuItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CMMenuItem.h; path = src/CMMenuItem.h; sourceTree = ""; }; 86 | A04489F6183B6D3E0056818A /* CMMenuItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CMMenuItem.m; path = src/CMMenuItem.m; sourceTree = ""; }; 87 | A04489F7183B6D3E0056818A /* CMMenuItem+InternalMethods.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "CMMenuItem+InternalMethods.h"; path = "src/CMMenuItem+InternalMethods.h"; sourceTree = ""; }; 88 | A04489F8183B6D3E0056818A /* CMMenuItemSeparatorView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CMMenuItemSeparatorView.h; path = src/CMMenuItemSeparatorView.h; sourceTree = ""; }; 89 | A04489F9183B6D3E0056818A /* CMMenuItemSeparatorView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CMMenuItemSeparatorView.m; path = src/CMMenuItemSeparatorView.m; sourceTree = ""; }; 90 | A04489FA183B6D3E0056818A /* CMMenuItemView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CMMenuItemView.h; path = src/CMMenuItemView.h; sourceTree = ""; }; 91 | A04489FB183B6D3E0056818A /* CMMenuItemView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CMMenuItemView.m; path = src/CMMenuItemView.m; sourceTree = ""; }; 92 | A04489FC183B6D3E0056818A /* CMMenuItemView+InternalMethods.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "CMMenuItemView+InternalMethods.h"; path = "src/CMMenuItemView+InternalMethods.h"; sourceTree = ""; }; 93 | A04489FD183B6D3E0056818A /* CMMenuKeyEventInterpreter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CMMenuKeyEventInterpreter.h; path = src/CMMenuKeyEventInterpreter.h; sourceTree = ""; }; 94 | A04489FE183B6D3E0056818A /* CMMenuKeyEventInterpreter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CMMenuKeyEventInterpreter.m; path = src/CMMenuKeyEventInterpreter.m; sourceTree = ""; }; 95 | A04489FF183B6D3E0056818A /* CMMenuScroller.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CMMenuScroller.h; path = src/CMMenuScroller.h; sourceTree = ""; }; 96 | A0448A00183B6D3E0056818A /* CMMenuScroller.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CMMenuScroller.m; path = src/CMMenuScroller.m; sourceTree = ""; }; 97 | A0448A01183B6D3E0056818A /* CMScrollDocumentView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CMScrollDocumentView.h; path = src/CMScrollDocumentView.h; sourceTree = ""; }; 98 | A0448A02183B6D3E0056818A /* CMScrollDocumentView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CMScrollDocumentView.m; path = src/CMScrollDocumentView.m; sourceTree = ""; }; 99 | A0448A03183B6D3E0056818A /* CMScrollView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CMScrollView.h; path = src/CMScrollView.h; sourceTree = ""; }; 100 | A0448A04183B6D3E0056818A /* CMScrollView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CMScrollView.m; path = src/CMScrollView.m; sourceTree = ""; }; 101 | A0448A05183B6D3E0056818A /* CMWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CMWindowController.h; path = src/CMWindowController.h; sourceTree = ""; }; 102 | A0448A06183B6D3E0056818A /* CMWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CMWindowController.m; path = src/CMWindowController.m; sourceTree = ""; }; 103 | A0448A07183B6D3E0056818A /* NSImage+CMMenuImageRepAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSImage+CMMenuImageRepAdditions.h"; path = "src/NSImage+CMMenuImageRepAdditions.h"; sourceTree = ""; }; 104 | A0448A08183B6D3E0056818A /* NSImage+CMMenuImageRepAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSImage+CMMenuImageRepAdditions.m"; path = "src/NSImage+CMMenuImageRepAdditions.m"; sourceTree = ""; }; 105 | A0448A27183B6D5D0056818A /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/CMMenuItemIconView.xib; sourceTree = ""; }; 106 | A0448A29183B6D5D0056818A /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/CMMenuItemSeparatorView.xib; sourceTree = ""; }; 107 | A0448A2B183B6D5D0056818A /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/CMMenuItemView.xib; sourceTree = ""; }; 108 | A0448A2F183B6F1D0056818A /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 109 | /* End PBXFileReference section */ 110 | 111 | /* Begin PBXFrameworksBuildPhase section */ 112 | A04489B9183B65840056818A /* Frameworks */ = { 113 | isa = PBXFrameworksBuildPhase; 114 | buildActionMask = 2147483647; 115 | files = ( 116 | A0448A30183B6F1D0056818A /* QuartzCore.framework in Frameworks */, 117 | A04489C1183B65840056818A /* Cocoa.framework in Frameworks */, 118 | ); 119 | runOnlyForDeploymentPostprocessing = 0; 120 | }; 121 | A04489D1183B65850056818A /* Frameworks */ = { 122 | isa = PBXFrameworksBuildPhase; 123 | buildActionMask = 2147483647; 124 | files = ( 125 | A04489DA183B65850056818A /* ChromeMenu.framework in Frameworks */, 126 | A04489D7183B65850056818A /* Cocoa.framework in Frameworks */, 127 | A04489D6183B65850056818A /* XCTest.framework in Frameworks */, 128 | ); 129 | runOnlyForDeploymentPostprocessing = 0; 130 | }; 131 | /* End PBXFrameworksBuildPhase section */ 132 | 133 | /* Begin PBXGroup section */ 134 | A04489B3183B65840056818A = { 135 | isa = PBXGroup; 136 | children = ( 137 | A04489C6183B65840056818A /* ChromeMenu */, 138 | A04489DB183B65850056818A /* ChromeMenuTests */, 139 | A04489BF183B65840056818A /* Frameworks */, 140 | A04489BE183B65840056818A /* Products */, 141 | ); 142 | sourceTree = ""; 143 | }; 144 | A04489BE183B65840056818A /* Products */ = { 145 | isa = PBXGroup; 146 | children = ( 147 | A04489BD183B65840056818A /* ChromeMenu.framework */, 148 | A04489D4183B65850056818A /* ChromeMenuTests.xctest */, 149 | ); 150 | name = Products; 151 | sourceTree = ""; 152 | }; 153 | A04489BF183B65840056818A /* Frameworks */ = { 154 | isa = PBXGroup; 155 | children = ( 156 | A0448A2F183B6F1D0056818A /* QuartzCore.framework */, 157 | A04489C0183B65840056818A /* Cocoa.framework */, 158 | A04489D5183B65850056818A /* XCTest.framework */, 159 | A04489C2183B65840056818A /* Other Frameworks */, 160 | ); 161 | name = Frameworks; 162 | sourceTree = ""; 163 | }; 164 | A04489C2183B65840056818A /* Other Frameworks */ = { 165 | isa = PBXGroup; 166 | children = ( 167 | A04489C3183B65840056818A /* Foundation.framework */, 168 | A04489C4183B65840056818A /* CoreData.framework */, 169 | A04489C5183B65840056818A /* AppKit.framework */, 170 | ); 171 | name = "Other Frameworks"; 172 | sourceTree = ""; 173 | }; 174 | A04489C6183B65840056818A /* ChromeMenu */ = { 175 | isa = PBXGroup; 176 | children = ( 177 | A04489EB183B6D250056818A /* src */, 178 | A04489C7183B65840056818A /* Supporting Files */, 179 | ); 180 | path = ChromeMenu; 181 | sourceTree = ""; 182 | }; 183 | A04489C7183B65840056818A /* Supporting Files */ = { 184 | isa = PBXGroup; 185 | children = ( 186 | A0448A26183B6D5D0056818A /* CMMenuItemIconView.xib */, 187 | A0448A28183B6D5D0056818A /* CMMenuItemSeparatorView.xib */, 188 | A0448A2A183B6D5D0056818A /* CMMenuItemView.xib */, 189 | A04489C8183B65840056818A /* ChromeMenu-Info.plist */, 190 | A04489C9183B65840056818A /* InfoPlist.strings */, 191 | A04489CC183B65840056818A /* ChromeMenu-Prefix.pch */, 192 | ); 193 | name = "Supporting Files"; 194 | sourceTree = ""; 195 | }; 196 | A04489DB183B65850056818A /* ChromeMenuTests */ = { 197 | isa = PBXGroup; 198 | children = ( 199 | A04489E1183B65850056818A /* ChromeMenuTests.m */, 200 | A04489DC183B65850056818A /* Supporting Files */, 201 | ); 202 | path = ChromeMenuTests; 203 | sourceTree = ""; 204 | }; 205 | A04489DC183B65850056818A /* Supporting Files */ = { 206 | isa = PBXGroup; 207 | children = ( 208 | A04489DD183B65850056818A /* ChromeMenuTests-Info.plist */, 209 | A04489DE183B65850056818A /* InfoPlist.strings */, 210 | ); 211 | name = "Supporting Files"; 212 | sourceTree = ""; 213 | }; 214 | A04489EB183B6D250056818A /* src */ = { 215 | isa = PBXGroup; 216 | children = ( 217 | A04489EC183B6D3E0056818A /* ChromeMenu.h */, 218 | A04489F2183B6D3E0056818A /* CMMenu.h */, 219 | A04489F3183B6D3E0056818A /* CMMenu.m */, 220 | A04489F5183B6D3E0056818A /* CMMenuItem.h */, 221 | A04489F6183B6D3E0056818A /* CMMenuItem.m */, 222 | A0448A05183B6D3E0056818A /* CMWindowController.h */, 223 | A0448A06183B6D3E0056818A /* CMWindowController.m */, 224 | A04489ED183B6D3E0056818A /* CMUnderlyingView.h */, 225 | A04489EE183B6D3E0056818A /* CMUnderlyingView.m */, 226 | A04489EF183B6D3E0056818A /* CMUnderlyingWindow.h */, 227 | A04489F0183B6D3E0056818A /* CMUnderlyingWindow.m */, 228 | A04489F1183B6D3E0056818A /* CMDebug.h */, 229 | A04489F4183B6D3E0056818A /* CMMenu+InternalMethods.h */, 230 | A04489F7183B6D3E0056818A /* CMMenuItem+InternalMethods.h */, 231 | A04489F8183B6D3E0056818A /* CMMenuItemSeparatorView.h */, 232 | A04489F9183B6D3E0056818A /* CMMenuItemSeparatorView.m */, 233 | A04489FA183B6D3E0056818A /* CMMenuItemView.h */, 234 | A04489FB183B6D3E0056818A /* CMMenuItemView.m */, 235 | A04489FC183B6D3E0056818A /* CMMenuItemView+InternalMethods.h */, 236 | A04489FD183B6D3E0056818A /* CMMenuKeyEventInterpreter.h */, 237 | A04489FE183B6D3E0056818A /* CMMenuKeyEventInterpreter.m */, 238 | A04489FF183B6D3E0056818A /* CMMenuScroller.h */, 239 | A0448A00183B6D3E0056818A /* CMMenuScroller.m */, 240 | A0448A01183B6D3E0056818A /* CMScrollDocumentView.h */, 241 | A0448A02183B6D3E0056818A /* CMScrollDocumentView.m */, 242 | A0448A03183B6D3E0056818A /* CMScrollView.h */, 243 | A0448A04183B6D3E0056818A /* CMScrollView.m */, 244 | A0448A07183B6D3E0056818A /* NSImage+CMMenuImageRepAdditions.h */, 245 | A0448A08183B6D3E0056818A /* NSImage+CMMenuImageRepAdditions.m */, 246 | ); 247 | name = src; 248 | sourceTree = ""; 249 | }; 250 | /* End PBXGroup section */ 251 | 252 | /* Begin PBXHeadersBuildPhase section */ 253 | A04489BA183B65840056818A /* Headers */ = { 254 | isa = PBXHeadersBuildPhase; 255 | buildActionMask = 2147483647; 256 | files = ( 257 | A0448A09183B6D3E0056818A /* ChromeMenu.h in Headers */, 258 | A0448A0F183B6D3E0056818A /* CMMenu.h in Headers */, 259 | A0448A12183B6D3E0056818A /* CMMenuItem.h in Headers */, 260 | A0448A20183B6D3E0056818A /* CMScrollView.h in Headers */, 261 | A0448A1E183B6D3E0056818A /* CMScrollDocumentView.h in Headers */, 262 | A0448A1A183B6D3E0056818A /* CMMenuKeyEventInterpreter.h in Headers */, 263 | A0448A24183B6D3E0056818A /* NSImage+CMMenuImageRepAdditions.h in Headers */, 264 | A0448A1C183B6D3E0056818A /* CMMenuScroller.h in Headers */, 265 | A0448A22183B6D3E0056818A /* CMWindowController.h in Headers */, 266 | A0448A19183B6D3E0056818A /* CMMenuItemView+InternalMethods.h in Headers */, 267 | A0448A14183B6D3E0056818A /* CMMenuItem+InternalMethods.h in Headers */, 268 | A0448A17183B6D3E0056818A /* CMMenuItemView.h in Headers */, 269 | A0448A0A183B6D3E0056818A /* CMUnderlyingView.h in Headers */, 270 | A0448A11183B6D3E0056818A /* CMMenu+InternalMethods.h in Headers */, 271 | A0448A15183B6D3E0056818A /* CMMenuItemSeparatorView.h in Headers */, 272 | A0448A0C183B6D3E0056818A /* CMUnderlyingWindow.h in Headers */, 273 | A0448A0E183B6D3E0056818A /* CMDebug.h in Headers */, 274 | ); 275 | runOnlyForDeploymentPostprocessing = 0; 276 | }; 277 | /* End PBXHeadersBuildPhase section */ 278 | 279 | /* Begin PBXNativeTarget section */ 280 | A04489BC183B65840056818A /* ChromeMenu */ = { 281 | isa = PBXNativeTarget; 282 | buildConfigurationList = A04489E5183B65850056818A /* Build configuration list for PBXNativeTarget "ChromeMenu" */; 283 | buildPhases = ( 284 | A04489B8183B65840056818A /* Sources */, 285 | A04489B9183B65840056818A /* Frameworks */, 286 | A04489BA183B65840056818A /* Headers */, 287 | A04489BB183B65840056818A /* Resources */, 288 | ); 289 | buildRules = ( 290 | ); 291 | dependencies = ( 292 | ); 293 | name = ChromeMenu; 294 | productName = ChromeMenu; 295 | productReference = A04489BD183B65840056818A /* ChromeMenu.framework */; 296 | productType = "com.apple.product-type.framework"; 297 | }; 298 | A04489D3183B65850056818A /* ChromeMenuTests */ = { 299 | isa = PBXNativeTarget; 300 | buildConfigurationList = A04489E8183B65850056818A /* Build configuration list for PBXNativeTarget "ChromeMenuTests" */; 301 | buildPhases = ( 302 | A04489D0183B65850056818A /* Sources */, 303 | A04489D1183B65850056818A /* Frameworks */, 304 | A04489D2183B65850056818A /* Resources */, 305 | ); 306 | buildRules = ( 307 | ); 308 | dependencies = ( 309 | A04489D9183B65850056818A /* PBXTargetDependency */, 310 | ); 311 | name = ChromeMenuTests; 312 | productName = ChromeMenuTests; 313 | productReference = A04489D4183B65850056818A /* ChromeMenuTests.xctest */; 314 | productType = "com.apple.product-type.bundle.unit-test"; 315 | }; 316 | /* End PBXNativeTarget section */ 317 | 318 | /* Begin PBXProject section */ 319 | A04489B4183B65840056818A /* Project object */ = { 320 | isa = PBXProject; 321 | attributes = { 322 | LastUpgradeCheck = 0500; 323 | ORGANIZATIONNAME = definemac.com; 324 | TargetAttributes = { 325 | A04489D3183B65850056818A = { 326 | TestTargetID = A04489BC183B65840056818A; 327 | }; 328 | }; 329 | }; 330 | buildConfigurationList = A04489B7183B65840056818A /* Build configuration list for PBXProject "ChromeMenu" */; 331 | compatibilityVersion = "Xcode 3.2"; 332 | developmentRegion = English; 333 | hasScannedForEncodings = 0; 334 | knownRegions = ( 335 | en, 336 | ); 337 | mainGroup = A04489B3183B65840056818A; 338 | productRefGroup = A04489BE183B65840056818A /* Products */; 339 | projectDirPath = ""; 340 | projectRoot = ""; 341 | targets = ( 342 | A04489BC183B65840056818A /* ChromeMenu */, 343 | A04489D3183B65850056818A /* ChromeMenuTests */, 344 | ); 345 | }; 346 | /* End PBXProject section */ 347 | 348 | /* Begin PBXResourcesBuildPhase section */ 349 | A04489BB183B65840056818A /* Resources */ = { 350 | isa = PBXResourcesBuildPhase; 351 | buildActionMask = 2147483647; 352 | files = ( 353 | A04489CB183B65840056818A /* InfoPlist.strings in Resources */, 354 | A0448A2D183B6D5D0056818A /* CMMenuItemSeparatorView.xib in Resources */, 355 | A0448A2E183B6D5D0056818A /* CMMenuItemView.xib in Resources */, 356 | A0448A2C183B6D5D0056818A /* CMMenuItemIconView.xib in Resources */, 357 | ); 358 | runOnlyForDeploymentPostprocessing = 0; 359 | }; 360 | A04489D2183B65850056818A /* Resources */ = { 361 | isa = PBXResourcesBuildPhase; 362 | buildActionMask = 2147483647; 363 | files = ( 364 | A04489E0183B65850056818A /* InfoPlist.strings in Resources */, 365 | ); 366 | runOnlyForDeploymentPostprocessing = 0; 367 | }; 368 | /* End PBXResourcesBuildPhase section */ 369 | 370 | /* Begin PBXSourcesBuildPhase section */ 371 | A04489B8183B65840056818A /* Sources */ = { 372 | isa = PBXSourcesBuildPhase; 373 | buildActionMask = 2147483647; 374 | files = ( 375 | A0448A0B183B6D3E0056818A /* CMUnderlyingView.m in Sources */, 376 | A0448A1B183B6D3E0056818A /* CMMenuKeyEventInterpreter.m in Sources */, 377 | A0448A10183B6D3E0056818A /* CMMenu.m in Sources */, 378 | A0448A25183B6D3E0056818A /* NSImage+CMMenuImageRepAdditions.m in Sources */, 379 | A0448A1F183B6D3E0056818A /* CMScrollDocumentView.m in Sources */, 380 | A0448A16183B6D3E0056818A /* CMMenuItemSeparatorView.m in Sources */, 381 | A0448A21183B6D3E0056818A /* CMScrollView.m in Sources */, 382 | A0448A0D183B6D3E0056818A /* CMUnderlyingWindow.m in Sources */, 383 | A0448A23183B6D3E0056818A /* CMWindowController.m in Sources */, 384 | A0448A18183B6D3E0056818A /* CMMenuItemView.m in Sources */, 385 | A0448A1D183B6D3E0056818A /* CMMenuScroller.m in Sources */, 386 | A0448A13183B6D3E0056818A /* CMMenuItem.m in Sources */, 387 | ); 388 | runOnlyForDeploymentPostprocessing = 0; 389 | }; 390 | A04489D0183B65850056818A /* Sources */ = { 391 | isa = PBXSourcesBuildPhase; 392 | buildActionMask = 2147483647; 393 | files = ( 394 | A04489E2183B65850056818A /* ChromeMenuTests.m in Sources */, 395 | ); 396 | runOnlyForDeploymentPostprocessing = 0; 397 | }; 398 | /* End PBXSourcesBuildPhase section */ 399 | 400 | /* Begin PBXTargetDependency section */ 401 | A04489D9183B65850056818A /* PBXTargetDependency */ = { 402 | isa = PBXTargetDependency; 403 | target = A04489BC183B65840056818A /* ChromeMenu */; 404 | targetProxy = A04489D8183B65850056818A /* PBXContainerItemProxy */; 405 | }; 406 | /* End PBXTargetDependency section */ 407 | 408 | /* Begin PBXVariantGroup section */ 409 | A04489C9183B65840056818A /* InfoPlist.strings */ = { 410 | isa = PBXVariantGroup; 411 | children = ( 412 | A04489CA183B65840056818A /* en */, 413 | ); 414 | name = InfoPlist.strings; 415 | sourceTree = ""; 416 | }; 417 | A04489DE183B65850056818A /* InfoPlist.strings */ = { 418 | isa = PBXVariantGroup; 419 | children = ( 420 | A04489DF183B65850056818A /* en */, 421 | ); 422 | name = InfoPlist.strings; 423 | sourceTree = ""; 424 | }; 425 | A0448A26183B6D5D0056818A /* CMMenuItemIconView.xib */ = { 426 | isa = PBXVariantGroup; 427 | children = ( 428 | A0448A27183B6D5D0056818A /* en */, 429 | ); 430 | name = CMMenuItemIconView.xib; 431 | sourceTree = ""; 432 | }; 433 | A0448A28183B6D5D0056818A /* CMMenuItemSeparatorView.xib */ = { 434 | isa = PBXVariantGroup; 435 | children = ( 436 | A0448A29183B6D5D0056818A /* en */, 437 | ); 438 | name = CMMenuItemSeparatorView.xib; 439 | sourceTree = ""; 440 | }; 441 | A0448A2A183B6D5D0056818A /* CMMenuItemView.xib */ = { 442 | isa = PBXVariantGroup; 443 | children = ( 444 | A0448A2B183B6D5D0056818A /* en */, 445 | ); 446 | name = CMMenuItemView.xib; 447 | sourceTree = ""; 448 | }; 449 | /* End PBXVariantGroup section */ 450 | 451 | /* Begin XCBuildConfiguration section */ 452 | A04489E3183B65850056818A /* Debug */ = { 453 | isa = XCBuildConfiguration; 454 | buildSettings = { 455 | ALWAYS_SEARCH_USER_PATHS = NO; 456 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 457 | CLANG_CXX_LIBRARY = "libc++"; 458 | CLANG_ENABLE_OBJC_ARC = NO; 459 | CLANG_WARN_BOOL_CONVERSION = YES; 460 | CLANG_WARN_CONSTANT_CONVERSION = YES; 461 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 462 | CLANG_WARN_EMPTY_BODY = YES; 463 | CLANG_WARN_ENUM_CONVERSION = YES; 464 | CLANG_WARN_INT_CONVERSION = YES; 465 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 466 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 467 | CODE_SIGN_IDENTITY = "Definemac Code Signing Certificate"; 468 | COPY_PHASE_STRIP = NO; 469 | GCC_C_LANGUAGE_STANDARD = gnu99; 470 | GCC_DYNAMIC_NO_PIC = NO; 471 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 472 | GCC_OPTIMIZATION_LEVEL = 0; 473 | GCC_PREPROCESSOR_DEFINITIONS = ( 474 | "DEBUG=1", 475 | "$(inherited)", 476 | ); 477 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 478 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 479 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 480 | GCC_WARN_UNDECLARED_SELECTOR = YES; 481 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 482 | GCC_WARN_UNUSED_FUNCTION = YES; 483 | GCC_WARN_UNUSED_VARIABLE = YES; 484 | MACOSX_DEPLOYMENT_TARGET = 10.7; 485 | ONLY_ACTIVE_ARCH = YES; 486 | SDKROOT = macosx; 487 | }; 488 | name = Debug; 489 | }; 490 | A04489E4183B65850056818A /* Release */ = { 491 | isa = XCBuildConfiguration; 492 | buildSettings = { 493 | ALWAYS_SEARCH_USER_PATHS = NO; 494 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 495 | CLANG_CXX_LIBRARY = "libc++"; 496 | CLANG_ENABLE_OBJC_ARC = NO; 497 | CLANG_WARN_BOOL_CONVERSION = YES; 498 | CLANG_WARN_CONSTANT_CONVERSION = YES; 499 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 500 | CLANG_WARN_EMPTY_BODY = YES; 501 | CLANG_WARN_ENUM_CONVERSION = YES; 502 | CLANG_WARN_INT_CONVERSION = YES; 503 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 504 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 505 | CODE_SIGN_IDENTITY = "Definemac Code Signing Certificate"; 506 | COPY_PHASE_STRIP = YES; 507 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 508 | ENABLE_NS_ASSERTIONS = NO; 509 | GCC_C_LANGUAGE_STANDARD = gnu99; 510 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 511 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 512 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 513 | GCC_WARN_UNDECLARED_SELECTOR = YES; 514 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 515 | GCC_WARN_UNUSED_FUNCTION = YES; 516 | GCC_WARN_UNUSED_VARIABLE = YES; 517 | MACOSX_DEPLOYMENT_TARGET = 10.7; 518 | SDKROOT = macosx; 519 | }; 520 | name = Release; 521 | }; 522 | A04489E6183B65850056818A /* Debug */ = { 523 | isa = XCBuildConfiguration; 524 | buildSettings = { 525 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 526 | CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; 527 | CODE_SIGN_IDENTITY = ""; 528 | COMBINE_HIDPI_IMAGES = YES; 529 | DYLIB_COMPATIBILITY_VERSION = 1; 530 | DYLIB_CURRENT_VERSION = 1; 531 | FRAMEWORK_VERSION = A; 532 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 533 | GCC_PREFIX_HEADER = "ChromeMenu/ChromeMenu-Prefix.pch"; 534 | GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES; 535 | GCC_WARN_ABOUT_MISSING_NEWLINE = YES; 536 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 537 | GCC_WARN_SIGN_COMPARE = YES; 538 | INFOPLIST_FILE = "ChromeMenu/ChromeMenu-Info.plist"; 539 | INSTALL_PATH = "@executable_path/../Frameworks"; 540 | PRODUCT_NAME = "$(TARGET_NAME)"; 541 | SKIP_INSTALL = YES; 542 | WRAPPER_EXTENSION = framework; 543 | }; 544 | name = Debug; 545 | }; 546 | A04489E7183B65850056818A /* Release */ = { 547 | isa = XCBuildConfiguration; 548 | buildSettings = { 549 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 550 | CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; 551 | CODE_SIGN_IDENTITY = ""; 552 | COMBINE_HIDPI_IMAGES = YES; 553 | DYLIB_COMPATIBILITY_VERSION = 1; 554 | DYLIB_CURRENT_VERSION = 1; 555 | FRAMEWORK_VERSION = A; 556 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 557 | GCC_PREFIX_HEADER = "ChromeMenu/ChromeMenu-Prefix.pch"; 558 | GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES; 559 | GCC_WARN_ABOUT_MISSING_NEWLINE = YES; 560 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 561 | GCC_WARN_SIGN_COMPARE = YES; 562 | INFOPLIST_FILE = "ChromeMenu/ChromeMenu-Info.plist"; 563 | INSTALL_PATH = "@executable_path/../Frameworks"; 564 | PRODUCT_NAME = "$(TARGET_NAME)"; 565 | SKIP_INSTALL = YES; 566 | WRAPPER_EXTENSION = framework; 567 | }; 568 | name = Release; 569 | }; 570 | A04489E9183B65850056818A /* Debug */ = { 571 | isa = XCBuildConfiguration; 572 | buildSettings = { 573 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/ChromeMenu.framework/Versions/A/ChromeMenu"; 574 | COMBINE_HIDPI_IMAGES = YES; 575 | FRAMEWORK_SEARCH_PATHS = ( 576 | "$(DEVELOPER_FRAMEWORKS_DIR)", 577 | "$(inherited)", 578 | ); 579 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 580 | GCC_PREFIX_HEADER = "ChromeMenu/ChromeMenu-Prefix.pch"; 581 | GCC_PREPROCESSOR_DEFINITIONS = ( 582 | "DEBUG=1", 583 | "$(inherited)", 584 | ); 585 | INFOPLIST_FILE = "ChromeMenuTests/ChromeMenuTests-Info.plist"; 586 | PRODUCT_NAME = "$(TARGET_NAME)"; 587 | TEST_HOST = "$(BUNDLE_LOADER)"; 588 | WRAPPER_EXTENSION = xctest; 589 | }; 590 | name = Debug; 591 | }; 592 | A04489EA183B65850056818A /* Release */ = { 593 | isa = XCBuildConfiguration; 594 | buildSettings = { 595 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/ChromeMenu.framework/Versions/A/ChromeMenu"; 596 | COMBINE_HIDPI_IMAGES = YES; 597 | FRAMEWORK_SEARCH_PATHS = ( 598 | "$(DEVELOPER_FRAMEWORKS_DIR)", 599 | "$(inherited)", 600 | ); 601 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 602 | GCC_PREFIX_HEADER = "ChromeMenu/ChromeMenu-Prefix.pch"; 603 | INFOPLIST_FILE = "ChromeMenuTests/ChromeMenuTests-Info.plist"; 604 | PRODUCT_NAME = "$(TARGET_NAME)"; 605 | TEST_HOST = "$(BUNDLE_LOADER)"; 606 | WRAPPER_EXTENSION = xctest; 607 | }; 608 | name = Release; 609 | }; 610 | /* End XCBuildConfiguration section */ 611 | 612 | /* Begin XCConfigurationList section */ 613 | A04489B7183B65840056818A /* Build configuration list for PBXProject "ChromeMenu" */ = { 614 | isa = XCConfigurationList; 615 | buildConfigurations = ( 616 | A04489E3183B65850056818A /* Debug */, 617 | A04489E4183B65850056818A /* Release */, 618 | ); 619 | defaultConfigurationIsVisible = 0; 620 | defaultConfigurationName = Release; 621 | }; 622 | A04489E5183B65850056818A /* Build configuration list for PBXNativeTarget "ChromeMenu" */ = { 623 | isa = XCConfigurationList; 624 | buildConfigurations = ( 625 | A04489E6183B65850056818A /* Debug */, 626 | A04489E7183B65850056818A /* Release */, 627 | ); 628 | defaultConfigurationIsVisible = 0; 629 | defaultConfigurationName = Release; 630 | }; 631 | A04489E8183B65850056818A /* Build configuration list for PBXNativeTarget "ChromeMenuTests" */ = { 632 | isa = XCConfigurationList; 633 | buildConfigurations = ( 634 | A04489E9183B65850056818A /* Debug */, 635 | A04489EA183B65850056818A /* Release */, 636 | ); 637 | defaultConfigurationIsVisible = 0; 638 | defaultConfigurationName = Release; 639 | }; 640 | /* End XCConfigurationList section */ 641 | }; 642 | rootObject = A04489B4183B65840056818A /* Project object */; 643 | } 644 | -------------------------------------------------------------------------------- /ChromeMenu.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ChromeMenu.xcodeproj/project.xcworkspace/xcshareddata/ChromeMenu.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | 2A8D01AB-64E1-4FCE-AFDA-D55B3126C76B 9 | IDESourceControlProjectName 10 | ChromeMenu 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | F8EBCAA4-8650-48BD-B46E-CFB1E1883918 14 | ssh://github.com/fuyu/ChromeMenu.git 15 | 16 | IDESourceControlProjectPath 17 | ChromeMenu.xcodeproj/project.xcworkspace 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | F8EBCAA4-8650-48BD-B46E-CFB1E1883918 21 | ../.. 22 | 23 | IDESourceControlProjectURL 24 | ssh://github.com/fuyu/ChromeMenu.git 25 | IDESourceControlProjectVersion 26 | 110 27 | IDESourceControlProjectWCCIdentifier 28 | F8EBCAA4-8650-48BD-B46E-CFB1E1883918 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | F8EBCAA4-8650-48BD-B46E-CFB1E1883918 36 | IDESourceControlWCCName 37 | ChromeMenu 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /ChromeMenu.xcodeproj/project.xcworkspace/xcuserdata/objective.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppPolice/ChromeMenu/517e820e34868a25ce556bb56d0452510df2598f/ChromeMenu.xcodeproj/project.xcworkspace/xcuserdata/objective.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /ChromeMenu.xcodeproj/xcuserdata/objective.xcuserdatad/xcschemes/ChromeMenu.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 52 | 53 | 54 | 55 | 61 | 62 | 64 | 65 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /ChromeMenu.xcodeproj/xcuserdata/objective.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | ChromeMenu.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | A04489BC183B65840056818A 16 | 17 | primary 18 | 19 | 20 | A04489D3183B65850056818A 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /ChromeMenu/ChromeMenu-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | com.definemac.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleShortVersionString 20 | 1.1 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 2 25 | NSHumanReadableCopyright 26 | Copyright © 2013 definemac.com. All rights reserved. 27 | NSPrincipalClass 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /ChromeMenu/ChromeMenu-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | #import 9 | #endif 10 | -------------------------------------------------------------------------------- /ChromeMenu/en.lproj/CMMenuItemIconView.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 | -------------------------------------------------------------------------------- /ChromeMenu/en.lproj/CMMenuItemSeparatorView.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 | -------------------------------------------------------------------------------- /ChromeMenu/en.lproj/CMMenuItemView.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 | -------------------------------------------------------------------------------- /ChromeMenu/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /ChromeMenu/src/CMDebug.h: -------------------------------------------------------------------------------- 1 | // 2 | // CMDebug.h 3 | // ChromeMenu 4 | // 5 | // Created by Maksym on 9/28/13. 6 | // Copyright (c) 2013 Maksym Stefanchuk. All rights reserved. 7 | // 8 | 9 | #ifndef ChromeMenu_CMDebug_h 10 | #define ChromeMenu_CMDebug_h 11 | 12 | // Note DEBUG macro is automated to be defined in "Preprocessor Macros" 13 | // in project target Build Settings only for Debug builds. 14 | 15 | //#define CM_DEBUG_ON 16 | #define CM_DEBUG_LEVEL 1 // 1 is the lowest level, 3 is the highest 17 | 18 | 19 | #if defined(DEBUG) && CM_DEBUG_LEVEL >= 1 20 | # define XLog(format, ...) NSLog(@format, ##__VA_ARGS__) 21 | #else 22 | # define XLog(format, ...) ((void)0) 23 | #endif 24 | 25 | #if defined(DEBUG) && CM_DEBUG_LEVEL >= 2 26 | # define XLog2(format, ...) NSLog(@format, ##__VA_ARGS__) 27 | #else 28 | # define XLog2(format, ...) ((void)0) 29 | #endif 30 | 31 | #if defined(DEBUG) && CM_DEBUG_LEVEL >= 3 32 | # define XLog3(format, ...) NSLog(@format, ##__VA_ARGS__) 33 | #else 34 | # define XLog3(format, ...) ((void)0) 35 | #endif 36 | 37 | 38 | #ifdef DEBUG 39 | # define EVAL_IF_DEBUG(code) do code while(0) 40 | #else 41 | # define EVAL_IF_DEBUG(code) ((void)0) 42 | #endif 43 | 44 | 45 | /* 46 | #if CM_DEBUG == 1 && (CM_DEBUG_LEVEL == 1 || CM_DEBUG_LEVEL == 2 || CM_DEBUG_LEVEL == 3) 47 | #define XLog(format, ...) NSLog(@format, ##__VA_ARGS__) 48 | 49 | 50 | #if CM_DEBUG_LEVEL == 2 51 | # define XLog2(format, ...) NSLog(@format, ##__VA_ARGS__) 52 | # define XLog3(format, ...) ((void)0) 53 | #elif CM_DEBUG_LEVEL == 3 54 | # define XLog2(format, ...) NSLog(@format, ##__VA_ARGS__) 55 | # define XLog3(format, ...) NSLog(@format, ##__VA_ARGS__) 56 | #else 57 | # define XLog2(format, ...) ((void)0) 58 | # define XLog3(format, ...) ((void)0) 59 | #endif 60 | 61 | 62 | 63 | #else 64 | 65 | #define XLog(format, ...) ((void)0) 66 | #define XLog2(format, ...) ((void)0) 67 | #define XLog3(format, ...) ((void)0) 68 | 69 | #endif 70 | 71 | */ 72 | 73 | 74 | #endif 75 | -------------------------------------------------------------------------------- /ChromeMenu/src/CMMenu+InternalMethods.h: -------------------------------------------------------------------------------- 1 | // 2 | // CMMenu+InternalMethods.h 3 | // ChromeMenu 4 | // 5 | // Created by Maksym on 7/12/13. 6 | // Copyright (c) 2013 Maksym Stefanchuk. All rights reserved. 7 | // 8 | 9 | 10 | #ifndef CMMenu_InternalMethods 11 | #define CMMenu_InternalMethods 12 | 13 | #import "CMMenu.h" 14 | #import "CMMenuKeyEventInterpreter.h" 15 | 16 | 17 | #define SUBMENU_POPUP_DELAY_DEFAULT 0.2 18 | #define SUBMENU_POPUP_DELAY_AFTER_TRACKING 0.15 19 | #define SUBMENU_POPUP_NO_DELAY 0 20 | 21 | 22 | 23 | #ifndef CMMenuEventTypes_h 24 | #define CMMenuEventTypes_h 25 | 26 | typedef enum : NSUInteger { 27 | CMMenuEventDuringTrackingAreaUpdate = 0x001, // when mouse event occurred because of scrolling 28 | CMMenuEventMouseEnteredItem = 0x002, 29 | CMMenuEventMouseExitedItem = 0x004, 30 | CMMenuEventMouseItem = 0x006, 31 | CMMenuEventMouseEnteredMenu = 0x008, 32 | CMMenuEventMouseExitedMenu = 0x010, 33 | CMMenuEventMouseMenu = 0x018, 34 | CMMenuEventMouseEnteredScroller = 0x020, 35 | CMMenuEventMouseExitedScroller = 0x040, 36 | CMMenuEventMouseScroller = 0x060 37 | } CMMenuEventType; 38 | //typedef NSUInteger CMMenuEventType; 39 | 40 | #endif 41 | 42 | 43 | enum { 44 | CMMenuOptionDefaults = 0x000, 45 | CMMenuOptionIgnoreMouse = 0x001, 46 | // CMMenuOptionTrackMouseMoved = 0x002, 47 | CMMenuOptionUpdateTrackingPrimitives = 0x004, 48 | CMMenuOptionUpdateScrollers = 0x008 49 | }; 50 | typedef NSUInteger CMMenuOptions; 51 | 52 | 53 | enum { 54 | CMMenuAlignedRight = 1, 55 | CMMenuAlignedLeft = 2, 56 | CMMenuAlignedTop = 3, 57 | CMMenuAlignedBottom = 4 58 | }; 59 | typedef NSUInteger CMMenuAlignment; 60 | 61 | 62 | @class CMMenuItem, CMMenuScroller, CMWindowController; 63 | 64 | 65 | @interface CMMenu (CMMenuInternalMethods) 66 | 67 | - (CMMenu *)rootMenu; 68 | 69 | - (void)setNeedsDisplay:(BOOL)needsDisplay; 70 | - (BOOL)needsDisplay; 71 | - (void)setSupermenu:(CMMenu *)aMenu; 72 | - (void)setParentItem:(CMMenuItem *)anItem; 73 | - (BOOL)isActive; 74 | - (void)setIsActive:(BOOL)isActive; 75 | - (CMMenu *)activeSubmenu; 76 | - (void)setActiveSubmenu:(CMMenu *)submenu; 77 | 78 | - (BOOL)isAttachedToStatusItem; 79 | - (NSRect)statusItemRect; 80 | 81 | - (CMMenuAlignment)horizontalAlignment; 82 | - (CMMenuAlignment)verticalAlignment; 83 | 84 | /** 85 | * @discussion Returns YES if menu is currently in NSEventTrackingRunLoopMode, NO otherwise. 86 | */ 87 | - (BOOL)isTracking; 88 | 89 | /** 90 | * @abstract Receiving menu begins tracking in NSEventTrackingRunLoopMode if it is not already. 91 | */ 92 | - (void)beginTrackingWithEvent:(NSEvent *)theEvent options:(CMMenuOptions)options; 93 | 94 | /** 95 | * @abstract End previously started tracking. 96 | */ 97 | - (void)endTracking; 98 | 99 | - (BOOL)isTrackingSubmenu; 100 | - (void)startTrackingSubmenu:(CMMenu *)submenu forItem:(CMMenuItem *)item; 101 | - (void)stopTrackingSubmenuReasonSuccess:(BOOL)reasonSuccess; 102 | - (void)updateTrackingPrimitiveForItem:(CMMenuItem *)item; 103 | 104 | /* Default 0: no event's are blocked */ 105 | - (NSEventMask)eventBlockingMask; 106 | - (void)blockEventsMatchingMask:(NSEventMask)mask; 107 | 108 | /** 109 | * @abstract Returns YES if menu wants to receive Mouse Moved events. This value is checked on RunLoop 110 | * to decide whether Moved events will be captured and sent to menu. 111 | */ 112 | - (BOOL)receivesMouseMovedEvents; 113 | 114 | /** 115 | * @abstract Set whether menu will receive Mouse Moved events. 116 | * @discussion With current implementation of this method it doesn't mean menu will necesseraly begin 117 | * receiving moved events. If receiving menu's supermenu is not set to receive moved events, and mouse 118 | * is not inside receiving menu's frame the method will simply return. If receiving menu's supermenu is 119 | * set to receive moved events and mouse is not withing the menu's frame, menu will receive moved events 120 | * but it will not update its tracking area to generate moved events within itself. 121 | */ 122 | - (void)setReceivesMouseMovedEvents:(BOOL)receiveEvents; 123 | 124 | /** 125 | * @abstract Pass mouse event to a menu for a processing. 126 | */ 127 | - (void)mouseEvent:(NSEvent *)theEvent __attribute__ ((deprecated)); 128 | - (void)mouseEventAtLocation:(NSPoint)mouseLocation type:(NSEventType)eventType; 129 | - (CMMenu *)menuAtPoint:(NSPoint)location; 130 | 131 | /* Perform item's action */ 132 | - (void)performActionForItem:(CMMenuItem *)item; 133 | 134 | //- (void)mouseMoved:(NSEvent *)theEvent; 135 | 136 | /** 137 | * @abstract Show menu as submenu of a certain item 138 | * @discussion All submenus must be started using this method and never call -showWithOptions: directly. 139 | * @param menuItem Supermenu's item which has the target menu set as a submenu. 140 | */ 141 | - (void)showAsSubmenuOf:(CMMenuItem *)menuItem withOptions:(CMMenuOptions)options; // may not be needed 142 | //- (void)orderFront; 143 | 144 | - (NSRect)frame; 145 | 146 | - (CMWindowController *)underlyingWindowController; 147 | - (NSInteger)windowLevel; 148 | 149 | - (NSRect)convertRectToScreen:(NSRect)aRect; 150 | - (NSPoint)convertPointToScreen:(NSPoint)aPoint; 151 | - (NSPoint)convertPointFromScreen:(NSPoint)aPoint; 152 | 153 | - (CMMenuScroller *)scrollerAtPoint:(NSPoint)aPoint; 154 | - (void)scrollWithActiveScroller:(CMMenuScroller *)scroller; 155 | 156 | @end 157 | 158 | #endif 159 | -------------------------------------------------------------------------------- /ChromeMenu/src/CMMenu.h: -------------------------------------------------------------------------------- 1 | // 2 | // ChromeMenu.h 3 | // ChromeMenu 4 | // 5 | // Created by Maksym on 7/3/13. 6 | // Copyright (c) 2013 Maksym Stefanchuk. All rights reserved. 7 | // 8 | 9 | 10 | @class CMMenuItem; 11 | @protocol CMMenuDelegate; 12 | 13 | 14 | @interface CMMenu : NSObject 15 | { 16 | @private 17 | NSString *_title; 18 | CMMenu *_supermenu; 19 | BOOL _performsActionInstantly; 20 | 21 | NSMutableArray *_menuItems; 22 | } 23 | 24 | 25 | /* Designated initializer. If this menu is used as a submenu of an item in the application's main menu, then the title is what appears in the menu bar. Otherwise, the title is ignored. Do not pass nil (an exception will result), but you may pass an empty string. 26 | */ 27 | - (id)initWithTitle:(NSString *)aTitle; 28 | 29 | /* Set and get the menu's title. The titles of the submenus of the application's main menu items appear in the menu bar. */ 30 | - (void)setTitle:(NSString *)aString; 31 | - (NSString *)title; 32 | 33 | - (BOOL)popUpMenuPositioningItem:(NSMenuItem *)item atLocation:(NSPoint)location inView:(NSView *)view; 34 | 35 | - (void)popUpMenuForStatusItemWithRect:(NSRect)rect; 36 | 37 | /* Returns the menu containing the item that has the receiver as a submenu, or nil if this menu is not the submenu of an item in a menu. */ 38 | - (CMMenu *)supermenu; 39 | 40 | /* Same as [anItem setSubmenu:aMenu]. anItem may not be nil. */ 41 | - (void)setSubmenu:(CMMenu *)aMenu forItem:(CMMenuItem *)anItem; 42 | 43 | /* Returns an array containing the receiver's menu items. */ 44 | - (NSArray *)itemArray; 45 | 46 | /* Returns the number of menu items in the menu. */ 47 | - (NSInteger)numberOfItems; 48 | 49 | /* Returns the item at the given index, which must be at least zero and less than the number of items. */ 50 | - (CMMenuItem *)itemAtIndex:(NSInteger)index; 51 | 52 | /* Returns the index of the item in the menu, or -1 if the item is not present in the menu */ 53 | - (NSInteger)indexOfItem:(CMMenuItem *)item; 54 | 55 | /* Returns the item at given point */ 56 | - (CMMenuItem *)itemAtPoint:(NSPoint)aPoint; 57 | 58 | /* Returns item of a supermenu the menu belongs to. Otherwise returns nil */ 59 | - (CMMenuItem *)parentItem; 60 | 61 | /* Inserts a menu item at the given index, which must be at least zero and no more than the receiver's item count. If newItem is nil, this raises an exception. */ 62 | - (void)insertItem:(CMMenuItem *)newItem atIndex:(NSUInteger)index animate:(BOOL)animate; 63 | 64 | /* Appends an item to the end of the menu. A nil item will raise an exception. */ 65 | - (void)addItem:(CMMenuItem *)newItem; 66 | - (void)addItem:(CMMenuItem *)newItem animate:(BOOL)animate; 67 | 68 | /* Removes the item at the given index, which must be at least zero and less than the number of items. All subsequent items will shift down one index. */ 69 | - (void)removeItemAtIndex:(NSInteger)index animate:(BOOL)animate; 70 | 71 | /* Removes the item from the menu. If the item is nil, or is not present in the receiver, an exception will be raised. */ 72 | - (void)removeItem:(CMMenuItem *)item animate:(BOOL)animate; 73 | - (void)removeItemsAtIndexes:(NSIndexSet *)indexes; 74 | /* Removes all items. This is more efficient than removing items one by one. */ 75 | - (void)removeAllItems; 76 | 77 | /* Dismisses the menu and ends all menu tracking */ 78 | - (void)cancelTracking; 79 | 80 | /* Dismisses the menu immediately, without any fade or other effect, and ends all menu tracking */ 81 | - (void)cancelTrackingWithoutAnimation; 82 | 83 | /* Default YES */ 84 | - (BOOL)cancelsTrackingOnAction; 85 | - (void)setCancelsTrackingOnAction:(BOOL)cancels; 86 | /* Default YES */ 87 | - (BOOL)cancelsTrackingOnMouseEventOutsideMenus; 88 | - (void)setCancelsTrackingOnMouseEventOutsideMenus:(BOOL)cancels; 89 | /* Reciever's parent menus (supermenus) will stop tracking mouse, allowing to make mouse movements outside of menu withough closing its tracking. */ 90 | - (BOOL)menusSuspended; 91 | - (void)setSuspendMenus:(BOOL)suspend; 92 | 93 | /* When menu item is selected its action could be processed instantly or with delay. Default is NO. */ 94 | - (void)setPerformsActionInstantly:(BOOL)instantly; 95 | - (BOOL)performsActionInstantly; 96 | 97 | /* Returns the highlighted item in the menu, or nil if no item in the menu is highlighted */ 98 | - (CMMenuItem *)highlightedItem; 99 | 100 | 101 | /* Set and get the delegate for the menu. See the NSMenuDelegate protocol for methods that the delegate may implement. */ 102 | - (void)setDelegate:(id )anObject; 103 | - (id )delegate; 104 | 105 | /* Provided popover is positioned relative to menu item */ 106 | - (void)showPopover:(NSPopover *)popover forItem:(CMMenuItem *)item preferredEdge:(NSRectEdge)preferredEdge; 107 | 108 | /* Set the minimum width of the menu, in screen coordinates. The menu will prefer to not draw smaller than its minimum width, but may draw larger if it needs more space. The default value is 0. 109 | */ 110 | - (CGFloat)minimumWidth; 111 | - (void)setMinimumWidth:(CGFloat)width; 112 | 113 | /* Returns the size of the menu, in screen coordinates. The menu may draw at a smaller size when shown, depending on its positioning and display configuration. 114 | */ 115 | - (NSSize)size; 116 | 117 | /* Returns Menu border radius */ 118 | - (CGFloat)borderRadius; 119 | - (void)setBorderRadius:(CGFloat)radius; 120 | 121 | 122 | @end 123 | 124 | @protocol CMMenuDelegate 125 | @optional 126 | - (void)menuNeedsUpdate:(CMMenu *)menu; 127 | 128 | @end 129 | 130 | APPKIT_EXTERN NSString * const CMMenuDidBeginTrackingNotification; 131 | APPKIT_EXTERN NSString * const CMMenuDidEndTrackingNotification; 132 | APPKIT_EXTERN NSString * const CMMenuSuspendStatusDidChangeNotification; 133 | 134 | //enum { 135 | // CMMenuAnimationEffectNone = 0x00, 136 | // CMMenuAnimationEffectFade = 0x01 137 | //}; 138 | //typedef NSUInteger CMMenuAnimationOptions; 139 | -------------------------------------------------------------------------------- /ChromeMenu/src/CMMenu.m: -------------------------------------------------------------------------------- 1 | // 2 | // ChromeMenu.m 3 | // ChromeMenu 4 | // 5 | // Created by Maksym on 7/3/13. 6 | // Copyright (c) 2013 Maksym Stefanchuk. All rights reserved. 7 | // 8 | 9 | #import "CMMenu.h" 10 | #import "CMMenuItem.h" 11 | #import "CMMenuItemView.h" 12 | #import "CMMenu+InternalMethods.h" 13 | #import "CMMenuItem+InternalMethods.h" 14 | #import "CMMenuItemView+InternalMethods.h" 15 | #import "CMWindowController.h" 16 | #import "CMMenuKeyEventInterpreter.h" 17 | #import "CMDebug.h" 18 | #include // malloc 19 | 20 | #define CM_FRAMEWORK_BUNDLE_IDENTIFIER @"com.definemac.ChromeMenu" 21 | #define CMMENU_PADDING_TO_SCREEN_EDGES 6 22 | 23 | // Posting notification types 24 | NSString * const CMMenuDidBeginTrackingNotification = @"CMMenuDidBeginTrackingNotification"; 25 | NSString * const CMMenuDidEndTrackingNotification = @"CMMenuDidEndTrackingNotification"; 26 | NSString * const CMMenuSuspendStatusDidChangeNotification = @"CMMenuSuspendStatusDidChangeNotification"; 27 | 28 | 29 | 30 | typedef struct { 31 | int active; 32 | NSPoint event_origin; 33 | NSRect item_rect; 34 | NSRect menu_rect; 35 | NSRect submenu_rect; 36 | CMMenuAlignment menu_horizontal_alignment; // left or right 37 | CMMenuAlignment submenu_vertical_alignment; 38 | NSPoint last_mouse_location; 39 | NSTimeInterval timestamp; 40 | NSTimeInterval timeLeftMenuBounds; 41 | NSTimer *timer; 42 | /* Alpha - corner of triangle lying in direction the menu is aligned. For example, 43 | if menu is aligned top the triangle will be at the bottom of the Parent Menu Item. */ 44 | CGFloat tanAlpha; 45 | /* Beta - corner of the triangle at the opposite side */ 46 | CGFloat tanBeta; 47 | CGFloat averageVelocity; 48 | CGFloat averageDeltaX; 49 | 50 | } tracking_event_t; 51 | 52 | 53 | 54 | /* 55 | * Private class declarations 56 | */ 57 | @interface CMMenu () 58 | { 59 | CMWindowController *_underlyingWindowController; 60 | 61 | CMMenuItem *_parentItem; 62 | BOOL _isActive; 63 | BOOL _isAttachedToStatusItem; 64 | BOOL _isPopUpMenu; 65 | NSRect _statusItemRect; 66 | NSPoint _popupLocation; 67 | CMMenu *_activeSubmenu; 68 | BOOL _needsDisplay; // underlying window with item views will be recalculated 69 | 70 | CMMenuAlignment _menuHorizontalAlignment; 71 | CMMenuAlignment _menuVerticalAlignment; 72 | CGFloat _minimumWidth; 73 | NSSize _size; 74 | CGFloat _borderRadius; 75 | 76 | BOOL _isTrackingSubmenu; 77 | BOOL _cancelsTrackingOnAction; 78 | BOOL _cancelsTrackingOnMouseEventOutsideMenus; 79 | BOOL _receiveMouseMovedEvents; 80 | NSUInteger _eventBlockingMask; 81 | tracking_event_t *_tracking_event; 82 | 83 | id _globalEventMonitor; 84 | id _delegate; 85 | } 86 | 87 | 88 | - (void)loadItemViews; 89 | - (void)showWithOptions:(CMMenuOptions)options; 90 | 91 | /** 92 | * @abstract Display menu in frame 93 | * @param frameRect Frame for a menu. Use NSZeroRect for a menu to automatically 94 | * calculate best frame. 95 | * @param options Options to display menu with. 96 | * @param display (BOOL) Specified whether any of the underlying views has changed 97 | * and the underlying Document View of NSScrollView needs to be updated. 98 | */ 99 | - (void)displayInFrame:(NSRect)frameRect options:(CMMenuOptions)options display:(BOOL)display; 100 | 101 | /** 102 | * @function getBestFrameForMenuWindow 103 | * @abstract Returns the frame in screen coordinates in which menu will be drawn. 104 | * @discussion Depending on the position of menu's parent item and the proximity to the screen 105 | * menu can be positioned either from the left or from the right of it, aligned to the top or to the bottom. 106 | * @result Frame in screen coordinates. 107 | */ 108 | - (NSRect)getBestFrameForMenuWindow; 109 | 110 | /** 111 | * @discussion If provided item is not in menu's visible area, move it so the item becomes completely visible. 112 | * @param item Menu item to make visible. 113 | * @param ignoreMouse When menu visible area is moved into new position this option defines whether the item 114 | * currently lying underneath mouse cursor should be selected. More info on this option is in 115 | * [CMWindownController moveVisibleRectToRect:ignoreMouse:]. 116 | * @see [CMWindownController moveVisibleRectToRect:ignoreMouse:] 117 | */ 118 | - (void)moveVisibleAreaToDisplayItem:(CMMenuItem *)item ignoreMouse:(BOOL)ignoreMouse updateTrackingPrimitives:(BOOL)updateTrackingPrimitives; 119 | 120 | - (void)submenuTrackingLoop:(NSTimer *)timer; 121 | 122 | - (void)selectPreviousItemAndShowSubmenu:(BOOL)showSubmenu; 123 | - (void)selectNextItemAndShowSubmenu:(BOOL)showSubmenu; 124 | - (void)selectFirstItemAndShowSubmenu:(BOOL)showSubmenu; 125 | //- (void)selectLastItemAndShowSubmenu:(BOOL)showSubmenu; 126 | 127 | @end 128 | 129 | 130 | @implementation CMMenu 131 | 132 | // Dedicated initializer method 133 | - (id)initWithTitle:(NSString *)aTitle { 134 | if (aTitle == nil) { 135 | [NSException raise:NSInvalidArgumentException format:@"nil provided as title for menu."]; 136 | return nil; 137 | } 138 | 139 | self = [super init]; 140 | if (self) { 141 | _title = [aTitle copy]; 142 | _needsDisplay = YES; 143 | _cancelsTrackingOnAction = YES; 144 | _cancelsTrackingOnMouseEventOutsideMenus = YES; 145 | _menuHorizontalAlignment = CMMenuAlignedLeft; 146 | _menuVerticalAlignment = CMMenuAlignedTop; 147 | _menuItems = [[NSMutableArray alloc] init]; 148 | _borderRadius = 5.0; // default border radius 149 | 150 | _receiveMouseMovedEvents = NO; 151 | } 152 | return self; 153 | } 154 | 155 | 156 | - (id)init { 157 | return [self initWithTitle:@""]; 158 | } 159 | 160 | //- (id)initWithItems:(NSArray *)items { 161 | // if (self = [super init]) { 162 | // [NSBundle loadNibNamed:[self className] owner:self]; 163 | // menuItems = items; 164 | // [menuItems retain]; 165 | // [menuTableView loadItemViews]; 166 | // } 167 | // return self; 168 | //} 169 | 170 | 171 | - (void)dealloc { 172 | [_title release]; 173 | [_menuItems release]; 174 | [_underlyingWindowController release]; 175 | 176 | [super dealloc]; 177 | } 178 | 179 | 180 | - (NSString *)title { 181 | return _title; 182 | } 183 | 184 | 185 | - (void)setTitle:(NSString *)aString { 186 | _title = [aString copy]; 187 | } 188 | 189 | 190 | - (CMMenu *)supermenu { 191 | return _supermenu; 192 | } 193 | 194 | 195 | - (CMMenuItem *)itemAtIndex:(NSInteger)index { 196 | if ( !_menuItems || index < 0 || (NSUInteger)index >= [_menuItems count]) 197 | return nil; 198 | 199 | // if (index < 0 || index >= [_menuItems count]) 200 | // [NSException raise:NSRangeException format:@"No item for -itemAtIndex: %ld", index]; 201 | return [_menuItems objectAtIndex:(NSUInteger)index]; 202 | } 203 | 204 | 205 | - (CMMenuItem *)itemAtPoint:(NSPoint)aPoint { 206 | aPoint = [self convertPointFromScreen:aPoint]; 207 | NSViewController *viewController = [_underlyingWindowController viewAtPoint:aPoint]; 208 | if (viewController) 209 | return [viewController representedObject]; 210 | else 211 | return nil; 212 | } 213 | 214 | 215 | - (NSArray *)itemArray { 216 | return _menuItems; 217 | } 218 | 219 | 220 | - (NSInteger)numberOfItems { 221 | return (NSInteger)[_menuItems count]; 222 | } 223 | 224 | 225 | - (CMMenuItem *)parentItem { 226 | return (_parentItem) ? _parentItem : nil; 227 | } 228 | 229 | - (NSInteger)indexOfItem:(CMMenuItem *)item { 230 | if (! item) 231 | return -1; 232 | 233 | NSUInteger i; 234 | NSUInteger count = [_menuItems count]; 235 | 236 | if (! count) 237 | return -1; 238 | 239 | for (i = 0; i < count; ++i) { 240 | if (item == [_menuItems objectAtIndex:i]) { 241 | return (NSInteger)i; 242 | } 243 | } 244 | 245 | return -1; 246 | } 247 | 248 | 249 | /* 250 | * 251 | */ 252 | - (void)insertItem:(CMMenuItem *)newItem atIndex:(NSUInteger)index animate:(BOOL)animate { 253 | if (newItem == nil) 254 | [NSException raise:NSInvalidArgumentException format:@"nil provided as Menu Item object."]; 255 | 256 | if (index > [_menuItems count]) 257 | [NSException raise:NSInvalidArgumentException format:@"Provided index is greater then the number of elements in Menu during -insertItem:atIndex:"]; 258 | 259 | XLog3("Adding menu item: %@", newItem); 260 | 261 | [_menuItems insertObject:newItem atIndex:index]; 262 | [newItem setMenu:self]; 263 | if ([newItem hasSubmenu]) 264 | [[newItem submenu] setSupermenu:self]; 265 | 266 | // Menu will update our newly added item itself 267 | if (_needsDisplay) 268 | return; 269 | 270 | // ..otherwise, we need to update it ourselves. 271 | NSViewController *viewController = [self viewForItem:newItem]; 272 | [newItem setRepresentedView:viewController]; 273 | [viewController setRepresentedObject:newItem]; 274 | 275 | if (! _isActive) 276 | animate = NO; 277 | 278 | [_underlyingWindowController insertView:viewController atIndex:index animate:animate]; 279 | if (_isActive) 280 | [self displayInFrame:NSZeroRect options:CMMenuOptionUpdateScrollers | CMMenuOptionUpdateTrackingPrimitives display:NO]; 281 | } 282 | 283 | 284 | /* 285 | * 286 | */ 287 | - (void)addItem:(CMMenuItem *)newItem { 288 | if (newItem == nil) 289 | [NSException raise:NSInvalidArgumentException format:@"nil provided as Menu Item object."]; 290 | 291 | [self insertItem:newItem atIndex:[_menuItems count] animate:NO]; 292 | } 293 | 294 | 295 | /* 296 | * 297 | */ 298 | - (void)addItem:(CMMenuItem *)newItem animate:(BOOL)animate { 299 | if (newItem == nil) 300 | [NSException raise:NSInvalidArgumentException format:@"nil provided as Menu Item object."]; 301 | 302 | [self insertItem:newItem atIndex:[_menuItems count] animate:animate]; 303 | } 304 | 305 | 306 | /* 307 | * 308 | */ 309 | - (void)removeItemAtIndex:(NSInteger)index animate:(BOOL)animate { 310 | NSUInteger itemsCount = [_menuItems count]; 311 | if (index < 0 || (NSUInteger)index >= itemsCount) { 312 | [NSException raise:NSInvalidArgumentException format:@"Provided index out of bounds for Menu's -removeItemAtIndex:"]; 313 | return; 314 | } 315 | 316 | NSUInteger i = (NSUInteger)index; 317 | CMMenuItem *item = [_menuItems objectAtIndex:i]; 318 | if ([item hasSubmenu]) { 319 | if ([[item submenu] isActive]) 320 | [[item submenu] cancelTrackingWithoutAnimation]; 321 | [[item submenu] setParentItem:nil]; 322 | [[item submenu] setSupermenu:nil]; 323 | [item setSubmenu:nil]; 324 | } 325 | XLog3("Removing menu item: %@", item); 326 | [_menuItems removeObjectAtIndex:i]; 327 | --itemsCount; 328 | 329 | // Menu will update items itself 330 | if (_needsDisplay) 331 | return; 332 | 333 | if (animate && _isActive) { 334 | [_underlyingWindowController removeViewAtIndex:i animate:YES complitionHandler:^(void) { 335 | if (! itemsCount) { // no items left in menu, hide it 336 | [self cancelTrackingWithoutAnimation]; 337 | } else { 338 | if (_isActive) // if menu hasn't been hidden during animation 339 | [self displayInFrame:NSZeroRect options:CMMenuOptionUpdateScrollers | CMMenuOptionUpdateTrackingPrimitives display:NO]; 340 | } 341 | }]; 342 | } else { 343 | [_underlyingWindowController removeViewAtIndex:i]; 344 | if (_isActive) { 345 | if (! itemsCount) { 346 | [self cancelTrackingWithoutAnimation]; 347 | } else { 348 | // if (_isActive) 349 | [self displayInFrame:NSZeroRect options:CMMenuOptionUpdateScrollers | CMMenuOptionUpdateTrackingPrimitives display:NO]; 350 | } 351 | } 352 | 353 | } 354 | } 355 | 356 | 357 | /* 358 | * 359 | */ 360 | - (void)removeItem:(CMMenuItem *)item animate:(BOOL)animate { 361 | if (! item) { 362 | [NSException raise:NSInvalidArgumentException format:@"nil provided for Menu -removeItem:"]; 363 | return; 364 | } 365 | 366 | NSUInteger i; 367 | NSUInteger count = [_menuItems count]; 368 | 369 | if (! count) 370 | return; 371 | 372 | for (i = 0; i < count; ++i) { 373 | if (item == [_menuItems objectAtIndex:i]) { 374 | [self removeItemAtIndex:(NSInteger)i animate:animate]; 375 | break; 376 | } 377 | } 378 | } 379 | 380 | 381 | /* 382 | * 383 | */ 384 | - (void)removeItemsAtIndexes:(NSIndexSet *)indexes { 385 | __block NSUInteger itemsCount = [_menuItems count]; 386 | if (! itemsCount) { 387 | [NSException raise:NSInvalidArgumentException format:@"Removing items from empty Menu at -removeItemsAtIndexes:"]; 388 | return; 389 | } 390 | if ([indexes indexGreaterThanOrEqualToIndex:itemsCount] != NSNotFound) { 391 | [NSException raise:NSRangeException format:@"Indexes out of bounds at CMMenu -removeItemsAtIndexes:"]; 392 | return; 393 | } 394 | 395 | 396 | [indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { 397 | // See if any of the removing items has submenus 398 | --itemsCount; 399 | CMMenuItem *item = [_menuItems objectAtIndex:idx]; 400 | if ([item hasSubmenu]) { 401 | if ([[item submenu] isActive]) 402 | [[item submenu] cancelTrackingWithoutAnimation]; 403 | [[item submenu] setParentItem:nil]; 404 | [[item submenu] setSupermenu:nil]; 405 | [item setSubmenu:nil]; 406 | } 407 | XLog3("Removing menu item: %@", item); 408 | }]; 409 | 410 | [_menuItems removeObjectsAtIndexes:indexes]; 411 | 412 | 413 | // Menu will update items itself 414 | if (_needsDisplay) 415 | return; 416 | 417 | __block NSUInteger offset = 0; 418 | [indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { 419 | [_underlyingWindowController removeViewAtIndex:(idx - offset)]; 420 | ++offset; 421 | }]; 422 | 423 | if (_isActive) { 424 | if (! itemsCount) 425 | [self cancelTrackingWithoutAnimation]; 426 | else 427 | [self displayInFrame:NSZeroRect options:CMMenuOptionUpdateScrollers | CMMenuOptionUpdateTrackingPrimitives display:NO]; 428 | } 429 | } 430 | 431 | 432 | /* 433 | * 434 | */ 435 | - (void)removeAllItems { 436 | if (_isActive) 437 | [self cancelTrackingWithoutAnimation]; 438 | 439 | for (CMMenuItem *item in _menuItems) { 440 | if ([item hasSubmenu]) { 441 | [[item submenu] setParentItem:nil]; 442 | [[item submenu] setSupermenu:nil]; 443 | [item setSubmenu:nil]; 444 | } 445 | } 446 | [_menuItems removeAllObjects]; 447 | [_underlyingWindowController removeAllViews]; 448 | XLog3("Removed all items from menu \"%@\"", _title); 449 | // Set menu as needing display next time (if) new items are added 450 | // to it. 451 | _needsDisplay = YES; 452 | } 453 | 454 | 455 | /* 456 | * 457 | */ 458 | - (void)setSubmenu:(CMMenu *)aMenu forItem:(CMMenuItem *)anItem { 459 | // if (aMenu == nil || anItem == nil) 460 | if (anItem == nil) 461 | [NSException raise:NSInvalidArgumentException format:@"Bad argument in -%@", NSStringFromSelector(_cmd)]; 462 | 463 | // pass to Menu Item method 464 | [anItem setSubmenu:aMenu]; 465 | } 466 | 467 | 468 | /* 469 | * 470 | */ 471 | - (void)showWithOptions:(CMMenuOptions)options { 472 | // Check if menu delegate want to update menu 473 | if (_delegate) { 474 | if ([_delegate respondsToSelector:@selector(menuNeedsUpdate:)]) { 475 | [_delegate performSelector:@selector(menuNeedsUpdate:) withObject:self]; 476 | } 477 | } 478 | 479 | // If menu has been updates by delegate or otherwise there are no items 480 | // to show -- return. 481 | if (! [_menuItems count]) 482 | return; 483 | 484 | 485 | // Second condition is evaluated when there are items in menu 486 | // but these items don't have according views drawn on window. 487 | // This could be after [CMMenu removeAllItems] and new items 488 | // added but not yet drawn. It is more efficient to process them 489 | // all at once then one by one. 490 | if ( !_underlyingWindowController || 491 | (_needsDisplay && NSEqualSizes([_underlyingWindowController intrinsicContentSize], NSZeroSize)) ) 492 | { 493 | if (! _underlyingWindowController) 494 | _underlyingWindowController = [[CMWindowController alloc] initWithOwner:self]; 495 | [self loadItemViews]; 496 | _needsDisplay = NO; 497 | } 498 | 499 | [self displayInFrame:NSZeroRect options:options display:NO]; 500 | _isActive = YES; 501 | 502 | // Root menu 503 | if (! _supermenu) { 504 | _globalEventMonitor = [NSEvent addGlobalMonitorForEventsMatchingMask:NSLeftMouseDownMask | NSRightMouseDownMask | NSOtherMouseDownMask 505 | handler:^(NSEvent *theEvent) { 506 | [self cancelTracking]; 507 | }]; 508 | 509 | 510 | if (! [NSApp isActive]) { // otherwise NSApp will not recieve events 511 | [NSApp activateIgnoringOtherApps:YES]; 512 | } 513 | 514 | // Use workspace to monitor if app gets deactived (e.g. by Command + Tab) 515 | // Cannot use NSApplicationDidResignActiveNotification as it doesn't work in NSEventTRackingRunLoopMode 516 | [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self 517 | selector:@selector(didDeactivateApplicationNotificationHandler:) 518 | name:NSWorkspaceDidDeactivateApplicationNotification 519 | object:nil]; 520 | 521 | [[NSNotificationCenter defaultCenter] postNotificationName:CMMenuDidBeginTrackingNotification object:self]; 522 | 523 | // root menu begins tracking immediately as it appears. 524 | // Submenus begin tracking only after the mouse enters its rect in tracking loop or 525 | // on other occasions, primarily when navigation with keyboard. 526 | [self beginTrackingWithEvent:nil options:CMMenuOptionDefaults]; 527 | } 528 | } 529 | 530 | 531 | /* 532 | * 533 | */ 534 | - (void)showAsSubmenuOf:(CMMenuItem *)menuItem withOptions:(CMMenuOptions)options { 535 | [[menuItem menu] setActiveSubmenu:self]; 536 | // _parentItem = menuItem; 537 | [self showWithOptions:options]; 538 | } 539 | 540 | 541 | 542 | /* 543 | * 544 | */ 545 | - (void)popUpMenuForStatusItemWithRect:(NSRect)rect { 546 | if (_isActive || [_menuItems count] == 0) 547 | return; 548 | 549 | _isAttachedToStatusItem = YES; 550 | _statusItemRect = rect; 551 | [self showWithOptions:CMMenuOptionDefaults]; 552 | } 553 | 554 | 555 | - (BOOL)popUpMenuPositioningItem:(NSMenuItem *)item atLocation:(NSPoint)location inView:(NSView *)view { 556 | // this method needs to be properly implemented 557 | // for now it's a temporary quick use 558 | _isPopUpMenu = YES; 559 | _popupLocation = location; 560 | [self showWithOptions:CMMenuOptionDefaults]; 561 | return NO; 562 | } 563 | 564 | 565 | 566 | /* 567 | * 568 | */ 569 | - (void)cancelTracking { 570 | if (! _isActive) 571 | return; 572 | 573 | if (_activeSubmenu) { 574 | [_activeSubmenu cancelTracking]; 575 | } 576 | 577 | [self endTracking]; 578 | 579 | 580 | [_underlyingWindowController fadeOutWithComplitionHandler:^(void) { 581 | if ([_menuItems count]) 582 | [self moveVisibleAreaToDisplayItem:[_menuItems objectAtIndex:0] ignoreMouse:YES updateTrackingPrimitives:NO]; 583 | 584 | [_underlyingWindowController hide]; 585 | 586 | CMMenuItem *selectedItem = [self highlightedItem]; 587 | if (selectedItem) 588 | [selectedItem deselect]; 589 | 590 | if (_supermenu) { 591 | [_supermenu setActiveSubmenu:nil]; 592 | [_parentItem deselect]; 593 | } else { // root menu 594 | // NSNotification *notification = [NSNotification notificationWithName:CMMenuDidEndTrackingNotification object:self]; 595 | [[NSNotificationCenter defaultCenter] postNotificationName:CMMenuDidEndTrackingNotification object:self]; 596 | } 597 | }]; 598 | 599 | _isActive = NO; 600 | _isAttachedToStatusItem = NO; 601 | _isPopUpMenu = NO; 602 | // Reset event blocking mask back to zero 603 | _eventBlockingMask = 0; 604 | 605 | if (! _supermenu) { 606 | if (_globalEventMonitor) { 607 | [NSEvent removeMonitor:_globalEventMonitor]; 608 | _globalEventMonitor = nil; 609 | } 610 | 611 | // [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self name:NSWorkspaceDidDeactivateApplicationNotification object:self]; 612 | [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self]; 613 | } 614 | } 615 | 616 | 617 | /* 618 | * 619 | */ 620 | - (void)cancelTrackingWithoutAnimation { 621 | if (! _isActive) 622 | return; 623 | 624 | if (_activeSubmenu) { 625 | [_activeSubmenu cancelTrackingWithoutAnimation]; 626 | } 627 | 628 | [self endTracking]; 629 | 630 | if ([_menuItems count]) 631 | [self moveVisibleAreaToDisplayItem:[_menuItems objectAtIndex:0] ignoreMouse:YES updateTrackingPrimitives:NO]; 632 | 633 | [_underlyingWindowController hide]; 634 | _isActive = NO; 635 | _isAttachedToStatusItem = NO; 636 | _isPopUpMenu = NO; 637 | // Reset event blocking mask back to zero 638 | _eventBlockingMask = 0; 639 | 640 | CMMenuItem *selectedItem = [self highlightedItem]; 641 | if (selectedItem) 642 | [selectedItem deselect]; 643 | 644 | if (_supermenu) { 645 | [_supermenu setActiveSubmenu:nil]; 646 | [_parentItem deselect]; 647 | } else { 648 | if (_globalEventMonitor) { 649 | [NSEvent removeMonitor:_globalEventMonitor]; 650 | _globalEventMonitor = nil; 651 | } 652 | 653 | // [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self name:NSWorkspaceDidDeactivateApplicationNotification object:self]; 654 | [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self]; 655 | [[NSNotificationCenter defaultCenter] postNotificationName:CMMenuDidEndTrackingNotification object:self]; 656 | } 657 | } 658 | 659 | 660 | /* 661 | * 662 | */ 663 | - (void)didDeactivateApplicationNotificationHandler:(NSNotification *)notification { 664 | NSRunningApplication *deactivatedApp = [[notification userInfo] objectForKey:NSWorkspaceApplicationKey]; 665 | // NSLog(@"did deactive application, current: %@, deactivated: %@", [NSRunningApplication currentApplication], deactivatedApp); 666 | if ([[NSRunningApplication currentApplication] isEqual:deactivatedApp]) { 667 | [self cancelTracking]; 668 | } 669 | } 670 | 671 | 672 | - (BOOL)cancelsTrackingOnAction { 673 | return _cancelsTrackingOnAction; 674 | } 675 | 676 | 677 | - (void)setCancelsTrackingOnAction:(BOOL)cancels { 678 | _cancelsTrackingOnAction = cancels; 679 | } 680 | 681 | 682 | - (BOOL)cancelsTrackingOnMouseEventOutsideMenus { 683 | return _cancelsTrackingOnMouseEventOutsideMenus; 684 | } 685 | 686 | 687 | - (void)setCancelsTrackingOnMouseEventOutsideMenus:(BOOL)cancels { 688 | _cancelsTrackingOnMouseEventOutsideMenus = cancels; 689 | } 690 | 691 | 692 | - (BOOL)menusSuspended { 693 | if (_eventBlockingMask) 694 | return YES; 695 | return ([self supermenu] && [[self supermenu] eventBlockingMask] != 0); 696 | } 697 | 698 | 699 | - (void)setSuspendMenus:(BOOL)suspend { 700 | BOOL changedStatus = NO; 701 | if (suspend) { 702 | // Receiving menu doesn't block itself. All others do. 703 | [self blockEventsMatchingMask:0]; 704 | CMMenu *menu = [self supermenu]; 705 | while (menu) { 706 | if (! [menu eventBlockingMask]) { 707 | [menu blockEventsMatchingMask:NSLeftMouseDownMask | NSRightMouseDownMask | NSOtherMouseDownMask | NSScrollWheelMask | NSMouseMovedMask | NSLeftMouseDraggedMask | NSRightMouseDraggedMask | NSOtherMouseDraggedMask]; 708 | changedStatus = YES; 709 | } 710 | // These three additional event masks define whether menu exits suspend status 711 | // on mouse move/dragged 712 | // | NSLeftMouseDraggedMask | NSRightMouseDraggedMask | NSOtherMouseDraggedMask 713 | menu = [menu supermenu]; 714 | } 715 | } else { 716 | CMMenu *menu = [self rootMenu]; 717 | do { 718 | if ([menu eventBlockingMask]) { 719 | [menu blockEventsMatchingMask:0]; 720 | changedStatus = YES; 721 | } 722 | } while ((menu = [menu activeSubmenu])); 723 | } 724 | 725 | if (changedStatus) { 726 | [[NSNotificationCenter defaultCenter] postNotificationName:CMMenuSuspendStatusDidChangeNotification 727 | object:self 728 | userInfo:@{ @"newStatus" : [NSNumber numberWithBool:suspend] }]; 729 | } 730 | } 731 | 732 | /* 733 | * 734 | */ 735 | - (NSEventMask)eventBlockingMask { 736 | return _eventBlockingMask; 737 | } 738 | 739 | 740 | - (void)blockEventsMatchingMask:(NSEventMask)mask { 741 | _eventBlockingMask = mask; 742 | } 743 | 744 | - (CMMenuItem *)highlightedItem { 745 | for (CMMenuItem *item in _menuItems) { 746 | if ([item isSelected]) 747 | return item; 748 | } 749 | return nil; 750 | } 751 | 752 | 753 | - (id)delegate { 754 | return _delegate; 755 | } 756 | 757 | 758 | - (void)setDelegate:(id)anObject { 759 | [_delegate autorelease]; 760 | _delegate = [anObject retain]; 761 | } 762 | 763 | 764 | - (void)setPerformsActionInstantly:(BOOL)instantly { 765 | _performsActionInstantly = instantly; 766 | } 767 | 768 | 769 | - (BOOL)performsActionInstantly { 770 | return _performsActionInstantly; 771 | } 772 | 773 | 774 | /* 775 | * 776 | */ 777 | - (void)showPopover:(NSPopover *)popover forItem:(CMMenuItem *)item preferredEdge:(NSRectEdge)preferredEdge { 778 | if (! popover) { 779 | [NSException raise:NSInvalidArgumentException format:@"nil provided for popover in -showPopover:forItem:"]; 780 | return; 781 | } 782 | if (! item) { 783 | [NSException raise:NSInvalidArgumentException format:@"nil provided for item in -showPopover:forItem:"]; 784 | return; 785 | } 786 | if ([_menuItems indexOfObject:item] == NSNotFound) { 787 | [NSException raise:NSInvalidArgumentException format:@"Provided item doesn't belong to the reciever's menu at -showPopover:forItem:"]; 788 | return; 789 | } 790 | 791 | NSView *view = [(NSViewController *)[item representedView] view]; 792 | [popover showRelativeToRect:[view bounds] ofView:view preferredEdge:preferredEdge]; 793 | } 794 | 795 | 796 | /* 797 | * 798 | */ 799 | - (CGFloat)minimumWidth { 800 | return _minimumWidth; 801 | } 802 | 803 | 804 | - (void)setMinimumWidth:(CGFloat)width { 805 | _minimumWidth = width; 806 | } 807 | 808 | 809 | - (NSSize)size { 810 | return (_underlyingWindowController) ? _underlyingWindowController.window.frame.size : NSMakeSize(0, 0); 811 | } 812 | 813 | 814 | - (CGFloat)borderRadius { 815 | return _borderRadius; 816 | } 817 | 818 | 819 | - (void)setBorderRadius:(CGFloat)radius { 820 | _borderRadius = radius; 821 | } 822 | 823 | 824 | - (NSRect)frame { 825 | return (_underlyingWindowController) ? _underlyingWindowController.window.frame : NSMakeRect(0, 0, 10, 10); 826 | } 827 | 828 | 829 | - (NSRect)convertRectToScreen:(NSRect)aRect { 830 | return [[_underlyingWindowController window] convertRectToScreen:aRect]; 831 | } 832 | 833 | 834 | - (NSPoint)convertPointToScreen:(NSPoint)aPoint { 835 | return [[_underlyingWindowController window] convertBaseToScreen:aPoint]; 836 | } 837 | 838 | 839 | - (NSPoint)convertPointFromScreen:(NSPoint)aPoint { 840 | return [[_underlyingWindowController window] convertScreenToBase:aPoint]; 841 | } 842 | 843 | 844 | /* 845 | * 846 | */ 847 | - (NSRect)getBestFrameForMenuWindow { 848 | NSRect frame; 849 | NSSize intrinsicSize = [_underlyingWindowController intrinsicContentSize]; 850 | 851 | // NSLog(@"intrinsic size: %@", NSStringFromSize(intrinsicSize)); 852 | 853 | // root menu 854 | if (! _parentItem) { 855 | // Root menu can be started only with a family of -popUpMenu.. methods. 856 | // Thus either location or positioning item is provided. 857 | NSScreen *screen = [[_underlyingWindowController window] screen]; 858 | NSRect screenFrame = [screen frame]; 859 | 860 | if (_isAttachedToStatusItem) { 861 | frame.size = intrinsicSize; 862 | frame.origin.y = _statusItemRect.origin.y - intrinsicSize.height; 863 | if (_statusItemRect.origin.x + intrinsicSize.width > NSMaxX(screenFrame)) { 864 | frame.origin.x = NSMaxX(screenFrame) - intrinsicSize.width; 865 | } else { 866 | frame.origin.x = _statusItemRect.origin.x; 867 | } 868 | } else { // _isPopUpMenu 869 | // TODO: this part needs to be replaced with proper code 870 | // that will take into account screen size and paddings to screen 871 | // edge. 872 | frame.size = intrinsicSize; 873 | frame.origin = _popupLocation; 874 | if (NSMaxX(frame) > NSMaxX(screenFrame)) 875 | frame.origin.x = NSMaxX(screenFrame) - intrinsicSize.width; 876 | } 877 | 878 | 879 | // frame.size.width = intrinsicSize.width; 880 | // frame.size.height = (intrinsicSize.height > 817) ? 825 : intrinsicSize.height; 881 | // frame.origin = NSMakePoint(70, screenFrame.size.height - frame.size.height - 50); 882 | // frame.size.height = 65; 883 | return frame; 884 | } 885 | 886 | 887 | NSPoint origin; 888 | NSSize size; 889 | // NSRect menuFrame = [self frame]; 890 | NSRect supermenuFrame = [_supermenu frame]; 891 | // NSRect parentItemFrame = [_supermenu frameOfItemRelativeToScreen:_parentItem]; 892 | NSRect parentItemFrame = [_parentItem frameRelativeToScreen]; 893 | NSScreen *screen = [[_underlyingWindowController window] screen]; 894 | CGFloat menuPadding = [_underlyingWindowController verticalPadding]; 895 | NSRect screenFrame = [screen frame]; 896 | 897 | // Menu X coordinate 898 | if ([_supermenu horizontalAlignment] == CMMenuAlignedLeft) { 899 | // if ((screenFrame.size.width - NSMaxX(supermenuFrame)) < intrinsicSize.width) { 900 | if (NSMaxX(supermenuFrame) + intrinsicSize.width + CMMENU_PADDING_TO_SCREEN_EDGES > NSMaxX(screenFrame)) { 901 | origin.x = supermenuFrame.origin.x - intrinsicSize.width; 902 | _menuHorizontalAlignment = CMMenuAlignedRight; 903 | } else { 904 | origin.x = NSMaxX(supermenuFrame); 905 | _menuHorizontalAlignment = CMMenuAlignedLeft; 906 | } 907 | } else { 908 | if (NSMinX(supermenuFrame) - intrinsicSize.width - CMMENU_PADDING_TO_SCREEN_EDGES < NSMinX(screenFrame)) { 909 | origin.x = NSMaxX(supermenuFrame); 910 | _menuHorizontalAlignment = CMMenuAlignedLeft; 911 | } else { 912 | origin.x = supermenuFrame.origin.x - intrinsicSize.width; 913 | _menuHorizontalAlignment = CMMenuAlignedRight; 914 | } 915 | } 916 | 917 | // Menu Y coordinate 918 | // default menu alignment at top of parent item 919 | if (NSMaxY(parentItemFrame) - intrinsicSize.height + menuPadding >= screenFrame.origin.y) { 920 | origin.y = NSMaxY(parentItemFrame) - intrinsicSize.height + menuPadding; 921 | size.height = intrinsicSize.height; 922 | _menuVerticalAlignment = CMMenuAlignedTop; 923 | } 924 | // else if (parentItemFrame.origin.y < 27) { 925 | else { 926 | // origin.y = parentItemFrame.origin.y - menuPadding; // TODO: also need to scroll content to bottom 927 | origin.y = CMMENU_PADDING_TO_SCREEN_EDGES; 928 | CGFloat statusBarThickness = [[NSStatusBar systemStatusBar] thickness]; 929 | if (origin.y + intrinsicSize.height > screenFrame.size.height - statusBarThickness) 930 | size.height = screenFrame.size.height - statusBarThickness - origin.y; 931 | else 932 | size.height = intrinsicSize.height; 933 | _menuVerticalAlignment = CMMenuAlignedBottom; 934 | } 935 | 936 | /* else { 937 | origin.y = screenFrame.origin.y; 938 | size.height = parentItemFrame.origin.y + parentItemFrame.size.height + menuPadding; 939 | _menuVerticalAlignment = CMMenuAlignedTop; 940 | } */ 941 | 942 | 943 | size.width = intrinsicSize.width; 944 | frame.origin = origin; 945 | frame.size = size; 946 | 947 | return frame; 948 | } 949 | 950 | 951 | /* 952 | * Based on Menu Items we create View Controllers and pass them for drawing to Window Controller 953 | */ 954 | - (void)loadItemViews { 955 | NSMutableArray *viewControllers = [NSMutableArray array]; 956 | 957 | for (CMMenuItem *menuItem in _menuItems) { 958 | NSViewController *viewController = [self viewForItem:menuItem]; 959 | [menuItem setRepresentedView:viewController]; 960 | [viewController setRepresentedObject:menuItem]; 961 | [viewControllers addObject:viewController]; 962 | 963 | } 964 | 965 | [_underlyingWindowController layoutViews:viewControllers]; 966 | } 967 | 968 | 969 | /* 970 | * 971 | */ 972 | - (NSViewController *)viewForItem:(CMMenuItem *)menuItem { 973 | NSViewController *viewController; 974 | 975 | NSBundle *frameworkBundle = [NSBundle bundleWithIdentifier:CM_FRAMEWORK_BUNDLE_IDENTIFIER]; 976 | 977 | if ([menuItem isSeparatorItem]) { 978 | viewController = [[NSViewController alloc] initWithNibName:@"CMMenuItemSeparatorView" bundle:frameworkBundle]; 979 | } else { 980 | CMMenuItemView *view; 981 | 982 | if ([menuItem icon]) { 983 | viewController = [[NSViewController alloc] initWithNibName:@"CMMenuItemIconView" bundle:frameworkBundle]; 984 | view = (CMMenuItemView *)viewController.view; 985 | [[view icon] setImage:[menuItem icon]]; 986 | } else { 987 | viewController = [[NSViewController alloc] initWithNibName:@"CMMenuItemView" bundle:frameworkBundle]; 988 | view = (CMMenuItemView *)viewController.view; 989 | } 990 | 991 | if ([menuItem state] != NSOffState) { 992 | NSImage *stateImage = ([menuItem state] == NSOnState) ? [menuItem onStateImage] : [menuItem mixedStateImage]; 993 | [[view state] setImage:stateImage]; 994 | } 995 | 996 | [[view title] setStringValue:[menuItem title]]; 997 | 998 | if ([menuItem hasSubmenu]) 999 | [view setHasSubmenuIcon:YES]; 1000 | 1001 | if ([menuItem indentationLevel]) 1002 | [view setIndentationLevel:[menuItem indentationLevel]]; 1003 | 1004 | [view setEnabled:[menuItem isEnabled]]; 1005 | } 1006 | 1007 | return [viewController autorelease]; 1008 | } 1009 | 1010 | 1011 | 1012 | 1013 | #pragma mark - 1014 | #pragma mark ***** CMMenu Internal Methods ***** 1015 | 1016 | 1017 | - (void)performActionForItem:(CMMenuItem *)item { 1018 | if ([item hasSubmenu]) 1019 | return; 1020 | 1021 | SEL action = [item action]; 1022 | if (action && [item isEnabled]) { 1023 | id target = [item target]; 1024 | if (! target) { // application delegate could be the one to handle it 1025 | target = [(NSApplication *)NSApp delegate]; 1026 | } 1027 | 1028 | if ([target respondsToSelector:action]) { 1029 | XLog2("Performing action on item: %@", item); 1030 | if (_performsActionInstantly) 1031 | [NSApp sendAction:action to:target from:item]; 1032 | else 1033 | [target performSelector:action withObject:item afterDelay:0.2 inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]]; 1034 | 1035 | } 1036 | } 1037 | 1038 | 1039 | if ([self cancelsTrackingOnAction]) { 1040 | if ( ![item isSeparatorItem] && [item isEnabled]) { 1041 | CMMenu *menu = self; 1042 | CMMenu *rootMenu; 1043 | do { 1044 | [menu endTracking]; 1045 | rootMenu = menu; 1046 | } while ((menu = [menu supermenu])); 1047 | 1048 | CMMenuItemView *view = (CMMenuItemView *)[[item representedView] view]; 1049 | [view blink]; 1050 | [rootMenu performSelector:@selector(cancelTracking) withObject:nil afterDelay:0.075 inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]]; 1051 | } else { 1052 | [[self rootMenu] cancelTracking]; 1053 | } 1054 | } 1055 | } 1056 | 1057 | 1058 | - (CMMenu *)rootMenu { 1059 | CMMenu *menu = self; 1060 | CMMenu *supermenu = menu; 1061 | while ((menu = [menu supermenu])) 1062 | supermenu = menu; 1063 | 1064 | return supermenu; 1065 | } 1066 | 1067 | 1068 | - (void)setNeedsDisplay:(BOOL)needsDisplay { 1069 | if (_needsDisplay == needsDisplay) 1070 | return; 1071 | 1072 | CMMenuOptions options = CMMenuOptionDefaults; 1073 | if ([self isTracking]) 1074 | options |= CMMenuOptionUpdateTrackingPrimitives; 1075 | 1076 | if (needsDisplay && _isActive) 1077 | [self displayInFrame:NSZeroRect options:options display:YES]; 1078 | } 1079 | 1080 | 1081 | - (BOOL)needsDisplay { 1082 | return _needsDisplay; 1083 | } 1084 | 1085 | 1086 | - (void)setSupermenu:(CMMenu *)aMenu { 1087 | _supermenu = aMenu; 1088 | } 1089 | 1090 | 1091 | - (void)setParentItem:(CMMenuItem *)anItem { 1092 | _parentItem = anItem; 1093 | } 1094 | 1095 | 1096 | - (BOOL)isActive { 1097 | return _isActive; 1098 | } 1099 | 1100 | 1101 | - (void)setIsActive:(BOOL)isActive { 1102 | _isActive = isActive; 1103 | } 1104 | 1105 | 1106 | - (CMMenu *)activeSubmenu { 1107 | return _activeSubmenu; 1108 | } 1109 | 1110 | 1111 | - (void)setActiveSubmenu:(CMMenu *)submenu { 1112 | _activeSubmenu = submenu; 1113 | } 1114 | 1115 | 1116 | - (BOOL)isAttachedToStatusItem { 1117 | return _isAttachedToStatusItem; 1118 | } 1119 | 1120 | 1121 | - (NSRect)statusItemRect { 1122 | return _statusItemRect; 1123 | } 1124 | 1125 | 1126 | - (void)displayInFrame:(NSRect)frameRect options:(CMMenuOptions)options display:(BOOL)display { 1127 | if (display || _needsDisplay) { 1128 | [_underlyingWindowController updateDocumentView]; 1129 | _needsDisplay = NO; 1130 | } 1131 | 1132 | if (NSEqualRects(frameRect, NSZeroRect)) { 1133 | // Store current horizontal alignment. For example, if menu was aligning to 1134 | // parent menu with its left side and after -getBestFrameForMenuWindow 1135 | // method call (if there was not enough room on screen) the menu changed to 1136 | // align with its right side, it is necessary in such a situation to redraw 1137 | // underlying window view to account new corner positions. 1138 | CMMenuAlignment horizontalAlignement = _menuHorizontalAlignment; 1139 | NSRect frame = [self getBestFrameForMenuWindow]; 1140 | NSNumber *radius = [NSNumber numberWithDouble:_borderRadius]; 1141 | 1142 | if (_isAttachedToStatusItem) { 1143 | // Do not worry if menu was previously drawn with correct corners. 1144 | // View will be re-drawn only when radiuses indeed change. 1145 | [_underlyingWindowController setBorderRadiuses:@[radius, @0, @0, radius]]; 1146 | } else if (_isPopUpMenu) { 1147 | [_underlyingWindowController setBorderRadiuses:@[radius, radius, radius, radius]]; 1148 | } else if (horizontalAlignement != _menuHorizontalAlignment) { 1149 | if (_menuHorizontalAlignment == CMMenuAlignedLeft) 1150 | [_underlyingWindowController setBorderRadiuses:@[radius, @0, radius, radius]]; // top left corner is square 1151 | else 1152 | [_underlyingWindowController setBorderRadiuses:@[radius, radius, @0, radius, radius]]; // top right corner is square 1153 | } 1154 | 1155 | [_underlyingWindowController displayInFrame:frame options:options]; 1156 | } else { 1157 | [_underlyingWindowController displayInFrame:frameRect options:options]; 1158 | } 1159 | 1160 | if ([self activeSubmenu]) 1161 | [[self activeSubmenu] displayInFrame:NSZeroRect options:CMMenuOptionDefaults display:NO]; 1162 | } 1163 | 1164 | 1165 | /* 1166 | * 1167 | */ 1168 | - (CMMenu *)menuAtPoint:(NSPoint)location { 1169 | CMMenu *menu = [self rootMenu]; 1170 | do { 1171 | if (NSPointInRect(location, [menu frame])) { 1172 | return menu; 1173 | } 1174 | } while ((menu = [menu activeSubmenu])); 1175 | 1176 | return nil; 1177 | } 1178 | 1179 | 1180 | - (CMMenuAlignment)horizontalAlignment { 1181 | return _menuHorizontalAlignment; 1182 | } 1183 | 1184 | 1185 | - (CMMenuAlignment)verticalAlignment { 1186 | return _menuVerticalAlignment; 1187 | } 1188 | 1189 | 1190 | - (CMWindowController *)underlyingWindowController { 1191 | return _underlyingWindowController; 1192 | } 1193 | 1194 | 1195 | - (NSInteger)windowLevel { 1196 | return (_underlyingWindowController) ? _underlyingWindowController.window.level : 0; 1197 | } 1198 | 1199 | 1200 | - (CMMenuScroller *)scrollerAtPoint:(NSPoint)aPoint { 1201 | aPoint = [self convertPointFromScreen:aPoint]; 1202 | return [_underlyingWindowController scrollerAtPoint:aPoint]; 1203 | } 1204 | 1205 | 1206 | - (void)scrollWithActiveScroller:(CMMenuScroller *)scroller { 1207 | [_underlyingWindowController scrollWithActiveScroller:scroller]; 1208 | } 1209 | 1210 | 1211 | - (void)moveVisibleAreaToDisplayItem:(CMMenuItem *)item ignoreMouse:(BOOL)ignoreMouse updateTrackingPrimitives:(BOOL)updateTrackingPrimitives { 1212 | [_underlyingWindowController moveVisibleRectToRect:[item frame] ignoreMouse:ignoreMouse updateTrackingPrimitives:updateTrackingPrimitives]; 1213 | } 1214 | 1215 | 1216 | #pragma mark - 1217 | #pragma mark ***** Menu Tracking ***** 1218 | 1219 | 1220 | - (BOOL)isTracking { 1221 | return [_underlyingWindowController isTracking]; 1222 | } 1223 | 1224 | 1225 | - (void)beginTrackingWithEvent:(NSEvent *)theEvent options:(CMMenuOptions)options { 1226 | [_underlyingWindowController beginTrackingWithEvent:theEvent options:options]; 1227 | } 1228 | 1229 | 1230 | - (void)endTracking { 1231 | [_underlyingWindowController endTracking]; 1232 | } 1233 | 1234 | 1235 | - (BOOL)isTrackingSubmenu { 1236 | return _isTrackingSubmenu; 1237 | } 1238 | 1239 | 1240 | /* 1241 | * 1242 | */ 1243 | - (void)mouseEventAtLocation:(NSPoint)mouseLocation type:(NSEventType)eventType { 1244 | 1245 | if (eventType == NSMouseEntered) { 1246 | // NSLog(@"mouse ENTER menu: %@", [self title]); 1247 | 1248 | /* 1249 | * Possible Events: 1250 | * 1. Mouse entered a menu when it is showing a submenu: 1251 | * a. mouse hovered submenu's parent item (returned from submenu); 1252 | * b. mouse hovered other area; 1253 | * 2. Mouse entered submenu when it was being tracked by the supermenu. 1254 | */ 1255 | 1256 | CMMenuItem *mousedItem = [self itemAtPoint:mouseLocation]; 1257 | if (_activeSubmenu) { // 1. 1258 | if (mousedItem && mousedItem == [_activeSubmenu parentItem]) { // 1.a 1259 | if ([_activeSubmenu activeSubmenu]) // if submenu has active submenus -- close them 1260 | [[_activeSubmenu activeSubmenu] cancelTrackingWithoutAnimation]; 1261 | } else 1262 | [_activeSubmenu cancelTrackingWithoutAnimation]; // 1.b. 1263 | } else if (_supermenu && [_supermenu isTrackingSubmenu]) { // 2. 1264 | [_supermenu stopTrackingSubmenuReasonSuccess:YES]; 1265 | } else { 1266 | CMMenuItem *selectedItem = [self highlightedItem]; 1267 | if (selectedItem && selectedItem != mousedItem) 1268 | [selectedItem deselect]; 1269 | } 1270 | 1271 | 1272 | } else if (eventType == NSMouseExited) { 1273 | 1274 | 1275 | } else if (eventType == NSLeftMouseDown || eventType == NSRightMouseDown || eventType == NSOtherMouseDown) { 1276 | // Find a menu of event 1277 | CMMenu *menu = self; 1278 | do { 1279 | if (NSPointInRect(mouseLocation, [menu frame])) 1280 | break; 1281 | } while ((menu = [menu supermenu])); 1282 | [menu rearrangeStateForNewMouse:mouseLocation]; 1283 | 1284 | } else if (eventType == NSLeftMouseUp || eventType == NSRightMouseUp || eventType == NSOtherMouseUp) { 1285 | // Find a menu of event 1286 | CMMenu *menu = self; 1287 | do { 1288 | if (NSPointInRect(mouseLocation, [menu frame])) 1289 | break; 1290 | } while ((menu = [menu supermenu])); 1291 | 1292 | 1293 | if ([menu eventBlockingMask]) { 1294 | [menu setSuspendMenus:NO]; 1295 | [menu rearrangeStateForNewMouse:mouseLocation]; 1296 | } else { 1297 | CMMenuItem *item = [menu itemAtPoint:mouseLocation]; 1298 | if (item) 1299 | [menu performActionForItem:item]; 1300 | } 1301 | 1302 | 1303 | } else if (eventType == NSMouseMoved) { 1304 | CMMenu *menu = self; 1305 | do { 1306 | [menu setReceivesMouseMovedEvents:NO]; 1307 | if (NSPointInRect(mouseLocation, [menu frame])) 1308 | break; 1309 | } while ((menu = [menu supermenu])); 1310 | 1311 | XLog3("Mouse MOVED in menu:\n\tMenu frame that owns RunLoop: %@,\n\tMenu frame with mouse: %@, \n\tMouse location: %@", 1312 | NSStringFromRect([self frame]), 1313 | NSStringFromRect([menu frame]), 1314 | NSStringFromPoint(mouseLocation)); 1315 | 1316 | [menu rearrangeStateForNewMouse:mouseLocation]; 1317 | } 1318 | 1319 | } 1320 | 1321 | 1322 | /* 1323 | * 1324 | */ 1325 | - (void)rearrangeStateForNewMouse:(NSPoint)mouseLocation { 1326 | CMMenuItem *selectedItem = [self highlightedItem]; 1327 | CMMenuItem *mousedItem = [self itemAtPoint:mouseLocation]; 1328 | 1329 | 1330 | if ([self activeSubmenu]) { 1331 | if (mousedItem == [[self activeSubmenu] parentItem]) { 1332 | CMMenu *activeSubmenuOfSubmenu = [[self activeSubmenu] activeSubmenu]; 1333 | if (activeSubmenuOfSubmenu) { 1334 | [activeSubmenuOfSubmenu cancelTrackingWithoutAnimation]; 1335 | } else { 1336 | CMMenuItem *item = [[self activeSubmenu] highlightedItem]; 1337 | if (item) 1338 | [item deselect]; 1339 | } 1340 | [[self activeSubmenu] endTracking]; 1341 | } else { 1342 | [[self activeSubmenu] cancelTrackingWithoutAnimation]; 1343 | if (mousedItem != selectedItem) 1344 | [mousedItem selectWithDelayForSubmenu:SUBMENU_POPUP_DELAY_DEFAULT]; 1345 | } 1346 | } else { 1347 | if (mousedItem && ![mousedItem isSeparatorItem] && [mousedItem isEnabled]) { 1348 | CGFloat delay = (mousedItem == selectedItem) ? SUBMENU_POPUP_NO_DELAY : SUBMENU_POPUP_DELAY_DEFAULT; 1349 | [mousedItem selectWithDelayForSubmenu:delay]; 1350 | } else 1351 | [selectedItem deselect]; 1352 | } 1353 | 1354 | if (! mousedItem) { 1355 | CMMenuScroller *scroller = [self scrollerAtPoint:mouseLocation]; 1356 | if (scroller) 1357 | [self performSelector:@selector(scrollWithActiveScroller:) withObject:scroller afterDelay:0.15 inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]]; 1358 | } 1359 | } 1360 | 1361 | 1362 | 1363 | 1364 | - (void)startTrackingSubmenu:(CMMenu *)submenu forItem:(CMMenuItem *)item { 1365 | _isTrackingSubmenu = YES; 1366 | 1367 | _tracking_event = (tracking_event_t *)malloc(sizeof(tracking_event_t)); 1368 | if (_tracking_event == 0) { 1369 | fputs("Memory exhausted", stderr); 1370 | exit(EXIT_FAILURE); 1371 | } 1372 | 1373 | 1374 | NSPoint mouseLocation = [NSEvent mouseLocation]; 1375 | _tracking_event->event_origin = mouseLocation; 1376 | _tracking_event->last_mouse_location = mouseLocation; 1377 | _tracking_event->item_rect = [item frameRelativeToScreen]; 1378 | _tracking_event->menu_rect = [self frame]; 1379 | _tracking_event->submenu_rect = [submenu frame]; 1380 | _tracking_event->menu_horizontal_alignment = [submenu horizontalAlignment]; 1381 | _tracking_event->submenu_vertical_alignment = [submenu verticalAlignment]; 1382 | _tracking_event->timestamp = [NSDate timeIntervalSinceReferenceDate]; 1383 | 1384 | // We extend area a little to give users space for maneuver 1385 | CGFloat extendHeight = 20; 1386 | 1387 | _tracking_event->tanAlpha = (NSMinY(_tracking_event->item_rect) - NSMinY(_tracking_event->submenu_rect) + 1388 | extendHeight) / NSWidth(_tracking_event->item_rect); 1389 | _tracking_event->tanBeta = (NSMaxY(_tracking_event->submenu_rect) - NSMaxY(_tracking_event->item_rect) + 1390 | extendHeight) / NSWidth(_tracking_event->item_rect); 1391 | 1392 | _tracking_event->averageVelocity = 0; 1393 | _tracking_event->averageDeltaX = 0; 1394 | _tracking_event->timeLeftMenuBounds = 0; 1395 | 1396 | NSTimeInterval interval = 0.07; 1397 | // NSLog(@" --------------------- CREATING SUBMENU TIMER ---------------------------- \n\n"); 1398 | _tracking_event->timer = [NSTimer timerWithTimeInterval:interval 1399 | target:self 1400 | selector:@selector(submenuTrackingLoop:) 1401 | userInfo:nil 1402 | repeats:YES]; 1403 | [[NSRunLoop currentRunLoop] addTimer:_tracking_event->timer forMode:NSRunLoopCommonModes]; 1404 | } 1405 | 1406 | 1407 | /** 1408 | * Discussion. 1409 | * Method can be called from locations: 1410 | * [self trackingLoop:] -- when any of the tracking criteria is satisfied; 1411 | * [CMMenuItem shouldChangeItemSelectionStatusForEvent:] -- when mouse returns to the original menu item. 1412 | * 1413 | */ 1414 | - (void)stopTrackingSubmenuReasonSuccess:(BOOL)reasonSuccess { 1415 | _isTrackingSubmenu = NO; 1416 | 1417 | // NSLog(@"STOP tracking menu: %@, submenu: %@. reason: %d", self, [self activeSubmenu], reasonSuccess); 1418 | 1419 | if (_tracking_event->timer) { 1420 | [_tracking_event->timer invalidate]; 1421 | // [_tracking_event->timer release]; 1422 | _tracking_event->timer = nil; 1423 | } 1424 | 1425 | if (_tracking_event) { 1426 | free(_tracking_event); 1427 | _tracking_event = NULL; 1428 | } 1429 | 1430 | if (reasonSuccess == NO) { 1431 | [_activeSubmenu cancelTrackingWithoutAnimation]; 1432 | NSPoint mouseLocation = [NSEvent mouseLocation]; 1433 | if (NSPointInRect(mouseLocation, [self frame])) { 1434 | CMMenuItem *item = [self itemAtPoint:mouseLocation]; 1435 | if (item) 1436 | [item selectWithDelayForSubmenu:SUBMENU_POPUP_DELAY_AFTER_TRACKING]; 1437 | } 1438 | } 1439 | } 1440 | 1441 | 1442 | - (void)submenuTrackingLoop:(NSTimer *)timer { 1443 | NSPoint mouseLocation = [NSEvent mouseLocation]; 1444 | NSTimeInterval timestamp = [NSDate timeIntervalSinceReferenceDate]; 1445 | 1446 | tracking_event_t *event = _tracking_event; 1447 | 1448 | /* Mouse moved in the opposite direction to the submenu */ 1449 | if (event->menu_horizontal_alignment == CMMenuAlignedLeft) { 1450 | if (mouseLocation.x < event->event_origin.x) { 1451 | XLog2("Stop submenu tracking, reason: opposite direction"); 1452 | [self stopTrackingSubmenuReasonSuccess:NO]; 1453 | return; 1454 | } 1455 | } else if (mouseLocation.x > event->event_origin.x) { 1456 | XLog2("Stop submenu tracking, reason: opposite direction"); 1457 | [self stopTrackingSubmenuReasonSuccess:NO]; 1458 | return; 1459 | } 1460 | 1461 | 1462 | 1463 | CGFloat mouseTravelDistance = sqrt(pow((event->last_mouse_location.x - mouseLocation.x), 2) + pow((event->last_mouse_location.y - mouseLocation.y), 2)); 1464 | CGFloat mouseVelocity = mouseTravelDistance / (timestamp - event->timestamp); 1465 | 1466 | 1467 | /* when mouse is moving too slowly */ 1468 | event->averageVelocity = (event->averageVelocity + mouseVelocity) / 2; 1469 | if (event->averageVelocity < 60) { 1470 | XLog2("Stop submenu tracking, reason: slow veloctiy"); 1471 | [self stopTrackingSubmenuReasonSuccess:NO]; 1472 | return; 1473 | } 1474 | 1475 | 1476 | /* mouse pointer has not left bounds of original menu */ 1477 | if ((event->menu_horizontal_alignment == CMMenuAlignedLeft && mouseLocation.x < NSMinX(event->submenu_rect)) || (event->menu_horizontal_alignment == CMMenuAlignedRight && mouseLocation.x > NSMaxX(event->submenu_rect))) { 1478 | 1479 | if (event->submenu_vertical_alignment == CMMenuAlignedTop) { 1480 | if (mouseLocation.y < NSMinY(event->item_rect)) { /* mouse went BELOW menu item */ 1481 | 1482 | CGFloat widthLeg; 1483 | BOOL mouseCloseToSubmenu; 1484 | if (event->menu_horizontal_alignment == CMMenuAlignedLeft) { 1485 | widthLeg = mouseLocation.x - NSMinX(event->item_rect); 1486 | event->averageDeltaX = (mouseLocation.x - event->last_mouse_location.x + event->averageDeltaX) / 2; 1487 | /* When mouse starts moving down closely to the submenu we can't expect much change in deltaX. 1488 | Let's give here some freedom */ 1489 | if ((mouseCloseToSubmenu = (NSMaxX(event->item_rect) - mouseLocation.x < 50))) 1490 | event->averageDeltaX += 2; 1491 | } else { 1492 | widthLeg = NSMaxX(event->item_rect) - mouseLocation.x; 1493 | event->averageDeltaX = (event->last_mouse_location.x - mouseLocation.x + event->averageDeltaX) / 2; 1494 | if ((mouseCloseToSubmenu = (NSMinX(event->item_rect) - mouseLocation.x < 50))) 1495 | event->averageDeltaX += 2; 1496 | } 1497 | 1498 | if (((NSMinY(event->item_rect) - mouseLocation.y) / widthLeg) > event->tanAlpha) { 1499 | XLog2("Stop submenu tracking, reason: bottom left hypotenuse crossed at loc.: %@", NSStringFromPoint(mouseLocation)); 1500 | [self stopTrackingSubmenuReasonSuccess:NO]; 1501 | return; 1502 | } 1503 | 1504 | /* we calculate here how fast the mouse pointer is moving towards submenu using Delta X and mouse velocity. 1505 | Multiplier at the end makes it easier for moving the mouse the closer to menu you get. */ 1506 | CGFloat multiplier = (mouseCloseToSubmenu) ? (2.5 * widthLeg / NSWidth(event->item_rect)) : 1; 1507 | CGFloat targetVector = event->averageVelocity * event->averageDeltaX * multiplier; 1508 | // NSLog(@"Val: %f, deltaX: %f, close to submenu: %d", targetVector, event->averageDeltaX, mouseCloseToSubmenu); 1509 | if (ABS(targetVector) < 1000) { 1510 | XLog2("Stop submenu tracking, reason: moving not enough fast * correct direction."); 1511 | [self stopTrackingSubmenuReasonSuccess:NO]; 1512 | return; 1513 | } 1514 | 1515 | } else { /* mouse went ABOVE menu item */ 1516 | 1517 | CGFloat widthLeg; 1518 | if (event->menu_horizontal_alignment == CMMenuAlignedLeft) { 1519 | widthLeg = mouseLocation.x - NSMinX(event->item_rect); 1520 | event->averageDeltaX = (mouseLocation.x - event->last_mouse_location.x + event->averageDeltaX) / 2; 1521 | } else { 1522 | widthLeg = NSMaxX(event->item_rect) - mouseLocation.x; 1523 | event->averageDeltaX = (event->last_mouse_location.x - mouseLocation.x + event->averageDeltaX) / 2; 1524 | } 1525 | 1526 | if ((mouseLocation.y - NSMaxY(event->item_rect)) / widthLeg > event->tanBeta) { 1527 | XLog2("Stop submenu tracking, reason: top left hypotenuse crossed at loc.: %@", NSStringFromPoint(mouseLocation)); 1528 | [self stopTrackingSubmenuReasonSuccess:NO]; 1529 | return; 1530 | } 1531 | 1532 | CGFloat targetVector = event->averageVelocity * event->averageDeltaX; 1533 | // NSLog(@"Val: %f, deltaX: %f", targetVector, averageDeltaX); 1534 | if (ABS(targetVector) < 4000) { 1535 | XLog2("Stop submenu tracking, reason: top direction moving not enough fast * correct direction."); 1536 | [self stopTrackingSubmenuReasonSuccess:NO]; 1537 | return; 1538 | } 1539 | } 1540 | 1541 | } else { // Menu aligned BOTTOM 1542 | 1543 | CGFloat widthLeg; 1544 | BOOL mouseCloseToSubmenu; 1545 | if (event->menu_horizontal_alignment == CMMenuAlignedLeft) { 1546 | widthLeg = mouseLocation.x - NSMinX(event->item_rect); 1547 | event->averageDeltaX = (mouseLocation.x - event->last_mouse_location.x + event->averageDeltaX) / 2; 1548 | /* When mouse starts moving down closely to the submenu we can't expect much change in deltaX. 1549 | Let's give here some freedom */ 1550 | if ((mouseCloseToSubmenu = (NSMaxX(event->item_rect) - mouseLocation.x < 50))) 1551 | event->averageDeltaX += 2; 1552 | } else { 1553 | widthLeg = NSMaxX(event->item_rect) - mouseLocation.x; 1554 | event->averageDeltaX = (event->last_mouse_location.x - mouseLocation.x + event->averageDeltaX) / 2; 1555 | if ((mouseCloseToSubmenu = (NSMinX(event->item_rect) - mouseLocation.x < 50))) 1556 | event->averageDeltaX += 2; 1557 | } 1558 | 1559 | BOOL mouseCrossedHypotenuse; 1560 | if (mouseLocation.y < NSMinY(event->item_rect)) { /* mouse went BELOW menu item */ 1561 | mouseCrossedHypotenuse = (((NSMinY(event->item_rect) - mouseLocation.y) / widthLeg) > event->tanAlpha); 1562 | } else { 1563 | mouseCrossedHypotenuse = (((mouseLocation.y - NSMaxY(event->item_rect)) / widthLeg) > event->tanBeta); 1564 | } 1565 | if (mouseCrossedHypotenuse) { 1566 | XLog2("Stop submenu tracking, reason: (menu aligned BOTTOM) hypotenuse crossed at loc.: %@", NSStringFromPoint(mouseLocation)); 1567 | [self stopTrackingSubmenuReasonSuccess:NO]; 1568 | return; 1569 | } 1570 | 1571 | /* we calculate here how fast the mouse pointer is moving towards submenu using Delta X and mouse velocity. 1572 | Multiplier at the end makes it easier for moving the mouse the closer to menu you get. */ 1573 | CGFloat multiplier = (mouseCloseToSubmenu) ? (2.5 * widthLeg / NSWidth(event->item_rect)) : 1; 1574 | CGFloat targetVector = event->averageVelocity * event->averageDeltaX * multiplier; 1575 | // NSLog(@"Val: %f, deltaX: %f, close to submenu: %d", targetVector, event->averageDeltaX, mouseCloseToSubmenu); 1576 | if (ABS(targetVector) < 1000) { 1577 | XLog2("Stop submenu tracking, reason: (menu aligned BOTTOM) moving not enough fast * correct direction."); 1578 | [self stopTrackingSubmenuReasonSuccess:NO]; 1579 | return; 1580 | } 1581 | 1582 | } 1583 | 1584 | } else { 1585 | /* mouse left menu bounds, and obviously hasn't entered submenu */ 1586 | 1587 | // if (event->submenu_vertical_alignment == CMMenuAlignedTop) { 1588 | if (mouseLocation.y - NSMaxY(event->submenu_rect) > 60 || NSMinY(event->submenu_rect) - mouseLocation.y > 60) { 1589 | XLog2("Stop submenu tracking, reason: mouse went vertically too far"); 1590 | [self stopTrackingSubmenuReasonSuccess:NO]; 1591 | return; 1592 | } 1593 | // } else { 1594 | 1595 | // } 1596 | 1597 | if (event->timeLeftMenuBounds == 0) 1598 | event->timeLeftMenuBounds = timestamp; 1599 | else if (timestamp - event->timeLeftMenuBounds > 0.5) { 1600 | XLog2("Stop submenu tracking, reason: time elapsed while out of menu bounds."); 1601 | [self stopTrackingSubmenuReasonSuccess:NO]; 1602 | return; 1603 | } 1604 | } 1605 | 1606 | XLog3("Submenu tracking loop stats:\n\tMouse location: %@\n\tVelocity: %f\n\tAverage velocity: %f", 1607 | NSStringFromPoint(mouseLocation), 1608 | mouseVelocity, 1609 | event->averageVelocity); 1610 | 1611 | event->timestamp = timestamp; 1612 | event->last_mouse_location = mouseLocation; 1613 | } 1614 | 1615 | 1616 | - (void)updateTrackingPrimitiveForItem:(CMMenuItem *)item { 1617 | [_underlyingWindowController updateTrackingPrimitiveForView:[item representedView] ignoreMouse:NO]; 1618 | } 1619 | 1620 | 1621 | 1622 | #pragma mark - 1623 | #pragma mark ****** Actions from Key Interpreter ****** 1624 | 1625 | 1626 | - (void)moveUp:(NSEvent *)originalEvent { 1627 | [self setReceivesMouseMovedEvents:YES]; 1628 | // [menu selectPreviousItemAndShowSubmenu:NO]; 1629 | if ([self activeSubmenu]) { 1630 | CMMenuItem *selectedItem = [self highlightedItem]; 1631 | [[self activeSubmenu] cancelTrackingWithoutAnimation]; 1632 | [selectedItem select]; 1633 | } 1634 | [self selectPreviousItemAndShowSubmenu:NO]; 1635 | } 1636 | 1637 | 1638 | - (void)moveDown:(NSEvent *)originalEvent { 1639 | [self setReceivesMouseMovedEvents:YES]; 1640 | // [menu selectNextItemAndShowSubmenu:NO]; 1641 | if ([self activeSubmenu]) { 1642 | CMMenuItem *selectedItem = [self highlightedItem]; 1643 | [[self activeSubmenu] cancelTrackingWithoutAnimation]; 1644 | [selectedItem select]; 1645 | } 1646 | [self selectNextItemAndShowSubmenu:NO]; 1647 | } 1648 | 1649 | 1650 | - (void)moveLeft:(NSEvent *)originalEvent { 1651 | if ([self supermenu]) { 1652 | CMMenuItem *parentItem = [[self supermenu] highlightedItem]; 1653 | [self cancelTrackingWithoutAnimation]; 1654 | // Cancel tracking also deseclects submenu parent item. Let us select it back 1655 | [parentItem select]; 1656 | } else if ([self activeSubmenu]) { 1657 | [self setReceivesMouseMovedEvents:YES]; 1658 | CMMenuItem *parentItem = [self highlightedItem]; 1659 | [[self activeSubmenu] cancelTrackingWithoutAnimation]; 1660 | // Cancel tracking also deseclect submenu parent item. Let us select it back 1661 | [parentItem select]; 1662 | } 1663 | } 1664 | 1665 | 1666 | - (void)moveRight:(NSEvent *)originalEvent { 1667 | CMMenuItem *selectedItem = [self highlightedItem]; 1668 | if (selectedItem) { 1669 | if ([selectedItem hasSubmenu]) { 1670 | CMMenu *submenu = [selectedItem submenu]; 1671 | [submenu showAsSubmenuOf:selectedItem withOptions:CMMenuOptionIgnoreMouse]; 1672 | CMMenuItem *firstItem = [submenu itemAtIndex:0]; 1673 | if (firstItem) { 1674 | [firstItem select]; 1675 | } 1676 | 1677 | // TODO: submenu? 1678 | [self setReceivesMouseMovedEvents:YES]; 1679 | [submenu setReceivesMouseMovedEvents:YES]; 1680 | [submenu beginTrackingWithEvent:originalEvent options:CMMenuOptionIgnoreMouse]; 1681 | } 1682 | } else { 1683 | [self selectFirstItemAndShowSubmenu:NO]; 1684 | [self setReceivesMouseMovedEvents:YES]; 1685 | } 1686 | } 1687 | 1688 | 1689 | - (void)selectPreviousItemAndShowSubmenu:(BOOL)showSubmenu { 1690 | CMMenuItem *previousItem = nil; 1691 | // CMMenuItem *selectedItem = nil; 1692 | for (CMMenuItem *item in _menuItems) { 1693 | if ([item isSelected]) { 1694 | // selectedItem = item; 1695 | break; 1696 | } 1697 | 1698 | if ( ![item isSeparatorItem] && [item isEnabled]) 1699 | previousItem = item; 1700 | } 1701 | 1702 | if (! previousItem) 1703 | return; 1704 | 1705 | // [selectedItem deselect]; 1706 | 1707 | if (showSubmenu) 1708 | [previousItem selectWithDelayForSubmenu:SUBMENU_POPUP_DELAY_DEFAULT]; 1709 | else 1710 | [previousItem select]; 1711 | // [selectedItem deselect]; 1712 | // [_underlyingWindowController moveVisibleRectToRect:[previousItem frame] ignoreMouse:YES]; 1713 | [self moveVisibleAreaToDisplayItem:previousItem ignoreMouse:YES updateTrackingPrimitives:YES]; 1714 | } 1715 | 1716 | 1717 | - (void)selectNextItemAndShowSubmenu:(BOOL)showSubmenu { 1718 | CMMenuItem *previousItem = nil; 1719 | // CMMenuItem *selectedItem = nil; 1720 | for (CMMenuItem *item in [_menuItems reverseObjectEnumerator]) { 1721 | if ([item isSelected]) { 1722 | // selectedItem = item; 1723 | break; 1724 | } 1725 | 1726 | if ( ![item isSeparatorItem] && [item isEnabled]) 1727 | previousItem = item; 1728 | } 1729 | 1730 | if (! previousItem) 1731 | return; 1732 | 1733 | if (showSubmenu) 1734 | [previousItem selectWithDelayForSubmenu:SUBMENU_POPUP_DELAY_DEFAULT]; 1735 | else 1736 | [previousItem select]; 1737 | [self moveVisibleAreaToDisplayItem:previousItem ignoreMouse:YES updateTrackingPrimitives:YES]; 1738 | } 1739 | 1740 | 1741 | - (void)selectFirstItemAndShowSubmenu:(BOOL)showSubmenu { 1742 | CMMenuItem *firstItem = [self itemAtIndex:0]; 1743 | if (! firstItem) 1744 | return; 1745 | 1746 | if (showSubmenu) 1747 | [firstItem selectWithDelayForSubmenu:SUBMENU_POPUP_DELAY_DEFAULT]; 1748 | else 1749 | [firstItem select]; 1750 | } 1751 | 1752 | 1753 | - (void)cancelOperation:(NSEvent *)originalEvent { 1754 | [[self rootMenu] cancelTracking]; 1755 | } 1756 | 1757 | 1758 | - (void)performSelected:(NSEvent *)originalEvent { 1759 | CMMenuItem *item = [self highlightedItem]; 1760 | if (item) { 1761 | if ([item hasSubmenu]) { 1762 | CMMenu *submenu = [item submenu]; 1763 | [submenu showAsSubmenuOf:item withOptions:CMMenuOptionIgnoreMouse]; 1764 | CMMenuItem *firstItem = [submenu itemAtIndex:0]; 1765 | if (firstItem) 1766 | [firstItem select]; 1767 | 1768 | [self setReceivesMouseMovedEvents:YES]; 1769 | [submenu setReceivesMouseMovedEvents:YES]; 1770 | [submenu beginTrackingWithEvent:originalEvent options:CMMenuOptionIgnoreMouse]; 1771 | } else { 1772 | [self performActionForItem:item]; 1773 | } 1774 | } else { 1775 | [[self rootMenu] cancelTracking]; 1776 | } 1777 | } 1778 | 1779 | 1780 | - (BOOL)receivesMouseMovedEvents { 1781 | return _receiveMouseMovedEvents; 1782 | } 1783 | 1784 | 1785 | - (void)setReceivesMouseMovedEvents:(BOOL)receiveEvents { 1786 | if (_receiveMouseMovedEvents == receiveEvents) 1787 | return; 1788 | 1789 | if (receiveEvents) { 1790 | BOOL mouseInMenu = (NSPointInRect([NSEvent mouseLocation], [self frame])); 1791 | if (([self supermenu] && [[self supermenu] receivesMouseMovedEvents]) || mouseInMenu) { 1792 | _receiveMouseMovedEvents = YES; 1793 | } 1794 | } else { 1795 | _receiveMouseMovedEvents = NO; 1796 | } 1797 | } 1798 | 1799 | 1800 | - (NSString *)description { 1801 | NSMutableString *description = [[NSMutableString alloc] initWithString:[super description]]; 1802 | 1803 | [description appendString:@"\n\tItems \t(\n"]; 1804 | for (CMMenuItem *item in _menuItems) { 1805 | [description appendString:@"\t"]; 1806 | [description appendString:[item description]]; 1807 | [description appendString:@"\n"]; 1808 | } 1809 | [description appendString:@")"]; 1810 | 1811 | return [description autorelease]; 1812 | } 1813 | 1814 | 1815 | @end 1816 | -------------------------------------------------------------------------------- /ChromeMenu/src/CMMenuItem+InternalMethods.h: -------------------------------------------------------------------------------- 1 | // 2 | // CMMenuItem+InternalMethods.h 3 | // ChromeMenu 4 | // 5 | // Created by Maksym on 7/12/13. 6 | // Copyright (c) 2013 Maksym Stefanchuk. All rights reserved. 7 | // 8 | 9 | #ifndef CMMenuItem_InternalMethods 10 | #define CMMenuItem_InternalMethods 11 | 12 | #import "CMMenu+InternalMethods.h" 13 | 14 | @interface CMMenuItem (CMMenuItemInternalMethods) 15 | 16 | - (NSViewController *)representedView; 17 | /* not copied, not retained */ 18 | - (void)setRepresentedView:(NSViewController *)viewController; 19 | 20 | - (void)setMenu:(CMMenu *)aMenu; 21 | 22 | - (BOOL)shouldChangeItemSelectionStatusForEvent:(CMMenuEventType)eventType; 23 | 24 | - (NSRect)frame; 25 | - (NSRect)frameRelativeToMenu; 26 | - (NSRect)frameRelativeToScreen; 27 | 28 | - (BOOL)isSelected; 29 | //- (BOOL)mouseOver; 30 | 31 | /** 32 | * @abstract Sets item selected and highlighted accordingly. Submenu, event if the item has it, is not being shown. 33 | */ 34 | - (void)select; 35 | /** 36 | * @abstract Sets item selected and highlighted accordingly and shows submenu after delay. 37 | * @param delay Delay after which submenu popups. 38 | */ 39 | - (void)selectWithDelayForSubmenu:(NSTimeInterval)delay; 40 | - (void)deselect; 41 | 42 | @end 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /ChromeMenu/src/CMMenuItem.h: -------------------------------------------------------------------------------- 1 | // 2 | // CMMenuItem.h 3 | // ChromeMenu 4 | // 5 | // Created by Maksym on 7/4/13. 6 | // Copyright (c) 2013 Maksym Stefanchuk. All rights reserved. 7 | // 8 | 9 | 10 | @class CMMenu; 11 | 12 | @interface CMMenuItem : NSObject 13 | { 14 | @private 15 | CMMenu *_menu; 16 | BOOL _enabled; 17 | NSImage *_icon; 18 | NSString *_title; 19 | CMMenu *_submenu; 20 | id _representedObject; 21 | BOOL _isSeparatorItem; 22 | NSInteger _state; 23 | 24 | NSInteger _indentationLevel; 25 | id _target; 26 | SEL _action; 27 | } 28 | 29 | + (CMMenuItem *)separatorItem; 30 | 31 | - (id)initWithTitle:(NSString *)aTitle action:(SEL)aSelector; 32 | - (id)initWithTitle:(NSString *)aTitle icon:(NSImage *)anImage action:(SEL)aSelector; 33 | 34 | /* returns menu to which item belongs */ 35 | - (CMMenu *)menu; 36 | 37 | - (BOOL)hasSubmenu; 38 | - (void)setSubmenu:(CMMenu *)submenu; 39 | - (CMMenu *)submenu; 40 | 41 | - (void)setTitle:(NSString *)aString; 42 | - (NSString *)title; 43 | - (void)setIcon:(NSImage *)anImage; 44 | - (NSImage *)icon; 45 | 46 | // An integer constant representing a state; it should be one of NSOffState, NSOnState, or NSMixedState. 47 | - (void)setState:(NSInteger)state; 48 | - (NSInteger)state; 49 | - (void)setOnStateImage:(NSImage *)image; // checkmark by default 50 | - (NSImage *)onStateImage; 51 | - (void)setOffStateImage:(NSImage *)image; // none by default 52 | - (NSImage *)offStateImage; 53 | - (void)setMixedStateImage:(NSImage *)image; // horizontal line by default? 54 | - (NSImage *)mixedStateImage; 55 | 56 | - (void)setEnabled:(BOOL)flag; 57 | - (BOOL)isEnabled; 58 | 59 | - (BOOL)isSeparatorItem; 60 | 61 | /*! 62 | @description Sets the menu item indentation level for the receiver 63 | @param indentationLevel The value for \p indentationLevel may be from 0 to 15. 64 | If \p indentationLevel is greater than 15, the value is pinned to the maximum. 65 | If \p indentationLevel is less than 0, an exception is raised. The default indentation level is 0. 66 | */ 67 | - (void)setIndentationLevel:(NSInteger)indentationLevel; 68 | - (NSInteger)indentationLevel; 69 | 70 | - (void)setTarget:(id)anObject; 71 | - (id)target; 72 | - (void)setAction:(SEL)aSelector; 73 | - (SEL)action; 74 | 75 | - (void)setRepresentedObject:(id)anObject; 76 | - (id)representedObject; 77 | 78 | /* Indicates whether the menu item should be drawn highlighted or not. */ 79 | - (BOOL)isHighlighted; 80 | 81 | 82 | @end 83 | -------------------------------------------------------------------------------- /ChromeMenu/src/CMMenuItem.m: -------------------------------------------------------------------------------- 1 | // 2 | // CMMenuItem.m 3 | // ChromeMenu 4 | // 5 | // Created by Maksym on 7/4/13. 6 | // Copyright (c) 2013 Maksym Stefanchuk. All rights reserved. 7 | // 8 | 9 | 10 | #import "CMMenuItem.h" 11 | #import "CMMenu.h" 12 | #import "CMMenuItemView.h" 13 | #import "CMMenuItemView+InternalMethods.h" 14 | #import "CMMenu+InternalMethods.h" 15 | #import "CMMenuItem+InternalMethods.h" 16 | #import "CMDebug.h" 17 | #import 18 | 19 | 20 | /* 21 | * Private declarations 22 | */ 23 | @interface CMMenuItem() 24 | { 25 | BOOL _isSelected; 26 | // Mouse can be over item if it is not selected (e.g. during submenu tracking) 27 | // BOOL _mouseOver; 28 | BOOL _submenuIntervalIsSetToPopup; 29 | NSViewController *_representedViewController; 30 | 31 | NSImage *_offStateImage; 32 | NSImage *_onStateImage; 33 | NSImage *_mixedStateImage; 34 | } 35 | 36 | - (void)showItemSubmenu; 37 | 38 | @end 39 | 40 | 41 | 42 | @implementation CMMenuItem 43 | 44 | 45 | - (id)initWithTitle:(NSString *)aTitle action:(SEL)aSelector { 46 | if (self = [super init]) { 47 | [self setTitle:aTitle]; 48 | _isSeparatorItem = NO; 49 | _enabled = YES; 50 | _action = aSelector; 51 | } 52 | return self; 53 | } 54 | 55 | - (id)initWithTitle:(NSString *)aTitle icon:(NSImage *)anImage action:(SEL)aSelector { 56 | self = [self initWithTitle:aTitle action:aSelector]; 57 | if (self) { 58 | [self setIcon:anImage]; 59 | } 60 | return self; 61 | } 62 | 63 | 64 | - (void)dealloc { 65 | [_title release]; 66 | [_icon release]; 67 | [_offStateImage release]; 68 | [_onStateImage release]; 69 | [_mixedStateImage release]; 70 | [_representedObject release]; 71 | [_submenu release]; 72 | 73 | [super dealloc]; 74 | } 75 | 76 | 77 | + (CMMenuItem *)separatorItem { 78 | CMMenuItem *instance = [[self alloc] init]; 79 | if (instance) { 80 | instance->_isSeparatorItem = YES; 81 | } 82 | return [instance autorelease]; 83 | } 84 | 85 | 86 | - (CMMenu *)menu { 87 | return _menu; 88 | } 89 | 90 | 91 | - (void)setTitle:(NSString *)aTitle { 92 | // if (! aTitle) { 93 | // [NSException raise:NSInvalidArgumentException format:@"No title provided for a menu item -setTitle:"]; 94 | // return; 95 | // } 96 | NSAssert(aTitle != nil, @"Invalid parameter not satisfying: aTitle != nil"); 97 | 98 | if (_title == aTitle) 99 | return; 100 | 101 | if (_title) 102 | [_title release]; 103 | _title = [aTitle copy]; 104 | if (_representedViewController) 105 | [[(CMMenuItemView *)[_representedViewController view] title] setStringValue:aTitle]; 106 | 107 | // if ([_menu needsDisplay]) // menu will update item's title itself, no more actions are required. 108 | // return; 109 | 110 | // Update menu's size and position if needed 111 | [_menu setNeedsDisplay:YES]; 112 | } 113 | 114 | 115 | - (NSString *)title { 116 | return _title; 117 | } 118 | 119 | 120 | - (void)setIcon:(NSImage *)anImage { 121 | if (_icon != anImage) { 122 | if (_icon) 123 | [_icon release]; 124 | 125 | _icon = [anImage copy]; 126 | } 127 | } 128 | 129 | 130 | - (NSImage *)icon { 131 | return _icon; 132 | } 133 | 134 | 135 | - (void)setState:(NSInteger)state { 136 | if (_state != state) { 137 | _state = state; 138 | // If item has never been shown yet it doesn't have |_representedViewController| 139 | // In that case just skip the part -- icon will be set when item is drawn first time. 140 | if (_representedViewController) { 141 | CMMenuItemView *view = (CMMenuItemView *)[_representedViewController view]; 142 | if (state == NSOffState) { 143 | [[view state] setImage:nil]; 144 | } else if (state == NSOnState) { 145 | [[view state] setImage:[self onStateImage]]; 146 | } else { 147 | [[view state] setImage:[self mixedStateImage]]; 148 | } 149 | [view setNeedsDisplay:YES]; 150 | } 151 | } 152 | } 153 | 154 | 155 | - (NSInteger)state { 156 | return _state; 157 | } 158 | 159 | 160 | - (NSImage *)offStateImage { 161 | return (_offStateImage) ? _offStateImage : nil; 162 | } 163 | 164 | 165 | - (void)setOffStateImage:(NSImage *)image { 166 | if (_offStateImage == image) 167 | return; 168 | // 169 | // if (_offStateImage) 170 | // [_offStateImage release]; 171 | // if (image) 172 | // _offStateImage = [image retain]; 173 | // else 174 | // _offStateImage = nil; 175 | [_offStateImage release]; 176 | _offStateImage = [image retain]; 177 | if (_representedViewController) { 178 | CMMenuItemView *view = (CMMenuItemView *)[_representedViewController view]; 179 | [[view state] setImage:[self offStateImage]]; 180 | [view setNeedsDisplay:YES]; 181 | } 182 | 183 | } 184 | 185 | 186 | - (NSImage *)onStateImage { 187 | return (_onStateImage) ? _onStateImage : [NSImage imageNamed:NSImageNameMenuOnStateTemplate]; 188 | } 189 | 190 | 191 | - (void)setOnStateImage:(NSImage *)image { 192 | if (_onStateImage == image) 193 | return; 194 | // 195 | // if (_onStateImage) 196 | // [_onStateImage release]; 197 | // if (image) 198 | // _onStateImage = [image retain]; 199 | // else 200 | // _onStateImage = nil; 201 | 202 | [_onStateImage release]; 203 | _onStateImage = [image retain]; 204 | if (_representedViewController) { 205 | CMMenuItemView *view = (CMMenuItemView *)[_representedViewController view]; 206 | [[view state] setImage:[self onStateImage]]; 207 | [view setNeedsDisplay:YES]; 208 | } 209 | } 210 | 211 | 212 | - (NSImage *)mixedStateImage { 213 | return (_mixedStateImage) ? _mixedStateImage : [NSImage imageNamed:NSImageNameMenuMixedStateTemplate]; 214 | } 215 | 216 | 217 | - (void)setMixedStateImage:(NSImage *)image { 218 | if (_mixedStateImage == image) 219 | return; 220 | // 221 | // if (_mixedStateImage) 222 | // [_mixedStateImage release]; 223 | // if (image) 224 | // _mixedStateImage = [image retain]; 225 | // else 226 | // _mixedStateImage = nil; 227 | [_mixedStateImage release]; 228 | _mixedStateImage = [image retain]; 229 | if (_representedViewController) { 230 | CMMenuItemView *view = (CMMenuItemView *)[_representedViewController view]; 231 | [[view state] setImage:[self mixedStateImage]]; 232 | [view setNeedsDisplay:YES]; 233 | } 234 | 235 | } 236 | 237 | 238 | - (BOOL)isEnabled { 239 | return _enabled; 240 | } 241 | 242 | 243 | - (void)setEnabled:(BOOL)flag { 244 | if (_isSeparatorItem) 245 | return; 246 | 247 | if (_enabled != flag) { 248 | _enabled = flag; 249 | CMMenuItemView *view = (CMMenuItemView *)[_representedViewController view]; 250 | [view setEnabled:flag]; 251 | [view setSelected:NO]; 252 | if (flag == NO && [_submenu isActive]) { 253 | [_submenu cancelTrackingWithoutAnimation]; 254 | } 255 | [[self menu] updateTrackingPrimitiveForItem:self]; 256 | } 257 | } 258 | 259 | 260 | - (void)setSubmenu:(CMMenu *)submenu { 261 | // if (submenu == nil) 262 | // [NSException raise:NSInvalidArgumentException format:@"Bad argument provided in -%@", NSStringFromSelector(_cmd)]; 263 | 264 | if (_isSeparatorItem) { 265 | [NSException raise:NSInternalInconsistencyException format:@"Menu separator item cannot have submenus."]; 266 | return; 267 | } 268 | 269 | if (_submenu != submenu) { 270 | [_submenu release]; 271 | if (submenu) { 272 | _submenu = [submenu retain]; 273 | [_submenu setSupermenu:[self menu]]; 274 | [_submenu setParentItem:self]; 275 | } else 276 | _submenu = nil; 277 | } 278 | } 279 | 280 | 281 | - (CMMenu *)submenu { 282 | return _submenu; 283 | } 284 | 285 | 286 | - (BOOL)hasSubmenu { 287 | return (_submenu) ? YES : NO; 288 | } 289 | 290 | 291 | - (BOOL)isSeparatorItem { 292 | return _isSeparatorItem; 293 | } 294 | 295 | 296 | - (void)setIndentationLevel:(NSInteger)indentationLevel { 297 | if (indentationLevel < 0) { 298 | [NSException raise:NSInvalidArgumentException format:@"CMMenuItem indentationLevel cannot be less then 0"]; 299 | return; 300 | } else if (indentationLevel > 15) 301 | indentationLevel = 15; 302 | 303 | _indentationLevel = indentationLevel; 304 | if (_representedViewController) { 305 | CMMenuItemView *view = (CMMenuItemView *)[_representedViewController view]; 306 | [view setIndentationLevel:indentationLevel]; 307 | // Indentation could go higher as well as lower. In any case 308 | // the menu could be redrawn wider and narrower 309 | [_menu setNeedsDisplay:YES]; 310 | } 311 | } 312 | 313 | 314 | - (NSInteger)indentationLevel { 315 | return _indentationLevel; 316 | } 317 | 318 | 319 | - (void)setTarget:(id)anObject { 320 | _target = anObject; // not retained? 321 | } 322 | 323 | 324 | - (id)target { 325 | return _target; 326 | } 327 | 328 | 329 | - (void)setAction:(SEL)aSelector { 330 | _action = aSelector; 331 | } 332 | 333 | 334 | - (SEL)action { 335 | return _action; 336 | } 337 | 338 | 339 | - (id)representedObject { 340 | return _representedObject; 341 | } 342 | 343 | 344 | - (void)setRepresentedObject:(id)anObject { 345 | // if (_representedObject == anObject) 346 | // return; 347 | // 348 | // if (_representedObject) { 349 | // [_representedObject release]; 350 | // _representedObject = nil; 351 | // } 352 | // if (anObject) 353 | // _representedObject = [anObject retain]; 354 | [_representedObject autorelease]; 355 | _representedObject = [anObject retain]; 356 | } 357 | 358 | 359 | - (BOOL)isHighlighted { 360 | return _isSelected; 361 | } 362 | 363 | 364 | #pragma mark - 365 | #pragma mark ***** Events and Tracking methods ***** 366 | 367 | 368 | - (BOOL)shouldChangeItemSelectionStatusForEvent:(CMMenuEventType)eventType { 369 | BOOL changeStatus = YES; 370 | 371 | if (eventType & CMMenuEventMouseEnteredItem) { 372 | // _mouseOver = YES; 373 | 374 | if (_isSelected) { 375 | changeStatus = NO; 376 | 377 | if ([self hasSubmenu]) { 378 | if ([[self menu] isTrackingSubmenu]) { // while tracking submenu, mouse returned to parent item 379 | [[self menu] stopTrackingSubmenuReasonSuccess:YES]; 380 | } else { 381 | CMMenu *activeSubmenu = [[self menu] activeSubmenu]; 382 | // TODO: Take care of this part: either provide comment what it does or edit it! 383 | if ( !activeSubmenu && activeSubmenu != _submenu) { 384 | [self performSelector:@selector(showItemSubmenu) 385 | withObject:nil 386 | afterDelay:SUBMENU_POPUP_DELAY_DEFAULT 387 | inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]]; 388 | _submenuIntervalIsSetToPopup = YES; 389 | } 390 | } 391 | } 392 | 393 | } else { 394 | if ([[self menu] activeSubmenu] && [[self menu] isTrackingSubmenu]) { 395 | changeStatus = NO; 396 | } else { 397 | _isSelected = YES; 398 | 399 | if ([self hasSubmenu]) { // && [_submenu numberOfItems] 400 | [self performSelector:@selector(showItemSubmenu) 401 | withObject:nil 402 | afterDelay:SUBMENU_POPUP_DELAY_DEFAULT 403 | inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]]; 404 | _submenuIntervalIsSetToPopup = YES; 405 | } 406 | } 407 | } 408 | } else if (eventType & CMMenuEventMouseExitedItem) { 409 | // _mouseOver = NO; 410 | 411 | CMMenu *activeSubmenu = [[self menu] activeSubmenu]; 412 | if (activeSubmenu && activeSubmenu == _submenu) { 413 | if (! [[self menu] isTrackingSubmenu]) { 414 | [[self menu] startTrackingSubmenu:_submenu forItem:self]; 415 | } 416 | changeStatus = NO; 417 | } else if ([self hasSubmenu]) { 418 | // if (_submenuIntervalIsSetToPopup) { // not yet showed a menu 419 | [NSObject cancelPreviousPerformRequestsWithTarget:self]; 420 | _isSelected = NO; 421 | // } else { 422 | // // need to see how exactly we're exiting a menu item 423 | // [_submenu cancelTrackingWithoutAnimation]; 424 | // changeStatus = YES; 425 | // _isSelected = NO; 426 | // } 427 | } else { 428 | _isSelected = NO; 429 | } 430 | } 431 | 432 | /* This block usually handles cases with keyboard item selection: 433 | mouse movements and keyboard selection could go in opposite directions, as a result two items 434 | get selected. */ 435 | if (_isSelected && changeStatus) { 436 | NSArray *items = [_menu itemArray]; 437 | for (CMMenuItem *item in items) { 438 | if ([item isSelected] && item != self) { 439 | [item deselect]; 440 | } 441 | } 442 | } 443 | 444 | return changeStatus; 445 | } 446 | 447 | 448 | /* 449 | * 450 | */ 451 | - (void)showItemSubmenu { 452 | // NSLog(@"show item submenu: %@", _title); 453 | _submenuIntervalIsSetToPopup = NO; 454 | [_submenu showAsSubmenuOf:self withOptions:CMMenuOptionDefaults]; 455 | } 456 | 457 | 458 | 459 | #pragma mark - 460 | #pragma mark ***** CMMenuItem Internal Methods ***** 461 | 462 | - (NSViewController *)representedView { 463 | return _representedViewController; 464 | } 465 | 466 | - (void)setRepresentedView:(NSViewController *)viewController { 467 | _representedViewController = viewController; 468 | } 469 | 470 | 471 | - (void)setMenu:(CMMenu *)aMenu { 472 | // if (_menu != aMenu) 473 | // _menu = aMenu; 474 | // If item is already assigned to some menu and aMenu is not nil 475 | if (_menu && aMenu) 476 | [NSException raise:NSInvalidArgumentException format:@"Item to be added to menu already is in another menu"]; 477 | 478 | _menu = aMenu; 479 | } 480 | 481 | 482 | - (NSRect)frame { 483 | return [[_representedViewController view] frame]; 484 | } 485 | 486 | 487 | - (NSRect)frameRelativeToMenu { 488 | NSRect frame = [[_representedViewController view] convertRect:[[_representedViewController view] bounds] toView:nil]; 489 | return frame; 490 | } 491 | 492 | 493 | - (NSRect)frameRelativeToScreen { 494 | NSRect frame = [self frameRelativeToMenu]; 495 | return [_menu convertRectToScreen:frame]; 496 | } 497 | 498 | - (BOOL)isSelected { 499 | return _isSelected; 500 | } 501 | 502 | //- (BOOL)mouseOver { 503 | // return _mouseOver; 504 | //} 505 | 506 | 507 | - (void)select { 508 | if (_isSelected || _isSeparatorItem || !_enabled) 509 | return; 510 | 511 | NSArray *items = [_menu itemArray]; 512 | for (CMMenuItem *item in items) { 513 | if (self != item && [item isSelected]) { 514 | [item deselect]; 515 | } 516 | } 517 | 518 | _isSelected = YES; 519 | [(CMMenuItemView *)[_representedViewController view] setSelected:YES]; 520 | } 521 | 522 | 523 | - (void)selectWithDelayForSubmenu:(NSTimeInterval)delay { 524 | if (_isSeparatorItem || !_enabled) 525 | return; 526 | 527 | [self select]; 528 | 529 | if ([self hasSubmenu]) { // && [_submenu numberOfItems] 530 | // [self performSelector:@selector(showItemSubmenu) withObject:nil afterDelay:delay inModes:[NSArray arrayWithObject:NSEventTrackingRunLoopMode]]; 531 | [self performSelector:@selector(showItemSubmenu) withObject:nil afterDelay:delay inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]]; 532 | _submenuIntervalIsSetToPopup = YES; 533 | } 534 | } 535 | 536 | 537 | - (void)deselect { 538 | if (! _isSelected) 539 | return; 540 | 541 | _isSelected = NO; 542 | [(CMMenuItemView *)[_representedViewController view] setSelected:NO]; 543 | } 544 | 545 | 546 | - (NSString *)description { 547 | NSMutableString *description = [[NSMutableString alloc] initWithString:[super description]]; 548 | 549 | /* 550 | id currentClass = [self class]; 551 | NSString *propertyName; 552 | unsigned int outCount, i; 553 | objc_property_t *properties = class_copyPropertyList(currentClass, &outCount); 554 | for (i = 0; i < outCount; ++i) { 555 | objc_property_t property = properties[i]; 556 | propertyName = [NSString stringWithCString:property_getName(property) encoding:NSASCIIStringEncoding]; 557 | [description appendFormat:@"\n\t%@: %@", propertyName, [self valueForKey:propertyName]]; 558 | } 559 | free(properties); 560 | 561 | // if object was subclassed, let's print parent's properties 562 | if (![[self className] isEqualToString:@"CMMenuItem"]) { 563 | [description appendFormat:@"\n\ttitle: %@", _title]; 564 | [description appendFormat:@"\n\ticon: %@", _icon]; 565 | } 566 | */ 567 | if (_isSeparatorItem) 568 | [description appendFormat:@" -----"]; 569 | else 570 | [description appendFormat:@" %@", _title]; 571 | 572 | return [description autorelease]; 573 | } 574 | 575 | 576 | @end 577 | -------------------------------------------------------------------------------- /ChromeMenu/src/CMMenuItemSeparatorView.h: -------------------------------------------------------------------------------- 1 | // 2 | // CMMenuItemSeparatorView.h 3 | // ChromeMenu 4 | // 5 | // Created by Maksym on 7/22/13. 6 | // Copyright (c) 2013 Maksym Stefanchuk. All rights reserved. 7 | // 8 | 9 | 10 | #import "CMMenuItemView.h" 11 | 12 | @interface CMMenuItemSeparatorView : CMMenuItemView 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /ChromeMenu/src/CMMenuItemSeparatorView.m: -------------------------------------------------------------------------------- 1 | // 2 | // CMMenuItemSeparatorView.m 3 | // ChromeMenu 4 | // 5 | // Created by Maksym on 7/22/13. 6 | // Copyright (c) 2013 Maksym Stefanchuk. All rights reserved. 7 | // 8 | 9 | #import "CMMenuItemSeparatorView.h" 10 | 11 | @implementation CMMenuItemSeparatorView 12 | 13 | 14 | - (void)drawRect:(NSRect)dirtyRect { 15 | // NSLog(@"separator frame: %@", NSStringFromRect([self frame])); 16 | 17 | NSBezierPath *line = [NSBezierPath bezierPath]; 18 | // we wan't pixel precies drawing, thus the shift by 0.5 19 | [line moveToPoint:NSMakePoint(1, 5.5)]; 20 | [line lineToPoint:NSMakePoint([self bounds].size.width - 1, 5.5)]; 21 | [[NSColor colorWithSRGBRed:0.85 green:0.85 blue:0.85 alpha:0.9] set]; 22 | [line stroke]; 23 | } 24 | 25 | 26 | - (BOOL)needsTracking { 27 | return NO; 28 | } 29 | 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /ChromeMenu/src/CMMenuItemView+InternalMethods.h: -------------------------------------------------------------------------------- 1 | // 2 | // CMMenuItemView+InternalMethods.h 3 | // ChromeMenu 4 | // 5 | // Created by Maksym on 7/20/13. 6 | // Copyright (c) 2013 Maksym Stefanchuk. All rights reserved. 7 | // 8 | 9 | 10 | @interface CMMenuItemView (CMMenuItemViewInternalMethods) 11 | 12 | - (void)setHasSubmenuIcon:(BOOL)hasIcon; 13 | 14 | - (BOOL)isSelected; 15 | - (void)setSelected:(BOOL)selected; 16 | 17 | - (BOOL)isEnabled; 18 | - (void)setEnabled:(BOOL)enabled; 19 | 20 | - (void)fadeIn; 21 | - (void)fadeOutWithComplitionHandler:(void (^)(void))handler; 22 | - (void)blink; 23 | 24 | - (void)setIndentationLevel:(NSInteger)indentationLevel; 25 | - (NSInteger)indentationLevel; 26 | 27 | /** 28 | * @function needsTracking 29 | * @abstract Tells whether a view will have a tracking area created. 30 | * @discussion This method is meant to be overridden by the Separator View. 31 | */ 32 | - (BOOL)needsTracking; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /ChromeMenu/src/CMMenuItemView.h: -------------------------------------------------------------------------------- 1 | // 2 | // CMTableCellView.h 3 | // ChromeMenu 4 | // 5 | // Created by Maksym on 7/3/13. 6 | // Copyright (c) 2013 Maksym Stefanchuk. All rights reserved. 7 | // 8 | 9 | 10 | @interface CMMenuItemView : NSView 11 | { 12 | @private 13 | NSImageView *_state; 14 | NSImageView *_icon; 15 | NSTextField *_title; 16 | } 17 | 18 | @property (assign) IBOutlet NSImageView *state; 19 | @property (assign) IBOutlet NSImageView *icon; 20 | @property (assign) IBOutlet NSTextField *title; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /ChromeMenu/src/CMMenuItemView.m: -------------------------------------------------------------------------------- 1 | // 2 | // CMTableCellView.m 3 | // ChromeMenu 4 | // 5 | // Created by Maksym on 7/3/13. 6 | // Copyright (c) 2013 Maksym Stefanchuk. All rights reserved. 7 | // 8 | 9 | //#import 10 | #import "CMMenuItemView.h" 11 | #import "CMMenuItemView+InternalMethods.h" 12 | //#import "NSImage+CMMenuImageRepAdditions.h" 13 | #import 14 | #import 15 | 16 | #define INDENTATION_SIZE 11.0 17 | 18 | @interface CMMenuItemView () 19 | { 20 | // NSView *_submenuIconView; 21 | NSImageView *_submenuIconView; 22 | NSMutableArray *_submenuIconConstraints; 23 | BOOL _isAnimating; 24 | BOOL _selected; 25 | BOOL _enabled; 26 | NSInteger _indentationLevel; 27 | // NSImage *_goRightImage; 28 | } 29 | 30 | @end 31 | 32 | 33 | @implementation CMMenuItemView 34 | 35 | @synthesize state = _state; 36 | @synthesize icon = _icon; 37 | @synthesize title = _title; 38 | 39 | 40 | - (void)dealloc { 41 | // NSLog(@"release item view"); 42 | [_submenuIconView release]; 43 | [_submenuIconConstraints release]; 44 | [super dealloc]; 45 | } 46 | 47 | 48 | - (void)drawRect:(NSRect)dirtyRect { 49 | // NSLog(@"Cell View draw rect called. Cell subviews: %@", [self subviews]); 50 | // NSLog(@"DRAW ItemView with rect: %@, title: %@", NSStringFromRect([self frame]), [[self title] stringValue]); 51 | // NSLog(@"frame: %@", NSStringFromRect([self frame])); 52 | 53 | 54 | // NSBezierPath *path = [NSBezierPath bezierPath]; 55 | // [path appendBezierPathWithRect:[self bounds]]; 56 | // [[NSColor blueColor] set]; 57 | // [path stroke]; 58 | 59 | if (! _enabled) { 60 | [_title setTextColor:[NSColor colorWithDeviceRed:0.0 green:0.0 blue:0.0 alpha:0.4]]; 61 | } else if (_selected) { 62 | NSGraphicsContext *currentContext = [NSGraphicsContext currentContext]; 63 | // [currentContext saveGraphicsState]; 64 | 65 | // Note that saving and restoreing GraphicsState is expensive thus we change one 66 | // parameter and then restore it back manually. More info on Apple Docs: 67 | // https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CocoaDrawingGuide/GraphicsContexts/GraphicsContexts.html#//apple_ref/doc/uid/TP40003290-CH203-SW9 68 | // Get the patter phase before modification 69 | NSPoint patternPhase = [currentContext patternPhase]; 70 | 71 | CGFloat xOffset = NSMinX([self convertRect:self.bounds toView:nil]); 72 | CGFloat yOffset = NSMaxY([self convertRect:self.bounds toView:nil]); 73 | [currentContext setPatternPhase:NSMakePoint(xOffset, yOffset)]; 74 | 75 | [[NSColor selectedMenuItemColor] setFill]; 76 | NSRectFill([self bounds]); 77 | // [currentContext restoreGraphicsState]; 78 | // Restore original pattern phase 79 | [currentContext setPatternPhase:patternPhase]; 80 | 81 | [_title setTextColor:[NSColor selectedMenuItemTextColor]]; 82 | } else { 83 | [_title setTextColor:[NSColor textColor]]; 84 | } 85 | 86 | 87 | // Let NSCell to choose which image to draw. For example if backgraund is dark, cell 88 | // may choose to draw the light image color. 89 | if ([_state image]) { 90 | NSCell *cell = [_state cell]; 91 | if (! _enabled) { 92 | [cell setBackgroundStyle:NSBackgroundStyleLight]; 93 | if (! _isAnimating) 94 | [_state setAlphaValue:0.6]; 95 | } else if (_selected) { 96 | [cell setBackgroundStyle:NSBackgroundStyleDark]; 97 | if (! _isAnimating) 98 | [_state setAlphaValue:1.0]; 99 | } else { 100 | [cell setBackgroundStyle:NSBackgroundStyleLight]; 101 | if (! _isAnimating) 102 | [_state setAlphaValue:1.0]; 103 | } 104 | } 105 | 106 | if (_icon && [_icon image]) { 107 | NSCell *cell = [_icon cell]; 108 | if (! _enabled) { 109 | [cell setBackgroundStyle:NSBackgroundStyleLight]; 110 | if (! _isAnimating) 111 | [_icon setAlphaValue:0.6]; 112 | } else if (_selected) { 113 | [cell setBackgroundStyle:NSBackgroundStyleDark]; 114 | if (! _isAnimating) 115 | [_icon setAlphaValue:1.0]; 116 | } else { 117 | [cell setBackgroundStyle:NSBackgroundStyleLight]; 118 | if (! _isAnimating) 119 | [_icon setAlphaValue:1.0]; 120 | } 121 | } 122 | 123 | if (_submenuIconView) { 124 | // NSImage *goRightImage = [NSImage imageNamed:NSImageNameGoRightTemplate]; 125 | // NSImageRep *rep; 126 | // if (_selected) { 127 | // rep = [goRightImage invertedImageRepresentation]; 128 | // [rep drawInRect:[_submenuIconView frame] fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0 respectFlipped:YES hints:nil]; 129 | // } else { 130 | // rep = [goRightImage defaultImageRepresentation]; 131 | // [rep drawInRect:[_submenuIconView frame] fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:0.7 respectFlipped:YES hints:nil]; 132 | // } 133 | 134 | NSCell *cell = [_submenuIconView cell]; 135 | if (! _enabled) { 136 | [cell setBackgroundStyle:NSBackgroundStyleLight]; 137 | if (! _isAnimating) 138 | [_submenuIconView setAlphaValue:0.5]; 139 | } else if (_selected) { 140 | [cell setBackgroundStyle:NSBackgroundStyleDark]; 141 | if (! _isAnimating) 142 | [_submenuIconView setAlphaValue:1.0]; 143 | } else { 144 | [cell setBackgroundStyle:NSBackgroundStyleLight]; 145 | if (! _isAnimating) 146 | [_submenuIconView setAlphaValue:1.0]; 147 | } 148 | } 149 | 150 | 151 | 152 | } 153 | 154 | 155 | 156 | - (void)updateConstraints { 157 | // This method is called twice by AppKit: first time when the view is 158 | // not added to superview (hence the check below) and second -- when 159 | // the view did move to superview and all constraints are properly set. 160 | // Same logic at -viewDidMoveToSuperview doesn't work: the first view 161 | // being called doesn't have horizontal constraints installed (??), 162 | // only vertical. Only starting from the second view everything seemed 163 | // alright. The logic is in this method as a result. 164 | 165 | // If indentation level was set, call the -setIndentationLeve: method 166 | // once again to actually update constraints once the view is in the superview. 167 | if (_indentationLevel && [self superview]) 168 | [self setIndentationLevel:_indentationLevel]; 169 | 170 | 171 | [super updateConstraints]; 172 | } 173 | 174 | 175 | 176 | - (BOOL)needsTracking { 177 | return (_enabled) ? YES : NO; 178 | } 179 | 180 | 181 | - (BOOL)isSelected { 182 | return _selected; 183 | } 184 | 185 | 186 | - (void)setSelected:(BOOL)selected { 187 | if (_selected != selected) { 188 | _selected = selected; 189 | [self setNeedsDisplay:YES]; 190 | } 191 | } 192 | 193 | 194 | - (BOOL)isEnabled { 195 | return _enabled; 196 | } 197 | 198 | 199 | - (void)setEnabled:(BOOL)enabled { 200 | if (_enabled != enabled) { 201 | _enabled = enabled; 202 | [self setNeedsDisplay:YES]; 203 | } 204 | } 205 | 206 | 207 | - (void)setIconProperty:(NSImage *)aImage { 208 | [_icon setImage:aImage]; 209 | } 210 | 211 | - (void)setTitleProperty:(NSString *)aTitle { 212 | [_title setStringValue:aTitle]; 213 | } 214 | 215 | 216 | - (void)setHasSubmenuIcon:(BOOL)hasIcon { 217 | if (hasIcon == NO) { 218 | if (_submenuIconView) { 219 | [self removeConstraints:_submenuIconConstraints]; 220 | [_submenuIconView removeFromSuperview]; 221 | [_submenuIconView release]; 222 | [_submenuIconConstraints release]; 223 | } 224 | return; 225 | } 226 | 227 | 228 | NSView *lastView = [[self subviews] lastObject]; 229 | NSMutableArray *constraints = [NSMutableArray array]; 230 | 231 | NSImage *goRightImage = [NSImage imageNamed:@"ImageNameMenuGoRightTemplate"]; 232 | if (! goRightImage) { 233 | // NSLog(@"createing goRight image"); 234 | goRightImage = [[[NSImage imageNamed:NSImageNameGoRightTemplate] copy] autorelease]; 235 | [goRightImage setSize:NSMakeSize(9, 10)]; 236 | [goRightImage setName:@"ImageNameMenuGoRightTemplate"]; 237 | } 238 | 239 | // if (! _goRightImage) { 240 | // _goRightImage = [[NSImage imageNamed:NSImageNameGoRightTemplate] copy]; 241 | // [_goRightImage setSize:NSMakeSize(9, 10)]; 242 | //// [goRightImage setName:@"ImageNameMenuGoRightTemplate"]; 243 | // } 244 | // NSImage *goRightImage = _goRightImage; 245 | 246 | // NSImage *goRightImage = [NSImage imageNamed:NSImageNameGoRightTemplate]; 247 | // Caution! Changing image size changes its global state. If it's drawn somewhere else 248 | // it will draw with this size if not set explicitly again. 249 | // [goRightImage setSize:NSMakeSize(9, 10)]; 250 | // NSLog(@"goRightImage size: %@", NSStringFromSize([goRightImage size])); 251 | 252 | _submenuIconView = [[NSImageView alloc] initWithFrame:NSMakeRect(0, 0, goRightImage.size.width, goRightImage.size.height)]; 253 | // _submenuIconView = [[NSView alloc] init]; 254 | [_submenuIconView setImage:goRightImage]; 255 | 256 | [_submenuIconView setTranslatesAutoresizingMaskIntoConstraints:NO]; 257 | [self addSubview:_submenuIconView]; 258 | [constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[lastView]-(>=27)-[_submenuIconView(9)]-(9)-|" 259 | options:0 260 | metrics:nil 261 | views:NSDictionaryOfVariableBindings(lastView, _submenuIconView)]]; 262 | 263 | // [constraints addObject:[NSLayoutConstraint constraintWithItem:lastView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:_submenuIconView attribute:NSLayoutAttributeLeading multiplier:1.0 constant:-27]]; 264 | // 265 | // [constraints addObject:[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:_submenuIconView attribute:NSLayoutAttributeTrailing multiplier:1.0 constant:9]]; 266 | 267 | // [constraints addObject:[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:_submenuIconView attribute:NSLayoutAttributeWidth multiplier:1.0 constant:9.0]]; 268 | 269 | [constraints addObject:[NSLayoutConstraint constraintWithItem:_submenuIconView 270 | attribute:NSLayoutAttributeHeight 271 | relatedBy:NSLayoutRelationEqual 272 | toItem:_submenuIconView 273 | attribute:NSLayoutAttributeHeight 274 | multiplier:0.0 275 | constant:10.0]]; 276 | 277 | [constraints addObject:[NSLayoutConstraint constraintWithItem:self 278 | attribute:NSLayoutAttributeCenterY 279 | relatedBy:NSLayoutRelationEqual 280 | toItem:_submenuIconView 281 | attribute:NSLayoutAttributeCenterY 282 | multiplier:1.0 283 | constant:0.0]]; 284 | 285 | // [constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[_submenuIconView]-|" options:NSLayoutFormatAlignAllCenterY metrics:nil views:NSDictionaryOfVariableBindings(_submenuIconView)]]; 286 | 287 | [self addConstraints:constraints]; 288 | _submenuIconConstraints = [constraints retain]; 289 | 290 | // [goRightImage setSize:NSMakeSize(9, 9)]; 291 | 292 | // NSWindow *window = [[[self superview] superview] window]; 293 | // NSLog(@"window: %@", window); 294 | // [window visualizeConstraints:constraints]; 295 | 296 | // NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(visualizeConstraints:) userInfo:constraints repeats:NO]; 297 | // [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; 298 | } 299 | 300 | 301 | //- (void)visualizeConstraints:(NSTimer *)timer { 302 | // NSArray *userInfo = [timer userInfo]; 303 | // NSLog(@"timer, contstraints: %@", userInfo); 304 | // 305 | //// NSWindow *window = [[[self superview] superview] window]; 306 | // NSWindow *window = [self window]; 307 | //// NSLog(@"window: %@", window); 308 | // [window visualizeConstraints:userInfo]; 309 | // 310 | //} 311 | 312 | /* 313 | * 314 | */ 315 | - (void)fadeIn { 316 | NSArray *subviews = [self subviews]; 317 | // NSLog(@"fade these subviews: %@", subviews); 318 | _isAnimating = YES; 319 | [NSAnimationContext beginGrouping]; 320 | [[NSAnimationContext currentContext] setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]]; 321 | [[NSAnimationContext currentContext] setDuration:0.25]; 322 | [[NSAnimationContext currentContext] setCompletionHandler:^(void) { 323 | _isAnimating = NO; 324 | }]; 325 | for (NSView *view in subviews) { 326 | [view setAlphaValue:0.0]; 327 | [[view animator] setAlphaValue:1.0]; 328 | } 329 | [NSAnimationContext endGrouping]; 330 | } 331 | 332 | 333 | /* 334 | * 335 | */ 336 | - (void)fadeOutWithComplitionHandler:(void (^)(void))handler { 337 | NSArray *subviews = [self subviews]; 338 | 339 | _isAnimating = YES; 340 | [NSAnimationContext beginGrouping]; 341 | [[NSAnimationContext currentContext] setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]]; 342 | [[NSAnimationContext currentContext] setDuration:0.25]; 343 | [[NSAnimationContext currentContext] setCompletionHandler:^(void) { 344 | _isAnimating = NO; 345 | if (handler) 346 | handler(); 347 | }]; 348 | 349 | for (NSView *view in subviews) { 350 | [[view animator] setAlphaValue:0.0]; 351 | } 352 | [NSAnimationContext endGrouping]; 353 | } 354 | 355 | 356 | /* 357 | * 358 | */ 359 | - (void)blink { 360 | if (_selected) { 361 | [self setSelected:NO]; 362 | [self performSelector:@selector(blink) withObject:nil afterDelay:0.05 inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]]; 363 | } else { 364 | [self setSelected:YES]; 365 | } 366 | } 367 | 368 | 369 | /* 370 | * 371 | */ 372 | - (void)setIndentationLevel:(NSInteger)indentationLevel { 373 | _indentationLevel = indentationLevel; 374 | // NSLog(@"set indent for view: %@", [_title stringValue]); 375 | // If the view hasn't been drawn yet, simply exit. 376 | // This method will be called again later from -updateConstraints. 377 | if ([self superview]) { 378 | NSArray *constraints = [_state constraintsAffectingLayoutForOrientation:NSLayoutConstraintOrientationHorizontal]; 379 | NSLayoutConstraint *indentationConstraint = nil; 380 | // Indentation is achieved by modifying constant of the leading constraint of 381 | // the leftmost view (state view) 382 | for (NSLayoutConstraint *constraint in constraints) { 383 | if ([constraint firstAttribute] == NSLayoutAttributeLeading) { 384 | indentationConstraint = constraint; 385 | break; 386 | } 387 | } 388 | if (indentationConstraint) { 389 | // NSLog(@"indent constraint: %@", indentationConstraint); 390 | [indentationConstraint setConstant:(indentationLevel * INDENTATION_SIZE) + 6]; 391 | } 392 | } 393 | } 394 | 395 | 396 | - (NSInteger)indentationLevel { 397 | return _indentationLevel; 398 | } 399 | 400 | 401 | - (NSSize)fittingSize { 402 | NSSize size = [super fittingSize]; 403 | // If the view has an indentation level but is not currently on the 404 | // superview fitting width needs to take into account indentation 405 | if (_indentationLevel && ![self superview]) 406 | size.width += _indentationLevel * INDENTATION_SIZE; 407 | return size; 408 | } 409 | 410 | 411 | /* 412 | * 413 | */ 414 | - (NSString *)description { 415 | NSMutableString *description = [[[NSMutableString alloc] initWithString:[super description]] autorelease]; 416 | [description appendString:@" Properties: ("]; 417 | 418 | id currentClass = [self class]; 419 | NSString *propertyName; 420 | unsigned int outCount, i; 421 | objc_property_t *properties = class_copyPropertyList(currentClass, &outCount); 422 | for (i = 0; i < outCount; ++i) { 423 | objc_property_t property = properties[i]; 424 | propertyName = [NSString stringWithCString:property_getName(property) encoding:NSASCIIStringEncoding]; 425 | [description appendFormat:@"\n\t%@: %@", propertyName, [self valueForKey:propertyName]]; 426 | } 427 | free(properties); 428 | 429 | // if object was subclassed, let's print parent's properties 430 | if (! [[self className] isEqualToString:@"CMMenuItemView"]) { 431 | [description appendFormat:@"\n\ticon: %@", _icon]; 432 | [description appendFormat:@"\n\ttitle: %@", _title]; 433 | } 434 | 435 | [description appendString:@")"]; 436 | 437 | return description; 438 | } 439 | 440 | 441 | @end 442 | -------------------------------------------------------------------------------- /ChromeMenu/src/CMMenuKeyEventInterpreter.h: -------------------------------------------------------------------------------- 1 | // 2 | // CMMenuKeyEventInterpreter.h 3 | // ChromeMenu 4 | // 5 | // Created by Maksym on 9/16/13. 6 | // Copyright (c) 2013 Maksym Stefanchuk. All rights reserved. 7 | // 8 | 9 | @protocol CMMenuKeyEventInterpreterDelegate, NSObject; 10 | 11 | 12 | @interface CMMenuKeyEventInterpreter : NSObject 13 | { 14 | @private 15 | id _delegate; 16 | } 17 | /** 18 | * @abstract Create local monitor for Key Events and route interpreted actions to an owning menu. 19 | * @param target A menu to receive interpreted actions. 20 | */ 21 | - (id)initWithDelegate:(id )delegate; 22 | - (void)setDelegate:(id )delegate; 23 | 24 | - (void)interpretEvent:(NSEvent *)theEvent; 25 | 26 | 27 | @end 28 | 29 | @protocol CMMenuKeyEventInterpreterDelegate 30 | @optional 31 | 32 | - (void)moveUp:(NSEvent *)originalEvent; 33 | - (void)moveDown:(NSEvent *)originalEvent; 34 | - (void)moveLeft:(NSEvent *)originalEvent; 35 | - (void)moveRight:(NSEvent *)originalEvent; 36 | - (void)cancelOperation:(NSEvent *)originalEvent; 37 | - (void)performSelected:(NSEvent *)originalEvent; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /ChromeMenu/src/CMMenuKeyEventInterpreter.m: -------------------------------------------------------------------------------- 1 | // 2 | // CMMenuKeyEventInterpreter.m 3 | // ChromeMenu 4 | // 5 | // Created by Maksym on 9/16/13. 6 | // Copyright (c) 2013 Maksym Stefanchuk. All rights reserved. 7 | // 8 | 9 | #import "CMMenuKeyEventInterpreter.h" 10 | 11 | 12 | @implementation CMMenuKeyEventInterpreter 13 | 14 | - (id)initWithDelegate:(id )delegate { 15 | self = [super init]; 16 | if (self) { 17 | if (delegate == nil) 18 | [NSException raise:NSInvalidArgumentException format:@"Menu key events interpreter target cannot be nil."]; 19 | _delegate = delegate; 20 | } 21 | 22 | return self; 23 | } 24 | 25 | 26 | - (void)setDelegate:(id )delegate { 27 | if (_delegate != delegate) { 28 | _delegate = delegate; 29 | } 30 | } 31 | 32 | 33 | - (void)interpretEvent:(NSEvent *)theEvent { 34 | if (! _delegate) 35 | return; 36 | 37 | NSEventType eventType = [theEvent type]; 38 | NSUInteger modifierFlags = [theEvent modifierFlags]; 39 | 40 | if (eventType == NSKeyDown) { 41 | unsigned short keyCode = [theEvent keyCode]; 42 | // NSLog(@"key code: %d, modifier: %lu", keyCode, modifierFlags); 43 | 44 | SEL action = nil; 45 | 46 | switch (keyCode) { 47 | case 123: 48 | action = @selector(moveLeft:); 49 | break; 50 | 51 | case 124: 52 | action = @selector(moveRight:); 53 | break; 54 | 55 | case 125: 56 | action = @selector(moveDown:); 57 | break; 58 | 59 | case 126: 60 | action = @selector(moveUp:); 61 | break; 62 | 63 | case 53: // Esc 64 | action = @selector(cancelOperation:); 65 | break; 66 | 67 | case 47: // . 68 | if (modifierFlags & NSCommandKeyMask) { 69 | action = @selector(cancelOperation:); 70 | } 71 | break; 72 | 73 | case 36: // Enter 74 | action = @selector(performSelected:); 75 | break; 76 | 77 | default: 78 | break; 79 | } 80 | 81 | if (action && [_delegate respondsToSelector:action]) 82 | [_delegate performSelector:action withObject:theEvent]; 83 | 84 | // theEvent = nil; 85 | 86 | } else if (eventType == NSFlagsChanged) { 87 | // NSLog(@"modifier flag changed! : %ld", modifierFlags); 88 | } 89 | } 90 | 91 | 92 | @end 93 | -------------------------------------------------------------------------------- /ChromeMenu/src/CMMenuScroller.h: -------------------------------------------------------------------------------- 1 | // 2 | // CMMenuScroller.h 3 | // ChromeMenu 4 | // 5 | // Created by Maksym on 9/7/13. 6 | // Copyright (c) 2013 Maksym Stefanchuk. All rights reserved. 7 | // 8 | 9 | 10 | enum { 11 | CMMenuScrollerTop = 1, 12 | CMMenuScrollerBottom = 2 13 | }; 14 | typedef NSUInteger CMMenuScrollerType; 15 | 16 | 17 | 18 | @interface CMMenuScroller : NSView 19 | { 20 | @private 21 | CMMenuScrollerType _scrollerType; 22 | } 23 | 24 | - (id)initWithScrollerType:(CMMenuScrollerType)scrollerType; 25 | 26 | - (CMMenuScrollerType)scrollerType; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /ChromeMenu/src/CMMenuScroller.m: -------------------------------------------------------------------------------- 1 | // 2 | // CMMenuScroller.m 3 | // ChromeMenu 4 | // 5 | // Created by Maksym on 9/7/13. 6 | // Copyright (c) 2013 Maksym Stefanchuk. All rights reserved. 7 | // 8 | 9 | #import "CMMenuScroller.h" 10 | //#import "NSImage+CMMenuImageRepAdditions.h" 11 | 12 | @implementation CMMenuScroller 13 | 14 | 15 | - (id)initWithScrollerType:(CMMenuScrollerType)scrollerType { 16 | self = [super init]; 17 | if (self) { 18 | _scrollerType = scrollerType; 19 | } 20 | 21 | return self; 22 | } 23 | 24 | 25 | - (void)dealloc { 26 | [super dealloc]; 27 | } 28 | 29 | 30 | 31 | - (void)drawRect:(NSRect)dirtyRect { 32 | [[NSColor colorWithCalibratedWhite:1.0 alpha:0.95] setFill]; 33 | NSRectFill([self bounds]); 34 | 35 | NSImage *goRightImage = [NSImage imageNamed:NSImageNameGoRightTemplate]; 36 | // [goRightImage setSize:NSMakeSize(8, 10)]; 37 | // NSSize imageSize = [goRightImage size]; 38 | // imageSize = NSMakeSize(imageSize.width - 1, imageSize.height + 1); // we want it to be 8 by 10 pixels 39 | NSSize imageSize = NSMakeSize(8.0, 10.0); 40 | // imageSize.width -= 1.5; 41 | 42 | 43 | NSAffineTransform *transform = [NSAffineTransform transform]; 44 | if (_scrollerType == CMMenuScrollerTop) { 45 | CGFloat translateX = floor((self.bounds.size.width + imageSize.height) / 2); 46 | [transform translateXBy:translateX yBy:0.0]; 47 | [transform rotateByDegrees:90.0]; 48 | [transform translateXBy:(self.bounds.size.height - imageSize.width) yBy:0.0]; 49 | } else { 50 | CGFloat translateX = floor((self.bounds.size.width - imageSize.height) / 2); 51 | [transform translateXBy:translateX yBy:0.0]; 52 | [transform rotateByDegrees:-90.0]; 53 | [transform translateXBy:-(imageSize.width + 1) yBy:0.0]; 54 | } 55 | // NSAffineTransformStruct matrix = [transform transformStruct]; 56 | // NSLog(@"matrix m1: %f, m2: %f, m3: %f, m4: %f, t1: %f, t2: %f", 57 | // matrix.m11, 58 | // matrix.m12, 59 | // matrix.m21, 60 | // matrix.m22, 61 | // matrix.tX, 62 | // matrix.tY); 63 | 64 | 65 | [transform concat]; 66 | 67 | [goRightImage drawInRect:NSMakeRect(0, 0, imageSize.width, imageSize.height) fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:0.75 respectFlipped:YES hints:nil]; 68 | } 69 | 70 | 71 | - (CMMenuScrollerType)scrollerType { 72 | return _scrollerType; 73 | } 74 | 75 | @end 76 | -------------------------------------------------------------------------------- /ChromeMenu/src/CMScrollDocumentView.h: -------------------------------------------------------------------------------- 1 | // 2 | // CMMenuScrollDocumentView.h 3 | // ChromeMenu 4 | // 5 | // Created by Maksym on 7/12/13. 6 | // Copyright (c) 2013 Maksym Stefanchuk. All rights reserved. 7 | // 8 | 9 | 10 | @interface CMScrollDocumentView : NSView 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /ChromeMenu/src/CMScrollDocumentView.m: -------------------------------------------------------------------------------- 1 | // 2 | // CMMenuScrollDocumentView.m 3 | // ChromeMenu 4 | // 5 | // Created by Maksym on 7/12/13. 6 | // Copyright (c) 2013 Maksym Stefanchuk. All rights reserved. 7 | // 8 | 9 | #import "CMScrollDocumentView.h" 10 | 11 | @implementation CMScrollDocumentView 12 | 13 | - (BOOL)isFlipped { 14 | return YES; 15 | } 16 | 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /ChromeMenu/src/CMScrollView.h: -------------------------------------------------------------------------------- 1 | // 2 | // CMScrollView.h 3 | // ChromeMenu 4 | // 5 | // Created by Maksym on 7/15/13. 6 | // Copyright (c) 2013 Maksym Stefanchuk. All rights reserved. 7 | // 8 | 9 | 10 | @interface CMScrollView : NSScrollView 11 | 12 | - (void)scrollWithEvent:(NSEvent *)theEvent; 13 | - (void)scrollUpByAmount:(CGFloat)amount; 14 | - (void)scrollDownByAmount:(CGFloat)amount; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /ChromeMenu/src/CMScrollView.m: -------------------------------------------------------------------------------- 1 | // 2 | // CMScrollView.m 3 | // ChromeMenu 4 | // 5 | // Created by Maksym on 7/15/13. 6 | // Copyright (c) 2013 Maksym Stefanchuk. All rights reserved. 7 | // 8 | 9 | #import "CMScrollView.h" 10 | 11 | static const short up = 0; 12 | static const short down = 1; 13 | 14 | 15 | @implementation CMScrollView 16 | 17 | /* 18 | - (void)scrollWheel:(NSEvent *)theEvent { 19 | CGFloat deltaY = theEvent.deltaY; 20 | CGFloat lineHeight = [self lineScroll]; 21 | // CGFloat yOrigin = NSMinY([[self contentView] bounds]); 22 | int multiplier; 23 | CGFloat amount; 24 | short direction; 25 | 26 | multiplier = (int)ceil(ABS(deltaY) / 2); 27 | amount = multiplier * lineHeight; 28 | direction = (deltaY < 0) ? down : up; 29 | 30 | [self scrollInDirection:direction byAmount:amount]; 31 | } 32 | */ 33 | 34 | 35 | - (void)scrollWithEvent:(NSEvent *)theEvent { 36 | // CGFloat deltaY = theEvent.deltaY; 37 | CGFloat deltaY = [theEvent scrollingDeltaY]; 38 | CGFloat lineHeight = [self lineScroll]; 39 | int multiplier; 40 | CGFloat amount; 41 | short direction; 42 | 43 | multiplier = (int)ceil(ABS(deltaY) / 2); 44 | amount = multiplier * lineHeight; 45 | direction = (deltaY < 0) ? down : up; 46 | 47 | [self scrollInDirection:direction byAmount:amount]; 48 | } 49 | 50 | 51 | - (void)scrollUpByAmount:(CGFloat)amount { 52 | [self scrollInDirection:up byAmount:amount]; 53 | } 54 | 55 | 56 | - (void)scrollDownByAmount:(CGFloat)amount { 57 | [self scrollInDirection:down byAmount:amount]; 58 | } 59 | 60 | 61 | - (void)scrollInDirection:(NSInteger)direction byAmount:(CGFloat)amount { 62 | CGFloat yOrigin = NSMinY([[self contentView] bounds]); 63 | 64 | if (direction == down) { 65 | CGFloat yBound = NSMaxY([[self documentView] bounds]) - NSHeight([[self contentView] bounds]); 66 | if (yOrigin == yBound) { // no need to update contentView 67 | // NSLog(@"no more down: %@", NSStringFromRect([[self contentView] bounds])); 68 | return; 69 | } 70 | 71 | yOrigin += amount; 72 | if (yOrigin > yBound) { 73 | // NSLog(@"new yOrigin"); 74 | yOrigin = yBound; 75 | } 76 | } else { 77 | if (yOrigin == 0) { // no need to update contentView 78 | // NSLog(@"no more up: %@", NSStringFromRect([[self contentView] bounds])); 79 | return; 80 | } 81 | yOrigin -= amount; 82 | if (yOrigin < 0) 83 | yOrigin = 0; 84 | } 85 | 86 | [[self contentView] setBoundsOrigin:NSMakePoint(0, yOrigin)]; 87 | // [[self documentView] scrollPoint:NSMakePoint(0, yOrigin)]; 88 | } 89 | 90 | @end 91 | -------------------------------------------------------------------------------- /ChromeMenu/src/CMUnderlyingView.h: -------------------------------------------------------------------------------- 1 | // 2 | // ChromMenuUnderlyingView.h 3 | // ChromeMenu 4 | // 5 | // Created by Maksym on 7/3/13. 6 | // Copyright (c) 2013 Maksym Stefanchuk. All rights reserved. 7 | // 8 | 9 | @protocol CMUnderlyingView 10 | 11 | /** 12 | * @abstract Designated initializer 13 | * @param radius Array of four radiuses for bottom left, top left, top right and bottom right radiuses. 14 | */ 15 | - (id)initWithFrame:(NSRect)frameRect borderRadiuses:(NSArray *)radiuses; 16 | 17 | - (void)setBorderRadiuses:(NSArray *)radiuses; 18 | 19 | @end 20 | 21 | 22 | 23 | 24 | @interface CMUnderlyingView : NSView 25 | 26 | @end 27 | 28 | 29 | 30 | @interface CMUnderlyingViewVibrant : NSVisualEffectView 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /ChromeMenu/src/CMUnderlyingView.m: -------------------------------------------------------------------------------- 1 | // 2 | // ChromMenuUnderlyingView.m 3 | // ChromeMenu 4 | // 5 | // Created by Maksym on 7/3/13. 6 | // Copyright (c) 2013 Maksym Stefanchuk. All rights reserved. 7 | // 8 | 9 | #import "CMUnderlyingView.h" 10 | 11 | @interface CMUnderlyingView() 12 | { 13 | NSArray *_borderRadiuses; 14 | } 15 | 16 | @end 17 | 18 | @implementation CMUnderlyingView 19 | 20 | - (id)initWithFrame:(NSRect)frameRect borderRadiuses:(NSArray *)radiuses { 21 | self = [super initWithFrame:frameRect]; 22 | if (self) { 23 | if (radiuses) { 24 | _borderRadiuses = [radiuses retain]; 25 | } 26 | } 27 | 28 | return self; 29 | } 30 | 31 | - (void)dealloc { 32 | [_borderRadiuses release]; 33 | [super dealloc]; 34 | } 35 | 36 | 37 | - (void)drawRect:(NSRect)dirtyRect { 38 | NSBezierPath *windowBorder = [NSBezierPath bezierPath]; 39 | 40 | NSRect rect = [self bounds]; 41 | if (! _borderRadiuses) { 42 | [windowBorder appendBezierPathWithRect:rect]; 43 | } else { 44 | CGFloat bottomLeft = [(NSNumber *)[_borderRadiuses objectAtIndex:0] doubleValue]; 45 | CGFloat topLeft = [(NSNumber *)[_borderRadiuses objectAtIndex:1] doubleValue]; 46 | CGFloat topRight = [(NSNumber *)[_borderRadiuses objectAtIndex:2] doubleValue]; 47 | CGFloat bottomRight = [(NSNumber *)[_borderRadiuses objectAtIndex:3] doubleValue]; 48 | 49 | CGFloat width = rect.size.width; 50 | CGFloat height = rect.size.height; 51 | 52 | if (bottomLeft) { 53 | [windowBorder moveToPoint:NSMakePoint(bottomLeft, 0)]; 54 | [windowBorder appendBezierPathWithArcFromPoint:NSMakePoint(0, 0) toPoint:NSMakePoint(0, bottomLeft) radius:bottomLeft]; 55 | } else { 56 | [windowBorder moveToPoint:NSMakePoint(0, 0)]; 57 | } 58 | 59 | if (topLeft) { 60 | [windowBorder lineToPoint:NSMakePoint(0, height - topLeft)]; 61 | [windowBorder appendBezierPathWithArcFromPoint:NSMakePoint(0, height) toPoint:NSMakePoint(topLeft, height) radius:topLeft]; 62 | } else { 63 | [windowBorder lineToPoint:NSMakePoint(0, height)]; 64 | } 65 | 66 | if (topRight) { 67 | [windowBorder lineToPoint:NSMakePoint(width - topRight, height)]; 68 | [windowBorder appendBezierPathWithArcFromPoint:NSMakePoint(width, height) toPoint:NSMakePoint(width, height - topRight) radius:topRight]; 69 | } else { 70 | [windowBorder lineToPoint:NSMakePoint(width, height)]; 71 | } 72 | 73 | if (bottomRight) { 74 | [windowBorder lineToPoint:NSMakePoint(width, bottomRight)]; 75 | [windowBorder appendBezierPathWithArcFromPoint:NSMakePoint(width, 0) toPoint:NSMakePoint(width - bottomRight, 0) radius:bottomRight]; 76 | } else { 77 | [windowBorder lineToPoint:NSMakePoint(width, 0)]; 78 | } 79 | 80 | [windowBorder closePath]; 81 | 82 | //[windowBorder appendBezierPathWithRoundedRect:[self bounds] xRadius:5.0 yRadius:5.0]; 83 | } 84 | 85 | 86 | //[[NSColor colorWithCalibratedWhite:1.0 alpha:0.95] set]; 87 | [[NSColor colorWithDeviceWhite:1.0 alpha:0.95] set]; 88 | 89 | [windowBorder fill]; 90 | // [[NSColor windowFrameColor] set]; 91 | // [[NSColor colorWithDeviceRed:1.0 green:0.5 blue:0.5 alpha:0.7] set]; 92 | // [windowBorder stroke]; 93 | 94 | 95 | } 96 | 97 | 98 | - (void)setBorderRadiuses:(NSArray *)radiuses { 99 | if (_borderRadiuses != radiuses) { 100 | BOOL radiusesEqual = YES; 101 | if (_borderRadiuses) { 102 | NSUInteger len = [_borderRadiuses count]; 103 | NSUInteger i; 104 | for (i = 0; i < len; ++i) { 105 | if (! [_borderRadiuses[i] isEqualToNumber:radiuses[i]]) { 106 | radiusesEqual = NO; 107 | break; 108 | } 109 | } 110 | } else { 111 | radiusesEqual = NO; 112 | } 113 | 114 | [_borderRadiuses release]; 115 | _borderRadiuses = [radiuses retain]; 116 | if (! radiusesEqual) { 117 | [self setNeedsDisplay:YES]; 118 | // NSLog(@"new radisues are set!!!"); 119 | } 120 | } 121 | } 122 | 123 | 124 | //- (BOOL)shouldDelayWindowOrderingForEvent:(NSEvent *)theEvent { 125 | // NSLog(@"should delay: %@", theEvent); 126 | // return YES; 127 | //} 128 | // 129 | //- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent { 130 | // NSLog(@"accepts first mouse: %@", theEvent); 131 | // return YES; 132 | //} 133 | // 134 | // 135 | //- (void)mouseDown:(NSEvent *)theEvent { 136 | // [NSApp preventWindowOrdering]; 137 | //} 138 | 139 | 140 | 141 | @end 142 | 143 | 144 | 145 | 146 | @interface CMUnderlyingViewVibrant() 147 | { 148 | NSArray *_borderRadiuses; 149 | } 150 | 151 | @end 152 | 153 | @implementation CMUnderlyingViewVibrant 154 | 155 | - (id)initWithFrame:(NSRect)frameRect borderRadiuses:(NSArray *)radiuses { 156 | self = [super initWithFrame:frameRect]; 157 | if (self) { 158 | if (radiuses) { 159 | _borderRadiuses = [radiuses retain]; 160 | } 161 | 162 | self.blendingMode = NSVisualEffectBlendingModeBehindWindow; 163 | //self.appearance = [NSAppearance appearanceNamed:NSAppearanceNameVibrantLight]; 164 | //self.material = NSVisualEffectMaterialLight; 165 | self.state = NSVisualEffectStateActive; 166 | } 167 | 168 | return self; 169 | } 170 | 171 | - (void)dealloc { 172 | [_borderRadiuses release]; 173 | [super dealloc]; 174 | } 175 | 176 | 177 | - (void)drawRect:(NSRect)dirtyRect { 178 | NSBezierPath *windowBorder = [NSBezierPath bezierPath]; 179 | 180 | NSRect rect = [self bounds]; 181 | if (! _borderRadiuses) { 182 | [windowBorder appendBezierPathWithRect:rect]; 183 | } else { 184 | CGFloat bottomLeft = [(NSNumber *)[_borderRadiuses objectAtIndex:0] doubleValue]; 185 | CGFloat topLeft = [(NSNumber *)[_borderRadiuses objectAtIndex:1] doubleValue]; 186 | CGFloat topRight = [(NSNumber *)[_borderRadiuses objectAtIndex:2] doubleValue]; 187 | CGFloat bottomRight = [(NSNumber *)[_borderRadiuses objectAtIndex:3] doubleValue]; 188 | 189 | CGFloat width = rect.size.width; 190 | CGFloat height = rect.size.height; 191 | 192 | if (bottomLeft) { 193 | [windowBorder moveToPoint:NSMakePoint(bottomLeft, 0)]; 194 | [windowBorder appendBezierPathWithArcFromPoint:NSMakePoint(0, 0) toPoint:NSMakePoint(0, bottomLeft) radius:bottomLeft]; 195 | } else { 196 | [windowBorder moveToPoint:NSMakePoint(0, 0)]; 197 | } 198 | 199 | if (topLeft) { 200 | [windowBorder lineToPoint:NSMakePoint(0, height - topLeft)]; 201 | [windowBorder appendBezierPathWithArcFromPoint:NSMakePoint(0, height) toPoint:NSMakePoint(topLeft, height) radius:topLeft]; 202 | } else { 203 | [windowBorder lineToPoint:NSMakePoint(0, height)]; 204 | } 205 | 206 | if (topRight) { 207 | [windowBorder lineToPoint:NSMakePoint(width - topRight, height)]; 208 | [windowBorder appendBezierPathWithArcFromPoint:NSMakePoint(width, height) toPoint:NSMakePoint(width, height - topRight) radius:topRight]; 209 | } else { 210 | [windowBorder lineToPoint:NSMakePoint(width, height)]; 211 | } 212 | 213 | if (bottomRight) { 214 | [windowBorder lineToPoint:NSMakePoint(width, bottomRight)]; 215 | [windowBorder appendBezierPathWithArcFromPoint:NSMakePoint(width, 0) toPoint:NSMakePoint(width - bottomRight, 0) radius:bottomRight]; 216 | } else { 217 | [windowBorder lineToPoint:NSMakePoint(width, 0)]; 218 | } 219 | 220 | [windowBorder closePath]; 221 | 222 | //[windowBorder appendBezierPathWithRoundedRect:[self bounds] xRadius:5.0 yRadius:5.0]; 223 | } 224 | 225 | 226 | //[[NSColor colorWithCalibratedWhite:1.0 alpha:0.95] set]; 227 | [[NSColor colorWithDeviceWhite:1.0 alpha:0.95] set]; 228 | //[[NSColor clearColor] set]; 229 | 230 | [windowBorder fill]; 231 | // [[NSColor windowFrameColor] set]; 232 | // [[NSColor colorWithDeviceRed:1.0 green:0.5 blue:0.5 alpha:0.7] set]; 233 | // [windowBorder stroke]; 234 | 235 | 236 | } 237 | 238 | - (BOOL)allowsVibrancy { 239 | return YES; 240 | } 241 | 242 | 243 | - (void)setBorderRadiuses:(NSArray *)radiuses { 244 | if (_borderRadiuses != radiuses) { 245 | BOOL radiusesEqual = YES; 246 | if (_borderRadiuses) { 247 | NSUInteger len = [_borderRadiuses count]; 248 | NSUInteger i; 249 | for (i = 0; i < len; ++i) { 250 | if (! [_borderRadiuses[i] isEqualToNumber:radiuses[i]]) { 251 | radiusesEqual = NO; 252 | break; 253 | } 254 | } 255 | } else { 256 | radiusesEqual = NO; 257 | } 258 | 259 | [_borderRadiuses release]; 260 | _borderRadiuses = [radiuses retain]; 261 | if (! radiusesEqual) { 262 | [self setNeedsDisplay:YES]; 263 | } 264 | } 265 | } 266 | 267 | @end 268 | -------------------------------------------------------------------------------- /ChromeMenu/src/CMUnderlyingWindow.h: -------------------------------------------------------------------------------- 1 | // 2 | // ChromMenuUnderlyingWindow.h 3 | // ChromeMenu 4 | // 5 | // Created by Maksym on 7/3/13. 6 | // Copyright (c) 2013 Maksym Stefanchuk. All rights reserved. 7 | // 8 | 9 | 10 | @interface CMUnderlyingWindow : NSPanel 11 | 12 | 13 | - (id)initWithContentRect:(NSRect)contentRect defer:(BOOL)flag; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /ChromeMenu/src/CMUnderlyingWindow.m: -------------------------------------------------------------------------------- 1 | // 2 | // ChromMenuUnderlyingWindow.m 3 | // ChromeMenu 4 | // 5 | // Created by Maksym on 7/3/13. 6 | // Copyright (c) 2013 Maksym Stefanchuk. All rights reserved. 7 | // 8 | 9 | #import "CMUnderlyingWindow.h" 10 | 11 | @implementation CMUnderlyingWindow 12 | 13 | - (id)initWithContentRect:(NSRect)contentRect defer:(BOOL)flag { 14 | return [self initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES]; 15 | } 16 | 17 | 18 | - (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag { 19 | if (self = [super initWithContentRect:contentRect styleMask:aStyle backing:bufferingType defer:flag]) { 20 | [self setBackgroundColor:[NSColor clearColor]]; 21 | [self setHasShadow:YES]; 22 | [self setOpaque:NO]; 23 | // [self setAlphaValue:0.5]; 24 | // [self setHasShadow:NO]; 25 | } 26 | return self; 27 | } 28 | 29 | //- (BOOL)canBecomeKeyWindow { 30 | // NSLog(@"aksed if canBecomeKeyWindow"); 31 | // return NO; 32 | //} 33 | 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /ChromeMenu/src/CMWindowController.h: -------------------------------------------------------------------------------- 1 | // 2 | // CMMenuWindowController.h 3 | // ChromeMenu 4 | // 5 | // Created by Maksym on 7/12/13. 6 | // Copyright (c) 2013 Maksym Stefanchuk. All rights reserved. 7 | // 8 | 9 | #import "CMMenu+InternalMethods.h" 10 | 11 | @class CMMenu, CMMenuScroller; 12 | 13 | @interface CMWindowController : NSWindowController 14 | 15 | 16 | /* The designated initializer. This window controller creates its own custom suggestions window. */ 17 | - (id)initWithOwner:(CMMenu *)owner; 18 | 19 | - (void)setBorderRadiuses:(NSArray *)radiuses; 20 | 21 | - (void)layoutViews:(NSMutableArray *)viewControllers; 22 | 23 | /** 24 | * @function displayInFrame: ignoreMouse: 25 | * @abstract Show underlying window in frame. 26 | * @param frame Rect in which to show a menu. 27 | * @param options Ignore mouse option is of most interest. 28 | * When menu is first displayed, tracking areas for its menu items are being created. 29 | * This option regulates whether we will capture the current mouse position and highlight according menu item 30 | * underneath it. In some situations, for example when use keyboard navigation and open submenu with right arrow, 31 | * you do not expect a menu item underneath mouse to be selected. It will get selected if mouse moves however. 32 | */ 33 | - (void)displayInFrame:(NSRect)frame options:(CMMenuOptions)options; 34 | - (void)hide; 35 | - (void)fadeOutWithComplitionHandler:(void (^)(void))handler; 36 | 37 | - (void)insertView:(NSViewController *)viewController atIndex:(NSUInteger)index animate:(BOOL)animate; 38 | - (void)addView:(NSViewController *)viewController animate:(BOOL)animate; 39 | - (void)removeViewAtIndex:(NSUInteger)index; 40 | - (void)removeViewAtIndex:(NSUInteger)index animate:(BOOL)animate complitionHandler:(void (^)(void))handler; 41 | - (void)removeAllViews; 42 | 43 | - (void)updateDocumentView; 44 | 45 | - (BOOL)isTracking; 46 | - (void)beginTrackingWithEvent:(NSEvent *)event options:(CMMenuOptions)options; 47 | - (void)endTracking; 48 | 49 | - (NSSize)intrinsicContentSize; 50 | 51 | /** 52 | * @function verticalPadding 53 | * @abstract The top and bottom padding for the menu. 54 | */ 55 | - (CGFloat)verticalPadding; 56 | 57 | /** 58 | * @function viewController: 59 | * @abastract Returns the view controller at a given point. 60 | * @discussion Point should be coordinates relative to Menu. 61 | * @param aPoint Point in NSWindow (Menu) coordinates. 62 | */ 63 | - (NSViewController *)viewAtPoint:(NSPoint)aPoint; 64 | 65 | 66 | - (CMMenuScroller *)scrollerAtPoint:(NSPoint)aPoint; 67 | - (void)scrollWithActiveScroller:(CMMenuScroller *)scroller; 68 | 69 | - (void)moveVisibleRectToRect:(NSRect)rect ignoreMouse:(BOOL)ignoreMouse updateTrackingPrimitives:(BOOL)updateTrackingPrimitives; 70 | 71 | /** 72 | * @abstract When certain menu item changes its needsTracking value (for example 73 | * when item is enabled or disabled) add or remove tracking area to/from the documentView of scrollView 74 | * for this item view only. It does not update tracking area literally if it already exists. 75 | */ 76 | - (void)updateTrackingPrimitiveForView:(NSViewController *)viewController ignoreMouse:(BOOL)ignoreMouse; 77 | 78 | @end 79 | -------------------------------------------------------------------------------- /ChromeMenu/src/ChromeMenu.h: -------------------------------------------------------------------------------- 1 | // 2 | // ChromMenu.h 3 | // ChromeMenu 4 | // 5 | // Created by Maksym on 7/4/13. 6 | // Copyright (c) 2013 Maksym Stefanchuk. All rights reserved. 7 | // 8 | 9 | 10 | #import "CMMenu.h" 11 | #import "CMMenuItem.h" 12 | -------------------------------------------------------------------------------- /ChromeMenu/src/NSImage+CMMenuImageRepAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSImage+NSImage_BitmapImageRep.h 3 | // ChromeMenu 4 | // 5 | // Created by Maksym on 9/10/13. 6 | // Copyright (c) 2013 Maksym Stefanchuk. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class NSBitmapImageRep; 12 | 13 | @interface NSImage (CMMenuImageRepAdditions) 14 | 15 | //- (NSBitmapImageRep *)bitmapImageRepresentation; 16 | - (void)createInvertedImageRepresentation; 17 | - (NSImageRep *)defaultImageRepresentation; 18 | - (NSImageRep *)invertedImageRepresentation; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /ChromeMenu/src/NSImage+CMMenuImageRepAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSImage+NSImage_BitmapImageRep.m 3 | // ChromeMenu 4 | // 5 | // Created by Maksym on 9/10/13. 6 | // Copyright (c) 2013 Maksym Stefanchuk. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "NSImage+CMMenuImageRepAdditions.h" 11 | 12 | @implementation NSImage (CMMenuImageRepAdditions) 13 | 14 | - (NSBitmapImageRep *)bitmapImageRepresentation { 15 | NSInteger width = (NSInteger)self.size.width; 16 | NSInteger height = (NSInteger)self.size.height; 17 | 18 | if (width < 1 || height < 1) 19 | return nil; 20 | 21 | NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] 22 | initWithBitmapDataPlanes:NULL 23 | pixelsWide:width 24 | pixelsHigh:height 25 | bitsPerSample:8 26 | samplesPerPixel:4 27 | hasAlpha:YES 28 | isPlanar:NO 29 | colorSpaceName:NSDeviceRGBColorSpace 30 | bytesPerRow:width * 4 31 | bitsPerPixel:32]; 32 | 33 | NSGraphicsContext *ctx = [NSGraphicsContext graphicsContextWithBitmapImageRep:rep]; 34 | [NSGraphicsContext saveGraphicsState]; 35 | [NSGraphicsContext setCurrentContext:ctx]; 36 | [self drawAtPoint: NSZeroPoint fromRect:NSZeroRect operation:NSCompositeCopy fraction:1.0]; 37 | [ctx flushGraphics]; 38 | [NSGraphicsContext restoreGraphicsState]; 39 | 40 | return [rep autorelease]; 41 | } 42 | 43 | 44 | - (void)createInvertedImageRepresentation { 45 | // We create new Inverted Colors image representation only if it hasn't been created earlier 46 | // Important. For future reference, if we will want to create more image representations 47 | // we would need to have more elaboreted checks in this place that a specific ImageRep was 48 | // indeed created. 49 | if ([[self representations] count] > 1) 50 | return; 51 | 52 | NSBitmapImageRep *bitmapImageRep = [self bitmapImageRepresentation]; 53 | 54 | CIImage *ciImage = [[[CIImage alloc] initWithBitmapImageRep:bitmapImageRep] autorelease]; 55 | CIFilter *ciFilter = [CIFilter filterWithName:@"CIColorInvert"]; 56 | [ciFilter setValue:ciImage forKey:@"inputImage"]; 57 | CIImage *resultImage = [ciFilter valueForKey:@"outputImage"]; 58 | 59 | NSImageRep *newImageRep = [NSCIImageRep imageRepWithCIImage:resultImage]; 60 | [self addRepresentation:newImageRep]; 61 | } 62 | 63 | 64 | - (NSImageRep *)defaultImageRepresentation { 65 | return [[self representations] objectAtIndex:0]; 66 | } 67 | 68 | 69 | - (NSImageRep *)invertedImageRepresentation { 70 | NSArray *reps = [self representations]; 71 | if ([reps count] < 2) 72 | [self createInvertedImageRepresentation]; 73 | return [[self representations] objectAtIndex:1]; 74 | } 75 | 76 | @end 77 | -------------------------------------------------------------------------------- /ChromeMenuTests/ChromeMenuTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | definemac.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /ChromeMenuTests/ChromeMenuTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // ChromeMenuTests.m 3 | // ChromeMenuTests 4 | // 5 | // Created by Maksym on 19/11/13. 6 | // Copyright (c) 2013 definemac.com. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ChromeMenuTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation ChromeMenuTests 16 | 17 | - (void)setUp 18 | { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown 24 | { 25 | // Put teardown code here. This method is called after the invocation of each test method in the class. 26 | [super tearDown]; 27 | } 28 | 29 | - (void)testExample 30 | { 31 | XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__); 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /ChromeMenuTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | Minimalistic Firefox add-on for Google and Alexa site statistics 294 | Copyright (C) 2013 fuyu 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ChromeMenu.Framework 2 | ==================== 3 | 4 | Mac OS X Framework that implements the functionality of AppKit's NSMenu and NSMenuItem. 5 | It is a core part of the ``AppPolice`` application that builds its functionality around ``ChromeMenu`` interface. 6 | ``AppPolice`` links against this framework during runtime, so if you are clonning ``AppPolice`` make sure to clone ``ChromeMenu`` repository as well. 7 | 8 | NSMenu provides a somewhat limited API as to the ability to change its behaviour. The purpose of ``ChromeMenu.Framework`` is to give a fine-grained control of menu representaion. 9 | Note however that not all NSMenu methods were implemented--only those that provide the necessary functionality for the ``AppPolice`` app. 10 | 11 | ## Install 12 | 13 | Framework can be installed either privately within application or to a local Frameworks directory. 14 | 15 | ## Use 16 | 17 | ChromeMenu classes: 18 | 19 | ``` 20 | CMMenu 21 | CMMenuItem 22 | ``` 23 | --------------------------------------------------------------------------------