├── .DS_Store ├── .gitignore ├── Commander ├── Commander.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Commander.xcscheme └── Commander │ ├── .DS_Store │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ └── appicon.png │ ├── Contents.json │ ├── menu.imageset │ │ ├── Contents.json │ │ ├── menu.png │ │ ├── menu@2x.png │ │ └── menu@3x.png │ └── tool.imageset │ │ ├── Contents.json │ │ ├── tool.png │ │ ├── tool@2x.png │ │ └── tool@3x.png │ ├── Base.lproj │ └── MainMenu.xib │ ├── Commander.entitlements │ ├── Info.plist │ ├── apps │ └── text2qr │ │ ├── QRApp.swift │ │ ├── QROutputViewController.swift │ │ └── QROutputViewController.xib │ ├── components │ └── common │ │ ├── BaseCellView.swift │ │ ├── BaseOutlineView.swift │ │ └── Extension.swift │ ├── config │ └── Config.swift │ ├── model │ ├── AppItem.swift │ ├── ToolItem.swift │ ├── ToolService.swift │ └── ToolSet.swift │ ├── pages │ ├── menu │ │ └── MenuController.swift │ └── setting │ │ ├── AppAddViewController.swift │ │ ├── AppAddViewController.xib │ │ ├── SettingViewController.swift │ │ ├── SettingViewController.xib │ │ ├── ToolAddViewController.swift │ │ ├── ToolAddViewController.xib │ │ ├── ToolsetAddViewController.swift │ │ ├── ToolsetAddViewController.xib │ │ └── view │ │ ├── SettingCellView.swift │ │ └── SettingCellView.xib │ └── resource │ ├── .DS_Store │ └── menubar │ ├── .DS_Store │ ├── Desktop │ ├── .DS_Store │ ├── hide.bundle │ │ └── main.sh │ └── show.bundle │ │ └── main.sh │ ├── Open link.bundle │ └── main.sh │ ├── QRCode.bundle │ ├── app.json │ └── icon.png │ └── Show Hidden Files │ ├── hide.bundle │ └── main.sh │ └── show.bundle │ └── main.sh ├── README.md └── scripts ├── dark mode ├── main.sh └── readme.md └── open file ├── main.sh └── readme.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appwoodpecker/commander/4c9c80bc6dc603b896a79227a071c631dfcd1abe/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | *.DS_Store 25 | 26 | ## Obj-C/Swift specific 27 | *.hmap 28 | *.ipa 29 | *.dSYM.zip 30 | *.dSYM 31 | 32 | ## Playgrounds 33 | timeline.xctimeline 34 | playground.xcworkspace 35 | 36 | # Swift Package Manager 37 | # 38 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 39 | # Packages/ 40 | # Package.pins 41 | # Package.resolved 42 | .build/ 43 | 44 | # CocoaPods 45 | # 46 | # We recommend against adding the Pods directory to your .gitignore. However 47 | # you should judge for yourself, the pros and cons are mentioned at: 48 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 49 | # 50 | # Pods/ 51 | 52 | # Carthage 53 | # 54 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 55 | # Carthage/Checkouts 56 | 57 | Carthage/Build 58 | 59 | # fastlane 60 | # 61 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 62 | # screenshots whenever they are needed. 63 | # For more information about the recommended setup visit: 64 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 65 | 66 | fastlane/report.xml 67 | fastlane/Preview.html 68 | fastlane/screenshots/**/*.png 69 | fastlane/test_output 70 | -------------------------------------------------------------------------------- /Commander/Commander.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1B7AA3FE233CA50E00F723A9 /* QROutputViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B7AA3FC233CA50E00F723A9 /* QROutputViewController.swift */; }; 11 | 1B7AA3FF233CA50E00F723A9 /* QROutputViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1B7AA3FD233CA50E00F723A9 /* QROutputViewController.xib */; }; 12 | 1B7AA401233CAD7200F723A9 /* QRApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B7AA400233CAD7200F723A9 /* QRApp.swift */; }; 13 | 1B7AA403233CAE2900F723A9 /* AppItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B7AA402233CAE2900F723A9 /* AppItem.swift */; }; 14 | 1B7AA406233CBC6700F723A9 /* AppAddViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B7AA404233CBC6700F723A9 /* AppAddViewController.swift */; }; 15 | 1B7AA407233CBC6700F723A9 /* AppAddViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1B7AA405233CBC6700F723A9 /* AppAddViewController.xib */; }; 16 | 1BB39348228128FA00E09525 /* SettingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BB39346228128FA00E09525 /* SettingViewController.swift */; }; 17 | 1BB39349228128FA00E09525 /* SettingViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1BB39347228128FA00E09525 /* SettingViewController.xib */; }; 18 | 1BB3934C228129F300E09525 /* SettingCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BB3934B228129F300E09525 /* SettingCellView.swift */; }; 19 | 1BB3934E22812A0300E09525 /* SettingCellView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1BB3934D22812A0300E09525 /* SettingCellView.xib */; }; 20 | 1BB393522282939900E09525 /* BaseOutlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BB393512282939900E09525 /* BaseOutlineView.swift */; }; 21 | 1BB39354228293B100E09525 /* BaseCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BB39353228293B100E09525 /* BaseCellView.swift */; }; 22 | 1BB393AF2283F78F00E09525 /* menubar in Resources */ = {isa = PBXBuildFile; fileRef = 1BB393AE2283F78F00E09525 /* menubar */; }; 23 | 1BB62931227EC742005B1295 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BB62930227EC742005B1295 /* AppDelegate.swift */; }; 24 | 1BB62933227EC745005B1295 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1BB62932227EC745005B1295 /* Assets.xcassets */; }; 25 | 1BB62936227EC745005B1295 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1BB62934227EC745005B1295 /* MainMenu.xib */; }; 26 | 1BB6294A22801CD6005B1295 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BB6294922801CD6005B1295 /* Config.swift */; }; 27 | 1BB6294D22801D75005B1295 /* Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BB6294C22801D75005B1295 /* Extension.swift */; }; 28 | 1F05267D227F1EFF003D84C1 /* ToolAddViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F05267B227F1EFF003D84C1 /* ToolAddViewController.swift */; }; 29 | 1F05267E227F1EFF003D84C1 /* ToolAddViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1F05267C227F1EFF003D84C1 /* ToolAddViewController.xib */; }; 30 | 1F052685227FA85E003D84C1 /* MenuController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F052684227FA85E003D84C1 /* MenuController.swift */; }; 31 | 1F052689227FAADA003D84C1 /* ToolItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F052688227FAADA003D84C1 /* ToolItem.swift */; }; 32 | 1F05268B227FAC55003D84C1 /* ToolSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F05268A227FAC55003D84C1 /* ToolSet.swift */; }; 33 | 1F05268D227FACB8003D84C1 /* ToolService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F05268C227FACB8003D84C1 /* ToolService.swift */; }; 34 | 1F0526902281C853003D84C1 /* ToolsetAddViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F05268E2281C853003D84C1 /* ToolsetAddViewController.swift */; }; 35 | 1F0526912281C853003D84C1 /* ToolsetAddViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1F05268F2281C853003D84C1 /* ToolsetAddViewController.xib */; }; 36 | /* End PBXBuildFile section */ 37 | 38 | /* Begin PBXFileReference section */ 39 | 1B7AA3FC233CA50E00F723A9 /* QROutputViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QROutputViewController.swift; sourceTree = ""; }; 40 | 1B7AA3FD233CA50E00F723A9 /* QROutputViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = QROutputViewController.xib; sourceTree = ""; }; 41 | 1B7AA400233CAD7200F723A9 /* QRApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRApp.swift; sourceTree = ""; }; 42 | 1B7AA402233CAE2900F723A9 /* AppItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppItem.swift; sourceTree = ""; }; 43 | 1B7AA404233CBC6700F723A9 /* AppAddViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppAddViewController.swift; sourceTree = ""; }; 44 | 1B7AA405233CBC6700F723A9 /* AppAddViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AppAddViewController.xib; sourceTree = ""; }; 45 | 1BB39346228128FA00E09525 /* SettingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingViewController.swift; sourceTree = ""; }; 46 | 1BB39347228128FA00E09525 /* SettingViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SettingViewController.xib; sourceTree = ""; }; 47 | 1BB3934B228129F300E09525 /* SettingCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingCellView.swift; sourceTree = ""; }; 48 | 1BB3934D22812A0300E09525 /* SettingCellView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SettingCellView.xib; sourceTree = ""; }; 49 | 1BB393512282939900E09525 /* BaseOutlineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseOutlineView.swift; sourceTree = ""; }; 50 | 1BB39353228293B100E09525 /* BaseCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseCellView.swift; sourceTree = ""; }; 51 | 1BB393AE2283F78F00E09525 /* menubar */ = {isa = PBXFileReference; lastKnownFileType = folder; path = menubar; sourceTree = ""; }; 52 | 1BB6292D227EC742005B1295 /* Commander.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Commander.app; sourceTree = BUILT_PRODUCTS_DIR; }; 53 | 1BB62930227EC742005B1295 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 54 | 1BB62932227EC745005B1295 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 55 | 1BB62935227EC745005B1295 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 56 | 1BB62937227EC745005B1295 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 57 | 1BB62938227EC745005B1295 /* Commander.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Commander.entitlements; sourceTree = ""; }; 58 | 1BB6294922801CD6005B1295 /* Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; }; 59 | 1BB6294C22801D75005B1295 /* Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extension.swift; sourceTree = ""; }; 60 | 1F05267B227F1EFF003D84C1 /* ToolAddViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolAddViewController.swift; sourceTree = ""; }; 61 | 1F05267C227F1EFF003D84C1 /* ToolAddViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ToolAddViewController.xib; sourceTree = ""; }; 62 | 1F052684227FA85E003D84C1 /* MenuController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuController.swift; sourceTree = ""; }; 63 | 1F052688227FAADA003D84C1 /* ToolItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolItem.swift; sourceTree = ""; }; 64 | 1F05268A227FAC55003D84C1 /* ToolSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolSet.swift; sourceTree = ""; }; 65 | 1F05268C227FACB8003D84C1 /* ToolService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolService.swift; sourceTree = ""; }; 66 | 1F05268E2281C853003D84C1 /* ToolsetAddViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolsetAddViewController.swift; sourceTree = ""; }; 67 | 1F05268F2281C853003D84C1 /* ToolsetAddViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ToolsetAddViewController.xib; sourceTree = ""; }; 68 | /* End PBXFileReference section */ 69 | 70 | /* Begin PBXFrameworksBuildPhase section */ 71 | 1BB6292A227EC742005B1295 /* Frameworks */ = { 72 | isa = PBXFrameworksBuildPhase; 73 | buildActionMask = 2147483647; 74 | files = ( 75 | ); 76 | runOnlyForDeploymentPostprocessing = 0; 77 | }; 78 | /* End PBXFrameworksBuildPhase section */ 79 | 80 | /* Begin PBXGroup section */ 81 | 1B7AA3F6233CA3B200F723A9 /* apps */ = { 82 | isa = PBXGroup; 83 | children = ( 84 | 1B7AA3F7233CA49300F723A9 /* text2qr */, 85 | ); 86 | path = apps; 87 | sourceTree = ""; 88 | }; 89 | 1B7AA3F7233CA49300F723A9 /* text2qr */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | 1B7AA400233CAD7200F723A9 /* QRApp.swift */, 93 | 1B7AA3FC233CA50E00F723A9 /* QROutputViewController.swift */, 94 | 1B7AA3FD233CA50E00F723A9 /* QROutputViewController.xib */, 95 | ); 96 | path = text2qr; 97 | sourceTree = ""; 98 | }; 99 | 1BB3934A228129D700E09525 /* view */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | 1BB3934B228129F300E09525 /* SettingCellView.swift */, 103 | 1BB3934D22812A0300E09525 /* SettingCellView.xib */, 104 | ); 105 | path = view; 106 | sourceTree = ""; 107 | }; 108 | 1BB393A72283D15500E09525 /* common */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | 1BB6294C22801D75005B1295 /* Extension.swift */, 112 | 1BB393512282939900E09525 /* BaseOutlineView.swift */, 113 | 1BB39353228293B100E09525 /* BaseCellView.swift */, 114 | ); 115 | path = common; 116 | sourceTree = ""; 117 | }; 118 | 1BB393AD2283F78100E09525 /* resource */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 1BB393AE2283F78F00E09525 /* menubar */, 122 | ); 123 | path = resource; 124 | sourceTree = ""; 125 | }; 126 | 1BB62924227EC742005B1295 = { 127 | isa = PBXGroup; 128 | children = ( 129 | 1BB6292F227EC742005B1295 /* Commander */, 130 | 1BB6292E227EC742005B1295 /* Products */, 131 | ); 132 | sourceTree = ""; 133 | }; 134 | 1BB6292E227EC742005B1295 /* Products */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | 1BB6292D227EC742005B1295 /* Commander.app */, 138 | ); 139 | name = Products; 140 | sourceTree = ""; 141 | }; 142 | 1BB6292F227EC742005B1295 /* Commander */ = { 143 | isa = PBXGroup; 144 | children = ( 145 | 1BB393AD2283F78100E09525 /* resource */, 146 | 1BB6294822801C87005B1295 /* config */, 147 | 1BB62944227FE383005B1295 /* components */, 148 | 1F052687227FA9ED003D84C1 /* model */, 149 | 1BB6293E227ED521005B1295 /* pages */, 150 | 1B7AA3F6233CA3B200F723A9 /* apps */, 151 | 1BB62932227EC745005B1295 /* Assets.xcassets */, 152 | 1BB62930227EC742005B1295 /* AppDelegate.swift */, 153 | 1BB62934227EC745005B1295 /* MainMenu.xib */, 154 | 1BB62937227EC745005B1295 /* Info.plist */, 155 | 1BB62938227EC745005B1295 /* Commander.entitlements */, 156 | ); 157 | path = Commander; 158 | sourceTree = ""; 159 | }; 160 | 1BB6293E227ED521005B1295 /* pages */ = { 161 | isa = PBXGroup; 162 | children = ( 163 | 1F05267A227F1A22003D84C1 /* setting */, 164 | 1BB6293F227ED528005B1295 /* menu */, 165 | ); 166 | path = pages; 167 | sourceTree = ""; 168 | }; 169 | 1BB6293F227ED528005B1295 /* menu */ = { 170 | isa = PBXGroup; 171 | children = ( 172 | 1F052684227FA85E003D84C1 /* MenuController.swift */, 173 | ); 174 | path = menu; 175 | sourceTree = ""; 176 | }; 177 | 1BB62944227FE383005B1295 /* components */ = { 178 | isa = PBXGroup; 179 | children = ( 180 | 1BB393A72283D15500E09525 /* common */, 181 | ); 182 | path = components; 183 | sourceTree = ""; 184 | }; 185 | 1BB6294822801C87005B1295 /* config */ = { 186 | isa = PBXGroup; 187 | children = ( 188 | 1BB6294922801CD6005B1295 /* Config.swift */, 189 | ); 190 | path = config; 191 | sourceTree = ""; 192 | }; 193 | 1F05267A227F1A22003D84C1 /* setting */ = { 194 | isa = PBXGroup; 195 | children = ( 196 | 1BB3934A228129D700E09525 /* view */, 197 | 1F05267B227F1EFF003D84C1 /* ToolAddViewController.swift */, 198 | 1F05267C227F1EFF003D84C1 /* ToolAddViewController.xib */, 199 | 1F05268E2281C853003D84C1 /* ToolsetAddViewController.swift */, 200 | 1F05268F2281C853003D84C1 /* ToolsetAddViewController.xib */, 201 | 1BB39346228128FA00E09525 /* SettingViewController.swift */, 202 | 1BB39347228128FA00E09525 /* SettingViewController.xib */, 203 | 1B7AA404233CBC6700F723A9 /* AppAddViewController.swift */, 204 | 1B7AA405233CBC6700F723A9 /* AppAddViewController.xib */, 205 | ); 206 | path = setting; 207 | sourceTree = ""; 208 | }; 209 | 1F052687227FA9ED003D84C1 /* model */ = { 210 | isa = PBXGroup; 211 | children = ( 212 | 1F052688227FAADA003D84C1 /* ToolItem.swift */, 213 | 1F05268A227FAC55003D84C1 /* ToolSet.swift */, 214 | 1F05268C227FACB8003D84C1 /* ToolService.swift */, 215 | 1B7AA402233CAE2900F723A9 /* AppItem.swift */, 216 | ); 217 | path = model; 218 | sourceTree = ""; 219 | }; 220 | /* End PBXGroup section */ 221 | 222 | /* Begin PBXNativeTarget section */ 223 | 1BB6292C227EC742005B1295 /* Commander */ = { 224 | isa = PBXNativeTarget; 225 | buildConfigurationList = 1BB6293B227EC745005B1295 /* Build configuration list for PBXNativeTarget "Commander" */; 226 | buildPhases = ( 227 | 1BB62929227EC742005B1295 /* Sources */, 228 | 1BB6292A227EC742005B1295 /* Frameworks */, 229 | 1BB6292B227EC742005B1295 /* Resources */, 230 | ); 231 | buildRules = ( 232 | ); 233 | dependencies = ( 234 | ); 235 | name = Commander; 236 | productName = Commander; 237 | productReference = 1BB6292D227EC742005B1295 /* Commander.app */; 238 | productType = "com.apple.product-type.application"; 239 | }; 240 | /* End PBXNativeTarget section */ 241 | 242 | /* Begin PBXProject section */ 243 | 1BB62925227EC742005B1295 /* Project object */ = { 244 | isa = PBXProject; 245 | attributes = { 246 | LastSwiftUpdateCheck = 1010; 247 | LastUpgradeCheck = 1010; 248 | ORGANIZATIONNAME = woodpecker; 249 | TargetAttributes = { 250 | 1BB6292C227EC742005B1295 = { 251 | CreatedOnToolsVersion = 10.1; 252 | LastSwiftMigration = ""; 253 | SystemCapabilities = { 254 | com.apple.Sandbox = { 255 | enabled = 0; 256 | }; 257 | }; 258 | }; 259 | }; 260 | }; 261 | buildConfigurationList = 1BB62928227EC742005B1295 /* Build configuration list for PBXProject "Commander" */; 262 | compatibilityVersion = "Xcode 9.3"; 263 | developmentRegion = en; 264 | hasScannedForEncodings = 0; 265 | knownRegions = ( 266 | en, 267 | Base, 268 | ); 269 | mainGroup = 1BB62924227EC742005B1295; 270 | productRefGroup = 1BB6292E227EC742005B1295 /* Products */; 271 | projectDirPath = ""; 272 | projectRoot = ""; 273 | targets = ( 274 | 1BB6292C227EC742005B1295 /* Commander */, 275 | ); 276 | }; 277 | /* End PBXProject section */ 278 | 279 | /* Begin PBXResourcesBuildPhase section */ 280 | 1BB6292B227EC742005B1295 /* Resources */ = { 281 | isa = PBXResourcesBuildPhase; 282 | buildActionMask = 2147483647; 283 | files = ( 284 | 1BB62933227EC745005B1295 /* Assets.xcassets in Resources */, 285 | 1B7AA3FF233CA50E00F723A9 /* QROutputViewController.xib in Resources */, 286 | 1F0526912281C853003D84C1 /* ToolsetAddViewController.xib in Resources */, 287 | 1F05267E227F1EFF003D84C1 /* ToolAddViewController.xib in Resources */, 288 | 1BB393AF2283F78F00E09525 /* menubar in Resources */, 289 | 1BB62936227EC745005B1295 /* MainMenu.xib in Resources */, 290 | 1BB3934E22812A0300E09525 /* SettingCellView.xib in Resources */, 291 | 1B7AA407233CBC6700F723A9 /* AppAddViewController.xib in Resources */, 292 | 1BB39349228128FA00E09525 /* SettingViewController.xib in Resources */, 293 | ); 294 | runOnlyForDeploymentPostprocessing = 0; 295 | }; 296 | /* End PBXResourcesBuildPhase section */ 297 | 298 | /* Begin PBXSourcesBuildPhase section */ 299 | 1BB62929227EC742005B1295 /* Sources */ = { 300 | isa = PBXSourcesBuildPhase; 301 | buildActionMask = 2147483647; 302 | files = ( 303 | 1F052689227FAADA003D84C1 /* ToolItem.swift in Sources */, 304 | 1BB6294A22801CD6005B1295 /* Config.swift in Sources */, 305 | 1F05268B227FAC55003D84C1 /* ToolSet.swift in Sources */, 306 | 1F05268D227FACB8003D84C1 /* ToolService.swift in Sources */, 307 | 1BB6294D22801D75005B1295 /* Extension.swift in Sources */, 308 | 1B7AA401233CAD7200F723A9 /* QRApp.swift in Sources */, 309 | 1BB39354228293B100E09525 /* BaseCellView.swift in Sources */, 310 | 1BB3934C228129F300E09525 /* SettingCellView.swift in Sources */, 311 | 1B7AA406233CBC6700F723A9 /* AppAddViewController.swift in Sources */, 312 | 1BB39348228128FA00E09525 /* SettingViewController.swift in Sources */, 313 | 1B7AA3FE233CA50E00F723A9 /* QROutputViewController.swift in Sources */, 314 | 1B7AA403233CAE2900F723A9 /* AppItem.swift in Sources */, 315 | 1BB393522282939900E09525 /* BaseOutlineView.swift in Sources */, 316 | 1BB62931227EC742005B1295 /* AppDelegate.swift in Sources */, 317 | 1F0526902281C853003D84C1 /* ToolsetAddViewController.swift in Sources */, 318 | 1F052685227FA85E003D84C1 /* MenuController.swift in Sources */, 319 | 1F05267D227F1EFF003D84C1 /* ToolAddViewController.swift in Sources */, 320 | ); 321 | runOnlyForDeploymentPostprocessing = 0; 322 | }; 323 | /* End PBXSourcesBuildPhase section */ 324 | 325 | /* Begin PBXVariantGroup section */ 326 | 1BB62934227EC745005B1295 /* MainMenu.xib */ = { 327 | isa = PBXVariantGroup; 328 | children = ( 329 | 1BB62935227EC745005B1295 /* Base */, 330 | ); 331 | name = MainMenu.xib; 332 | sourceTree = ""; 333 | }; 334 | /* End PBXVariantGroup section */ 335 | 336 | /* Begin XCBuildConfiguration section */ 337 | 1BB62939227EC745005B1295 /* Debug */ = { 338 | isa = XCBuildConfiguration; 339 | buildSettings = { 340 | ALWAYS_SEARCH_USER_PATHS = NO; 341 | CLANG_ANALYZER_NONNULL = YES; 342 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 343 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 344 | CLANG_CXX_LIBRARY = "libc++"; 345 | CLANG_ENABLE_MODULES = YES; 346 | CLANG_ENABLE_OBJC_ARC = YES; 347 | CLANG_ENABLE_OBJC_WEAK = YES; 348 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 349 | CLANG_WARN_BOOL_CONVERSION = YES; 350 | CLANG_WARN_COMMA = YES; 351 | CLANG_WARN_CONSTANT_CONVERSION = YES; 352 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 353 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 354 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 355 | CLANG_WARN_EMPTY_BODY = YES; 356 | CLANG_WARN_ENUM_CONVERSION = YES; 357 | CLANG_WARN_INFINITE_RECURSION = YES; 358 | CLANG_WARN_INT_CONVERSION = YES; 359 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 360 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 361 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 362 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 363 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 364 | CLANG_WARN_STRICT_PROTOTYPES = YES; 365 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 366 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 367 | CLANG_WARN_UNREACHABLE_CODE = YES; 368 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 369 | CODE_SIGN_IDENTITY = "-"; 370 | COPY_PHASE_STRIP = NO; 371 | DEBUG_INFORMATION_FORMAT = dwarf; 372 | ENABLE_STRICT_OBJC_MSGSEND = YES; 373 | ENABLE_TESTABILITY = YES; 374 | GCC_C_LANGUAGE_STANDARD = gnu11; 375 | GCC_DYNAMIC_NO_PIC = NO; 376 | GCC_NO_COMMON_BLOCKS = YES; 377 | GCC_OPTIMIZATION_LEVEL = 0; 378 | GCC_PREPROCESSOR_DEFINITIONS = ( 379 | "DEBUG=1", 380 | "$(inherited)", 381 | ); 382 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 383 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 384 | GCC_WARN_UNDECLARED_SELECTOR = YES; 385 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 386 | GCC_WARN_UNUSED_FUNCTION = YES; 387 | GCC_WARN_UNUSED_VARIABLE = YES; 388 | MACOSX_DEPLOYMENT_TARGET = 10.14; 389 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 390 | MTL_FAST_MATH = YES; 391 | ONLY_ACTIVE_ARCH = YES; 392 | SDKROOT = macosx; 393 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 394 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 395 | }; 396 | name = Debug; 397 | }; 398 | 1BB6293A227EC745005B1295 /* Release */ = { 399 | isa = XCBuildConfiguration; 400 | buildSettings = { 401 | ALWAYS_SEARCH_USER_PATHS = NO; 402 | CLANG_ANALYZER_NONNULL = YES; 403 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 404 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 405 | CLANG_CXX_LIBRARY = "libc++"; 406 | CLANG_ENABLE_MODULES = YES; 407 | CLANG_ENABLE_OBJC_ARC = YES; 408 | CLANG_ENABLE_OBJC_WEAK = YES; 409 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 410 | CLANG_WARN_BOOL_CONVERSION = YES; 411 | CLANG_WARN_COMMA = YES; 412 | CLANG_WARN_CONSTANT_CONVERSION = YES; 413 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 414 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 415 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 416 | CLANG_WARN_EMPTY_BODY = YES; 417 | CLANG_WARN_ENUM_CONVERSION = YES; 418 | CLANG_WARN_INFINITE_RECURSION = YES; 419 | CLANG_WARN_INT_CONVERSION = YES; 420 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 421 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 422 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 423 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 424 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 425 | CLANG_WARN_STRICT_PROTOTYPES = YES; 426 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 427 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 428 | CLANG_WARN_UNREACHABLE_CODE = YES; 429 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 430 | CODE_SIGN_IDENTITY = "-"; 431 | COPY_PHASE_STRIP = NO; 432 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 433 | ENABLE_NS_ASSERTIONS = NO; 434 | ENABLE_STRICT_OBJC_MSGSEND = YES; 435 | GCC_C_LANGUAGE_STANDARD = gnu11; 436 | GCC_NO_COMMON_BLOCKS = YES; 437 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 438 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 439 | GCC_WARN_UNDECLARED_SELECTOR = YES; 440 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 441 | GCC_WARN_UNUSED_FUNCTION = YES; 442 | GCC_WARN_UNUSED_VARIABLE = YES; 443 | MACOSX_DEPLOYMENT_TARGET = 10.14; 444 | MTL_ENABLE_DEBUG_INFO = NO; 445 | MTL_FAST_MATH = YES; 446 | SDKROOT = macosx; 447 | SWIFT_COMPILATION_MODE = wholemodule; 448 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 449 | }; 450 | name = Release; 451 | }; 452 | 1BB6293C227EC745005B1295 /* Debug */ = { 453 | isa = XCBuildConfiguration; 454 | buildSettings = { 455 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 456 | CODE_SIGN_STYLE = Automatic; 457 | COMBINE_HIDPI_IMAGES = YES; 458 | FRAMEWORK_SEARCH_PATHS = ( 459 | "$(inherited)", 460 | "$(PROJECT_DIR)", 461 | ); 462 | INFOPLIST_FILE = Commander/Info.plist; 463 | LD_RUNPATH_SEARCH_PATHS = ( 464 | "$(inherited)", 465 | "@executable_path/../Frameworks", 466 | ); 467 | MACOSX_DEPLOYMENT_TARGET = 10.13; 468 | PRODUCT_BUNDLE_IDENTIFIER = lifebetter.commander; 469 | PRODUCT_NAME = "$(TARGET_NAME)"; 470 | SWIFT_VERSION = 4.2; 471 | }; 472 | name = Debug; 473 | }; 474 | 1BB6293D227EC745005B1295 /* Release */ = { 475 | isa = XCBuildConfiguration; 476 | buildSettings = { 477 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 478 | CODE_SIGN_STYLE = Automatic; 479 | COMBINE_HIDPI_IMAGES = YES; 480 | FRAMEWORK_SEARCH_PATHS = ( 481 | "$(inherited)", 482 | "$(PROJECT_DIR)", 483 | ); 484 | INFOPLIST_FILE = Commander/Info.plist; 485 | LD_RUNPATH_SEARCH_PATHS = ( 486 | "$(inherited)", 487 | "@executable_path/../Frameworks", 488 | ); 489 | MACOSX_DEPLOYMENT_TARGET = 10.13; 490 | PRODUCT_BUNDLE_IDENTIFIER = lifebetter.commander; 491 | PRODUCT_NAME = "$(TARGET_NAME)"; 492 | SWIFT_VERSION = 4.2; 493 | }; 494 | name = Release; 495 | }; 496 | /* End XCBuildConfiguration section */ 497 | 498 | /* Begin XCConfigurationList section */ 499 | 1BB62928227EC742005B1295 /* Build configuration list for PBXProject "Commander" */ = { 500 | isa = XCConfigurationList; 501 | buildConfigurations = ( 502 | 1BB62939227EC745005B1295 /* Debug */, 503 | 1BB6293A227EC745005B1295 /* Release */, 504 | ); 505 | defaultConfigurationIsVisible = 0; 506 | defaultConfigurationName = Release; 507 | }; 508 | 1BB6293B227EC745005B1295 /* Build configuration list for PBXNativeTarget "Commander" */ = { 509 | isa = XCConfigurationList; 510 | buildConfigurations = ( 511 | 1BB6293C227EC745005B1295 /* Debug */, 512 | 1BB6293D227EC745005B1295 /* Release */, 513 | ); 514 | defaultConfigurationIsVisible = 0; 515 | defaultConfigurationName = Release; 516 | }; 517 | /* End XCConfigurationList section */ 518 | }; 519 | rootObject = 1BB62925227EC742005B1295 /* Project object */; 520 | } 521 | -------------------------------------------------------------------------------- /Commander/Commander.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Commander/Commander.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Commander/Commander.xcodeproj/xcshareddata/xcschemes/Commander.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /Commander/Commander/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appwoodpecker/commander/4c9c80bc6dc603b896a79227a071c631dfcd1abe/Commander/Commander/.DS_Store -------------------------------------------------------------------------------- /Commander/Commander/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Commander 4 | // 5 | // Created by zhangxiaogang on 2019/5/5. 6 | // Copyright © 2019 lifebetter. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate { 13 | 14 | @IBOutlet weak var window: NSWindow! 15 | var controller: MenuController! 16 | 17 | func applicationDidFinishLaunching(_ aNotification: Notification) { 18 | Config.shared().setupEnvt() 19 | otherSetting() 20 | let controller = MenuController.shared() 21 | controller.window = self.window; 22 | controller.setup() 23 | self.controller = controller; 24 | } 25 | 26 | func otherSetting() { 27 | //disable smart quote 28 | UserDefaults.standard.set(false, forKey: "NSAutomaticQuoteSubstitutionEnabled") 29 | } 30 | 31 | 32 | func applicationWillTerminate(_ aNotification: Notification) { 33 | 34 | } 35 | 36 | 37 | } 38 | 39 | -------------------------------------------------------------------------------- /Commander/Commander/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x", 6 | "size" : "16x16" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "2x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "1x", 16 | "size" : "32x32" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "2x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "1x", 26 | "size" : "128x128" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "1x", 36 | "size" : "256x256" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "2x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "1x", 46 | "size" : "512x512" 47 | }, 48 | { 49 | "filename" : "appicon.png", 50 | "idiom" : "mac", 51 | "scale" : "2x", 52 | "size" : "512x512" 53 | } 54 | ], 55 | "info" : { 56 | "author" : "xcode", 57 | "version" : 1 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Commander/Commander/Assets.xcassets/AppIcon.appiconset/appicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appwoodpecker/commander/4c9c80bc6dc603b896a79227a071c631dfcd1abe/Commander/Commander/Assets.xcassets/AppIcon.appiconset/appicon.png -------------------------------------------------------------------------------- /Commander/Commander/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Commander/Commander/Assets.xcassets/menu.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "menu.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "menu@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "menu@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Commander/Commander/Assets.xcassets/menu.imageset/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appwoodpecker/commander/4c9c80bc6dc603b896a79227a071c631dfcd1abe/Commander/Commander/Assets.xcassets/menu.imageset/menu.png -------------------------------------------------------------------------------- /Commander/Commander/Assets.xcassets/menu.imageset/menu@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appwoodpecker/commander/4c9c80bc6dc603b896a79227a071c631dfcd1abe/Commander/Commander/Assets.xcassets/menu.imageset/menu@2x.png -------------------------------------------------------------------------------- /Commander/Commander/Assets.xcassets/menu.imageset/menu@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appwoodpecker/commander/4c9c80bc6dc603b896a79227a071c631dfcd1abe/Commander/Commander/Assets.xcassets/menu.imageset/menu@3x.png -------------------------------------------------------------------------------- /Commander/Commander/Assets.xcassets/tool.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "tool.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "tool@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "tool@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Commander/Commander/Assets.xcassets/tool.imageset/tool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appwoodpecker/commander/4c9c80bc6dc603b896a79227a071c631dfcd1abe/Commander/Commander/Assets.xcassets/tool.imageset/tool.png -------------------------------------------------------------------------------- /Commander/Commander/Assets.xcassets/tool.imageset/tool@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appwoodpecker/commander/4c9c80bc6dc603b896a79227a071c631dfcd1abe/Commander/Commander/Assets.xcassets/tool.imageset/tool@2x.png -------------------------------------------------------------------------------- /Commander/Commander/Assets.xcassets/tool.imageset/tool@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appwoodpecker/commander/4c9c80bc6dc603b896a79227a071c631dfcd1abe/Commander/Commander/Assets.xcassets/tool.imageset/tool@3x.png -------------------------------------------------------------------------------- /Commander/Commander/Base.lproj/MainMenu.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | Default 531 | 532 | 533 | 534 | 535 | 536 | 537 | Left to Right 538 | 539 | 540 | 541 | 542 | 543 | 544 | Right to Left 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | Default 556 | 557 | 558 | 559 | 560 | 561 | 562 | Left to Right 563 | 564 | 565 | 566 | 567 | 568 | 569 | Right to Left 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | -------------------------------------------------------------------------------- /Commander/Commander/Commander.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Commander/Commander/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0.2 21 | CFBundleVersion 22 | 3 23 | LSApplicationCategoryType 24 | public.app-category.productivity 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | LSUIElement 28 | 29 | NSHumanReadableCopyright 30 | Copyright © 2019 lifebetter. All rights reserved. 31 | NSMainNibFile 32 | MainMenu 33 | NSPrincipalClass 34 | NSApplication 35 | 36 | 37 | -------------------------------------------------------------------------------- /Commander/Commander/apps/text2qr/QRApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QRApp.swift 3 | // Commander 4 | // 5 | // Created by zhangxiaogang on 2019/9/26. 6 | // Copyright © 2019 woodpecker. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class QRApp: AppItem { 12 | 13 | override class func name() -> String { 14 | return "Pasteboard QRCode" 15 | } 16 | 17 | override func run() -> Void { 18 | if let text = NSPasteboard.general.string(forType: NSPasteboard.PasteboardType.string) { 19 | MenuController.shared().disimss() 20 | let resultPage = QROutputViewController.init(nibName: "QROutputViewController", bundle: nil) 21 | resultPage.text = text 22 | if let window = MenuController.shared().window { 23 | window.contentViewController = resultPage 24 | window.title = "" 25 | window.makeKeyAndOrderFront(nil) 26 | window.orderFrontRegardless() 27 | } 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /Commander/Commander/apps/text2qr/QROutputViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QROutputViewController.swift 3 | // Commander 4 | // 5 | // Created by zhangxiaogang on 2019/9/26. 6 | // Copyright © 2019 woodpecker. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class QROutputViewController: NSViewController { 12 | 13 | var text: String? 14 | 15 | @IBOutlet weak var imageView: NSImageView! 16 | 17 | @IBOutlet weak var textTextField: NSTextField! 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | if(self.text == nil) { 21 | return 22 | } 23 | textTextField.maximumNumberOfLines = 6 24 | genQR() 25 | } 26 | 27 | func genQR() { 28 | guard let text = text else { 29 | return 30 | } 31 | guard let filter = CIFilter.init(name: "CIQRCodeGenerator") else { 32 | textTextField.stringValue = "convert to qr failed" 33 | return 34 | } 35 | filter.setDefaults() 36 | if let data = text.data(using: String.Encoding.utf8) { 37 | filter.setValue(data, forKey: "inputMessage") 38 | filter.setValue("H", forKey: "inputCorrectionLevel"); 39 | } 40 | if let ciImage = filter.outputImage { 41 | //scale qr 10x 42 | let scale = 10.0 as CGFloat 43 | let transform = CGAffineTransform.init(scaleX: scale, y: scale) 44 | let scaledImage = ciImage.transformed(by: transform) 45 | let context = CIContext(options: nil) 46 | if let cgImage = context.createCGImage(scaledImage, from: scaledImage.extent) { 47 | let size = scaledImage.extent.size 48 | let image = NSImage.init(cgImage: cgImage, size: size) 49 | self.imageView.image = image 50 | } 51 | } 52 | textTextField.stringValue = text 53 | textTextField.toolTip = text 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /Commander/Commander/apps/text2qr/QROutputViewController.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 | -------------------------------------------------------------------------------- /Commander/Commander/components/common/BaseCellView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseCellView.swift 3 | // Commander 4 | // 5 | // Created by zhangxiaogang on 2019/5/8. 6 | // Copyright © 2019 woodpecker. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class BaseCellView: NSTableCellView { 12 | 13 | weak var delegate: BaseCellViewDelegate? 14 | 15 | override func mouseDown(with event: NSEvent) { 16 | let clickCount = event.clickCount; 17 | if clickCount == 1 { 18 | if let delegate = self.delegate { 19 | delegate.cellClicked(self) 20 | } 21 | super.mouseDown(with: event) 22 | }else if(clickCount == 2){ 23 | if let delegate = self.delegate { 24 | delegate.cellDoubleClicked(self) 25 | } 26 | } 27 | } 28 | 29 | override func rightMouseUp(with event: NSEvent) { 30 | let windowPos = event.locationInWindow; 31 | let point = self.convert(windowPos, from: nil) 32 | if let delegate = self.delegate { 33 | delegate.cellRightClicked(self,position: point) 34 | } 35 | super.rightMouseUp(with: event) 36 | } 37 | 38 | } 39 | 40 | 41 | protocol BaseCellViewDelegate : AnyObject { 42 | 43 | func cellClicked(_ cell: BaseCellView) 44 | func cellDoubleClicked(_ cell: BaseCellView) 45 | func cellRightClicked(_ cell: BaseCellView, position: NSPoint) 46 | 47 | } 48 | -------------------------------------------------------------------------------- /Commander/Commander/components/common/BaseOutlineView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseOutlineView.swift 3 | // Commander 4 | // 5 | // Created by zhangxiaogang on 2019/5/8. 6 | // Copyright © 2019 woodpecker. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class BaseOutlineView: NSOutlineView { 12 | 13 | override func validateProposedFirstResponder(_ responder: NSResponder, for event: NSEvent?) -> Bool { 14 | return true 15 | } 16 | 17 | 18 | 19 | 20 | 21 | } 22 | -------------------------------------------------------------------------------- /Commander/Commander/components/common/Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extension.swift 3 | // Commander 4 | // 5 | // Created by zhangxiaogang on 2019/5/6. 6 | // Copyright © 2019 woodpecker. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AppKit 11 | 12 | 13 | extension String { 14 | 15 | func appendingPathComponent(_ path: String) -> String { 16 | let slashText = "/" 17 | let hasSlash = self.hasSuffix(slashText) || path.hasPrefix(slashText) 18 | var mPath = "" 19 | mPath.append(self) 20 | if !hasSlash { 21 | mPath.append(slashText) 22 | } 23 | mPath.append(path) 24 | return mPath 25 | } 26 | 27 | //test.py -> py 28 | func pathExtension() -> String? { 29 | let path = self 30 | let components = path.components(separatedBy: ".") 31 | var ext :String? 32 | if components.count > 1 { 33 | ext = components.last 34 | } 35 | return ext 36 | } 37 | 38 | func jsonObject() -> [String: Any]? { 39 | var dict :[String: Any]? 40 | if let data = self.data(using: String.Encoding.utf8) { 41 | do { 42 | let result = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.init(rawValue: 0)) 43 | if result is [String: Any] { 44 | dict = result as? [String : Any] 45 | } 46 | }catch { 47 | 48 | } 49 | } 50 | return dict 51 | } 52 | } 53 | 54 | extension Dictionary { 55 | func jsonText() -> String? { 56 | var text : String? 57 | do { 58 | let data = try JSONSerialization.data(withJSONObject: self, options: JSONSerialization.WritingOptions.init(rawValue: 0)) 59 | text = String.init(data: data, encoding: String.Encoding.utf8) 60 | }catch { 61 | print(error) 62 | } 63 | return text 64 | } 65 | } 66 | 67 | extension NSImage { 68 | func roundCorners(withRadius radius: CGFloat) -> NSImage { 69 | let rect = NSRect(origin: NSPoint.zero, size: size) 70 | if 71 | let cgImage = self.cgImage, 72 | let context = CGContext(data: nil, 73 | width: Int(size.width), 74 | height: Int(size.height), 75 | bitsPerComponent: 8, 76 | bytesPerRow: 4 * Int(size.width), 77 | space: CGColorSpaceCreateDeviceRGB(), 78 | bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue) { 79 | context.beginPath() 80 | context.addPath(CGPath(roundedRect: rect, cornerWidth: radius, cornerHeight: radius, transform: nil)) 81 | context.closePath() 82 | context.clip() 83 | context.draw(cgImage, in: rect) 84 | 85 | if let composedImage = context.makeImage() { 86 | return NSImage(cgImage: composedImage, size: size) 87 | } 88 | } 89 | return self 90 | } 91 | 92 | func roundImage() -> NSImage { 93 | let width = self.size.width 94 | let height = self.size.height 95 | let radius = min(width, height)/2.0 96 | return self.roundCorners(withRadius: radius) 97 | 98 | } 99 | } 100 | 101 | fileprivate extension NSImage { 102 | var cgImage: CGImage? { 103 | var rect = CGRect.init(origin: .zero, size: self.size) 104 | return self.cgImage(forProposedRect: &rect, context: nil, hints: nil) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /Commander/Commander/config/Config.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Config.swift 3 | // Commander 4 | // 5 | // Created by zhangxiaogang on 2019/5/6. 6 | // Copyright © 2019 woodpecker. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class Config: NSObject { 12 | 13 | private override init() { 14 | 15 | } 16 | 17 | private static let sharedInstance = Config.init() 18 | 19 | class func shared() -> Config { 20 | return sharedInstance 21 | } 22 | 23 | func supportScriptTypes() -> [String] { 24 | return ["sh","py"] 25 | } 26 | 27 | func selectScriptTypes() -> [String] { 28 | let scriptTypes = ["Shell (.sh)","Python (.py)"] 29 | return scriptTypes 30 | } 31 | 32 | func exeForScriptFile(_ scriptPath: String) -> String? { 33 | var exe: String? 34 | if scriptPath.hasSuffix(".sh") { 35 | exe = "/bin/bash" 36 | }else if scriptPath.hasSuffix(".py") { 37 | exe = "/usr/bin/python" 38 | } 39 | return exe 40 | } 41 | 42 | func setupEnvt() { 43 | let path = rootPath() 44 | let fm = FileManager.default 45 | let exists = fm.fileExists(atPath: path) 46 | if !exists { 47 | try? fm.createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil) 48 | if let defaultPath = Bundle.main.path(forResource: "menubar", ofType: nil) { 49 | try? fm.copyItem(atPath: defaultPath, toPath: workPath()) 50 | } 51 | } 52 | } 53 | 54 | func rootPath() -> String { 55 | let paths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true) 56 | let docPath = paths.first 57 | let rootPath = docPath?.appendingPathComponent("Commander") 58 | return rootPath! 59 | } 60 | 61 | func workPath() -> String { 62 | return rootPath().appendingPathComponent("menubar") 63 | } 64 | 65 | func getAppList() -> [String] { 66 | return ["QRApp"] 67 | } 68 | 69 | func defaultPackage() -> String { 70 | return "Commander" 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /Commander/Commander/model/AppItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppItem.swift 3 | // Commander 4 | // 5 | // Created by zhangxiaogang on 2019/9/26. 6 | // Copyright © 2019 woodpecker. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class AppItem: NSObject { 12 | 13 | required override init() { 14 | super.init() 15 | } 16 | 17 | class func name() -> String { 18 | return "" 19 | } 20 | 21 | func run() -> Void { 22 | 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /Commander/Commander/model/ToolItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ToolItem.swift 3 | // Commander 4 | // 5 | // Created by zhangxiaogang on 2019/5/6. 6 | // Copyright © 2019 woodpecker. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class ToolItem: NSObject { 12 | 13 | var title: String! 14 | var scriptFile: String? 15 | var appId: String? 16 | var toolset: ToolSet! 17 | 18 | func fileName() -> String { 19 | return self.title + ".bundle" 20 | } 21 | 22 | func path() -> String { 23 | return self.toolset.absolutePath().appendingPathComponent(fileName()) 24 | } 25 | 26 | func scriptPath() -> String { 27 | return path().appendingPathComponent(scriptFile!) 28 | } 29 | 30 | func iconPath() -> String { 31 | return path().appendingPathComponent("icon.png") 32 | } 33 | 34 | func isApp() -> Bool { 35 | return appId != nil 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /Commander/Commander/model/ToolService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ToolService.swift 3 | // Commander 4 | // 5 | // Created by zhangxiaogang on 2019/5/6. 6 | // Copyright © 2019 woodpecker. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class ToolService: NSObject { 12 | 13 | private override init() { 14 | 15 | } 16 | 17 | private static let sharedInstance = ToolService.init() 18 | 19 | class func shared() -> ToolService { 20 | return sharedInstance 21 | } 22 | 23 | func loadTool() -> ToolSet { 24 | let path = Config.shared().workPath() 25 | let rootSet = ToolSet.init() 26 | rootSet.title = "root" 27 | rootSet.path = "/" 28 | rootSet.children = [] 29 | traversePath(path: rootSet.path, parentSet: rootSet, workPath: path) 30 | return rootSet 31 | } 32 | 33 | func traversePath(path: String, parentSet: ToolSet, workPath: String) { 34 | let filePath = workPath.appendingPathComponent(path) 35 | let fm = FileManager.default 36 | let filenames = try? fm.contentsOfDirectory(atPath: filePath) 37 | for name in filenames! { 38 | if name.hasPrefix(".") { 39 | continue 40 | } 41 | let itemPath = filePath.appendingPathComponent(name) 42 | let attributes = try? fm .attributesOfItem(atPath: itemPath) 43 | if attributes == nil { 44 | continue; 45 | } 46 | let type: String = attributes![FileAttributeKey.type] as! String 47 | if(type == FileAttributeType.typeDirectory.rawValue) { 48 | if name.hasSuffix("bundle") { 49 | //tool or app 50 | var title: String? 51 | var scriptFile: String? 52 | var appId: String? 53 | 54 | let appFilename = "app.json" 55 | let appPath = itemPath.appendingPathComponent(appFilename) 56 | if fm.fileExists(atPath: appPath) { 57 | do { 58 | let infoText = try String.init(contentsOfFile: appPath) 59 | if let appInfo = infoText.jsonObject() { 60 | appId = appInfo["id"] as? String 61 | } 62 | }catch { 63 | 64 | } 65 | } 66 | //title 67 | title = name.replacingOccurrences(of: ".bundle", with: "") 68 | //script 69 | let types = Config.shared().supportScriptTypes() 70 | var scriptFileName :String? 71 | for type in types { 72 | let fileName = "main." + type 73 | let filePath = itemPath.appendingPathComponent(fileName) 74 | let exists = fm.fileExists(atPath: filePath) 75 | if exists { 76 | scriptFileName = fileName 77 | break 78 | } 79 | } 80 | if scriptFileName != nil { 81 | scriptFile = scriptFileName 82 | } 83 | if title != nil && (scriptFileName != nil || appId != nil) { 84 | let toolItem = ToolItem.init() 85 | toolItem.title = title 86 | toolItem.scriptFile = scriptFile 87 | toolItem.appId = appId 88 | parentSet.children!.append(toolItem) 89 | toolItem.toolset = parentSet 90 | } 91 | }else { 92 | //set 93 | var title: String? 94 | var path: String? 95 | //title 96 | title = name 97 | //path 98 | path = parentSet.path.appendingPathComponent(name) 99 | if title != nil && path != nil { 100 | let set = ToolSet.init() 101 | set.title = title 102 | set.path = path; 103 | set.children = [] 104 | parentSet.children.append(set) 105 | set.parentSet = parentSet 106 | //continue traverse 107 | traversePath(path: set.path, parentSet: set, workPath: workPath) 108 | } 109 | } 110 | } 111 | } 112 | ///sort children 113 | let children = parentSet.children 114 | let sortList = parentSet.getSortList() 115 | var sortedChildren = [Any].init() 116 | for name in sortList { 117 | var targetItem: Any? 118 | for item in children! { 119 | if item is ToolItem { 120 | let toolItem = item as! ToolItem 121 | if toolItem.fileName() == name { 122 | targetItem = item 123 | break 124 | } 125 | }else if item is ToolSet { 126 | let toolSet = item as! ToolSet 127 | if toolSet.title == name { 128 | targetItem = item 129 | break; 130 | } 131 | } 132 | } 133 | if targetItem != nil { 134 | sortedChildren.append(targetItem!) 135 | } 136 | } 137 | //items that not in sort list 138 | var leftItems = [Any].init() 139 | for item in children! { 140 | var exists = false 141 | exists = sortedChildren.contains(where: { (aItem) -> Bool in 142 | if item is ToolItem && aItem is ToolItem { 143 | let toolItem = item as! ToolItem 144 | let aToolItem = aItem as! ToolItem 145 | return (toolItem == aToolItem) 146 | }else if item is ToolSet && aItem is ToolSet { 147 | let toolItem = item as! ToolSet 148 | let aToolItem = aItem as! ToolSet 149 | return (toolItem == aToolItem) 150 | }else { 151 | return false 152 | } 153 | }) 154 | if !exists { 155 | leftItems.append(item) 156 | } 157 | } 158 | //sort left items 159 | leftItems.sort { (item1, item2) -> Bool in 160 | var title1: String? 161 | var title2: String? 162 | if item1 is ToolItem { 163 | let toolItem = item1 as! ToolItem 164 | title1 = toolItem.title 165 | }else if item1 is ToolSet { 166 | let toolSet = item1 as! ToolSet 167 | title1 = toolSet.title 168 | } 169 | 170 | if item2 is ToolItem { 171 | let toolItem = item2 as! ToolItem 172 | title2 = toolItem.title 173 | }else if item2 is ToolSet { 174 | let toolSet = item2 as! ToolSet 175 | title2 = toolSet.title 176 | } 177 | if title1 == nil || title2 == nil { 178 | return false 179 | }else { 180 | let result = title1!.compare(title2!) 181 | if result == ComparisonResult.orderedDescending { 182 | return false 183 | }else { 184 | return true 185 | } 186 | } 187 | } 188 | //append left items 189 | sortedChildren.append(contentsOf: leftItems) 190 | parentSet.children = sortedChildren 191 | } 192 | 193 | //add tool 194 | func doAddTool(title: String, scriptType:String, scriptText: String, iconURL:URL?, parent: ToolSet) -> ToolItem? { 195 | let groupPath = parent.absolutePath() 196 | let groupURL = URL.init(fileURLWithPath: groupPath) 197 | 198 | ///everything is okay, now let's create it 199 | //1.tool bundle 200 | let bundleName = title + ".bundle" 201 | let bundleURL = groupURL.appendingPathComponent(bundleName) 202 | let fm = FileManager.default 203 | try? fm.createDirectory(at: bundleURL, withIntermediateDirectories: true, attributes: nil) 204 | //2.save code 205 | let scriptName = "main." + scriptType 206 | let scriptURL = bundleURL.appendingPathComponent(scriptName) 207 | let scriptPath = scriptURL.path 208 | let scriptData = scriptText.data(using: String.Encoding.utf8) 209 | let result = fm.createFile(atPath: scriptPath, contents: scriptData, attributes: nil) 210 | if !result { 211 | return nil; 212 | } 213 | //3.icon 214 | if let iconFileURL = iconURL { 215 | let targetURL = bundleURL.appendingPathComponent("icon.png") 216 | try? fm.copyItem(at: iconFileURL, to: targetURL) 217 | } 218 | let toolItem = ToolItem.init() 219 | toolItem.title = title 220 | toolItem.scriptFile = scriptName 221 | toolItem.toolset = parent 222 | parent.children.append(toolItem) 223 | return toolItem 224 | } 225 | 226 | //edit tool 227 | func doEditTool(title: String, scriptType:String, scriptText: String, iconURL:URL?, toolItem: ToolItem) { 228 | let setURL = toolItem.toolset.absoluteURL() 229 | let newFileName = title + ".bundle" 230 | let fm = FileManager.default 231 | //1.name 232 | if toolItem.title != title { 233 | let oldURL = setURL.appendingPathComponent(toolItem.fileName()) 234 | let newURL = setURL.appendingPathComponent(newFileName) 235 | try? fm.moveItem(at: oldURL, to: newURL) 236 | toolItem.title = title 237 | } 238 | let bundleURL = toolItem.toolset.absoluteURL().appendingPathComponent(newFileName) 239 | //2.code 240 | let scriptName = "main." + scriptType 241 | if let scriptData = scriptText.data(using: String.Encoding.utf8) { 242 | let scriptURL = bundleURL.appendingPathComponent(scriptName) 243 | try? scriptData.write(to:scriptURL) 244 | toolItem.scriptFile = scriptName 245 | } 246 | //3.icon 247 | if let iconFileURL = iconURL { 248 | let targetURL = bundleURL.appendingPathComponent("icon.png") 249 | //remove old icon 250 | try? fm.removeItem(at: targetURL) 251 | try? fm.copyItem(at: iconFileURL, to: targetURL) 252 | } 253 | } 254 | 255 | //delete tool item 256 | func doDeleteTool(_ toolItem: ToolItem) { 257 | let path = toolItem.path() 258 | let fm = FileManager.default 259 | try? fm.removeItem(atPath: path) 260 | } 261 | 262 | //delete toolset 263 | func doDeleteToolset(_ toolset: ToolSet) { 264 | let path = toolset.absolutePath() 265 | let fm = FileManager.default 266 | try? fm.removeItem(atPath: path) 267 | } 268 | 269 | //save order 270 | func doSaveToolsetOrder(_ toolset: ToolSet) { 271 | var configData: [String:Any]? 272 | let configPath = toolset.configPath() 273 | if let data = NSData.init(contentsOfFile: configPath) { 274 | configData = try? JSONSerialization.jsonObject(with: data as Data, options: JSONSerialization.ReadingOptions.init(rawValue: 0)) as! [String:Any] 275 | } 276 | var sortList = [String].init() 277 | for item in toolset.children { 278 | if item is ToolItem { 279 | let toolItem = item as! ToolItem 280 | sortList.append(toolItem.fileName()) 281 | }else if item is ToolSet { 282 | let toolSet = item as! ToolSet 283 | sortList.append(toolSet.title) 284 | } 285 | } 286 | if configData == nil { 287 | configData = [String:Any].init() 288 | } 289 | configData!["sort"] = sortList 290 | let fm = FileManager.default 291 | if !fm.fileExists(atPath: configPath) { 292 | fm.createFile(atPath: configPath, contents: nil, attributes: nil) 293 | } 294 | if let fileData = try? JSONSerialization.data(withJSONObject: configData!, options: JSONSerialization.WritingOptions.init(rawValue: 0)) { 295 | let fileURL = URL.init(fileURLWithPath: configPath) 296 | try? fileData.write(to: fileURL) 297 | } 298 | } 299 | 300 | //add app 301 | func doAddApp(title: String, appId: String, iconURL:URL?, parent: ToolSet) -> ToolItem? { 302 | let groupPath = parent.absolutePath() 303 | let groupURL = URL.init(fileURLWithPath: groupPath) 304 | 305 | ///everything is okay, now let's create it 306 | //1.tool bundle 307 | let bundleName = title + ".bundle" 308 | let bundleURL = groupURL.appendingPathComponent(bundleName) 309 | let fm = FileManager.default 310 | try? fm.createDirectory(at: bundleURL, withIntermediateDirectories: true, attributes: nil) 311 | //2.save app 312 | let appFilename = "app.json" 313 | let appURL = bundleURL.appendingPathComponent(appFilename) 314 | let appPath = appURL.path 315 | let appInfo = ["id":appId] 316 | if let infoText = appInfo.jsonText() { 317 | let data = infoText.data(using: String.Encoding.utf8) 318 | let result = fm.createFile(atPath: appPath, contents: data, attributes: nil) 319 | if !result { 320 | return nil; 321 | } 322 | } 323 | 324 | //3.icon 325 | if let iconFileURL = iconURL { 326 | let targetURL = bundleURL.appendingPathComponent("icon.png") 327 | try? fm.copyItem(at: iconFileURL, to: targetURL) 328 | } 329 | let toolItem = ToolItem.init() 330 | toolItem.title = title 331 | toolItem.appId = appId 332 | toolItem.toolset = parent 333 | parent.children.append(toolItem) 334 | return toolItem 335 | } 336 | 337 | //edit app 338 | func doEditApp(title: String, appId: String, iconURL:URL?, toolItem: ToolItem) { 339 | let setURL = toolItem.toolset.absoluteURL() 340 | let newFileName = title + ".bundle" 341 | let fm = FileManager.default 342 | //1.name 343 | if toolItem.title != title { 344 | let oldURL = setURL.appendingPathComponent(toolItem.fileName()) 345 | let newURL = setURL.appendingPathComponent(newFileName) 346 | try? fm.moveItem(at: oldURL, to: newURL) 347 | toolItem.title = title 348 | } 349 | let bundleURL = toolItem.toolset.absoluteURL().appendingPathComponent(newFileName) 350 | //2.app 351 | let appFilename = "app.json" 352 | let appURL = bundleURL.appendingPathComponent(appFilename) 353 | let appPath = appURL.path 354 | let appInfo = ["id":appId] 355 | if let infoText = appInfo.jsonText() { 356 | let data = infoText.data(using: String.Encoding.utf8) 357 | fm.createFile(atPath: appPath, contents: data, attributes: nil) 358 | } 359 | //3.icon 360 | if let iconFileURL = iconURL { 361 | let targetURL = bundleURL.appendingPathComponent("icon.png") 362 | //remove old icon 363 | try? fm.removeItem(at: targetURL) 364 | try? fm.copyItem(at: iconFileURL, to: targetURL) 365 | } 366 | } 367 | 368 | } 369 | -------------------------------------------------------------------------------- /Commander/Commander/model/ToolSet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ToolSet.swift 3 | // Commander 4 | // 5 | // Created by zhangxiaogang on 2019/5/6. 6 | // Copyright © 2019 woodpecker. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class ToolSet: NSObject { 12 | 13 | var title: String! 14 | var path: String! 15 | var children: [Any]! 16 | var parentSet: ToolSet? 17 | 18 | func isRoot() -> Bool { 19 | return self.path == "/" 20 | } 21 | 22 | func iconPath() -> String { 23 | return absolutePath().appendingPathComponent("icon.png") 24 | } 25 | 26 | func absolutePath() -> String { 27 | return Config.shared().workPath().appendingPathComponent(self.path) 28 | } 29 | 30 | func absoluteURL() -> URL { 31 | let url = URL.init(fileURLWithPath: absolutePath()) 32 | return url 33 | } 34 | 35 | func configPath() -> String { 36 | return absolutePath().appendingPathComponent("config.json") 37 | } 38 | 39 | func getSortList() -> [String] { 40 | var sortList :[String]? 41 | let configPath = self.configPath() 42 | if let data = NSData.init(contentsOfFile: configPath) { 43 | if let jsonObj = try? JSONSerialization.jsonObject(with: data as Data, options: JSONSerialization.ReadingOptions.init(rawValue: 0)) { 44 | let configData = jsonObj as! [String : Any] 45 | sortList = (configData["sort"] as! [String]) 46 | } 47 | } 48 | if sortList == nil { 49 | sortList = [String].init() 50 | } 51 | return sortList! 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /Commander/Commander/pages/menu/MenuController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StatusMenuController.swift 3 | // Commander 4 | // 5 | // Created by zhangxiaogang on 2019/5/6. 6 | // Copyright © 2019 woodpecker. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class MenuController: NSObject { 12 | 13 | var statusItem: NSStatusItem! 14 | var toolSet: ToolSet! 15 | var menu: NSMenu! 16 | var window: NSWindow! 17 | var contentVC: NSViewController? 18 | 19 | private override init() { 20 | 21 | } 22 | 23 | private static let sharedInstance = MenuController.init() 24 | 25 | class func shared() -> MenuController { 26 | return sharedInstance 27 | } 28 | 29 | func disimss() -> Void { 30 | if let menu = self.statusItem.menu { 31 | menu.cancelTracking() 32 | } 33 | } 34 | 35 | func setup() { 36 | //setup status item 37 | let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) 38 | if let image = NSImage.init(named: "menu") { 39 | image.isTemplate = true 40 | statusItem.button?.image = image 41 | } 42 | // statusItem.button?.title = "🐈" 43 | self.statusItem = statusItem 44 | loadContent() 45 | } 46 | 47 | func loadContent() { 48 | //load tools 49 | let toolset = ToolService.shared().loadTool() 50 | self.toolSet = toolset 51 | //load menu 52 | reloadMenu() 53 | } 54 | 55 | func reloadMenu() { 56 | if let toolSet = self.toolSet { 57 | //menu 58 | let menu = NSMenu.init() 59 | statusItem.menu = menu 60 | self.menu = menu; 61 | traverseAddMenuItem(toolSet:toolSet, menu: menu) 62 | //separator 63 | if menu.items.count > 0 { 64 | let lineItem = NSMenuItem.separator() 65 | menu.addItem(lineItem) 66 | } 67 | //setting 68 | let settingItem = NSMenuItem.init() 69 | settingItem.title = "Setting" 70 | settingItem.target = self; 71 | settingItem.action = #selector(MenuController.settingMenuSelected) 72 | menu.addItem(settingItem) 73 | let exitItem = NSMenuItem.init() 74 | exitItem.title = "Quit" 75 | exitItem.target = self; 76 | exitItem.action = #selector(MenuController.quitMenuSelected) 77 | menu.addItem(exitItem) 78 | } 79 | } 80 | 81 | func traverseAddMenuItem(toolSet:ToolSet, menu: NSMenu) { 82 | let children = toolSet.children 83 | for item in children! { 84 | if item is ToolItem { 85 | let toolItem = item as! ToolItem 86 | let menuItem = menuItemWithTool(toolItem) 87 | menu.addItem(menuItem) 88 | }else if item is ToolSet { 89 | let toolSet = item as! ToolSet 90 | let menuItem = menuItemWithToolSet(toolSet) 91 | menu.addItem(menuItem) 92 | let subMenu = menuItem.submenu! 93 | traverseAddMenuItem(toolSet: toolSet, menu: subMenu) 94 | } 95 | } 96 | } 97 | 98 | func menuItemWithTool(_ toolItem :ToolItem) -> NSMenuItem { 99 | let menuItem = NSMenuItem.init() 100 | menuItem.title = toolItem.title 101 | let iconPath = toolItem.iconPath() 102 | if let image = NSImage.init(contentsOfFile: iconPath) { 103 | image.size = NSMakeSize(16, 16) 104 | menuItem.image = image 105 | } 106 | menuItem.representedObject = toolItem 107 | menuItem.target = self 108 | menuItem.action = #selector(MenuController.toolMenuSelected(menuItem:)) 109 | return menuItem 110 | } 111 | 112 | func menuItemWithToolSet(_ toolSet :ToolSet) -> NSMenuItem { 113 | let menuItem = NSMenuItem.init() 114 | menuItem.title = toolSet.title 115 | let iconPath = toolSet.iconPath() 116 | if let image = NSImage.init(contentsOfFile: iconPath) { 117 | let roundImage = image.roundImage() 118 | roundImage.size = NSMakeSize(16, 16) 119 | menuItem.image = roundImage 120 | } 121 | let subMenu = NSMenu.init() 122 | menuItem.submenu = subMenu 123 | return menuItem 124 | } 125 | 126 | @objc func toolMenuSelected(menuItem: NSMenuItem) { 127 | let toolItem = menuItem.representedObject as! ToolItem 128 | if toolItem.isApp() { 129 | let appId = toolItem.appId! 130 | let className = Config.shared().defaultPackage() + "." + appId; 131 | let clazz = NSClassFromString(className) as! AppItem.Type 132 | let appObj = clazz.init() 133 | appObj.run() 134 | }else { 135 | let scriptPath = toolItem.scriptPath() 136 | let exe = Config.shared().exeForScriptFile(scriptPath) 137 | if let exePath = exe { 138 | let task = Process.init() 139 | task.launchPath = exePath 140 | task.arguments = [scriptPath] 141 | task.launch() 142 | } 143 | } 144 | } 145 | 146 | @objc func settingMenuSelected() { 147 | let vc = SettingViewController.init(nibName: "SettingViewController", bundle: nil) 148 | self.contentVC = vc 149 | vc.setToolset(toolset: self.toolSet) 150 | self.window.contentViewController = self.contentVC 151 | self.window.title = "Setting" 152 | self.window.makeKeyAndOrderFront(nil) 153 | self.window.orderFrontRegardless() 154 | } 155 | 156 | @objc func quitMenuSelected() { 157 | exit(1) 158 | } 159 | 160 | 161 | 162 | } 163 | -------------------------------------------------------------------------------- /Commander/Commander/pages/setting/AppAddViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppAddViewController.swift 3 | // Commander 4 | // 5 | // Created by zhangxiaogang on 2019/9/26. 6 | // Copyright © 2019 woodpecker. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class AppAddViewController: NSViewController { 12 | 13 | 14 | @IBOutlet weak var iconImageView: NSImageView! 15 | @IBOutlet weak var nameTextfield: NSTextField! 16 | @IBOutlet weak var appPopupButton: NSPopUpButton! 17 | 18 | var iconURL: URL? 19 | //add 20 | var toolset: ToolSet? 21 | //edit 22 | var toolItem: ToolItem? 23 | var completionCallback: ((ToolItem) -> Void)? 24 | var cancelCallback: (() -> Void)? 25 | 26 | override func viewDidLoad() { 27 | super.viewDidLoad() 28 | setup() 29 | loadContent() 30 | } 31 | 32 | func setup() { 33 | appPopupButton.removeAllItems() 34 | let list = Config.shared().getAppList() 35 | for item in list { 36 | let className = Config.shared().defaultPackage() + "." + item 37 | let clazz = NSClassFromString(className) as! AppItem.Type 38 | let name = clazz.name() 39 | appPopupButton.addItem(withTitle: name) 40 | } 41 | } 42 | 43 | func isEdit() -> Bool { 44 | return (self.toolItem != nil) 45 | } 46 | 47 | func isAddNew() -> Bool { 48 | return !isEdit() 49 | } 50 | 51 | func loadContent() { 52 | if !isEdit() { 53 | return 54 | } 55 | let toolItem = self.toolItem! 56 | //icon 57 | let iconPath = toolItem.iconPath() 58 | let image = NSImage.init(contentsOfFile: iconPath) 59 | self.iconImageView.image = image 60 | //name 61 | self.nameTextfield.stringValue = toolItem.title 62 | //app 63 | if let appId = toolItem.appId { 64 | let list = Config.shared().getAppList() 65 | if let appIndex = list.firstIndex(of: appId) { 66 | self.appPopupButton.selectItem(at: appIndex) 67 | } 68 | } 69 | } 70 | 71 | @IBAction func iconButtonPressed(_ sender: Any) { 72 | let panel = NSOpenPanel.init() 73 | panel.allowedFileTypes = ["png"] 74 | let result = panel.runModal() 75 | if result != NSApplication.ModalResponse.OK || panel.url == nil { 76 | return 77 | } 78 | if let fileURL = panel.url { 79 | self.iconURL = fileURL 80 | let image = NSImage.init(contentsOf: fileURL) 81 | self.iconImageView.image = image 82 | } 83 | } 84 | 85 | @IBAction func saveButtonPressed(_ sender: Any) { 86 | if isAddNew() { 87 | let title = self.nameTextfield.stringValue 88 | if title.count == 0 { 89 | return; 90 | } 91 | let appIndex = self.appPopupButton.indexOfSelectedItem 92 | let appId = Config.shared().getAppList()[appIndex] 93 | if let toolItem = ToolService.shared().doAddApp(title: title, appId: appId, iconURL: self.iconURL, parent: self.toolset!) { 94 | if let callback = self.completionCallback { 95 | callback(toolItem) 96 | } 97 | } 98 | }else { 99 | let title = self.nameTextfield.stringValue 100 | if title.count == 0 { 101 | return; 102 | } 103 | let appIndex = self.appPopupButton.indexOfSelectedItem 104 | let appId = Config.shared().getAppList()[appIndex] 105 | ToolService.shared().doEditApp(title: title, appId: appId, iconURL: self.iconURL, toolItem: self.toolItem!) 106 | if let callback = self.completionCallback { 107 | callback(self.toolItem!) 108 | } 109 | } 110 | } 111 | 112 | @IBAction func cancelButtonPressed(_ sender: Any) { 113 | if let callback = self.cancelCallback { 114 | callback() 115 | } 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /Commander/Commander/pages/setting/AppAddViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 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 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /Commander/Commander/pages/setting/SettingViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingViewController.swift 3 | // Commander 4 | // 5 | // Created by zhangxiaogang on 2019/5/7. 6 | // Copyright © 2019 woodpecker. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class SettingViewController: NSViewController,NSOutlineViewDataSource,NSOutlineViewDelegate, SettingCellDelegate { 12 | 13 | var mToolset: ToolSet? 14 | 15 | @IBOutlet weak var splitView: NSSplitView! 16 | @IBOutlet weak var outlineView: NSOutlineView! 17 | 18 | @IBOutlet weak var outlineLayout: NSView! 19 | @IBOutlet weak var editLayout: NSView! 20 | @IBOutlet weak var editDefaultLayout: NSTextField! 21 | 22 | @IBOutlet weak var addButton: NSButton! 23 | 24 | 25 | var editVC: AnyObject? 26 | var editView: NSView? 27 | 28 | override func viewDidLoad() { 29 | super.viewDidLoad() 30 | setup() 31 | loadContent() 32 | self.outlineView.expandItem(self.mToolset) 33 | } 34 | 35 | func setup() { 36 | let nib = NSNib.init(nibNamed: "SettingCellView", bundle: nil) 37 | self.outlineView.register(nib, forIdentifier: NSUserInterfaceItemIdentifier(rawValue: "SettingCellView")) 38 | self.outlineView.rowHeight = 24.0 39 | self.outlineView.usesAlternatingRowBackgroundColors = true 40 | setEditAreaWidth(preferedEditAreaWidth()) 41 | 42 | } 43 | 44 | func setToolset(toolset: ToolSet) { 45 | self.mToolset = toolset 46 | } 47 | 48 | func loadContent() { 49 | self.outlineView.reloadData() 50 | } 51 | 52 | func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int { 53 | var count = 0 54 | if item == nil { 55 | if let toolSet = self.mToolset { 56 | count = toolSet.children.count 57 | } 58 | }else if item is ToolSet { 59 | let tSet = item as! ToolSet 60 | count = tSet.children.count 61 | } 62 | return count 63 | } 64 | 65 | func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any { 66 | var childItem: Any? 67 | if item == nil { 68 | if let toolSet = self.mToolset { 69 | childItem = toolSet.children[index] 70 | } 71 | }else { 72 | if item is ToolSet { 73 | let tSet = item as! ToolSet 74 | childItem = tSet.children[index] 75 | } 76 | } 77 | return childItem! 78 | } 79 | 80 | func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool { 81 | var expandable = false 82 | if item is ToolSet { 83 | expandable = true 84 | } 85 | return expandable 86 | } 87 | 88 | func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? { 89 | let cell = outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "SettingCellView"), owner: nil) as! SettingCellView 90 | cell.setData(item as AnyObject) 91 | cell.delegate = self 92 | return cell 93 | } 94 | 95 | func cellClicked(_ cell: BaseCellView) { 96 | let row = self.outlineView.row(for: cell) 97 | let item = self.outlineView.item(atRow: row) 98 | if item is ToolItem { 99 | let toolItem = item as! ToolItem 100 | if toolItem.isApp() { 101 | doEditApp(toolItem) 102 | }else { 103 | doEditTool(toolItem) 104 | } 105 | }else if item is ToolSet { 106 | let toolset = item as! ToolSet 107 | if !toolset.isRoot() { 108 | doEditToolset(toolset) 109 | }else { 110 | resetEditArea() 111 | } 112 | }else { 113 | resetEditArea() 114 | } 115 | } 116 | 117 | func cellDoubleClicked(_ cell: BaseCellView) { 118 | let row = self.outlineView.row(for: cell) 119 | let item = self.outlineView.item(atRow: row) 120 | if item is ToolSet { 121 | if !self.outlineView.isItemExpanded(item) { 122 | self.outlineView.expandItem(item, expandChildren: false) 123 | }else { 124 | self.outlineView.collapseItem(item, collapseChildren: false) 125 | } 126 | } 127 | } 128 | 129 | func cellRightClicked(_ cell: BaseCellView, position: NSPoint) { 130 | 131 | } 132 | 133 | func doActionWithToolset(_ toolset: ToolSet, sender: NSView, point: NSPoint) { 134 | if toolset.isRoot() { 135 | return 136 | } 137 | let menu = NSMenu.init() 138 | //add tool 139 | let toolItem = NSMenuItem.init() 140 | toolItem.title = "Add Tool" 141 | toolItem.representedObject = toolset 142 | toolItem.target = self 143 | toolItem.action = #selector(SettingViewController.addToolMenuClicked(_:)) 144 | menu.addItem(toolItem) 145 | //add app tool 146 | let appItem = NSMenuItem.init() 147 | appItem.title = "Add App" 148 | appItem.representedObject = toolset 149 | appItem.target = self 150 | appItem.action = #selector(SettingViewController.addAppMenuClicked(_:)) 151 | menu.addItem(appItem) 152 | //add toolset 153 | let setItem = NSMenuItem.init() 154 | setItem.title = "Add Toolset" 155 | setItem.representedObject = toolset 156 | setItem.target = self 157 | setItem.action = #selector(SettingViewController.addToolsetMenuClicked(_:)) 158 | menu.addItem(setItem) 159 | //edit 160 | let editItem = NSMenuItem.init() 161 | editItem.title = "Edit" 162 | editItem.representedObject = toolset 163 | editItem.target = self 164 | editItem.action = #selector(SettingViewController.editMenuClicked(_:)) 165 | menu.addItem(editItem) 166 | //move up 167 | let index = outlineView.childIndex(forItem: toolset) 168 | if index > 0 { 169 | let upItem = NSMenuItem.init() 170 | upItem.title = "Move Up" 171 | upItem.representedObject = toolset 172 | upItem.target = self 173 | upItem.action = #selector(SettingViewController.moveupMenuClicked(_:)) 174 | menu.addItem(upItem) 175 | } 176 | //remove 177 | let removeItem = NSMenuItem.init() 178 | removeItem.title = "Delete" 179 | removeItem.representedObject = toolset 180 | removeItem.target = self 181 | removeItem.action = #selector(SettingViewController.deleteMenuClicked(_:)) 182 | menu.addItem(removeItem) 183 | //show in finder 184 | let finderItem = NSMenuItem.init() 185 | finderItem.title = "Show in Finder" 186 | finderItem.representedObject = toolset 187 | finderItem.target = self 188 | finderItem.action = #selector(SettingViewController.finderMenuClicked(_:)) 189 | menu.addItem(finderItem) 190 | 191 | menu.popUp(positioning: nil, at:point, in: sender) 192 | } 193 | 194 | func doActionWithTool(_ toolItem: ToolItem, sender: NSView, point: NSPoint) { 195 | let menu = NSMenu.init() 196 | //edit 197 | let editItem = NSMenuItem.init() 198 | editItem.title = "Edit" 199 | editItem.representedObject = toolItem 200 | editItem.target = self 201 | editItem.action = #selector(SettingViewController.editMenuClicked(_:)) 202 | menu.addItem(editItem) 203 | //move up 204 | let index = outlineView.childIndex(forItem: toolItem) 205 | if index > 0 { 206 | let upItem = NSMenuItem.init() 207 | upItem.title = "Move Up" 208 | upItem.representedObject = toolItem 209 | upItem.target = self 210 | upItem.action = #selector(SettingViewController.moveupMenuClicked(_:)) 211 | menu.addItem(upItem) 212 | } 213 | //remove 214 | let removeItem = NSMenuItem.init() 215 | removeItem.title = "Delete" 216 | removeItem.representedObject = toolItem 217 | removeItem.target = self 218 | removeItem.action = #selector(SettingViewController.deleteMenuClicked(_:)) 219 | menu.addItem(removeItem) 220 | //show in finder 221 | let finderItem = NSMenuItem.init() 222 | finderItem.title = "Show in Finder" 223 | finderItem.representedObject = toolItem 224 | finderItem.target = self 225 | finderItem.action = #selector(SettingViewController.finderMenuClicked(_:)) 226 | menu.addItem(finderItem) 227 | 228 | menu.popUp(positioning: nil, at: point, in: sender) 229 | } 230 | 231 | func doActionWithRootset(sender: NSView, point: NSPoint) { 232 | if let toolset = self.mToolset { 233 | let menu = NSMenu.init() 234 | //add tool 235 | let toolItem = NSMenuItem.init() 236 | toolItem.title = "Add Tool" 237 | toolItem.representedObject = toolset 238 | toolItem.target = self 239 | toolItem.action = #selector(SettingViewController.addToolMenuClicked(_:)) 240 | menu.addItem(toolItem) 241 | //add app tool 242 | let appItem = NSMenuItem.init() 243 | appItem.title = "Add App" 244 | appItem.representedObject = toolset 245 | appItem.target = self 246 | appItem.action = #selector(SettingViewController.addAppMenuClicked(_:)) 247 | menu.addItem(appItem) 248 | //add folder 249 | let setItem = NSMenuItem.init() 250 | setItem.title = "Add Folder" 251 | setItem.representedObject = toolset 252 | setItem.target = self 253 | setItem.action = #selector(SettingViewController.addToolsetMenuClicked(_:)) 254 | menu.addItem(setItem) 255 | menu.popUp(positioning: nil, at: point, in: sender) 256 | } 257 | } 258 | 259 | @IBAction func homeButtonPressed(_ sender: Any) { 260 | let path = Config.shared().workPath() 261 | NSWorkspace.shared.openFile(path) 262 | } 263 | 264 | @IBAction func addButtonPressed(_ sender: Any) { 265 | let point = NSPoint(x: addButton.bounds.size.width/2, y: addButton.bounds.size.height) 266 | doActionWithRootset(sender:addButton ,point: point) 267 | } 268 | 269 | //add tool 270 | @objc func addToolMenuClicked(_ menuItem: NSMenuItem) { 271 | let item = menuItem.representedObject 272 | if item == nil { 273 | return; 274 | } 275 | if item is ToolSet { 276 | doAddTool(item as! ToolSet) 277 | } 278 | } 279 | 280 | //add app 281 | @objc func addAppMenuClicked(_ menuItem: NSMenuItem) { 282 | let item = menuItem.representedObject 283 | if item == nil { 284 | return; 285 | } 286 | if item is ToolSet { 287 | doAddApp(item as! ToolSet) 288 | } 289 | } 290 | 291 | //add toolset 292 | @objc func addToolsetMenuClicked(_ menuItem: NSMenuItem) { 293 | let item = menuItem.representedObject 294 | if item == nil { 295 | return; 296 | } 297 | if item is ToolSet { 298 | doAddToolset(item as! ToolSet) 299 | } 300 | } 301 | 302 | //move up 303 | @objc func moveupMenuClicked(_ menuItem: NSMenuItem) { 304 | let item = menuItem.representedObject 305 | if item == nil { 306 | return; 307 | } 308 | var toolSet: ToolSet? 309 | if item is ToolItem { 310 | let toolItem = item as! ToolItem 311 | toolSet = toolItem.toolset 312 | }else if item is ToolSet { 313 | let thisItem = item as! ToolSet 314 | toolSet = thisItem.parentSet 315 | } 316 | if toolSet != nil { 317 | let index = self.outlineView.childIndex(forItem: item!) 318 | if index >= 1 { 319 | let targetIndex = index-1 320 | toolSet!.children.remove(at: index) 321 | toolSet!.children.insert(item!, at: targetIndex) 322 | if((toolSet?.isRoot())!) { 323 | self.outlineView.reloadItem(nil, reloadChildren: true) 324 | }else { 325 | self.outlineView.reloadItem(toolSet, reloadChildren: true) 326 | } 327 | //save sorted order 328 | ToolService.shared().doSaveToolsetOrder(toolSet!) 329 | MenuController.shared().reloadMenu() 330 | } 331 | } 332 | } 333 | 334 | //edit 335 | @objc func editMenuClicked(_ menuItem: NSMenuItem) { 336 | let item = menuItem.representedObject 337 | if item == nil { 338 | return; 339 | } 340 | if item is ToolItem { 341 | let toolItem = item as! ToolItem 342 | if toolItem.isApp() { 343 | doEditApp(toolItem) 344 | }else { 345 | doEditTool(toolItem) 346 | } 347 | }else if item is ToolSet { 348 | let toolSet = item as! ToolSet 349 | doEditToolset(toolSet) 350 | } 351 | } 352 | 353 | //delete 354 | @objc func deleteMenuClicked(_ menuItem: NSMenuItem) { 355 | let alert = NSAlert.init() 356 | alert.messageText = "Are you sure to delete it" 357 | alert.addButton(withTitle: "Delete") 358 | alert.addButton(withTitle: "Cancel") 359 | let result = alert.runModal() 360 | if result.rawValue != 1000 { 361 | return 362 | } 363 | 364 | let item = menuItem.representedObject 365 | if item == nil { 366 | return; 367 | } 368 | var toolSet: ToolSet? 369 | if item is ToolItem { 370 | let toolItem = item as! ToolItem 371 | toolSet = toolItem.toolset 372 | }else if item is ToolSet { 373 | let thisItem = item as! ToolSet 374 | toolSet = thisItem.parentSet 375 | } 376 | if toolSet != nil { 377 | let index = self.outlineView.childIndex(forItem: item!) 378 | if index >= 0 { 379 | toolSet?.children.remove(at: index) 380 | if((toolSet?.isRoot())!) { 381 | self.outlineView.reloadItem(nil, reloadChildren: true) 382 | }else { 383 | self.outlineView.reloadItem(toolSet, reloadChildren: true) 384 | } 385 | //delete local file 386 | if item is ToolItem { 387 | let toolItem = item as! ToolItem 388 | ToolService.shared().doDeleteTool(toolItem) 389 | }else if item is ToolSet { 390 | let toolset = item as! ToolSet 391 | ToolService.shared().doDeleteToolset(toolset) 392 | } 393 | //save sorted order 394 | ToolService.shared().doSaveToolsetOrder(toolSet!) 395 | MenuController.shared().reloadMenu() 396 | resetEditArea() 397 | } 398 | } 399 | } 400 | 401 | //show in finder 402 | @objc func finderMenuClicked(_ menuItem: NSMenuItem) { 403 | let item = menuItem.representedObject 404 | if item is ToolSet { 405 | let toolset = item as!ToolSet 406 | NSWorkspace.shared.openFile(toolset.absolutePath()) 407 | }else if item is ToolItem { 408 | let toolItem = item as!ToolItem 409 | let fileURL = URL.init(fileURLWithPath: toolItem.path()) 410 | NSWorkspace.shared.activateFileViewerSelecting([fileURL]) 411 | } 412 | 413 | } 414 | 415 | //add tool 416 | func doAddTool(_ toolset: ToolSet) { 417 | resetEditArea() 418 | let addVC = ToolAddViewController.init(nibName: "ToolAddViewController", bundle: nil) 419 | addVC.toolset = toolset 420 | self.editVC = addVC 421 | let view = addVC.view 422 | self.editLayout.addSubview(view) 423 | self.editView = view 424 | setEditAreaWidth(preferedEditAreaWidth()) 425 | view.frame = self.editLayout.bounds 426 | addVC.completionCallback = {(toolItem: ToolItem) -> Void in 427 | if(toolset.isRoot()) { 428 | self.outlineView.reloadItem(nil, reloadChildren: true) 429 | self.outlineView.expandItem(nil) 430 | }else { 431 | self.outlineView.reloadItem(toolset, reloadChildren: true) 432 | self.outlineView.expandItem(toolset) 433 | } 434 | self.resetEditArea() 435 | self.outlineView.deselectAll(nil) 436 | let toolIndex = self.outlineView.row(forItem: toolItem) 437 | self.outlineView.selectRowIndexes(IndexSet.init(integer: toolIndex), byExtendingSelection: false) 438 | //save sorted order 439 | ToolService.shared().doSaveToolsetOrder(toolset) 440 | MenuController.shared().reloadMenu() 441 | } 442 | addVC.cancelCallback = {() -> Void in 443 | self.resetEditArea() 444 | } 445 | self.editDefaultLayout.isHidden = true 446 | } 447 | 448 | //edit tool 449 | func doEditTool(_ toolItem: ToolItem) { 450 | resetEditArea() 451 | let addVC = ToolAddViewController.init(nibName: "ToolAddViewController", bundle: nil) 452 | addVC.toolitem = toolItem 453 | self.editVC = addVC 454 | let view = addVC.view 455 | self.editLayout.addSubview(view) 456 | self.editView = view 457 | setEditAreaWidth(preferedEditAreaWidth()) 458 | view.frame = self.editLayout.bounds 459 | addVC.completionCallback = {(toolItem: ToolItem) -> Void in 460 | self.outlineView.reloadItem(toolItem) 461 | self.resetEditArea() 462 | MenuController.shared().reloadMenu() 463 | } 464 | addVC.cancelCallback = {() -> Void in 465 | self.resetEditArea() 466 | } 467 | self.editDefaultLayout.isHidden = true 468 | } 469 | 470 | //add app 471 | func doAddApp(_ toolset: ToolSet) { 472 | resetEditArea() 473 | let addVC = AppAddViewController.init(nibName: "AppAddViewController", bundle: nil) 474 | addVC.toolset = toolset 475 | self.editVC = addVC 476 | let view = addVC.view 477 | self.editLayout.addSubview(view) 478 | self.editView = view 479 | setEditAreaWidth(preferedEditAreaWidth()) 480 | view.frame = self.editLayout.bounds 481 | addVC.completionCallback = {(toolItem: ToolItem) -> Void in 482 | if(toolset.isRoot()) { 483 | self.outlineView.reloadItem(nil, reloadChildren: true) 484 | self.outlineView.expandItem(nil) 485 | }else { 486 | self.outlineView.reloadItem(toolset, reloadChildren: true) 487 | self.outlineView.expandItem(toolset) 488 | } 489 | self.resetEditArea() 490 | self.outlineView.deselectAll(nil) 491 | let toolIndex = self.outlineView.row(forItem: toolItem) 492 | self.outlineView.selectRowIndexes(IndexSet.init(integer: toolIndex), byExtendingSelection: false) 493 | //save sorted order 494 | ToolService.shared().doSaveToolsetOrder(toolset) 495 | MenuController.shared().reloadMenu() 496 | } 497 | addVC.cancelCallback = {() -> Void in 498 | self.resetEditArea() 499 | } 500 | self.editDefaultLayout.isHidden = true 501 | } 502 | 503 | //edit app 504 | func doEditApp(_ toolItem: ToolItem) { 505 | resetEditArea() 506 | let addVC = AppAddViewController.init(nibName: "AppAddViewController", bundle: nil) 507 | addVC.toolItem = toolItem 508 | self.editVC = addVC 509 | let view = addVC.view 510 | self.editLayout.addSubview(view) 511 | self.editView = view 512 | setEditAreaWidth(preferedEditAreaWidth()) 513 | view.frame = self.editLayout.bounds 514 | addVC.completionCallback = {(toolItem: ToolItem) -> Void in 515 | self.outlineView.reloadItem(toolItem) 516 | self.resetEditArea() 517 | MenuController.shared().reloadMenu() 518 | } 519 | addVC.cancelCallback = {() -> Void in 520 | self.resetEditArea() 521 | } 522 | self.editDefaultLayout.isHidden = true 523 | } 524 | 525 | //edit tooset 526 | func doAddToolset(_ parentSet: ToolSet) { 527 | resetEditArea() 528 | let addVC = ToolsetAddViewController.init(nibName: "ToolsetAddViewController", bundle: nil) 529 | addVC.parentSet = parentSet 530 | self.editVC = addVC 531 | let view = addVC.view 532 | self.editLayout.addSubview(view) 533 | self.editView = view 534 | setEditAreaWidth(preferedEditAreaWidth()) 535 | view.frame = self.editLayout.bounds 536 | addVC.completionCallback = {(toolset: ToolSet) -> Void in 537 | if parentSet.isRoot() { 538 | self.outlineView.reloadItem(nil, reloadChildren: true) 539 | self.outlineView.expandItem(nil) 540 | }else { 541 | self.outlineView.reloadItem(parentSet, reloadChildren: true) 542 | self.outlineView.expandItem(parentSet) 543 | } 544 | self.resetEditArea() 545 | self.outlineView.deselectAll(nil) 546 | let setIndex = self.outlineView.row(forItem: toolset) 547 | self.outlineView.selectRowIndexes(IndexSet.init(integer: setIndex), byExtendingSelection: false) 548 | //save sorted order 549 | ToolService.shared().doSaveToolsetOrder(parentSet) 550 | MenuController.shared().reloadMenu() 551 | } 552 | addVC.cancelCallback = {() -> Void in 553 | self.resetEditArea() 554 | } 555 | self.editDefaultLayout.isHidden = true 556 | } 557 | 558 | //edit toolset 559 | func doEditToolset(_ toolset: ToolSet) { 560 | resetEditArea() 561 | let addVC = ToolsetAddViewController.init(nibName: "ToolsetAddViewController", bundle: nil) 562 | addVC.toolSet = toolset 563 | self.editVC = addVC 564 | let view = addVC.view 565 | self.editLayout.addSubview(view) 566 | self.editView = view 567 | setEditAreaWidth(preferedEditAreaWidth()) 568 | view.frame = self.editLayout.bounds 569 | addVC.completionCallback = { (toolset: ToolSet) -> Void in 570 | self.outlineView.reloadItem(toolset) 571 | self.resetEditArea() 572 | MenuController.shared().reloadMenu() 573 | } 574 | addVC.cancelCallback = {() -> Void in 575 | self.resetEditArea() 576 | } 577 | self.editDefaultLayout.isHidden = true 578 | } 579 | 580 | override func rightMouseUp(with event: NSEvent) { 581 | super.rightMouseUp(with: event) 582 | let windowPos = event.locationInWindow 583 | let point = self.view.convert(windowPos, from: nil) 584 | let sender = self.view 585 | let row = self.outlineView.selectedRow 586 | if row >= 0 { 587 | if let item = self.outlineView.item(atRow: row) { 588 | if item is ToolSet { 589 | doActionWithToolset(item as! ToolSet,sender: sender,point: point) 590 | }else if item is ToolItem { 591 | doActionWithTool(item as! ToolItem, sender: sender,point: point) 592 | } 593 | }else { 594 | doActionWithRootset(sender: sender,point: point) 595 | } 596 | }else { 597 | doActionWithRootset(sender: sender,point: point) 598 | } 599 | } 600 | 601 | 602 | public func outlineViewSelectionDidChange(_ notification: Notification) { 603 | let row = self.outlineView.selectedRow 604 | if row < 0 { 605 | resetEditArea() 606 | } 607 | } 608 | 609 | func resetEditArea() { 610 | if self.editView != nil { 611 | self.editView?.removeFromSuperview() 612 | self.editView = nil 613 | self.editDefaultLayout.isHidden = false 614 | } 615 | } 616 | 617 | func setEditAreaWidth(_ width: CGFloat) { 618 | //tree frame 619 | let totalWidth = self.splitView.bounds.size.width 620 | var treeFrame = self.outlineLayout.frame 621 | var treeSize = treeFrame.size 622 | treeSize.width = totalWidth - width - self.splitView.dividerThickness; 623 | treeFrame.size = treeSize; 624 | self.outlineLayout.frame = treeFrame 625 | //edit frame 626 | var frame = self.editLayout.frame 627 | var size = frame.size 628 | size.width = width; 629 | frame.size = size 630 | self.editLayout.frame = frame 631 | self.splitView.needsLayout = true 632 | } 633 | 634 | func preferedEditAreaWidth() -> CGFloat { 635 | return 520 636 | } 637 | 638 | 639 | 640 | } 641 | -------------------------------------------------------------------------------- /Commander/Commander/pages/setting/SettingViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 72 | 76 | 77 | 78 | 79 | 80 | 81 | 92 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /Commander/Commander/pages/setting/ToolAddViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AddViewController.swift 3 | // Commander 4 | // 5 | // Created by zhangxiaogang on 2019/5/5. 6 | // Copyright © 2019 woodpecker. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class ToolAddViewController: NSViewController { 12 | 13 | @IBOutlet weak var iconImageView: NSImageView! 14 | @IBOutlet weak var nameTextfield: NSTextField! 15 | @IBOutlet var codeTextView: NSTextView! 16 | @IBOutlet weak var scriptTypeButton: NSPopUpButton! 17 | 18 | var iconURL: URL? 19 | //add 20 | var toolset: ToolSet? 21 | //edit 22 | var toolitem: ToolItem? 23 | var completionCallback: ((ToolItem) -> Void)? 24 | var cancelCallback: (() -> Void)? 25 | 26 | override func viewDidLoad() { 27 | super.viewDidLoad() 28 | setup() 29 | loadContent() 30 | } 31 | 32 | func setup() { 33 | //script types 34 | scriptTypeButton.removeAllItems() 35 | let scriptTypes = Config.shared().selectScriptTypes() 36 | for type in scriptTypes { 37 | scriptTypeButton.addItem(withTitle: type) 38 | } 39 | } 40 | 41 | func isEdit() -> Bool { 42 | return (self.toolitem != nil) 43 | } 44 | 45 | func isAddNew() -> Bool { 46 | return !isEdit() 47 | } 48 | 49 | func loadContent() { 50 | if isEdit() { 51 | if let toolItem = self.toolitem { 52 | //icon 53 | let iconPath = toolItem.iconPath() 54 | let image = NSImage.init(contentsOfFile: iconPath) 55 | self.iconImageView.image = image 56 | //name 57 | self.nameTextfield.stringValue = toolItem.title 58 | //script type 59 | let scriptFile = toolItem.scriptFile 60 | if let ext = scriptFile!.pathExtension() { 61 | let scriptTypes = Config.shared().supportScriptTypes() 62 | if let scriptIndex = scriptTypes.firstIndex(of: ext) { 63 | self.scriptTypeButton.selectItem(at: scriptIndex) 64 | } 65 | } 66 | //script code 67 | let scriptPath = toolItem.scriptPath() 68 | if let data = NSData.init(contentsOfFile: scriptPath) { 69 | if let text = String.init(data: data as Data, encoding: String.Encoding.utf8) { 70 | self.codeTextView.string = text 71 | } 72 | } 73 | } 74 | } 75 | } 76 | 77 | @IBAction func iconButtonPressed(_ sender: Any) { 78 | let panel = NSOpenPanel.init() 79 | panel.allowedFileTypes = ["png"] 80 | let result = panel.runModal() 81 | if result != NSApplication.ModalResponse.OK || panel.url == nil { 82 | return 83 | } 84 | if let fileURL = panel.url { 85 | self.iconURL = fileURL 86 | let image = NSImage.init(contentsOf: fileURL) 87 | self.iconImageView.image = image 88 | } 89 | } 90 | 91 | @IBAction func scriptFileButtonPressed(_ sender: Any) { 92 | let panel = NSOpenPanel.init() 93 | panel.allowedFileTypes = Config.shared().supportScriptTypes() 94 | let result = panel.runModal() 95 | if result != NSApplication.ModalResponse.OK || panel.url == nil { 96 | return 97 | } 98 | var text : String? 99 | let fileURL = panel.url 100 | let data = try? Data.init(contentsOf: fileURL!) 101 | if data != nil { 102 | text = String.init(data: data!, encoding: String.Encoding.utf8) 103 | } 104 | if text != nil { 105 | self.codeTextView.string = text! 106 | } 107 | } 108 | 109 | @IBAction func addButtonPressed(_ sender: Any) { 110 | if isAddNew() { 111 | let scriptText = self.codeTextView.string 112 | let title = self.nameTextfield.stringValue 113 | if title.count == 0 || scriptText.count == 0 { 114 | return; 115 | } 116 | let scriptIndex = self.scriptTypeButton.indexOfSelectedItem 117 | let scriptType = Config.shared().supportScriptTypes()[scriptIndex] 118 | if let toolItem = ToolService.shared().doAddTool(title:title, scriptType: scriptType, scriptText: scriptText, iconURL: self.iconURL, parent: self.toolset!) { 119 | if let callback = self.completionCallback { 120 | callback(toolItem) 121 | } 122 | } 123 | }else { 124 | if let toolItem = self.toolitem { 125 | let scriptText = self.codeTextView.string 126 | let title = self.nameTextfield.stringValue 127 | if title.count == 0 || scriptText.count == 0 { 128 | return; 129 | } 130 | let scriptIndex = self.scriptTypeButton.indexOfSelectedItem 131 | let scriptType = Config.shared().supportScriptTypes()[scriptIndex] 132 | ToolService.shared().doEditTool(title: title, scriptType: scriptType, scriptText: scriptText, iconURL: self.iconURL, toolItem: toolItem) 133 | if let callback = self.completionCallback { 134 | callback(toolItem) 135 | } 136 | } 137 | } 138 | } 139 | 140 | @IBAction func cancelButtonPressed(_ sender: Any) { 141 | if let callback = self.cancelCallback { 142 | callback() 143 | } 144 | } 145 | 146 | 147 | } 148 | -------------------------------------------------------------------------------- /Commander/Commander/pages/setting/ToolAddViewController.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 | 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 | 75 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 127 | 128 | 129 | 130 | 131 | 132 | 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /Commander/Commander/pages/setting/ToolsetAddViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ToolsetAddViewController.swift 3 | // Commander 4 | // 5 | // Created by zhangxiaogang on 2019/5/7. 6 | // Copyright © 2019 woodpecker. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class ToolsetAddViewController: NSViewController { 12 | 13 | @IBOutlet weak var iconImageView: NSImageView! 14 | @IBOutlet weak var nameTextfield: NSTextField! 15 | 16 | //add 17 | var parentSet: ToolSet? 18 | //edit 19 | var toolSet: ToolSet? 20 | var completionCallback: ((ToolSet) -> Void)? 21 | var cancelCallback: (() -> Void)? 22 | var iconURL: URL? 23 | 24 | override func viewDidLoad() { 25 | super.viewDidLoad() 26 | setup() 27 | loadContent() 28 | } 29 | 30 | func setup() { 31 | 32 | } 33 | 34 | func loadContent() { 35 | if isEdit() { 36 | if let toolset = self.toolSet { 37 | //icon 38 | let iconPath = toolset.iconPath() 39 | let image = NSImage.init(contentsOfFile: iconPath) 40 | self.iconImageView.image = image 41 | //name 42 | self.nameTextfield.stringValue = toolset.title 43 | } 44 | } 45 | } 46 | 47 | func isEdit() -> Bool { 48 | return (self.toolSet != nil) 49 | } 50 | 51 | func isAddNew() -> Bool { 52 | return !isEdit() 53 | } 54 | 55 | @IBAction func iconButtonPressed(_ sender: Any) { 56 | let panel = NSOpenPanel.init() 57 | panel.allowedFileTypes = ["png"] 58 | let result = panel.runModal() 59 | if result != NSApplication.ModalResponse.OK || panel.url == nil { 60 | return 61 | } 62 | if let fileURL = panel.url { 63 | self.iconURL = fileURL 64 | let image = NSImage.init(contentsOf: fileURL) 65 | self.iconImageView.image = image 66 | } 67 | } 68 | 69 | @IBAction func saveButtonPressed(_ sender: Any) { 70 | if isAddNew() { 71 | let title = self.nameTextfield.stringValue 72 | if title.count == 0 { 73 | return 74 | } 75 | //1. create folder 76 | let parentURL = self.parentSet!.absoluteURL() 77 | let setURL = parentURL.appendingPathComponent(title) 78 | let fm = FileManager.default 79 | do { 80 | try fm.createDirectory(at: setURL, withIntermediateDirectories: true, attributes: nil) 81 | }catch { 82 | print(error) 83 | } 84 | //2. icon 85 | if let iconURL = self.iconURL { 86 | let targetURL = setURL.appendingPathComponent("icon.png") 87 | try? fm.copyItem(at: iconURL, to: targetURL) 88 | } 89 | let toolSet = ToolSet.init() 90 | toolSet.title = title; 91 | toolSet.path = self.parentSet?.path.appendingPathComponent(title) 92 | toolSet.children = [Any].init() 93 | toolSet.parentSet = self.parentSet 94 | self.parentSet?.children.append(toolSet) 95 | 96 | if let callback = self.completionCallback { 97 | callback(toolSet) 98 | } 99 | }else { 100 | if let toolset = self.toolSet { 101 | let title = self.nameTextfield.stringValue 102 | if title.count == 0 { 103 | return 104 | } 105 | //1. name 106 | let fm = FileManager.default 107 | let parentURL = toolset.parentSet!.absoluteURL() 108 | let setURL = parentURL.appendingPathComponent(title) 109 | if title != toolSet?.title { 110 | let oldSetURL = parentURL.appendingPathComponent(toolset.title) 111 | try? fm.moveItem(at: oldSetURL, to: setURL) 112 | toolset.title = title 113 | toolset.path = toolset.parentSet?.path.appendingPathComponent(title) 114 | } 115 | //2. icon 116 | if let iconURL = self.iconURL { 117 | let targetURL = setURL.appendingPathComponent("icon.png") 118 | //remove old icon 119 | try? fm.removeItem(at: targetURL) 120 | try? fm.copyItem(at: iconURL, to: targetURL) 121 | } 122 | if let callback = self.completionCallback { 123 | callback(toolset) 124 | } 125 | } 126 | } 127 | } 128 | 129 | @IBAction func cancelButtonPressed(_ sender: Any) { 130 | if let callback = self.cancelCallback { 131 | callback() 132 | } 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /Commander/Commander/pages/setting/ToolsetAddViewController.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 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 67 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /Commander/Commander/pages/setting/view/SettingCellView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingCellView.swift 3 | // Commander 4 | // 5 | // Created by zhangxiaogang on 2019/5/7. 6 | // Copyright © 2019 woodpecker. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class SettingCellView: BaseCellView { 12 | 13 | @IBOutlet weak var iconImageView: NSImageView! 14 | @IBOutlet weak var titleLabel: NSTextField! 15 | 16 | weak var item: AnyObject? 17 | 18 | func setData(_ item :AnyObject) { 19 | if item is ToolItem { 20 | let toolItem = item as! ToolItem 21 | self.titleLabel.stringValue = toolItem.title 22 | if let image = NSImage.init(contentsOfFile: toolItem.iconPath()) { 23 | self.iconImageView.image = image 24 | } else { 25 | if toolItem.appId != nil { 26 | self.iconImageView.image = NSImage.init(named:NSImage.actionTemplateName); 27 | }else { 28 | self.iconImageView.image = NSImage.init(named: "tool") 29 | } 30 | } 31 | 32 | }else if item is ToolSet { 33 | let toolSet = item as! ToolSet 34 | self.titleLabel.stringValue = toolSet.title 35 | self.iconImageView.image = NSImage.init(named:NSImage.folderName); 36 | 37 | } 38 | self.item = item 39 | } 40 | 41 | } 42 | 43 | protocol SettingCellDelegate : BaseCellViewDelegate { 44 | 45 | } 46 | -------------------------------------------------------------------------------- /Commander/Commander/pages/setting/view/SettingCellView.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 | -------------------------------------------------------------------------------- /Commander/Commander/resource/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appwoodpecker/commander/4c9c80bc6dc603b896a79227a071c631dfcd1abe/Commander/Commander/resource/.DS_Store -------------------------------------------------------------------------------- /Commander/Commander/resource/menubar/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appwoodpecker/commander/4c9c80bc6dc603b896a79227a071c631dfcd1abe/Commander/Commander/resource/menubar/.DS_Store -------------------------------------------------------------------------------- /Commander/Commander/resource/menubar/Desktop/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appwoodpecker/commander/4c9c80bc6dc603b896a79227a071c631dfcd1abe/Commander/Commander/resource/menubar/Desktop/.DS_Store -------------------------------------------------------------------------------- /Commander/Commander/resource/menubar/Desktop/hide.bundle/main.sh: -------------------------------------------------------------------------------- 1 | defaults write com.apple.finder CreateDesktop -bool false; killall Finder 2 | -------------------------------------------------------------------------------- /Commander/Commander/resource/menubar/Desktop/show.bundle/main.sh: -------------------------------------------------------------------------------- 1 | defaults write com.apple.finder CreateDesktop -bool true; killall Finder 2 | -------------------------------------------------------------------------------- /Commander/Commander/resource/menubar/Open link.bundle/main.sh: -------------------------------------------------------------------------------- 1 | open https://www.github.com 2 | -------------------------------------------------------------------------------- /Commander/Commander/resource/menubar/QRCode.bundle/app.json: -------------------------------------------------------------------------------- 1 | {"id":"QRApp"} -------------------------------------------------------------------------------- /Commander/Commander/resource/menubar/QRCode.bundle/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appwoodpecker/commander/4c9c80bc6dc603b896a79227a071c631dfcd1abe/Commander/Commander/resource/menubar/QRCode.bundle/icon.png -------------------------------------------------------------------------------- /Commander/Commander/resource/menubar/Show Hidden Files/hide.bundle/main.sh: -------------------------------------------------------------------------------- 1 | defaults write com.apple.finder AppleShowAllFiles -boolean false ; killall Finder 2 | -------------------------------------------------------------------------------- /Commander/Commander/resource/menubar/Show Hidden Files/show.bundle/main.sh: -------------------------------------------------------------------------------- 1 | defaults write com.apple.finder AppleShowAllFiles -boolean true ; killall Finder 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # commander 2 | Run your script with this mac menubar app 3 | 4 | ![menubar](http://www.woodpeck.cn/res/img/cmd_menu.png) 5 | 6 | # Usage 7 | 8 | 1. Download the released app, drag the Commander.app to your Application folder 9 | 2. Add your tool or toolset, enjoy! 10 | 3. Find more scripts or share your script at this repo's scripts folder -------------------------------------------------------------------------------- /scripts/dark mode/main.sh: -------------------------------------------------------------------------------- 1 | osascript -e ' 2 | tell application "System Events" 3 | tell appearance preferences 4 | if dark mode is true then 5 | set dark mode to false 6 | else 7 | set dark mode to true 8 | end if 9 | end tell 10 | end tell' -------------------------------------------------------------------------------- /scripts/dark mode/readme.md: -------------------------------------------------------------------------------- 1 | toggle dark mode -------------------------------------------------------------------------------- /scripts/open file/main.sh: -------------------------------------------------------------------------------- 1 | open ~/Desktop -------------------------------------------------------------------------------- /scripts/open file/readme.md: -------------------------------------------------------------------------------- 1 | - open file with application 2 | 3 | ``` 4 | #open hosts file 5 | open /etc/hosts 6 | ``` 7 | 8 | - open file with specified application 9 | 10 | ``` 11 | open -a "Terminal" ~/Desktop/demo.txt 12 | ``` --------------------------------------------------------------------------------