├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── RTTinyPNGWebAPIPlugin.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ └── RTTinyPNGWebAPIPlugin.xcscheme ├── RTTinyPNGWebAPIPlugin ├── Info.plist ├── NSObject_Extension.h ├── NSObject_Extension.m ├── RTImageController.h ├── RTImageController.m ├── RTImageController.xib ├── RTImageItem.h ├── RTImageItem.m ├── RTTinyPNGWebAPIPlugin.h ├── RTTinyPNGWebAPIPlugin.m ├── RTWorkspace.h ├── RTWorkspace.m └── XcodeIDE.h └── ScreenShots ├── load-bundle.png ├── s0.png ├── s1.png ├── s2.png ├── s3.png └── sorting.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ### OSX ### 6 | .DS_Store 7 | .AppleDouble 8 | .LSOverride 9 | 10 | # Thumbnails 11 | ._* 12 | 13 | # Files that might appear on external disk 14 | .Spotlight-V100 15 | .Trashes 16 | 17 | ### AppCode ### 18 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 19 | 20 | *.iml 21 | 22 | ## Directory-based project format: 23 | .idea/ 24 | # if you remove the above rule, at least ignore the following: 25 | 26 | # User-specific stuff: 27 | # .idea/workspace.xml 28 | # .idea/tasks.xml 29 | # .idea/dictionaries 30 | 31 | # Sensitive or high-churn files: 32 | # .idea/dataSources.ids 33 | # .idea/dataSources.xml 34 | # .idea/sqlDataSources.xml 35 | # .idea/dynamic.xml 36 | # .idea/uiDesigner.xml 37 | 38 | # Gradle: 39 | # .idea/gradle.xml 40 | # .idea/libraries 41 | 42 | # Mongo Explorer plugin: 43 | # .idea/mongoSettings.xml 44 | 45 | ## File-based project format: 46 | *.ipr 47 | *.iws 48 | 49 | ## Build generated 50 | build/ 51 | DerivedData 52 | 53 | ## Various settings 54 | *.pbxuser 55 | !default.pbxuser 56 | *.mode1v3 57 | !default.mode1v3 58 | *.mode2v3 59 | !default.mode2v3 60 | *.perspectivev3 61 | !default.perspectivev3 62 | xcuserdata 63 | 64 | ## Other 65 | *.xccheckout 66 | *.moved-aside 67 | *.xcuserstate 68 | *.xcscmblueprint 69 | 70 | ## Obj-C/Swift specific 71 | *.hmap 72 | *.ipa 73 | 74 | # CocoaPods 75 | # 76 | # We recommend against adding the Pods directory to your .gitignore. However 77 | # you should judge for yourself, the pros and cons are mentioned at: 78 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 79 | # 80 | # Pods/ 81 | 82 | # Carthage 83 | # 84 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 85 | # Carthage/Checkouts 86 | 87 | Carthage/Build 88 | 89 | # fastlane 90 | # 91 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 92 | # screenshots whenever they are needed. 93 | # For more information about the recommended setup visit: 94 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 95 | 96 | fastlane/report.xml 97 | fastlane/screenshots 98 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | script: xctool -project RTTinyPNGWebAPIPlugin.xcodeproj -scheme RTTinyPNGWebAPIPlugin 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Ricky Tan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RTTinyPNGWebAPIPlugin [![Build Status](https://travis-ci.org/rickytan/RTTinyPNGWebAPIPlugin.svg)](https://travis-ci.org/rickytan/RTTinyPNGWebAPIPlugin) [![License](http://img.shields.io/badge/license-MIT-yellowgreen.svg)](./LICENSE) 2 | 3 | [![Donate](https://www.paypalobjects.com/webstatic/en_US/btn/btn_donate_pp_142x27.png "Donate me a cup of coffee")](http://rickytan.cn/donate/ "Donate me a cup of coffee") 4 | 5 | ## Introduction 6 | 7 | This is a simple **Xcode Plugin** to help **iOS** developers to minimize app assets, it calls api to compress your `.png`, `.jpg` image resources. So before you using this, please get your api key [HERE](https://tinypng.com/developers). 8 | 9 | ## Usage 10 | 11 | ### State 12 | 13 | A image source has 4 states, whith are: 14 | 15 | - *Normal*, not optimized 16 | 17 | ![Normal State](./ScreenShots/s0.png) 18 | 19 | - *Pending*, waiting for optimization 20 | 21 | ![Processing State](./ScreenShots/s1.png) 22 | 23 | - *Optimized*, 24 | 25 | ![Processing State](./ScreenShots/s2.png) 26 | 27 | - *Error*, a network error occurred 28 | 29 | ### Selection 30 | 31 | When **Start** button pressed, the selected(with a check mark on the left) images are pushed into a processing queue, waiting for compressing. Your can click the checkbox on the table header to toggle select/deselect all, or click and drag to multi-select, then use the context menu to select/deselect multiple items. 32 | 33 | ![Context Menu](./ScreenShots/s3.png) 34 | 35 | ### Sorting 36 | 37 | The **Image Name**, **Image Path**, **File Size**, **Optimized** columns support sorting, use them well :smile:. 38 | 39 | ![Sorting](./ScreenShots/sorting.png) 40 | 41 | ## How to install 42 | 43 | * Just clone or download the Repo, and build with your **Xcode**, then restart **Xcode**. 44 | * Download the binary [HERE](https://github.com/rickytan/RTTinyPNGWebAPIPlugin/releases), decompress and move it to `~/Library/Application Support/Developer/Shared/Xcode/Plug-ins` 45 | * Open [Package Manager](http://alcatraz.io/) and search `RTTinyPNGWebAPIPlugin` 46 | 47 | > **Note**: If Xcode prompt a dialog, please click **Load Bundle**. 48 | > 49 | > ![Load Bundle](./ScreenShots/load-bundle.png) 50 | 51 | After you successfully installed the plugin, you can open the plugin window in **File** menu, or press `Shift Control t` for short. 52 | 53 | ## Issues 54 | 55 | If you find any bug(s), please let me know: 56 | 57 | ## License 58 | 59 | **MIT** 60 | 61 | ``` 62 | The MIT License (MIT) 63 | 64 | Copyright (c) 2016 Ricky Tan 65 | 66 | Permission is hereby granted, free of charge, to any person obtaining a copy 67 | of this software and associated documentation files (the "Software"), to deal 68 | in the Software without restriction, including without limitation the rights 69 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 70 | copies of the Software, and to permit persons to whom the Software is 71 | furnished to do so, subject to the following conditions: 72 | 73 | The above copyright notice and this permission notice shall be included in all 74 | copies or substantial portions of the Software. 75 | 76 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 77 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 78 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 79 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 80 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 81 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 82 | SOFTWARE. 83 | ``` 84 | -------------------------------------------------------------------------------- /RTTinyPNGWebAPIPlugin.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 6D30B96A1C633158002B419A /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D30B9691C633158002B419A /* AppKit.framework */; }; 11 | 6D30B96C1C633158002B419A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D30B96B1C633158002B419A /* Foundation.framework */; }; 12 | 6D30B9701C633158002B419A /* RTTinyPNGWebAPIPlugin.xcscheme in Resources */ = {isa = PBXBuildFile; fileRef = 6D30B96F1C633158002B419A /* RTTinyPNGWebAPIPlugin.xcscheme */; }; 13 | 6D30B9731C633158002B419A /* RTTinyPNGWebAPIPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = 6D30B9721C633158002B419A /* RTTinyPNGWebAPIPlugin.m */; }; 14 | 6D30B9761C633158002B419A /* NSObject_Extension.m in Sources */ = {isa = PBXBuildFile; fileRef = 6D30B9751C633158002B419A /* NSObject_Extension.m */; }; 15 | 6D30B9801C633240002B419A /* RTImageController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6D30B97E1C633240002B419A /* RTImageController.m */; }; 16 | 6D30B9811C633240002B419A /* RTImageController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D30B97F1C633240002B419A /* RTImageController.xib */; }; 17 | 6D30B9851C633F8F002B419A /* RTWorkspace.m in Sources */ = {isa = PBXBuildFile; fileRef = 6D30B9831C633F8F002B419A /* RTWorkspace.m */; }; 18 | 6D30B9881C63426A002B419A /* RTImageItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 6D30B9871C63426A002B419A /* RTImageItem.m */; }; 19 | 6DF5F7C61C6447680095CB95 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6DF5F7C51C6447680095CB95 /* QuartzCore.framework */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXFileReference section */ 23 | 6D30B9661C633158002B419A /* RTTinyPNGWebAPIPlugin.xcplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RTTinyPNGWebAPIPlugin.xcplugin; sourceTree = BUILT_PRODUCTS_DIR; }; 24 | 6D30B9691C633158002B419A /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; 25 | 6D30B96B1C633158002B419A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; 26 | 6D30B96F1C633158002B419A /* RTTinyPNGWebAPIPlugin.xcscheme */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = RTTinyPNGWebAPIPlugin.xcscheme; path = RTTinyPNGWebAPIPlugin.xcodeproj/xcshareddata/xcschemes/RTTinyPNGWebAPIPlugin.xcscheme; sourceTree = SOURCE_ROOT; }; 27 | 6D30B9711C633158002B419A /* RTTinyPNGWebAPIPlugin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RTTinyPNGWebAPIPlugin.h; sourceTree = ""; }; 28 | 6D30B9721C633158002B419A /* RTTinyPNGWebAPIPlugin.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RTTinyPNGWebAPIPlugin.m; sourceTree = ""; }; 29 | 6D30B9741C633158002B419A /* NSObject_Extension.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NSObject_Extension.h; sourceTree = ""; }; 30 | 6D30B9751C633158002B419A /* NSObject_Extension.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NSObject_Extension.m; sourceTree = ""; }; 31 | 6D30B9771C633158002B419A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 32 | 6D30B97D1C633240002B419A /* RTImageController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RTImageController.h; sourceTree = ""; }; 33 | 6D30B97E1C633240002B419A /* RTImageController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RTImageController.m; sourceTree = ""; }; 34 | 6D30B97F1C633240002B419A /* RTImageController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = RTImageController.xib; sourceTree = ""; }; 35 | 6D30B9821C633F8F002B419A /* RTWorkspace.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RTWorkspace.h; sourceTree = ""; }; 36 | 6D30B9831C633F8F002B419A /* RTWorkspace.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RTWorkspace.m; sourceTree = ""; }; 37 | 6D30B9841C633F8F002B419A /* XcodeIDE.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XcodeIDE.h; sourceTree = ""; }; 38 | 6D30B9861C63426A002B419A /* RTImageItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RTImageItem.h; sourceTree = ""; }; 39 | 6D30B9871C63426A002B419A /* RTImageItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RTImageItem.m; sourceTree = ""; }; 40 | 6DF5F7C51C6447680095CB95 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 41 | /* End PBXFileReference section */ 42 | 43 | /* Begin PBXFrameworksBuildPhase section */ 44 | 6D30B9641C633158002B419A /* Frameworks */ = { 45 | isa = PBXFrameworksBuildPhase; 46 | buildActionMask = 2147483647; 47 | files = ( 48 | 6DF5F7C61C6447680095CB95 /* QuartzCore.framework in Frameworks */, 49 | 6D30B96A1C633158002B419A /* AppKit.framework in Frameworks */, 50 | 6D30B96C1C633158002B419A /* Foundation.framework in Frameworks */, 51 | ); 52 | runOnlyForDeploymentPostprocessing = 0; 53 | }; 54 | /* End PBXFrameworksBuildPhase section */ 55 | 56 | /* Begin PBXGroup section */ 57 | 6D30B95D1C633158002B419A = { 58 | isa = PBXGroup; 59 | children = ( 60 | 6D30B96D1C633158002B419A /* RTTinyPNGWebAPIPlugin */, 61 | 6D30B9681C633158002B419A /* Frameworks */, 62 | 6D30B9671C633158002B419A /* Products */, 63 | ); 64 | sourceTree = ""; 65 | }; 66 | 6D30B9671C633158002B419A /* Products */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | 6D30B9661C633158002B419A /* RTTinyPNGWebAPIPlugin.xcplugin */, 70 | ); 71 | name = Products; 72 | sourceTree = ""; 73 | }; 74 | 6D30B9681C633158002B419A /* Frameworks */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | 6DF5F7C51C6447680095CB95 /* QuartzCore.framework */, 78 | 6D30B9691C633158002B419A /* AppKit.framework */, 79 | 6D30B96B1C633158002B419A /* Foundation.framework */, 80 | ); 81 | name = Frameworks; 82 | sourceTree = ""; 83 | }; 84 | 6D30B96D1C633158002B419A /* RTTinyPNGWebAPIPlugin */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | 6D30B9741C633158002B419A /* NSObject_Extension.h */, 88 | 6D30B9751C633158002B419A /* NSObject_Extension.m */, 89 | 6D30B97D1C633240002B419A /* RTImageController.h */, 90 | 6D30B97E1C633240002B419A /* RTImageController.m */, 91 | 6D30B97F1C633240002B419A /* RTImageController.xib */, 92 | 6D30B9861C63426A002B419A /* RTImageItem.h */, 93 | 6D30B9871C63426A002B419A /* RTImageItem.m */, 94 | 6D30B9711C633158002B419A /* RTTinyPNGWebAPIPlugin.h */, 95 | 6D30B9721C633158002B419A /* RTTinyPNGWebAPIPlugin.m */, 96 | 6D30B9821C633F8F002B419A /* RTWorkspace.h */, 97 | 6D30B9831C633F8F002B419A /* RTWorkspace.m */, 98 | 6D30B96E1C633158002B419A /* Supporting Files */, 99 | 6D30B9841C633F8F002B419A /* XcodeIDE.h */, 100 | ); 101 | path = RTTinyPNGWebAPIPlugin; 102 | sourceTree = ""; 103 | }; 104 | 6D30B96E1C633158002B419A /* Supporting Files */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | 6D30B9771C633158002B419A /* Info.plist */, 108 | 6D30B96F1C633158002B419A /* RTTinyPNGWebAPIPlugin.xcscheme */, 109 | ); 110 | name = "Supporting Files"; 111 | sourceTree = ""; 112 | }; 113 | /* End PBXGroup section */ 114 | 115 | /* Begin PBXNativeTarget section */ 116 | 6D30B9651C633158002B419A /* RTTinyPNGWebAPIPlugin */ = { 117 | isa = PBXNativeTarget; 118 | buildConfigurationList = 6D30B97A1C633158002B419A /* Build configuration list for PBXNativeTarget "RTTinyPNGWebAPIPlugin" */; 119 | buildPhases = ( 120 | 6D30B9621C633158002B419A /* Sources */, 121 | 6D30B9631C633158002B419A /* Resources */, 122 | 6D30B9641C633158002B419A /* Frameworks */, 123 | ); 124 | buildRules = ( 125 | ); 126 | dependencies = ( 127 | ); 128 | name = RTTinyPNGWebAPIPlugin; 129 | productName = RTTinyPNGWebAPIPlugin; 130 | productReference = 6D30B9661C633158002B419A /* RTTinyPNGWebAPIPlugin.xcplugin */; 131 | productType = "com.apple.product-type.bundle"; 132 | }; 133 | /* End PBXNativeTarget section */ 134 | 135 | /* Begin PBXProject section */ 136 | 6D30B95E1C633158002B419A /* Project object */ = { 137 | isa = PBXProject; 138 | attributes = { 139 | LastUpgradeCheck = 0720; 140 | ORGANIZATIONNAME = Shiqu; 141 | TargetAttributes = { 142 | 6D30B9651C633158002B419A = { 143 | CreatedOnToolsVersion = 7.2; 144 | }; 145 | }; 146 | }; 147 | buildConfigurationList = 6D30B9611C633158002B419A /* Build configuration list for PBXProject "RTTinyPNGWebAPIPlugin" */; 148 | compatibilityVersion = "Xcode 3.2"; 149 | developmentRegion = English; 150 | hasScannedForEncodings = 0; 151 | knownRegions = ( 152 | en, 153 | ); 154 | mainGroup = 6D30B95D1C633158002B419A; 155 | productRefGroup = 6D30B9671C633158002B419A /* Products */; 156 | projectDirPath = ""; 157 | projectRoot = ""; 158 | targets = ( 159 | 6D30B9651C633158002B419A /* RTTinyPNGWebAPIPlugin */, 160 | ); 161 | }; 162 | /* End PBXProject section */ 163 | 164 | /* Begin PBXResourcesBuildPhase section */ 165 | 6D30B9631C633158002B419A /* Resources */ = { 166 | isa = PBXResourcesBuildPhase; 167 | buildActionMask = 2147483647; 168 | files = ( 169 | 6D30B9701C633158002B419A /* RTTinyPNGWebAPIPlugin.xcscheme in Resources */, 170 | 6D30B9811C633240002B419A /* RTImageController.xib in Resources */, 171 | ); 172 | runOnlyForDeploymentPostprocessing = 0; 173 | }; 174 | /* End PBXResourcesBuildPhase section */ 175 | 176 | /* Begin PBXSourcesBuildPhase section */ 177 | 6D30B9621C633158002B419A /* Sources */ = { 178 | isa = PBXSourcesBuildPhase; 179 | buildActionMask = 2147483647; 180 | files = ( 181 | 6D30B9731C633158002B419A /* RTTinyPNGWebAPIPlugin.m in Sources */, 182 | 6D30B9851C633F8F002B419A /* RTWorkspace.m in Sources */, 183 | 6D30B9881C63426A002B419A /* RTImageItem.m in Sources */, 184 | 6D30B9801C633240002B419A /* RTImageController.m in Sources */, 185 | 6D30B9761C633158002B419A /* NSObject_Extension.m in Sources */, 186 | ); 187 | runOnlyForDeploymentPostprocessing = 0; 188 | }; 189 | /* End PBXSourcesBuildPhase section */ 190 | 191 | /* Begin XCBuildConfiguration section */ 192 | 6D30B9781C633158002B419A /* Debug */ = { 193 | isa = XCBuildConfiguration; 194 | buildSettings = { 195 | ALWAYS_SEARCH_USER_PATHS = NO; 196 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 197 | CLANG_CXX_LIBRARY = "libc++"; 198 | CLANG_ENABLE_MODULES = YES; 199 | CLANG_ENABLE_OBJC_ARC = YES; 200 | CLANG_WARN_BOOL_CONVERSION = YES; 201 | CLANG_WARN_CONSTANT_CONVERSION = YES; 202 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 203 | CLANG_WARN_EMPTY_BODY = YES; 204 | CLANG_WARN_ENUM_CONVERSION = YES; 205 | CLANG_WARN_INT_CONVERSION = YES; 206 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 207 | CLANG_WARN_UNREACHABLE_CODE = YES; 208 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 209 | COPY_PHASE_STRIP = NO; 210 | DEBUG_INFORMATION_FORMAT = dwarf; 211 | ENABLE_STRICT_OBJC_MSGSEND = YES; 212 | ENABLE_TESTABILITY = YES; 213 | GCC_C_LANGUAGE_STANDARD = gnu99; 214 | GCC_DYNAMIC_NO_PIC = NO; 215 | GCC_NO_COMMON_BLOCKS = YES; 216 | GCC_OPTIMIZATION_LEVEL = 0; 217 | GCC_PREPROCESSOR_DEFINITIONS = ( 218 | "DEBUG=1", 219 | "$(inherited)", 220 | ); 221 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 222 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 223 | GCC_WARN_UNDECLARED_SELECTOR = YES; 224 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 225 | GCC_WARN_UNUSED_FUNCTION = YES; 226 | GCC_WARN_UNUSED_VARIABLE = YES; 227 | MTL_ENABLE_DEBUG_INFO = YES; 228 | ONLY_ACTIVE_ARCH = YES; 229 | }; 230 | name = Debug; 231 | }; 232 | 6D30B9791C633158002B419A /* Release */ = { 233 | isa = XCBuildConfiguration; 234 | buildSettings = { 235 | ALWAYS_SEARCH_USER_PATHS = NO; 236 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 237 | CLANG_CXX_LIBRARY = "libc++"; 238 | CLANG_ENABLE_MODULES = YES; 239 | CLANG_ENABLE_OBJC_ARC = YES; 240 | CLANG_WARN_BOOL_CONVERSION = YES; 241 | CLANG_WARN_CONSTANT_CONVERSION = YES; 242 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 243 | CLANG_WARN_EMPTY_BODY = YES; 244 | CLANG_WARN_ENUM_CONVERSION = YES; 245 | CLANG_WARN_INT_CONVERSION = YES; 246 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 247 | CLANG_WARN_UNREACHABLE_CODE = YES; 248 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 249 | COPY_PHASE_STRIP = NO; 250 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 251 | ENABLE_NS_ASSERTIONS = NO; 252 | ENABLE_STRICT_OBJC_MSGSEND = YES; 253 | GCC_C_LANGUAGE_STANDARD = gnu99; 254 | GCC_NO_COMMON_BLOCKS = YES; 255 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 256 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 257 | GCC_WARN_UNDECLARED_SELECTOR = YES; 258 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 259 | GCC_WARN_UNUSED_FUNCTION = YES; 260 | GCC_WARN_UNUSED_VARIABLE = YES; 261 | MTL_ENABLE_DEBUG_INFO = NO; 262 | }; 263 | name = Release; 264 | }; 265 | 6D30B97B1C633158002B419A /* Debug */ = { 266 | isa = XCBuildConfiguration; 267 | buildSettings = { 268 | COMBINE_HIDPI_IMAGES = YES; 269 | DEPLOYMENT_LOCATION = YES; 270 | DSTROOT = "$(HOME)"; 271 | INFOPLIST_FILE = RTTinyPNGWebAPIPlugin/Info.plist; 272 | INSTALL_PATH = "/Library/Application Support/Developer/Shared/Xcode/Plug-ins"; 273 | MACOSX_DEPLOYMENT_TARGET = 10.9; 274 | PRODUCT_BUNDLE_IDENTIFIER = cn.rickytan.RTTinyPNGWebAPIPlugin; 275 | PRODUCT_NAME = "$(TARGET_NAME)"; 276 | WRAPPER_EXTENSION = xcplugin; 277 | }; 278 | name = Debug; 279 | }; 280 | 6D30B97C1C633158002B419A /* Release */ = { 281 | isa = XCBuildConfiguration; 282 | buildSettings = { 283 | COMBINE_HIDPI_IMAGES = YES; 284 | DEPLOYMENT_LOCATION = YES; 285 | DSTROOT = "$(HOME)"; 286 | INFOPLIST_FILE = RTTinyPNGWebAPIPlugin/Info.plist; 287 | INSTALL_PATH = "/Library/Application Support/Developer/Shared/Xcode/Plug-ins"; 288 | MACOSX_DEPLOYMENT_TARGET = 10.9; 289 | PRODUCT_BUNDLE_IDENTIFIER = cn.rickytan.RTTinyPNGWebAPIPlugin; 290 | PRODUCT_NAME = "$(TARGET_NAME)"; 291 | WRAPPER_EXTENSION = xcplugin; 292 | }; 293 | name = Release; 294 | }; 295 | /* End XCBuildConfiguration section */ 296 | 297 | /* Begin XCConfigurationList section */ 298 | 6D30B9611C633158002B419A /* Build configuration list for PBXProject "RTTinyPNGWebAPIPlugin" */ = { 299 | isa = XCConfigurationList; 300 | buildConfigurations = ( 301 | 6D30B9781C633158002B419A /* Debug */, 302 | 6D30B9791C633158002B419A /* Release */, 303 | ); 304 | defaultConfigurationIsVisible = 0; 305 | defaultConfigurationName = Release; 306 | }; 307 | 6D30B97A1C633158002B419A /* Build configuration list for PBXNativeTarget "RTTinyPNGWebAPIPlugin" */ = { 308 | isa = XCConfigurationList; 309 | buildConfigurations = ( 310 | 6D30B97B1C633158002B419A /* Debug */, 311 | 6D30B97C1C633158002B419A /* Release */, 312 | ); 313 | defaultConfigurationIsVisible = 0; 314 | defaultConfigurationName = Release; 315 | }; 316 | /* End XCConfigurationList section */ 317 | }; 318 | rootObject = 6D30B95E1C633158002B419A /* Project object */; 319 | } 320 | -------------------------------------------------------------------------------- /RTTinyPNGWebAPIPlugin.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /RTTinyPNGWebAPIPlugin.xcodeproj/xcshareddata/xcschemes/RTTinyPNGWebAPIPlugin.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 46 | 47 | 48 | 49 | 61 | 65 | 66 | 67 | 73 | 74 | 75 | 76 | 77 | 78 | 84 | 85 | 87 | 88 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /RTTinyPNGWebAPIPlugin/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 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 | BNDL 19 | CFBundleShortVersionString 20 | 0.5.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | DVTPlugInCompatibilityUUIDs 26 | 27 | C4A681B0-4A26-480E-93EC-1218098B9AA0 28 | F41BD31E-2683-44B8-AE7F-5F09E919790E 29 | AD68E85B-441B-4301-B564-A45E4919A6AD 30 | A16FF353-8441-459E-A50C-B071F53F51B7 31 | 9F75337B-21B4-4ADC-B558-F9CADF7073A7 32 | E969541F-E6F9-4D25-8158-72DC3545A6C6 33 | 8DC44374-2B35-4C57-A6FE-2AD66A36AAD9 34 | AABB7188-E14E-4433-AD3B-5CD791EAD9A3 35 | 8DC44374-2B35-4C57-A6FE-2AD66A36AAD9 36 | AABB7188-E14E-4433-AD3B-5CD791EAD9A3 37 | 7FDF5C7A-131F-4ABB-9EDC-8C5F8F0B8A90 38 | 0420B86A-AA43-4792-9ED0-6FE0F2B16A13 39 | 7265231C-39B4-402C-89E1-16167C4CC990 40 | 41 | LSMinimumSystemVersion 42 | $(MACOSX_DEPLOYMENT_TARGET) 43 | NSPrincipalClass 44 | RTTinyPNGWebAPIPlugin 45 | XC4Compatible 46 | 47 | XCPluginHasUI 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /RTTinyPNGWebAPIPlugin/NSObject_Extension.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject_Extension.h 3 | // RTTinyPNGWebAPIPlugin 4 | // 5 | // Created by benfen on 16/2/4. 6 | // Copyright © 2016年 Shiqu. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSObject (Xcode_Plugin_Template_Extension) 12 | 13 | + (void)pluginDidLoad:(NSBundle *)plugin; 14 | 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /RTTinyPNGWebAPIPlugin/NSObject_Extension.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject_Extension.m 3 | // RTTinyPNGWebAPIPlugin 4 | // 5 | // Created by benfen on 16/2/4. 6 | // Copyright © 2016年 Shiqu. All rights reserved. 7 | // 8 | 9 | 10 | #import "NSObject_Extension.h" 11 | #import "RTTinyPNGWebAPIPlugin.h" 12 | 13 | @implementation NSObject (Xcode_Plugin_Template_Extension) 14 | 15 | + (void)pluginDidLoad:(NSBundle *)plugin 16 | { 17 | static dispatch_once_t onceToken; 18 | NSString *currentApplicationName = [[NSBundle mainBundle] infoDictionary][@"CFBundleName"]; 19 | if ([currentApplicationName isEqual:@"Xcode"]) { 20 | dispatch_once(&onceToken, ^{ 21 | sharedPlugin = [[RTTinyPNGWebAPIPlugin alloc] initWithBundle:plugin]; 22 | }); 23 | } 24 | } 25 | @end 26 | -------------------------------------------------------------------------------- /RTTinyPNGWebAPIPlugin/RTImageController.h: -------------------------------------------------------------------------------- 1 | // 2 | // RTImageController.h 3 | // RTTinyPNGWebAPIPlugin 4 | // 5 | // Created by benfen on 16/2/4. 6 | // Copyright © 2016年 Shiqu. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface RTImageController : NSWindowController 12 | - (void)clearImageItems; 13 | @end 14 | -------------------------------------------------------------------------------- /RTTinyPNGWebAPIPlugin/RTImageController.m: -------------------------------------------------------------------------------- 1 | // 2 | // RTImageController.m 3 | // RTTinyPNGWebAPIPlugin 4 | // 5 | // Created by benfen on 16/2/4. 6 | // Copyright © 2016年 Shiqu. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "RTImageController.h" 12 | 13 | #import "RTWorkspace.h" 14 | #import "RTImageItem.h" 15 | 16 | @interface RTHeaderCell : NSTableHeaderCell 17 | @property (strong) NSButtonCell *checkBox; 18 | @end 19 | 20 | @implementation RTHeaderCell 21 | 22 | - (instancetype)init 23 | { 24 | self = [super init]; 25 | if (self) { 26 | self.checkBox = [[NSButtonCell alloc] init]; 27 | self.checkBox.title = @""; 28 | self.checkBox.controlSize = NSRegularControlSize; 29 | self.checkBox.buttonType = NSSwitchButton; 30 | } 31 | return self; 32 | } 33 | 34 | - (void)drawInteriorWithFrame:(NSRect)cellFrame 35 | inView:(NSView *)controlView 36 | { 37 | NSSize size = [self.checkBox cellSize]; 38 | 39 | NSRect rect = NSMakeRect(cellFrame.origin.x + (cellFrame.size.width - size.width) / 2, 40 | cellFrame.origin.y + (cellFrame.size.height - size.height) / 2, 41 | size.width, size.height); 42 | [self.checkBox drawInteriorWithFrame:rect 43 | inView:controlView]; 44 | } 45 | 46 | @end 47 | 48 | @interface RTImageController () 49 | @property (weak) IBOutlet NSTableView *tableView; 50 | @property (weak) IBOutlet NSTextField *apiKeyField; 51 | @property (weak) IBOutlet NSProgressIndicator *progressIndicator; 52 | @property (weak) IBOutlet NSButton *startButton; 53 | @property (weak) IBOutlet NSButton *cancelButton; 54 | @property (weak) IBOutlet NSPopUpButton *concurrencyButton; 55 | @property (weak) IBOutlet NSTextField *statsLabel; 56 | 57 | @property (strong) RTHeaderCell *selectAllCell; 58 | 59 | @property (nonatomic, strong) NSMutableArray *imageItems; 60 | @property (assign) NSUInteger totalBytes, optimizedTotalBytes; 61 | @property (nonatomic, assign, getter=isLoading) BOOL loading; 62 | @property (assign, getter=isProcessing) BOOL processing; 63 | @end 64 | 65 | @implementation RTImageController 66 | @synthesize processing = _processing; 67 | 68 | static NSString *const TINY_PNG_HOST = @"https://api.tinify.com/shrink"; 69 | static void *observingContext = &observingContext; 70 | static NSOperationQueue *RTImageCompressingQueue() { 71 | static NSOperationQueue * _theQueue = nil; 72 | static dispatch_once_t onceToken; 73 | dispatch_once(&onceToken, ^{ 74 | _theQueue = [[NSOperationQueue alloc] init]; 75 | _theQueue.maxConcurrentOperationCount = 2; 76 | }); 77 | return _theQueue; 78 | } 79 | 80 | - (void)windowDidLoad { 81 | [super windowDidLoad]; 82 | 83 | // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file. 84 | 85 | { 86 | RTHeaderCell *cell = [[RTHeaderCell alloc] init]; 87 | self.selectAllCell = cell; 88 | ((NSTableColumn *)self.tableView.tableColumns.firstObject).headerCell = cell; 89 | } 90 | 91 | [self reloadImages]; 92 | } 93 | 94 | - (void)showWindow:(id)sender { 95 | [super showWindow:sender]; 96 | 97 | if (self.isWindowLoaded) 98 | [self reloadImages]; 99 | } 100 | 101 | - (void)observeValueForKeyPath:(NSString *)keyPath 102 | ofObject:(id)object 103 | change:(NSDictionary *)change 104 | context:(void *)context 105 | { 106 | if (context == observingContext) { 107 | if ([@"operationCount" isEqualToString:keyPath]) { 108 | if (RTImageCompressingQueue().operationCount == 0) { 109 | self.processing = NO; 110 | } 111 | } 112 | } 113 | } 114 | 115 | #pragma mark - Actions 116 | 117 | - (void)onToggleSelectionAll:(id)sender 118 | { 119 | if (self.selectAllCell.checkBox.state == NSOffState) { 120 | [self.imageItems enumerateObjectsUsingBlock:^(RTImageItem *item, NSUInteger idx, BOOL *stop) { 121 | item.selected = YES; 122 | }]; 123 | self.selectAllCell.checkBox.state = NSOnState; 124 | } 125 | else if (self.selectAllCell.checkBox.state == NSOnState) { 126 | [self.imageItems enumerateObjectsUsingBlock:^(RTImageItem *item, NSUInteger idx, BOOL *stop) { 127 | item.selected = NO; 128 | }]; 129 | self.selectAllCell.checkBox.state = NSOffState; 130 | } 131 | // Redraw the cell 132 | [self.tableView.headerView setNeedsDisplayInRect:[self.tableView.headerView headerRectOfColumn:0]]; 133 | 134 | [self.tableView reloadDataForRowIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, self.imageItems.count)] 135 | columnIndexes:[NSIndexSet indexSetWithIndex:[self.tableView columnWithIdentifier:@"Selection"]]]; 136 | 137 | } 138 | 139 | - (IBAction)onMarkSelected:(id)sender 140 | { 141 | [self.tableView.selectedRowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { 142 | ((RTImageItem *)self.imageItems[idx]).selected = YES; 143 | }]; 144 | [self.tableView reloadDataForRowIndexes:self.tableView.selectedRowIndexes 145 | columnIndexes:[NSIndexSet indexSetWithIndex:[self.tableView columnWithIdentifier:@"Selection"]]]; 146 | [self.tableView deselectAll:sender]; 147 | } 148 | 149 | - (IBAction)onMarkDeselected:(id)sender 150 | { 151 | [self.tableView.selectedRowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { 152 | ((RTImageItem *)self.imageItems[idx]).selected = NO; 153 | }]; 154 | [self.tableView reloadDataForRowIndexes:self.tableView.selectedRowIndexes 155 | columnIndexes:[NSIndexSet indexSetWithIndex:[self.tableView columnWithIdentifier:@"Selection"]]]; 156 | [self.tableView deselectAll:sender]; 157 | } 158 | 159 | - (IBAction)onViewInFinder:(id)sender 160 | { 161 | NSInteger row = [self.tableView rowForView:sender]; 162 | if (row >= 0) { 163 | RTImageItem *item = self.imageItems[row]; 164 | [[NSWorkspace sharedWorkspace] selectFile:item.imagePath 165 | inFileViewerRootedAtPath:item.imagePath.stringByDeletingLastPathComponent]; 166 | } 167 | } 168 | 169 | - (IBAction)onClickHeader:(NSTableView *)tableView { 170 | NSInteger rol = tableView.clickedRow; 171 | NSInteger column = tableView.clickedColumn; 172 | if (rol < 0 && column == 0) { 173 | [self onToggleSelectionAll:self]; 174 | } 175 | } 176 | 177 | - (IBAction)onDoubleClickRow:(NSTableView *)tableView { 178 | if ([tableView selectedRow] >= 0) { 179 | RTImageItem *item = self.imageItems[tableView.selectedRow]; 180 | item.selected = !item.isSelected; 181 | [self.tableView reloadDataForRowIndexes:[NSIndexSet indexSetWithIndex:tableView.selectedRow] 182 | columnIndexes:[NSIndexSet indexSetWithIndex:0]]; 183 | } 184 | } 185 | 186 | - (IBAction)onConcurrencyChanged:(id)sender { 187 | NSInteger value = [self.concurrencyButton.selectedItem.title integerValue]; 188 | RTImageCompressingQueue().maxConcurrentOperationCount = value; 189 | } 190 | 191 | - (IBAction)onSelect:(NSButton *)checkBox { 192 | NSInteger row = [self.tableView rowForView:checkBox]; 193 | if (row >= 0) { 194 | RTImageItem *item = self.imageItems[row]; 195 | item.selected = !item.isSelected; 196 | } 197 | } 198 | 199 | - (IBAction)onShowHelp:(id)sender 200 | { 201 | [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://tinypng.com/developers"]]; 202 | } 203 | 204 | - (IBAction)onStart:(id)sender { 205 | NSString *apiKey = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] stringForKey:@"TINY_PNG_APIKEY"]; 206 | if (apiKey.length <= 0) { 207 | NSBeginAlertSheet(@"API key required!", @"OK", @"Show me where to find", nil, self.window, self, @selector(sheetDidEnd:returnCode:contextInfo:), NULL, NULL, @"Please input your Api key and hit ENTER"); 208 | return; 209 | } 210 | 211 | NSString *base64encodedKey = [[[NSString stringWithFormat:@"api:%@", apiKey] dataUsingEncoding:NSUTF8StringEncoding] base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithCarriageReturn]; 212 | NSString *auth = [NSString stringWithFormat:@"Basic %@", base64encodedKey]; 213 | 214 | self.processing = YES; 215 | 216 | [RTImageCompressingQueue() addObserver:self 217 | forKeyPath:@"operationCount" 218 | options:NSKeyValueObservingOptionNew 219 | context:observingContext]; 220 | 221 | [self.imageItems enumerateObjectsUsingBlock:^(RTImageItem *obj, NSUInteger idx, BOOL *stop) { 222 | if (obj.selected) { 223 | [RTImageCompressingQueue() addOperationWithBlock:^{ 224 | NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:TINY_PNG_HOST] 225 | cachePolicy:NSURLRequestReloadIgnoringCacheData 226 | timeoutInterval:8.f]; 227 | request.timeoutInterval = 10.f; 228 | request.HTTPMethod = @"POST"; 229 | [request setValue:auth 230 | forHTTPHeaderField:@"Authorization"]; 231 | request.HTTPBodyStream = [NSInputStream inputStreamWithFileAtPath:obj.imagePath]; 232 | 233 | // Call web api synchronizely 234 | 235 | NSHTTPURLResponse *response = nil; 236 | NSError *error = nil; 237 | 238 | NSData *data = [NSURLConnection sendSynchronousRequest:request 239 | returningResponse:&response 240 | error:&error]; 241 | id json = nil; 242 | if (data) { 243 | json = [NSJSONSerialization JSONObjectWithData:data 244 | options:0 245 | error:NULL]; 246 | } 247 | 248 | 249 | if (response.statusCode == 201 && json) { 250 | obj.imageSizeOptimized = [json[@"output"][@"size"] integerValue]; 251 | NSURL *compressURL = [NSURL URLWithString:json[@"output"][@"url"]]; 252 | if (compressURL && [[NSData dataWithContentsOfURL:compressURL] writeToFile:obj.imagePath 253 | atomically:YES]) { 254 | obj.state = RTImageOptimizeStateOptimized; 255 | obj.selected = NO; 256 | self.optimizedTotalBytes -= obj.imageSize - obj.imageSizeOptimized; 257 | } 258 | } 259 | else { 260 | obj.state = RTImageOptimizeStateFailed; 261 | 262 | if (error) { 263 | // Api error, we should stop 264 | if (json) { 265 | [RTImageCompressingQueue() cancelAllOperations]; 266 | dispatch_async(dispatch_get_main_queue(), ^{ 267 | NSBeginAlertSheet(json[@"error"] ?: error.localizedFailureReason, @"OK", nil, nil, self.window, nil, NULL, NULL, NULL, @"%@", json[@"message"] ?: error.localizedDescription); 268 | }); 269 | } 270 | // Network error, skip 271 | else { 272 | NSLog(@"%@", error); 273 | } 274 | } 275 | // API call limit exceeded, we should stop 276 | else if (response.statusCode == 429) { 277 | [RTImageCompressingQueue() cancelAllOperations]; 278 | dispatch_async(dispatch_get_main_queue(), ^{ 279 | NSBeginAlertSheet(json[@"error"], @"OK", nil, nil, self.window, nil, NULL, NULL, NULL, @"%@", json[@"message"]); 280 | }); 281 | } 282 | else { 283 | NSLog(@"%@", response); 284 | } 285 | } 286 | dispatch_async(dispatch_get_main_queue(), ^{ 287 | [self updateStats]; 288 | [self.tableView reloadDataForRowIndexes:[NSIndexSet indexSetWithIndex:idx] 289 | columnIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, self.tableView.tableColumns.count)]]; 290 | }); 291 | }]; 292 | 293 | obj.state = RTImageOptimizeStatePending; 294 | [self.tableView reloadDataForRowIndexes:[NSIndexSet indexSetWithIndex:idx] 295 | columnIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, self.tableView.tableColumns.count)]]; 296 | } 297 | }]; 298 | } 299 | 300 | - (IBAction)onCancel:(id)sender { 301 | self.processing = NO; 302 | } 303 | 304 | #pragma mark - Methods 305 | 306 | 307 | - (void)sheetDidEnd:(NSWindow *)sheet 308 | returnCode:(NSInteger)returnCode 309 | contextInfo:(void *)contextInfo 310 | { 311 | if (returnCode == NSAlertAlternateReturn) { 312 | [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://tinypng.com/developers"]]; 313 | } 314 | } 315 | 316 | - (void)setProcessing:(BOOL)processing 317 | { 318 | @synchronized(self) { 319 | _processing = processing; 320 | if (_processing) { 321 | self.startButton.enabled = NO; 322 | self.concurrencyButton.enabled = NO; 323 | self.tableView.enabled = NO; 324 | self.progressIndicator.hidden = NO; 325 | [self.progressIndicator startAnimation:self]; 326 | } 327 | else { 328 | self.startButton.enabled = YES; 329 | self.concurrencyButton.enabled = YES; 330 | self.tableView.enabled = YES; 331 | self.progressIndicator.hidden = YES; 332 | [self.progressIndicator stopAnimation:self]; 333 | } 334 | } 335 | } 336 | 337 | - (BOOL)isProcessing 338 | { 339 | @synchronized(self) { 340 | return _processing; 341 | } 342 | } 343 | 344 | - (NSMutableArray *)imageItems 345 | { 346 | if (!_imageItems) { 347 | _imageItems = [NSMutableArray array]; 348 | } 349 | return _imageItems; 350 | } 351 | 352 | - (void)setLoading:(BOOL)loading 353 | { 354 | _loading = loading; 355 | if (_loading) { 356 | self.startButton.enabled = NO; 357 | self.progressIndicator.hidden = NO; 358 | [self.progressIndicator startAnimation:self]; 359 | } 360 | else { 361 | self.startButton.enabled = YES; 362 | [self.progressIndicator stopAnimation:self]; 363 | self.progressIndicator.hidden = YES; 364 | } 365 | } 366 | 367 | - (void)updateStats 368 | { 369 | self.statsLabel.stringValue = [NSString stringWithFormat:@"%lu/%lu, Compress ratio:%.2f", (unsigned long)self.optimizedTotalBytes, (unsigned long)self.totalBytes, (float)1.f * self.optimizedTotalBytes / self.totalBytes]; 370 | } 371 | 372 | - (void)reloadImages 373 | { 374 | NSString *path = [RTWorkspace currentWorkspacePath].stringByDeletingLastPathComponent; 375 | if (!path || self.isLoading || self.isProcessing) 376 | return; 377 | 378 | self.loading = YES; 379 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 380 | 381 | // Find all png/jpg/jpeg image files 382 | NSArray *allowedImageTypes = @[@"png", @"jpg", @"jpeg"]; 383 | 384 | NSDirectoryEnumerator *enumerator = [[NSFileManager defaultManager] enumeratorAtPath:path]; 385 | NSString *relaventPath = nil; 386 | 387 | [self.imageItems removeAllObjects]; 388 | self.totalBytes = self.optimizedTotalBytes = 0; 389 | 390 | BOOL allSelected = YES; 391 | while (relaventPath = [enumerator nextObject]) { 392 | if ([allowedImageTypes containsObject:relaventPath.pathExtension.lowercaseString]) { 393 | RTImageItem *item = [RTImageItem itemWithPath:[path stringByAppendingPathComponent:relaventPath]]; 394 | item.selected = !item.hasOptimized; 395 | 396 | self.totalBytes += item.imageSize; 397 | if (item.imageSizeOptimized) 398 | self.optimizedTotalBytes += item.imageSizeOptimized; 399 | else 400 | self.optimizedTotalBytes += item.imageSize; 401 | 402 | if (!item.isSelected) { 403 | allSelected = NO; 404 | } 405 | [self.imageItems addObject:item]; 406 | } 407 | } 408 | [self.imageItems sortUsingDescriptors:self.tableView.sortDescriptors]; 409 | 410 | dispatch_async(dispatch_get_main_queue(), ^{ 411 | self.loading = NO; 412 | self.selectAllCell.state = NSOffState; 413 | if (allSelected) 414 | [self onToggleSelectionAll:self]; 415 | 416 | [self updateStats]; 417 | [self.tableView reloadData]; 418 | }); 419 | }); 420 | } 421 | 422 | - (NSImage *)makeImageForState:(RTImageOptimizeState)state 423 | { 424 | NSString *imageNames[] = { 425 | @"NSStatusNone", 426 | @"NSStatusPartiallyAvailable", 427 | @"NSStatusAvailable", 428 | @"NSStatusUnavailable", 429 | }; 430 | return [NSImage imageNamed:imageNames[state]]; 431 | } 432 | 433 | - (void)clearImageItems 434 | { 435 | [self.imageItems removeAllObjects]; 436 | [self.tableView reloadData]; 437 | } 438 | 439 | #pragma mark - NSTableView 440 | 441 | - (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView 442 | { 443 | return self.imageItems.count; 444 | } 445 | 446 | - (NSView *)tableView:(NSTableView *)tableView 447 | viewForTableColumn:(NSTableColumn *)tableColumn 448 | row:(NSInteger)row 449 | { 450 | NSInteger col = [tableView.tableColumns indexOfObject:tableColumn]; 451 | RTImageItem *item = self.imageItems[row]; 452 | NSTableCellView *cell = [tableView makeViewWithIdentifier:tableColumn.identifier 453 | owner:self]; 454 | switch (col) { 455 | case 0: 456 | { 457 | ((NSButton *)cell).state = item.isSelected ? NSOnState : NSOffState; 458 | } 459 | break; 460 | case 1: 461 | cell.imageView.image = item.imageIcon; 462 | cell.textField.stringValue = item.imageName; 463 | cell.toolTip = item.imageName; 464 | break; 465 | case 2: 466 | cell.textField.stringValue = item.imagePath; 467 | cell.toolTip = item.imagePath; 468 | break; 469 | case 3: 470 | cell.textField.objectValue = @(item.imageSize); 471 | break; 472 | case 4: 473 | cell.textField.objectValue = @(item.imageSizeOptimized); 474 | break; 475 | case 5: 476 | cell.textField.stringValue = [NSString stringWithFormat:@"%dx%d", (int)item.size.width, (int)item.size.height]; 477 | break; 478 | case 6: 479 | { 480 | // cell.textField.stringValue = item.hasOptimized ? @"✔︎" : @"✘"; 481 | cell.imageView.image = [self makeImageForState:item.state]; 482 | } 483 | break; 484 | default: 485 | break; 486 | } 487 | 488 | return cell; 489 | } 490 | 491 | - (void)tableView:(NSTableView *)tableView 492 | sortDescriptorsDidChange:(NSArray *)oldDescriptors 493 | { 494 | [self.imageItems sortUsingDescriptors:tableView.sortDescriptors]; 495 | [tableView reloadData]; 496 | } 497 | 498 | @end 499 | -------------------------------------------------------------------------------- /RTTinyPNGWebAPIPlugin/RTImageController.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 | 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 | 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 | 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 | 375 | 379 | 380 | 381 | 382 | 383 | 384 | 394 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 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 | -------------------------------------------------------------------------------- /RTTinyPNGWebAPIPlugin/RTImageItem.h: -------------------------------------------------------------------------------- 1 | // 2 | // RTImageItem.h 3 | // RTTinyPNGWebAPIPlugin 4 | // 5 | // Created by benfen on 16/2/4. 6 | // Copyright © 2016年 Shiqu. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef NS_ENUM(NSInteger, RTImageOptimizeState) { 12 | RTImageOptimizeStateNormal, 13 | RTImageOptimizeStatePending, 14 | RTImageOptimizeStateOptimized, 15 | RTImageOptimizeStateFailed 16 | }; 17 | 18 | @interface RTImageItem : NSObject 19 | @property (nonatomic, strong) NSString *imageName; 20 | @property (nonatomic, strong) NSString *imagePath; 21 | @property (nonatomic, readonly) NSImage *imageIcon; 22 | @property (nonatomic, assign) NSSize size; 23 | 24 | /** 25 | * @author Ricky, 16-02-04 19:02:56 26 | * 27 | * File size in bytes 28 | */ 29 | @property (nonatomic, assign) NSInteger imageSize; 30 | 31 | /** 32 | * @author Ricky, 16-02-04 19:02:06 33 | * 34 | * File size after optimization 35 | */ 36 | @property (nonatomic, assign) NSInteger imageSizeOptimized; 37 | 38 | @property (nonatomic, strong) NSDate *lastUpdateDate; 39 | @property (nonatomic, assign, getter=isSelected) BOOL selected; 40 | @property (nonatomic, assign, getter=hasOptimized, readonly) BOOL optimized; 41 | @property (nonatomic, assign) RTImageOptimizeState state; 42 | 43 | + (instancetype)itemWithPath:(NSString *)path; 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /RTTinyPNGWebAPIPlugin/RTImageItem.m: -------------------------------------------------------------------------------- 1 | // 2 | // RTImageItem.m 3 | // RTTinyPNGWebAPIPlugin 4 | // 5 | // Created by benfen on 16/2/4. 6 | // Copyright © 2016年 Shiqu. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | #import "RTImageItem.h" 13 | 14 | static const char *RT_IMAGE_OPTIMIZED_KEY = "com.tinypng.hasOptimized"; 15 | static const char *RT_IMAGE_ORIGIN_SIZE_KEY = "com.tinypng.originSize"; 16 | 17 | @implementation RTImageItem 18 | @synthesize optimized = _optimized; 19 | 20 | + (instancetype)itemWithPath:(NSString *)path 21 | { 22 | RTImageItem *item = [[RTImageItem alloc] initWithPath:path]; 23 | 24 | return item; 25 | } 26 | 27 | - (instancetype)initWithPath:(NSString *)filePath 28 | { 29 | self = [super init]; 30 | if (self) { 31 | self.imagePath = filePath; 32 | NSDictionary *attr = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath 33 | error:NULL]; 34 | if (attr) { 35 | self.lastUpdateDate = [attr fileModificationDate]; 36 | 37 | if (self.hasOptimized) { 38 | NSInteger size = 0; 39 | getxattr(self.imagePath.UTF8String, RT_IMAGE_ORIGIN_SIZE_KEY, &size, sizeof(size), 0, 0); 40 | self.imageSize = size; 41 | self.imageSizeOptimized = [attr fileSize]; 42 | 43 | // If fail to get origin size, use optimized size instead 44 | if (!size) 45 | self.imageSize = self.imageSizeOptimized; 46 | _state = RTImageOptimizeStateOptimized; 47 | } 48 | else { 49 | self.imageSize = [attr fileSize]; 50 | _state = RTImageOptimizeStateNormal; 51 | } 52 | } 53 | NSImage *image = [[NSImage alloc] initWithContentsOfFile:filePath]; 54 | NSImageRep *rep = image.representations.firstObject; 55 | self.size = NSMakeSize(rep.pixelsWide, rep.pixelsHigh); 56 | } 57 | return self; 58 | } 59 | 60 | - (NSImage *)imageIcon 61 | { 62 | return [[NSWorkspace sharedWorkspace] iconForFile:self.imagePath]; 63 | } 64 | 65 | - (BOOL)hasOptimized 66 | { 67 | if (self.imagePath) { 68 | BOOL optimized = NO; 69 | getxattr(self.imagePath.UTF8String, RT_IMAGE_OPTIMIZED_KEY, &optimized, sizeof(optimized), 0, 0); 70 | _optimized = optimized; 71 | } 72 | return _optimized; 73 | } 74 | 75 | - (void)refreshState 76 | { 77 | if (self.imagePath) { 78 | BOOL optimized = NO; 79 | setxattr(self.imagePath.UTF8String, RT_IMAGE_OPTIMIZED_KEY, &optimized, sizeof(optimized), 0, 0); 80 | 81 | NSDictionary *attr = [[NSFileManager defaultManager] attributesOfItemAtPath:self.imagePath 82 | error:NULL]; 83 | if (attr) { 84 | self.lastUpdateDate = [attr fileModificationDate]; 85 | self.imageSize = [attr fileSize]; 86 | } 87 | } 88 | } 89 | 90 | - (void)setState:(RTImageOptimizeState)state 91 | { 92 | if (_state == state) 93 | return; 94 | 95 | _state = state; 96 | switch (_state) { 97 | case RTImageOptimizeStateOptimized: 98 | { 99 | if (self.imagePath) { 100 | BOOL optimized = YES; 101 | setxattr(self.imagePath.UTF8String, RT_IMAGE_OPTIMIZED_KEY, &optimized, sizeof(optimized), 0, 0); 102 | NSInteger size = self.imageSize; 103 | setxattr(self.imagePath.UTF8String, RT_IMAGE_ORIGIN_SIZE_KEY, &size, sizeof(size), 0, 0); 104 | 105 | NSDictionary *attr = [[NSFileManager defaultManager] attributesOfItemAtPath:self.imagePath 106 | error:NULL]; 107 | if (attr) { 108 | self.lastUpdateDate = [attr fileModificationDate]; 109 | self.imageSizeOptimized = [attr fileSize]; 110 | } 111 | } 112 | } 113 | break; 114 | 115 | default: 116 | break; 117 | } 118 | } 119 | 120 | - (void)setImagePath:(NSString *)imagePath 121 | { 122 | _imagePath = imagePath; 123 | if (!_imageName) { 124 | self.imageName = imagePath.pathComponents.lastObject; 125 | } 126 | } 127 | 128 | @end 129 | -------------------------------------------------------------------------------- /RTTinyPNGWebAPIPlugin/RTTinyPNGWebAPIPlugin.h: -------------------------------------------------------------------------------- 1 | // 2 | // RTTinyPNGWebAPIPlugin.h 3 | // RTTinyPNGWebAPIPlugin 4 | // 5 | // Created by benfen on 16/2/4. 6 | // Copyright © 2016年 Shiqu. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class RTTinyPNGWebAPIPlugin; 12 | 13 | static RTTinyPNGWebAPIPlugin *sharedPlugin; 14 | 15 | @interface RTTinyPNGWebAPIPlugin : NSObject 16 | 17 | + (instancetype)sharedPlugin; 18 | - (id)initWithBundle:(NSBundle *)plugin; 19 | 20 | @property (nonatomic, strong, readonly) NSBundle* bundle; 21 | @end -------------------------------------------------------------------------------- /RTTinyPNGWebAPIPlugin/RTTinyPNGWebAPIPlugin.m: -------------------------------------------------------------------------------- 1 | // 2 | // RTTinyPNGWebAPIPlugin.m 3 | // RTTinyPNGWebAPIPlugin 4 | // 5 | // Created by benfen on 16/2/4. 6 | // Copyright © 2016年 Shiqu. All rights reserved. 7 | // 8 | 9 | #import "RTTinyPNGWebAPIPlugin.h" 10 | 11 | #import "RTImageController.h" 12 | 13 | @interface RTTinyPNGWebAPIPlugin() 14 | @property (nonatomic, strong, readwrite) NSBundle *bundle; 15 | 16 | @property (nonatomic, strong) RTImageController *imageController; 17 | @property (nonatomic, strong) NSMenuItem *actionMenuItem; 18 | @end 19 | 20 | @implementation RTTinyPNGWebAPIPlugin 21 | 22 | + (instancetype)sharedPlugin 23 | { 24 | return sharedPlugin; 25 | } 26 | 27 | - (id)initWithBundle:(NSBundle *)plugin 28 | { 29 | if (self = [super init]) { 30 | // reference to plugin's bundle, for resource access 31 | self.bundle = plugin; 32 | 33 | [[NSNotificationCenter defaultCenter] addObserver:self 34 | selector:@selector(onLaunch:) 35 | name:NSApplicationDidFinishLaunchingNotification 36 | object:nil]; 37 | 38 | [[NSNotificationCenter defaultCenter] addObserver:self 39 | selector:@selector(onProjectChanged:) 40 | name:@"PBXProjectDidChangeNotification" 41 | object:nil]; 42 | 43 | [[NSNotificationCenter defaultCenter] addObserver:self 44 | selector:@selector(onProjectOpen:) 45 | name:@"PBXProjectDidOpenNotification" 46 | object:nil]; 47 | 48 | [[NSNotificationCenter defaultCenter] addObserver:self 49 | selector:@selector(onProjectClose:) 50 | name:@"PBXProjectWillCloseNotification" 51 | object:nil]; 52 | } 53 | return self; 54 | } 55 | 56 | - (RTImageController *)imageController 57 | { 58 | if (!_imageController) { 59 | _imageController = [[RTImageController alloc] initWithWindowNibName:NSStringFromClass([RTImageController class])]; 60 | } 61 | return _imageController; 62 | } 63 | 64 | - (NSMenuItem *)actionMenuItem 65 | { 66 | if (!_actionMenuItem) { 67 | NSMenuItem *menuItem = [[NSApp mainMenu] itemWithTitle:@"File"]; 68 | if (menuItem) { 69 | [[menuItem submenu] addItem:[NSMenuItem separatorItem]]; 70 | NSMenuItem *actionMenuItem = [[NSMenuItem alloc] initWithTitle:@"Tiny PNG" 71 | action:@selector(showWindow:) 72 | keyEquivalent:@"T"]; 73 | [actionMenuItem setKeyEquivalentModifierMask:NSAlphaShiftKeyMask | NSControlKeyMask]; 74 | [actionMenuItem setTarget:self.imageController]; 75 | [[menuItem submenu] addItem:actionMenuItem]; 76 | 77 | _actionMenuItem = actionMenuItem; 78 | } 79 | } 80 | return _actionMenuItem; 81 | } 82 | 83 | - (void)onLaunch:(NSNotification*)notif 84 | { 85 | //removeObserver 86 | [[NSNotificationCenter defaultCenter] removeObserver:self name:NSApplicationDidFinishLaunchingNotification object:nil]; 87 | 88 | self.actionMenuItem.enabled = NO; 89 | } 90 | 91 | - (void)onProjectOpen:(NSNotification *)notif 92 | { 93 | self.actionMenuItem.enabled = YES; 94 | } 95 | 96 | - (void)onProjectChanged:(NSNotification *)notif 97 | { 98 | [self.imageController clearImageItems]; 99 | [self.imageController close]; 100 | 101 | self.actionMenuItem.enabled = YES; 102 | } 103 | 104 | - (void)onProjectClose:(NSNotification *)notif 105 | { 106 | self.actionMenuItem.enabled = NO; 107 | 108 | [self.imageController clearImageItems]; 109 | [self.imageController close]; 110 | } 111 | 112 | - (void)dealloc 113 | { 114 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 115 | } 116 | 117 | @end 118 | -------------------------------------------------------------------------------- /RTTinyPNGWebAPIPlugin/RTWorkspace.h: -------------------------------------------------------------------------------- 1 | // 2 | // IAWorkspace.h 3 | // RTImageAssets 4 | // 5 | // Created by ricky on 14-12-10. 6 | // Copyright (c) 2014年 rickytan. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface RTWorkspace : NSObject 12 | 13 | + (NSString *)currentWorkspacePath; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /RTTinyPNGWebAPIPlugin/RTWorkspace.m: -------------------------------------------------------------------------------- 1 | // 2 | // IAWorkspace.m 3 | // RTImageAssets 4 | // 5 | // Created by ricky on 14-12-10. 6 | // Copyright (c) 2014年 rickytan. All rights reserved. 7 | // 8 | 9 | #import "XcodeIDE.h" 10 | #import "RTWorkspace.h" 11 | 12 | @implementation RTWorkspace 13 | 14 | + (IDEWorkspaceWindowController *)keyWindowController 15 | { 16 | NSArray *workspaceWindowControllers = [NSClassFromString(@"IDEWorkspaceWindowController") valueForKey:@"workspaceWindowControllers"]; 17 | for (IDEWorkspaceWindowController *controller in workspaceWindowControllers) { 18 | if (controller.window.isKeyWindow) { 19 | return controller; 20 | } 21 | } 22 | return workspaceWindowControllers.firstObject; 23 | } 24 | 25 | + (IDEWorkspace *)workspaceForKeyWindow 26 | { 27 | return [[self keyWindowController] valueForKey:@"_workspace"]; 28 | } 29 | 30 | + (NSString *)currentWorkspacePath 31 | { 32 | return [self workspaceForKeyWindow].representingFilePath.pathString; 33 | } 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /RTTinyPNGWebAPIPlugin/XcodeIDE.h: -------------------------------------------------------------------------------- 1 | // 2 | // XcodeIDE.h 3 | // RTImageAssets 4 | // 5 | // Created by ricky on 14-12-10. 6 | // Copyright (c) 2014年 rickytan. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class IDEWorkspace; 12 | @class IDEWorkspaceSettings; 13 | @class IDEWorkspaceSharedSettings; 14 | @class IDEWorkspaceUserSettings; 15 | 16 | @interface DVTFilePath : NSObject 17 | @property(readonly) DVTFilePath *symbolicLinkDestinationFilePath; 18 | @property(readonly) NSURL *fileReferenceURL; 19 | @property(readonly) NSDictionary *fileSystemAttributes; 20 | @property(readonly) NSDictionary *fileAttributes; 21 | @property(readonly) NSString *fileTypeAttribute; 22 | @property(readonly) NSArray *sortedDirectoryContents; 23 | @property(readonly) NSArray *directoryContents; 24 | @property(readonly) NSDate *modificationDate; 25 | @property(readonly) BOOL isExcludedFromBackup; 26 | @property(readonly) BOOL isExecutable; 27 | @property(readonly) BOOL isDeletable; 28 | @property(readonly) BOOL isWritable; 29 | @property(readonly) BOOL isReadable; 30 | @property(readonly) BOOL existsInFileSystem; 31 | @property(readonly) NSString *fileName; 32 | @property(readonly) NSURL *fileURL; 33 | @property(readonly) NSString *pathString; 34 | @property(readonly) DVTFilePath *volumeFilePath; 35 | @property(readonly) DVTFilePath *parentFilePath; 36 | @end 37 | 38 | @interface IDEWorkspaceWindowController : NSWindowController 39 | @end 40 | 41 | @interface IDEWorkspaceArena : NSObject 42 | @property(readonly) IDEWorkspace *workspace; 43 | @property(readonly) DVTFilePath *testResultsFolderPath; 44 | @property(readonly) DVTFilePath *logFolderPath; 45 | @property(readonly) DVTFilePath *indexPrecompiledHeadersFolderPath; 46 | @property(readonly) DVTFilePath *indexFolderPath; 47 | @property(readonly) DVTFilePath *precompiledHeadersFolderPath; 48 | @property(readonly) DVTFilePath *installingBuildFolderPath; 49 | @property(readonly) DVTFilePath *archivingBuildFolderPath; 50 | @property(readonly) DVTFilePath *buildIntermediatesFolderPath; 51 | @end 52 | 53 | 54 | 55 | @interface IDEWorkspace : NSObject 56 | @property BOOL isCleaningBuildFolder; 57 | @property(nonatomic) BOOL finishedLoading; 58 | @property(nonatomic) BOOL pendingFileReferencesAndContainers; 59 | @property BOOL initialContainerScanComplete; 60 | @property(retain, nonatomic) IDEWorkspaceArena *workspaceArena; 61 | @property(readonly) DVTFilePath *wrappedXcode3ProjectPath; 62 | @property(readonly) NSString *representingTitle; 63 | @property(readonly) DVTFilePath *representingFilePath; 64 | @property(retain, nonatomic) IDEWorkspaceSharedSettings *sharedSettings; 65 | @property(retain, nonatomic) IDEWorkspaceUserSettings *userSettings; 66 | @end 67 | 68 | @interface DVTLayoutView_ML : NSView 69 | @end 70 | 71 | @protocol IBViewDragDelegate 72 | - (BOOL)view:(id)arg1 performDragOperation:(id)arg2; 73 | - (BOOL)view:(id)arg1 prepareForDragOperation:(id)arg2; 74 | - (unsigned long long)view:(id)arg1 draggingEntered:(id)arg2; 75 | - (id)dragTypesForView:(id)arg1; 76 | 77 | @optional 78 | - (void)view:(id)arg1 draggingEnded:(id)arg2; 79 | - (void)view:(id)arg1 draggingExited:(id)arg2; 80 | - (void)view:(id)arg1 concludeDragOperation:(id)arg2; 81 | - (unsigned long long)view:(id)arg1 draggingUpdated:(id)arg2; 82 | @end 83 | 84 | @protocol IBICMultipartImageViewDelegate 85 | - (id)multipartImageView:(id)arg1 imageForImageRepIdentifier:(id)arg2; 86 | - (id)multipartImageView:(id)arg1 titleForImageRepIdentifier:(id)arg2; 87 | - (void)multipartImageViewWillLayout:(id)arg1; 88 | - (BOOL)multipartImageView:(id)arg1 interceptMouseUp:(id)arg2; 89 | - (BOOL)multipartImageView:(id)arg1 interceptMouseDragged:(id)arg2 withOriginalMouseDown:(id)arg3; 90 | - (BOOL)multipartImageView:(id)arg1 interceptMouseDown:(id)arg2; 91 | - (void)multipartImageView:(id)arg1 userDidEditTitle:(id)arg2; 92 | - (void)multipartImageView:(id)arg1 performDelete:(id)arg2; 93 | @end 94 | 95 | @interface IBICMultipartImageView : DVTLayoutView_ML 96 | @property(nonatomic) __weak id delegate; 97 | - (id)effectiveOuterBorderColor; 98 | - (id)effectiveTitleColor; 99 | @end 100 | 101 | @protocol IDEApplicationEventDelegate 102 | @optional 103 | - (BOOL)application:(id)arg1 shouldSendEvent:(id)arg2; 104 | @end 105 | 106 | 107 | @interface DVTApplication : NSApplication 108 | - (id)addActionMonitorWithHandlerBlock:(id)arg1; 109 | @end 110 | 111 | 112 | @interface IDEApplication : DVTApplication 113 | @property(retain) id eventDelegate; // @synthesize 114 | @end 115 | 116 | 117 | @interface IBICAbstractCatalogItem : NSObject 118 | @property(readonly, nonatomic) NSSet *children; // @synthesize children=_children; 119 | @property(readonly, nonatomic) IBICAbstractCatalogItem *parent; // @synthesize parent=_parent; 120 | @property(readonly, nonatomic) NSDate *manifestModificationDate; // @synthesize manifestModificationDate=_manifestModificationDate; 121 | @property(readonly, nonatomic) NSDate *modificatoinDate; // @synthesize modificatoinDate=_modificatoinDate; 122 | @property(copy, nonatomic) NSURL *absoluteFileURL; // @synthesize absoluteFileURL=_absoluteFileURL; 123 | @property(copy, nonatomic) NSString *explicitContainingDirectory; // @synthesize explicitContainingDirectory=_explicitContainingDirectory; 124 | @property(copy, nonatomic) NSString *fileName; // @synthesize fileName=_fileName; 125 | @property long long changeCount; // @synthesize changeCount=_changeCount; 126 | @property(readonly, nonatomic) NSArray *displayOrderedChildren; 127 | @property(readonly) NSString *absoluteManifestFilePath; 128 | @property(readonly) NSData *manifestFileData; 129 | @property(readonly) NSString *manifestFileName; 130 | @property(readonly, nonatomic) NSString *relativeFilePathFromRoot; 131 | @property(readonly, nonatomic) NSString *relativeIdentifierPath; 132 | @property(copy, nonatomic) NSString *absoluteFilePath; 133 | @property(readonly, nonatomic) BOOL canBeEmbeddedInFolder; 134 | @property(readonly, nonatomic) BOOL canHaveChildren; 135 | @property(readonly, nonatomic) NSString *identifier; 136 | @property(readonly, nonatomic) NSString *displayName; 137 | @end 138 | 139 | @interface IBICFolder : IBICAbstractCatalogItem 140 | - (BOOL)fileStructureSnapshotChildWouldMapToModelChild:(id)arg1; 141 | - (void)replaceChildrenFromFileSystemSnapshot:(id)arg1 results:(id)arg2; 142 | - (Class)itemClassForDirectoryExtension:(id)arg1; 143 | - (id)imageSetWithFileName:(id)arg1; 144 | - (id)imageSetWithName:(id)arg1; 145 | - (id)folderForFileName:(id)arg1; 146 | - (id)childWithFileName:(id)arg1; 147 | - (id)validatedFileNameForProposedDisplayName:(id)arg1; 148 | - (id)enclosingFolderIncludingReceiver; 149 | - (BOOL)canBeEmbeddedInFolder; 150 | - (BOOL)canHaveChildren; 151 | - (id)identifier; 152 | - (id)displayName; 153 | - (id)descriptionShortClassName; 154 | 155 | @end 156 | 157 | @interface IBICCatalog : IBICFolder 158 | - (void)replaceChildrenWithDiskContent:(id)arg1; 159 | - (BOOL)canBeEmbeddedInFolder; 160 | - (BOOL)canHaveChildren; 161 | - (id)displayName; 162 | - (id)allIconSets; 163 | - (id)allImageSets; 164 | - (id)catalog; 165 | - (id)descriptionShortClassName; 166 | 167 | @end 168 | 169 | 170 | @interface IBICMultipartImage : IBICAbstractCatalogItem 171 | { 172 | } 173 | 174 | + (Class)repIdentifierClass; 175 | + (Class)slotClass; 176 | + (id)pluralTypeNameForIssues; 177 | + (id)typeNameForIssues; 178 | + (id)keysThatImpactImageName; 179 | + (id)fileNameForImageSetName:(id)arg1; 180 | + (Class)imageRepClass; 181 | + (id)fileExtension; 182 | + (id)defaultInstanceForPlatforms:(id)arg1; 183 | + (id)defaultImageName; 184 | + (Class)classForDirectoryExtension:(id)arg1; 185 | + (id)multipartImageClassesInImportOrder; 186 | + (id)multipartImageClasses; 187 | + (id)allocWithZone:(struct _NSZone *)arg1; 188 | - (void)populateMutatorsToAddRequiredChildCounterparts:(id)arg1; 189 | - (id)suggestedFileNameForImageRepInSlot:(id)arg1; 190 | - (void)populateIssues:(id)arg1 context:(id)arg2; 191 | - (void)assertChildIsLegalToAdd:(id)arg1; 192 | - (id)pluralTypeNameForIssues; 193 | - (id)typeNameForIssues; 194 | - (id)descriptionShortClassName; 195 | - (id)imageRepForImageRepIdentifier:(id)arg1; 196 | - (id)imageRepForSlot:(id)arg1; 197 | - (id)imageRepForIdentifier:(id)arg1; 198 | - (id)childForIdentifier:(id)arg1; 199 | - (BOOL)canBeEmbeddedInFolder; 200 | - (BOOL)canHaveChildren; 201 | - (id)identifier; 202 | - (id)displayName; 203 | @property(copy, nonatomic) NSString *imageName; 204 | - (id)validatedFileNameForProposedImageName:(id)arg1; 205 | - (id)enclosingMultipartImageIncludingReceiver; 206 | 207 | @end 208 | 209 | 210 | @interface IBICMappedMultipartImage : IBICMultipartImage 211 | { 212 | } 213 | 214 | + (id)orderedSlotComponentClasses; 215 | + (double)currentContentsJSONVersionNumber; 216 | + (double)latestUnderstoodContentsJSONVersionNumber; 217 | + (double)earliestUnderstoodContentsJSONVersionNumber; 218 | - (id)contentsDictionary; 219 | - (void)populateContentsJSONImageEntry:(id)arg1 forImageRep:(id)arg2; 220 | - (BOOL)shouldIncludeImageRepInContentsJSON:(id)arg1; 221 | - (void)replaceChildrenFromFileSystemSnapshot:(id)arg1 results:(id)arg2; 222 | - (id)imageRepsByMergingLooseFilesContentFromSnapshot:(id)arg1 withJSONReferencedContent:(id)arg2 results:(id)arg3; 223 | - (id)imageRepsFromContentsJSONImageEntries:(id)arg1 results:(id)arg2; 224 | - (id)imageRepFromImageEntry:(id)arg1 results:(id)arg2; 225 | - (id)validatedContentsJSONImageEntriesFromSnapshot:(id)arg1 results:(id)arg2; 226 | - (id)readContentsJSONFromSnapshot:(id)arg1 results:(id)arg2; 227 | - (BOOL)fileStructureSnapshotChildWouldMapToModelChild:(id)arg1; 228 | - (id)manifestFileData; 229 | - (id)manifestFileName; 230 | - (id)orderedSlotComponentClasses; 231 | 232 | @end 233 | 234 | 235 | 236 | @interface IBICAppIconSet : IBICMappedMultipartImage 237 | { 238 | BOOL _preRendered; 239 | } 240 | 241 | + (id)pluralTypeNameForIssues; 242 | + (id)typeNameForIssues; 243 | + (id)fileExtension; 244 | + (id)defaultInstanceForPlatforms:(id)arg1; 245 | + (id)defaultImageName; 246 | + (double)currentContentsJSONVersionNumber; 247 | + (double)latestUnderstoodContentsJSONVersionNumber; 248 | + (double)earliestUnderstoodContentsJSONVersionNumber; 249 | + (Class)imageRepClass; 250 | @property(nonatomic, getter=isPreRendered) BOOL preRendered; // @synthesize preRendered=_preRendered; 251 | - (id)descriptionShortClassName; 252 | - (id)contentsDictionary; 253 | - (id)readContentsJSONFromSnapshot:(id)arg1 results:(id)arg2; 254 | - (id)suggestedFileNameForImageRepInSlot:(id)arg1; 255 | - (id)childForIdentifier:(id)arg1; 256 | - (id)imageRepForIdentifier:(id)arg1; 257 | - (id)imageRepForImageRepIdentifier:(id)arg1; 258 | - (id)imageRepForSlot:(id)arg1; 259 | 260 | @end 261 | 262 | @interface IBICLaunchImageSet : IBICMappedMultipartImage 263 | { 264 | } 265 | 266 | + (id)pluralTypeNameForIssues; 267 | + (id)typeNameForIssues; 268 | + (id)fileExtension; 269 | + (id)defaultInstanceForPlatforms:(id)arg1; 270 | + (id)defaultImageName; 271 | + (double)currentContentsJSONVersionNumber; 272 | + (double)latestUnderstoodContentsJSONVersionNumber; 273 | + (double)earliestUnderstoodContentsJSONVersionNumber; 274 | + (Class)imageRepClass; 275 | - (id)descriptionShortClassName; 276 | - (id)validatedContentsJSONImageEntriesFromSnapshot:(id)arg1 results:(id)arg2; 277 | - (id)suggestedFileNameForImageRepInSlot:(id)arg1; 278 | - (id)childForIdentifier:(id)arg1; 279 | - (id)imageRepForIdentifier:(id)arg1; 280 | - (id)imageRepForImageRepIdentifier:(id)arg1; 281 | - (id)imageRepForSlot:(id)arg1; 282 | 283 | @end 284 | 285 | 286 | @interface IBICMultipartImageRepSlot : NSObject 287 | { 288 | NSDictionary *_componentsByClass; 289 | } 290 | 291 | + (id)orderedComponentClasses; 292 | + (id)defaultSlot; 293 | + (id)emptySlot; 294 | + (id)allocWithZone:(struct _NSZone *)arg1; 295 | + (id)slotWithComponents:(id)arg1; 296 | + (id)slotWithComponents:(id *)arg1 count:(unsigned long long)arg2; 297 | - (id)requiredPointSize; 298 | - (id)requiredPixelSize; 299 | - (id)suggestedRepNameForMultipartImageSetName:(id)arg1; 300 | - (id)detailAreaKey; 301 | - (id)requiredFileName; 302 | - (id)displayName; 303 | - (id)description; 304 | - (id)stringRepresentation; 305 | - (id)shortDisplayNameDefiningItem; 306 | - (unsigned long long)hash; 307 | - (BOOL)isEqual:(id)arg1; 308 | - (BOOL)isEqualToMultipartImageRepSlot:(id)arg1; 309 | - (long long)compareDisplayOrder:(id)arg1; 310 | - (id)slotComponentsForClasses:(id)arg1; 311 | - (id)slotComponentForClass:(Class)arg1; 312 | - (void)enumerateOrderedSlotComponentsAndValues:(id)arg1; 313 | - (id)initWithComponents:(id)arg1; 314 | - (void)captureComponents; 315 | 316 | @end 317 | 318 | 319 | 320 | @interface IBICMultipartImageRep : IBICAbstractCatalogItem 321 | + (id)keysThatImpactDisplayOrder; 322 | + (id)validSourceImageExtensions; 323 | + (id)imageRepWithRepIdentifier:(id)arg1; 324 | + (id)imageRepWithSlot:(id)arg1 fileName:(id)arg2 andUnassigned:(BOOL)arg3; 325 | + (Class)slotClass; 326 | + (Class)multiplartImageClass; 327 | + (Class)repIdentifierClass; 328 | + (id)allocWithZone:(struct _NSZone *)arg1; 329 | @property(copy, nonatomic) NSData *imageData; // @synthesize imageData=_imageData; 330 | @property(copy, nonatomic) IBICMultipartImageRepSlot *slot; // @synthesize slot=_slot; 331 | @property(nonatomic, getter=isUnassigned) BOOL unassigned; // @synthesize unassigned=_unassigned; 332 | - (id)suggestedFileName; 333 | @property(readonly) NSValue *imageDataPixelSize; 334 | @property(readonly) NSValue *requiredPointSize; 335 | @property(readonly) NSValue *requiredPixelSize; 336 | - (void)populateIssues:(id)arg1 context:(id)arg2; 337 | - (BOOL)updateModificationDatesWithMutationResult:(id)arg1; 338 | - (void)setImageDataFromPath:(id)arg1; 339 | - (BOOL)fileStructureSnapshotChildWouldMapToModelChild:(id)arg1; 340 | - (long long)compareDisplayOrder:(id)arg1; 341 | - (void)replaceChildrenFromFileSystemSnapshot:(id)arg1 results:(id)arg2; 342 | - (id)fileWrapperRepresentationWithOptions:(unsigned long long)arg1; 343 | - (id)parent; 344 | - (void)enumerateDescriptionAttributeComponents:(id)arg1; 345 | - (BOOL)isBrokenFileReference; 346 | - (id)identifier; 347 | - (id)structuredIdentifier; 348 | - (BOOL)canBeEmbeddedInFolder; 349 | - (BOOL)canHaveChildren; 350 | - (id)descriptionShortClassName; 351 | - (BOOL)isMinimallyFitForCompiling; 352 | @property(readonly) NSString *shortDisplayName; 353 | - (id)displayName; 354 | - (id)initWithSlot:(id)arg1; 355 | 356 | @end 357 | 358 | 359 | @interface IBICIconSetRep : IBICMultipartImageRep 360 | { 361 | } 362 | 363 | + (id)outputImageExtension; 364 | + (Class)multiplartImageClass; 365 | + (Class)repIdentifierClass; 366 | + (id)imageRepWithSlot:(id)arg1 fileName:(id)arg2 andUnassigned:(BOOL)arg3; 367 | + (id)imageRepWithRepIdentifier:(id)arg1; 368 | - (BOOL)isMinimallyFitForCompiling; 369 | - (BOOL)isImageDataSizedProperly; 370 | - (id)descriptionShortClassName; 371 | - (void)setSlot:(id)arg1; 372 | - (id)slot; 373 | - (id)structuredIdentifier; 374 | - (id)parent; 375 | - (id)initWithSlot:(id)arg1; 376 | 377 | @end 378 | 379 | 380 | @interface IBICCatalogSynchronizer : NSObject 381 | + (id)synchronizerForCatalogAtPath:(NSString *)path; 382 | - (void)preventSynchronizationDuring:(id)arg1; 383 | - (BOOL)isSynchronizationEnabled; 384 | - (void)enableSynchronization; 385 | - (void)disableSynchronization; 386 | - (void)applyMutationToModelAndScheduleForDisk:(id)arg1; 387 | - (id)replaceCatalogWithContentsOfPathWhileItIsKnowThatSyncOperationsAreNotInflightAndAreDisabled:(id)arg1; 388 | - (id)replaceCatalogWithContentsOfPath:(id)arg1; 389 | - (void)validateBatchedChanges:(id)arg1; 390 | - (void)validateChangesToDiskIfNeeded; 391 | - (void)validateChangesFromDiskIfNeeded; 392 | - (void)resetContentFromDisk; 393 | @property(readonly) IBICCatalog *catalog; 394 | - (void)primitiveInvalidate; 395 | - (id)init; 396 | - (id)initByTakingOwnershipsOfCatalog:(id)arg1; 397 | 398 | // Remaining properties 399 | @property(readonly, nonatomic, getter=isValid) BOOL valid; 400 | @end 401 | 402 | @interface DTAssetiLifeDelegate : NSObject 403 | @end 404 | -------------------------------------------------------------------------------- /ScreenShots/load-bundle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickytan/RTTinyPNGWebAPIPlugin/b82c3d6b44f2972ae5dcbe8c50815b520839089f/ScreenShots/load-bundle.png -------------------------------------------------------------------------------- /ScreenShots/s0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickytan/RTTinyPNGWebAPIPlugin/b82c3d6b44f2972ae5dcbe8c50815b520839089f/ScreenShots/s0.png -------------------------------------------------------------------------------- /ScreenShots/s1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickytan/RTTinyPNGWebAPIPlugin/b82c3d6b44f2972ae5dcbe8c50815b520839089f/ScreenShots/s1.png -------------------------------------------------------------------------------- /ScreenShots/s2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickytan/RTTinyPNGWebAPIPlugin/b82c3d6b44f2972ae5dcbe8c50815b520839089f/ScreenShots/s2.png -------------------------------------------------------------------------------- /ScreenShots/s3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickytan/RTTinyPNGWebAPIPlugin/b82c3d6b44f2972ae5dcbe8c50815b520839089f/ScreenShots/s3.png -------------------------------------------------------------------------------- /ScreenShots/sorting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickytan/RTTinyPNGWebAPIPlugin/b82c3d6b44f2972ae5dcbe8c50815b520839089f/ScreenShots/sorting.png --------------------------------------------------------------------------------