├── .gitignore ├── Free Ruler Icon.pxm ├── Free Ruler.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings │ └── xcuserdata │ │ └── jeffh.xcuserdatad │ │ └── UserInterfaceState.xcuserstate ├── xcshareddata │ └── xcschemes │ │ ├── App Icon Layout.xcscheme │ │ └── Free Ruler.xcscheme └── xcuserdata │ └── jeffh.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── Free Ruler ├── AppDelegate.swift ├── AppIconLayout.swift ├── Base.lproj │ ├── MainMenu.xib │ └── PreferencesController.xib ├── FreeRuler.help │ └── Contents │ │ ├── Info.plist │ │ └── Resources │ │ ├── English.lproj │ │ ├── English.lproj.helpindex │ │ └── FreeRuler.html │ │ └── shrd │ │ ├── freeruler.png │ │ └── styles.css ├── Free_Ruler.entitlements ├── HorizontalRule.swift ├── Images.xcassets │ └── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── icon_128x128.png │ │ ├── icon_128x128@2x.png │ │ ├── icon_16x16.png │ │ ├── icon_16x16@2x.png │ │ ├── icon_256x256.png │ │ ├── icon_256x256@2x.png │ │ ├── icon_32x32.png │ │ ├── icon_32x32@2x.png │ │ ├── icon_512x512.png │ │ └── icon_512x512@2x.png ├── Info.plist ├── Notifications.swift ├── PreferencesController.swift ├── Prefs.swift ├── RuleView.swift ├── Ruler.swift ├── RulerController.swift ├── RulerWindow.swift ├── VerticalRule.swift ├── de.lproj │ ├── MainMenu.strings │ └── PreferencesController.strings ├── fi.lproj │ ├── MainMenu.strings │ └── PreferencesController.strings ├── ja.lproj │ ├── MainMenu.strings │ └── PreferencesController.strings └── zh-hans.lproj │ ├── MainMenu.strings │ └── PreferencesController.strings ├── HOW-TO-RELEASE.md ├── LICENSE ├── README.md ├── dist ├── free-ruler-2.0.0.zip ├── free-ruler-2.0.1.zip ├── free-ruler-2.0.2.zip ├── free-ruler-2.0.3.zip ├── free-ruler-2.0.4.zip ├── free-ruler-2.0.5.zip ├── free-ruler-2.0.6.zip ├── free-ruler-2.0.7.zip └── free-ruler-2.0.8.zip └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | xcuserdata 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /Free Ruler Icon.pxm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pascalpp/FreeRuler/886780dab939e082a37c7401f632a27e980394f4/Free Ruler Icon.pxm -------------------------------------------------------------------------------- /Free Ruler.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 54; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 50008B6122846FCD001E3EE4 /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50008B6022846FCD001E3EE4 /* Notifications.swift */; }; 11 | 5012CAAD226AB09000BD9565 /* VerticalRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5012CAAC226AB09000BD9565 /* VerticalRule.swift */; }; 12 | 507FED43227FFF5300BD77DC /* PreferencesController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507FED41227FFF5300BD77DC /* PreferencesController.swift */; }; 13 | 507FED44227FFF5300BD77DC /* PreferencesController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 507FED42227FFF5300BD77DC /* PreferencesController.xib */; }; 14 | 507FED602280E13200BD77DC /* Prefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507FED5F2280E13200BD77DC /* Prefs.swift */; }; 15 | 50C6D891228BDBAD0091F19E /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50C6D890228BDBAD0091F19E /* Images.xcassets */; }; 16 | 50CCB206227FCD26004645C5 /* AppIconLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50CCB205227FCD26004645C5 /* AppIconLayout.swift */; }; 17 | 50D7BEE7227D42FD0008B95E /* RulerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50D7BEE6227D42FD0008B95E /* RulerController.swift */; }; 18 | 50D7BEE9227D43270008B95E /* Ruler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50D7BEE8227D43270008B95E /* Ruler.swift */; }; 19 | 50D7BEEB227D432E0008B95E /* RulerWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50D7BEEA227D432E0008B95E /* RulerWindow.swift */; }; 20 | 50D7BEED227D5C810008B95E /* RuleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50D7BEEC227D5C810008B95E /* RuleView.swift */; }; 21 | 50FC527E25BF326800B84228 /* FreeRuler.help in Resources */ = {isa = PBXBuildFile; fileRef = 50FC527D25BF326800B84228 /* FreeRuler.help */; }; 22 | 6F4102892260712F00F06A10 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F4102882260712F00F06A10 /* AppDelegate.swift */; }; 23 | 6F41028E2260713100F06A10 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6F41028C2260713100F06A10 /* MainMenu.xib */; }; 24 | 6F41029F22607DC900F06A10 /* HorizontalRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F41029E22607DC900F06A10 /* HorizontalRule.swift */; }; 25 | /* End PBXBuildFile section */ 26 | 27 | /* Begin PBXCopyFilesBuildPhase section */ 28 | 5077EFD2227E6D9D00A13D57 /* CopyFiles */ = { 29 | isa = PBXCopyFilesBuildPhase; 30 | buildActionMask = 2147483647; 31 | dstPath = ""; 32 | dstSubfolderSpec = 16; 33 | files = ( 34 | ); 35 | runOnlyForDeploymentPostprocessing = 0; 36 | }; 37 | 5077EFD9227E6E6400A13D57 /* Embed Frameworks */ = { 38 | isa = PBXCopyFilesBuildPhase; 39 | buildActionMask = 2147483647; 40 | dstPath = ""; 41 | dstSubfolderSpec = 16; 42 | files = ( 43 | ); 44 | name = "Embed Frameworks"; 45 | runOnlyForDeploymentPostprocessing = 0; 46 | }; 47 | /* End PBXCopyFilesBuildPhase section */ 48 | 49 | /* Begin PBXFileReference section */ 50 | 50008B6022846FCD001E3EE4 /* Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifications.swift; sourceTree = ""; }; 51 | 5012CAAC226AB09000BD9565 /* VerticalRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerticalRule.swift; sourceTree = ""; }; 52 | 507FED41227FFF5300BD77DC /* PreferencesController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesController.swift; sourceTree = ""; }; 53 | 507FED5F2280E13200BD77DC /* Prefs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Prefs.swift; sourceTree = ""; }; 54 | 50C6D890228BDBAD0091F19E /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 55 | 50CCB205227FCD26004645C5 /* AppIconLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIconLayout.swift; sourceTree = ""; }; 56 | 50D7BEE6227D42FD0008B95E /* RulerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RulerController.swift; sourceTree = ""; }; 57 | 50D7BEE8227D43270008B95E /* Ruler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Ruler.swift; sourceTree = ""; }; 58 | 50D7BEEA227D432E0008B95E /* RulerWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RulerWindow.swift; sourceTree = ""; }; 59 | 50D7BEEC227D5C810008B95E /* RuleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleView.swift; sourceTree = ""; }; 60 | 50FC527D25BF326800B84228 /* FreeRuler.help */ = {isa = PBXFileReference; lastKnownFileType = folder; path = FreeRuler.help; sourceTree = ""; }; 61 | 53AF6A4225A456E30076AAB7 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/MainMenu.strings; sourceTree = ""; }; 62 | 53AF6A4325A456E30076AAB7 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/PreferencesController.strings; sourceTree = ""; }; 63 | 6F4102852260712F00F06A10 /* Free Ruler.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Free Ruler.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 64 | 6F4102882260712F00F06A10 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 65 | 6F41028D2260713100F06A10 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 66 | 6F41028F2260713100F06A10 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 67 | 6F4102902260713100F06A10 /* Free_Ruler.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Free_Ruler.entitlements; sourceTree = ""; }; 68 | 6F41029E22607DC900F06A10 /* HorizontalRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HorizontalRule.swift; sourceTree = ""; }; 69 | 6F8CAA3929CC8F7D00C4ED57 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/MainMenu.strings"; sourceTree = ""; }; 70 | 6F8CAA3A29CC8F7D00C4ED57 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/PreferencesController.strings"; sourceTree = ""; }; 71 | 8F629823243003EA004F9099 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/MainMenu.strings; sourceTree = ""; }; 72 | 8F629825243003F6004F9099 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/PreferencesController.xib; sourceTree = ""; }; 73 | 8F629828243003FF004F9099 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/PreferencesController.strings; sourceTree = ""; }; 74 | B894A5002BBFE61A005A3B6F /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/MainMenu.strings; sourceTree = ""; }; 75 | B894A5012BBFE61A005A3B6F /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/PreferencesController.strings; sourceTree = ""; }; 76 | /* End PBXFileReference section */ 77 | 78 | /* Begin PBXFrameworksBuildPhase section */ 79 | 6F4102822260712F00F06A10 /* Frameworks */ = { 80 | isa = PBXFrameworksBuildPhase; 81 | buildActionMask = 2147483647; 82 | files = ( 83 | ); 84 | runOnlyForDeploymentPostprocessing = 0; 85 | }; 86 | /* End PBXFrameworksBuildPhase section */ 87 | 88 | /* Begin PBXGroup section */ 89 | 6F41027C2260712F00F06A10 = { 90 | isa = PBXGroup; 91 | children = ( 92 | 6F4102872260712F00F06A10 /* Free Ruler */, 93 | 6F4102862260712F00F06A10 /* Products */, 94 | ); 95 | sourceTree = ""; 96 | }; 97 | 6F4102862260712F00F06A10 /* Products */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | 6F4102852260712F00F06A10 /* Free Ruler.app */, 101 | ); 102 | name = Products; 103 | sourceTree = ""; 104 | }; 105 | 6F4102872260712F00F06A10 /* Free Ruler */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | 50FC527D25BF326800B84228 /* FreeRuler.help */, 109 | 507FED5F2280E13200BD77DC /* Prefs.swift */, 110 | 50008B6022846FCD001E3EE4 /* Notifications.swift */, 111 | 6F4102882260712F00F06A10 /* AppDelegate.swift */, 112 | 6F41028C2260713100F06A10 /* MainMenu.xib */, 113 | 50D7BEE8227D43270008B95E /* Ruler.swift */, 114 | 50D7BEE6227D42FD0008B95E /* RulerController.swift */, 115 | 50D7BEEA227D432E0008B95E /* RulerWindow.swift */, 116 | 50D7BEEC227D5C810008B95E /* RuleView.swift */, 117 | 6F41029E22607DC900F06A10 /* HorizontalRule.swift */, 118 | 5012CAAC226AB09000BD9565 /* VerticalRule.swift */, 119 | 507FED41227FFF5300BD77DC /* PreferencesController.swift */, 120 | 507FED42227FFF5300BD77DC /* PreferencesController.xib */, 121 | 50CCB205227FCD26004645C5 /* AppIconLayout.swift */, 122 | 6F4102902260713100F06A10 /* Free_Ruler.entitlements */, 123 | 6F41028F2260713100F06A10 /* Info.plist */, 124 | 50C6D890228BDBAD0091F19E /* Images.xcassets */, 125 | ); 126 | path = "Free Ruler"; 127 | sourceTree = ""; 128 | }; 129 | /* End PBXGroup section */ 130 | 131 | /* Begin PBXNativeTarget section */ 132 | 6F4102842260712F00F06A10 /* Free Ruler */ = { 133 | isa = PBXNativeTarget; 134 | buildConfigurationList = 6F4102932260713100F06A10 /* Build configuration list for PBXNativeTarget "Free Ruler" */; 135 | buildPhases = ( 136 | 6F4102812260712F00F06A10 /* Sources */, 137 | 6F4102822260712F00F06A10 /* Frameworks */, 138 | 6F4102832260712F00F06A10 /* Resources */, 139 | 5077EFD2227E6D9D00A13D57 /* CopyFiles */, 140 | 5077EFD9227E6E6400A13D57 /* Embed Frameworks */, 141 | ); 142 | buildRules = ( 143 | ); 144 | dependencies = ( 145 | ); 146 | name = "Free Ruler"; 147 | productName = "Free Ruler"; 148 | productReference = 6F4102852260712F00F06A10 /* Free Ruler.app */; 149 | productType = "com.apple.product-type.application"; 150 | }; 151 | /* End PBXNativeTarget section */ 152 | 153 | /* Begin PBXProject section */ 154 | 6F41027D2260712F00F06A10 /* Project object */ = { 155 | isa = PBXProject; 156 | attributes = { 157 | BuildIndependentTargetsInParallel = YES; 158 | LastSwiftUpdateCheck = 1020; 159 | LastUpgradeCheck = 1530; 160 | ORGANIZATIONNAME = "Free Ruler"; 161 | TargetAttributes = { 162 | 6F4102842260712F00F06A10 = { 163 | CreatedOnToolsVersion = 10.2; 164 | }; 165 | }; 166 | }; 167 | buildConfigurationList = 6F4102802260712F00F06A10 /* Build configuration list for PBXProject "Free Ruler" */; 168 | compatibilityVersion = "Xcode 9.3"; 169 | developmentRegion = en; 170 | hasScannedForEncodings = 0; 171 | knownRegions = ( 172 | en, 173 | Base, 174 | de, 175 | fi, 176 | "zh-Hans", 177 | ja, 178 | ); 179 | mainGroup = 6F41027C2260712F00F06A10; 180 | productRefGroup = 6F4102862260712F00F06A10 /* Products */; 181 | projectDirPath = ""; 182 | projectRoot = ""; 183 | targets = ( 184 | 6F4102842260712F00F06A10 /* Free Ruler */, 185 | ); 186 | }; 187 | /* End PBXProject section */ 188 | 189 | /* Begin PBXResourcesBuildPhase section */ 190 | 6F4102832260712F00F06A10 /* Resources */ = { 191 | isa = PBXResourcesBuildPhase; 192 | buildActionMask = 2147483647; 193 | files = ( 194 | 50C6D891228BDBAD0091F19E /* Images.xcassets in Resources */, 195 | 50FC527E25BF326800B84228 /* FreeRuler.help in Resources */, 196 | 507FED44227FFF5300BD77DC /* PreferencesController.xib in Resources */, 197 | 6F41028E2260713100F06A10 /* MainMenu.xib in Resources */, 198 | ); 199 | runOnlyForDeploymentPostprocessing = 0; 200 | }; 201 | /* End PBXResourcesBuildPhase section */ 202 | 203 | /* Begin PBXSourcesBuildPhase section */ 204 | 6F4102812260712F00F06A10 /* Sources */ = { 205 | isa = PBXSourcesBuildPhase; 206 | buildActionMask = 2147483647; 207 | files = ( 208 | 50008B6122846FCD001E3EE4 /* Notifications.swift in Sources */, 209 | 507FED602280E13200BD77DC /* Prefs.swift in Sources */, 210 | 50CCB206227FCD26004645C5 /* AppIconLayout.swift in Sources */, 211 | 6F41029F22607DC900F06A10 /* HorizontalRule.swift in Sources */, 212 | 50D7BEEB227D432E0008B95E /* RulerWindow.swift in Sources */, 213 | 507FED43227FFF5300BD77DC /* PreferencesController.swift in Sources */, 214 | 50D7BEE7227D42FD0008B95E /* RulerController.swift in Sources */, 215 | 5012CAAD226AB09000BD9565 /* VerticalRule.swift in Sources */, 216 | 6F4102892260712F00F06A10 /* AppDelegate.swift in Sources */, 217 | 50D7BEED227D5C810008B95E /* RuleView.swift in Sources */, 218 | 50D7BEE9227D43270008B95E /* Ruler.swift in Sources */, 219 | ); 220 | runOnlyForDeploymentPostprocessing = 0; 221 | }; 222 | /* End PBXSourcesBuildPhase section */ 223 | 224 | /* Begin PBXVariantGroup section */ 225 | 507FED42227FFF5300BD77DC /* PreferencesController.xib */ = { 226 | isa = PBXVariantGroup; 227 | children = ( 228 | 8F629825243003F6004F9099 /* Base */, 229 | 8F629828243003FF004F9099 /* de */, 230 | 53AF6A4325A456E30076AAB7 /* fi */, 231 | 6F8CAA3A29CC8F7D00C4ED57 /* zh-Hans */, 232 | B894A5012BBFE61A005A3B6F /* ja */, 233 | ); 234 | name = PreferencesController.xib; 235 | sourceTree = ""; 236 | }; 237 | 6F41028C2260713100F06A10 /* MainMenu.xib */ = { 238 | isa = PBXVariantGroup; 239 | children = ( 240 | 6F41028D2260713100F06A10 /* Base */, 241 | 8F629823243003EA004F9099 /* de */, 242 | 53AF6A4225A456E30076AAB7 /* fi */, 243 | 6F8CAA3929CC8F7D00C4ED57 /* zh-Hans */, 244 | B894A5002BBFE61A005A3B6F /* ja */, 245 | ); 246 | name = MainMenu.xib; 247 | sourceTree = ""; 248 | }; 249 | /* End PBXVariantGroup section */ 250 | 251 | /* Begin XCBuildConfiguration section */ 252 | 6F4102912260713100F06A10 /* Debug */ = { 253 | isa = XCBuildConfiguration; 254 | buildSettings = { 255 | ALWAYS_SEARCH_USER_PATHS = NO; 256 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 257 | CLANG_ANALYZER_NONNULL = YES; 258 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 259 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 260 | CLANG_CXX_LIBRARY = "libc++"; 261 | CLANG_ENABLE_MODULES = YES; 262 | CLANG_ENABLE_OBJC_ARC = YES; 263 | CLANG_ENABLE_OBJC_WEAK = YES; 264 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 265 | CLANG_WARN_BOOL_CONVERSION = YES; 266 | CLANG_WARN_COMMA = YES; 267 | CLANG_WARN_CONSTANT_CONVERSION = YES; 268 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 269 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 270 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 271 | CLANG_WARN_EMPTY_BODY = YES; 272 | CLANG_WARN_ENUM_CONVERSION = YES; 273 | CLANG_WARN_INFINITE_RECURSION = YES; 274 | CLANG_WARN_INT_CONVERSION = YES; 275 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 276 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 277 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 278 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 279 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 280 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 281 | CLANG_WARN_STRICT_PROTOTYPES = YES; 282 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 283 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 284 | CLANG_WARN_UNREACHABLE_CODE = YES; 285 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 286 | CODE_SIGN_IDENTITY = "Mac Developer"; 287 | COPY_PHASE_STRIP = NO; 288 | DEAD_CODE_STRIPPING = YES; 289 | DEBUG_INFORMATION_FORMAT = dwarf; 290 | ENABLE_STRICT_OBJC_MSGSEND = YES; 291 | ENABLE_TESTABILITY = YES; 292 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 293 | GCC_C_LANGUAGE_STANDARD = gnu11; 294 | GCC_DYNAMIC_NO_PIC = NO; 295 | GCC_NO_COMMON_BLOCKS = YES; 296 | GCC_OPTIMIZATION_LEVEL = 0; 297 | GCC_PREPROCESSOR_DEFINITIONS = ( 298 | "DEBUG=1", 299 | "$(inherited)", 300 | ); 301 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 302 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 303 | GCC_WARN_UNDECLARED_SELECTOR = YES; 304 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 305 | GCC_WARN_UNUSED_FUNCTION = YES; 306 | GCC_WARN_UNUSED_VARIABLE = YES; 307 | MACOSX_DEPLOYMENT_TARGET = 10.12; 308 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 309 | MTL_FAST_MATH = YES; 310 | ONLY_ACTIVE_ARCH = YES; 311 | SDKROOT = macosx; 312 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 313 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 314 | SWIFT_VERSION = 5.0; 315 | }; 316 | name = Debug; 317 | }; 318 | 6F4102922260713100F06A10 /* Release */ = { 319 | isa = XCBuildConfiguration; 320 | buildSettings = { 321 | ALWAYS_SEARCH_USER_PATHS = NO; 322 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 323 | CLANG_ANALYZER_NONNULL = YES; 324 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 325 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 326 | CLANG_CXX_LIBRARY = "libc++"; 327 | CLANG_ENABLE_MODULES = YES; 328 | CLANG_ENABLE_OBJC_ARC = YES; 329 | CLANG_ENABLE_OBJC_WEAK = YES; 330 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 331 | CLANG_WARN_BOOL_CONVERSION = YES; 332 | CLANG_WARN_COMMA = YES; 333 | CLANG_WARN_CONSTANT_CONVERSION = YES; 334 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 335 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 336 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 337 | CLANG_WARN_EMPTY_BODY = YES; 338 | CLANG_WARN_ENUM_CONVERSION = YES; 339 | CLANG_WARN_INFINITE_RECURSION = YES; 340 | CLANG_WARN_INT_CONVERSION = YES; 341 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 342 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 343 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 344 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 345 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 346 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 347 | CLANG_WARN_STRICT_PROTOTYPES = YES; 348 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 349 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 350 | CLANG_WARN_UNREACHABLE_CODE = YES; 351 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 352 | CODE_SIGN_IDENTITY = "Mac Developer"; 353 | COPY_PHASE_STRIP = NO; 354 | DEAD_CODE_STRIPPING = YES; 355 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 356 | ENABLE_NS_ASSERTIONS = NO; 357 | ENABLE_STRICT_OBJC_MSGSEND = YES; 358 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 359 | GCC_C_LANGUAGE_STANDARD = gnu11; 360 | GCC_NO_COMMON_BLOCKS = YES; 361 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 362 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 363 | GCC_WARN_UNDECLARED_SELECTOR = YES; 364 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 365 | GCC_WARN_UNUSED_FUNCTION = YES; 366 | GCC_WARN_UNUSED_VARIABLE = YES; 367 | MACOSX_DEPLOYMENT_TARGET = 10.12; 368 | MTL_ENABLE_DEBUG_INFO = NO; 369 | MTL_FAST_MATH = YES; 370 | SDKROOT = macosx; 371 | SWIFT_COMPILATION_MODE = wholemodule; 372 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 373 | SWIFT_VERSION = 5.0; 374 | }; 375 | name = Release; 376 | }; 377 | 6F4102942260713100F06A10 /* Debug */ = { 378 | isa = XCBuildConfiguration; 379 | buildSettings = { 380 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 381 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 382 | CODE_SIGN_ENTITLEMENTS = "Free Ruler/Free_Ruler.entitlements"; 383 | CODE_SIGN_IDENTITY = "Apple Development"; 384 | CODE_SIGN_STYLE = Automatic; 385 | COMBINE_HIDPI_IMAGES = YES; 386 | CURRENT_PROJECT_VERSION = 302; 387 | DEAD_CODE_STRIPPING = YES; 388 | DEVELOPMENT_TEAM = X88CY268NZ; 389 | ENABLE_HARDENED_RUNTIME = YES; 390 | INFOPLIST_FILE = "Free Ruler/Info.plist"; 391 | LD_RUNPATH_SEARCH_PATHS = ( 392 | "@loader_path/../Frameworks", 393 | "@executable_path/../Frameworks", 394 | ); 395 | MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; 396 | MARKETING_VERSION = 2.0.8; 397 | PRODUCT_BUNDLE_IDENTIFIER = com.pascal.freeruler; 398 | PRODUCT_NAME = "$(TARGET_NAME)"; 399 | PROVISIONING_PROFILE_SPECIFIER = ""; 400 | SWIFT_VERSION = 5.0; 401 | }; 402 | name = Debug; 403 | }; 404 | 6F4102952260713100F06A10 /* Release */ = { 405 | isa = XCBuildConfiguration; 406 | buildSettings = { 407 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 408 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 409 | CODE_SIGN_ENTITLEMENTS = "Free Ruler/Free_Ruler.entitlements"; 410 | CODE_SIGN_IDENTITY = "Apple Development"; 411 | CODE_SIGN_STYLE = Automatic; 412 | COMBINE_HIDPI_IMAGES = YES; 413 | CURRENT_PROJECT_VERSION = 302; 414 | DEAD_CODE_STRIPPING = YES; 415 | DEVELOPMENT_TEAM = X88CY268NZ; 416 | ENABLE_HARDENED_RUNTIME = YES; 417 | INFOPLIST_FILE = "Free Ruler/Info.plist"; 418 | LD_RUNPATH_SEARCH_PATHS = ( 419 | "@loader_path/../Frameworks", 420 | "@executable_path/../Frameworks", 421 | ); 422 | MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; 423 | MARKETING_VERSION = 2.0.8; 424 | PRODUCT_BUNDLE_IDENTIFIER = com.pascal.freeruler; 425 | PRODUCT_NAME = "$(TARGET_NAME)"; 426 | PROVISIONING_PROFILE_SPECIFIER = ""; 427 | SWIFT_VERSION = 5.0; 428 | }; 429 | name = Release; 430 | }; 431 | /* End XCBuildConfiguration section */ 432 | 433 | /* Begin XCConfigurationList section */ 434 | 6F4102802260712F00F06A10 /* Build configuration list for PBXProject "Free Ruler" */ = { 435 | isa = XCConfigurationList; 436 | buildConfigurations = ( 437 | 6F4102912260713100F06A10 /* Debug */, 438 | 6F4102922260713100F06A10 /* Release */, 439 | ); 440 | defaultConfigurationIsVisible = 0; 441 | defaultConfigurationName = Release; 442 | }; 443 | 6F4102932260713100F06A10 /* Build configuration list for PBXNativeTarget "Free Ruler" */ = { 444 | isa = XCConfigurationList; 445 | buildConfigurations = ( 446 | 6F4102942260713100F06A10 /* Debug */, 447 | 6F4102952260713100F06A10 /* Release */, 448 | ); 449 | defaultConfigurationIsVisible = 0; 450 | defaultConfigurationName = Release; 451 | }; 452 | /* End XCConfigurationList section */ 453 | }; 454 | rootObject = 6F41027D2260712F00F06A10 /* Project object */; 455 | } 456 | -------------------------------------------------------------------------------- /Free Ruler.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Free Ruler.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Free Ruler.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Free Ruler.xcodeproj/project.xcworkspace/xcuserdata/jeffh.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pascalpp/FreeRuler/886780dab939e082a37c7401f632a27e980394f4/Free Ruler.xcodeproj/project.xcworkspace/xcuserdata/jeffh.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Free Ruler.xcodeproj/xcshareddata/xcschemes/App Icon Layout.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 67 | 68 | 69 | 70 | 76 | 77 | 79 | 80 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /Free Ruler.xcodeproj/xcshareddata/xcschemes/Free Ruler.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /Free Ruler.xcodeproj/xcuserdata/jeffh.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /Free Ruler.xcodeproj/xcuserdata/jeffh.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Free Ruler.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Free Ruler/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | let env = ProcessInfo.processInfo.environment 4 | let APP_ICON_HELPER = env["APP_ICON_HELPER"] != nil 5 | 6 | @NSApplicationMain 7 | class AppDelegate: NSObject, NSApplicationDelegate { 8 | 9 | var observers: [NSKeyValueObservation] = [] 10 | 11 | var rulers: [RulerController] = [] 12 | 13 | var timer: Timer? 14 | let foregroundTimerInterval: TimeInterval = 1 / 60 // 60 fps 15 | let backgroundTimerInterval: TimeInterval = 1 / 30 // 30 fps 16 | 17 | let crosshair = NSCursor.crosshair 18 | 19 | @IBOutlet weak var pixelsMenuItem: NSMenuItem! 20 | @IBOutlet weak var millimetersMenuItem: NSMenuItem! 21 | @IBOutlet weak var inchesMenuItem: NSMenuItem! 22 | @IBOutlet weak var cycleUnitsMenuItem: NSMenuItem! 23 | 24 | @IBOutlet weak var floatRulersMenuItem: NSMenuItem! 25 | @IBOutlet weak var groupRulersMenuItem: NSMenuItem! 26 | @IBOutlet weak var rulerShadowMenuItem: NSMenuItem! 27 | @IBOutlet weak var alignRulersMenuItem: NSMenuItem! 28 | 29 | var preferencesController: PreferencesController? = nil 30 | 31 | // MARK: - Lifecycle 32 | 33 | func applicationDidFinishLaunching(_ aNotification: Notification) { 34 | 35 | subscribeToPrefs() 36 | updateDisplay() 37 | 38 | if APP_ICON_HELPER { 39 | let helper = AppIconLayout() 40 | helper.show() 41 | } else { 42 | showRulers() 43 | } 44 | 45 | } 46 | 47 | func subscribeToPrefs() { 48 | observers = [ 49 | prefs.observe(\Prefs.unit, options: .new) { prefs, changed in 50 | self.updateUnitMenu() 51 | self.redrawRulers() 52 | }, 53 | prefs.observe(\Prefs.floatRulers, options: .new) { prefs, changed in 54 | self.updateFloatRulersMenuItem() 55 | }, 56 | prefs.observe(\Prefs.groupRulers, options: .new) { prefs, changed in 57 | self.updateGroupRulersMenuItem() 58 | }, 59 | prefs.observe(\Prefs.rulerShadow, options: .new) { prefs, changed in 60 | self.updateRulerShadowMenuItem() 61 | }, 62 | ] 63 | } 64 | 65 | func updateDisplay() { 66 | updateUnitMenu() 67 | updateFloatRulersMenuItem() 68 | updateGroupRulersMenuItem() 69 | updateRulerShadowMenuItem() 70 | } 71 | 72 | func updateUnitMenu() { 73 | pixelsMenuItem?.state = prefs.unit == .pixels ? .on : .off 74 | millimetersMenuItem?.state = prefs.unit == .millimeters ? .on : .off 75 | inchesMenuItem?.state = prefs.unit == .inches ? .on : .off 76 | } 77 | 78 | func redrawRulers() { 79 | for ruler in rulers { 80 | ruler.rulerWindow.rule.setNeedsDisplay(ruler.rulerWindow.rule.visibleRect) 81 | } 82 | } 83 | 84 | func updateFloatRulersMenuItem() { 85 | floatRulersMenuItem?.state = prefs.floatRulers ? .on : .off 86 | } 87 | 88 | func updateGroupRulersMenuItem() { 89 | groupRulersMenuItem?.state = prefs.groupRulers ? .on : .off 90 | } 91 | 92 | func updateRulerShadowMenuItem() { 93 | rulerShadowMenuItem?.state = prefs.rulerShadow ? .on : .off 94 | } 95 | 96 | func showRulers() { 97 | rulers = [ 98 | RulerController(Ruler(.vertical, name: "vertical-ruler")), 99 | RulerController(Ruler(.horizontal, name: "horizontal-ruler")), 100 | ] 101 | 102 | // let rulers know about each other 103 | // TODO: provide each ruler with otherRulers: [RulerWindow] 104 | rulers[0].otherWindow = rulers[1].rulerWindow 105 | rulers[1].otherWindow = rulers[0].rulerWindow 106 | 107 | for ruler in rulers { 108 | ruler.showWindow(self) 109 | } 110 | } 111 | 112 | func applicationDidBecomeActive(_ notification: Notification) { 113 | for ruler in rulers { 114 | ruler.foreground() 115 | } 116 | 117 | startTimer(timeInterval: foregroundTimerInterval) 118 | 119 | crosshair.push() 120 | } 121 | 122 | func applicationDidResignActive(_ notification: Notification) { 123 | for ruler in rulers { 124 | ruler.background() 125 | } 126 | 127 | startTimer(timeInterval: backgroundTimerInterval) 128 | 129 | crosshair.pop() 130 | } 131 | 132 | @IBAction func setUnitPixels(_ sender: Any) { 133 | prefs.unit = .pixels 134 | } 135 | @IBAction func setUnitMillimetres(_ sender: Any) { 136 | prefs.unit = .millimeters 137 | } 138 | @IBAction func setUnitInches(_ sender: Any) { 139 | prefs.unit = .inches 140 | } 141 | @IBAction func cycleUnits(_ sender: Any) { 142 | switch prefs.unit { 143 | case .pixels: 144 | prefs.unit = .millimeters 145 | case .millimeters: 146 | prefs.unit = .inches 147 | case .inches: 148 | prefs.unit = .pixels 149 | } 150 | } 151 | 152 | @IBAction func toggleFloatRulers(_ sender: Any) { 153 | prefs.floatRulers = !prefs.floatRulers 154 | } 155 | 156 | @IBAction func toggleGroupRulers(_ sender: Any) { 157 | prefs.groupRulers = !prefs.groupRulers 158 | } 159 | @IBAction func toggleRulerShadow(_ sender: Any) { 160 | prefs.rulerShadow = !prefs.rulerShadow 161 | } 162 | 163 | @IBAction func openPreferences(_ sender: Any) { 164 | if preferencesController == nil { 165 | preferencesController = PreferencesController() 166 | } 167 | 168 | if preferencesController != nil { 169 | preferencesController?.showWindow(self) 170 | } 171 | } 172 | 173 | @IBAction func alignRulersAtMouseLocation(_ sender: Any) { 174 | var mouseLoc = NSEvent.mouseLocation 175 | mouseLoc.x = mouseLoc.x.rounded() 176 | mouseLoc.y = mouseLoc.y.rounded() 177 | for ruler in rulers { 178 | ruler.alignRuler(at: mouseLoc) 179 | } 180 | } 181 | 182 | @IBAction func resetRulerPositions(_ sender: Any) { 183 | // ungroup rulers during reset operation 184 | let groupRulers = prefs.groupRulers 185 | prefs.groupRulers = false 186 | for ruler in rulers { 187 | ruler.resetPosition() 188 | } 189 | // reset groupRulers to previous value 190 | prefs.groupRulers = groupRulers 191 | } 192 | 193 | // MARK: - Application Quit 194 | 195 | func applicationWillTerminate(_ aNotification: Notification) { 196 | prefs.save() 197 | } 198 | 199 | } 200 | 201 | 202 | 203 | // MARK: - Timer 204 | extension AppDelegate { 205 | 206 | private func startTimer(timeInterval: TimeInterval) { 207 | timer?.invalidate() 208 | 209 | timer = Timer.scheduledTimer( 210 | timeInterval: timeInterval, 211 | target: self, 212 | selector: #selector(self.onInterval), 213 | userInfo: nil, 214 | repeats: true 215 | ) 216 | } 217 | 218 | @objc func onInterval() { 219 | self.updateMouseLocation() 220 | } 221 | 222 | private func updateMouseLocation() { 223 | var mouseLoc = NSEvent.mouseLocation 224 | mouseLoc.x = mouseLoc.x.rounded() 225 | mouseLoc.y = mouseLoc.y.rounded() 226 | for ruler in rulers { 227 | ruler.rulerWindow.rule.drawMouseTick(at: mouseLoc) 228 | } 229 | } 230 | 231 | } 232 | -------------------------------------------------------------------------------- /Free Ruler/AppIconLayout.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | /* 4 | 5 | This file draws a special set of windows to be used as the app icon, with some additional image manipulation. 6 | 7 | Steps taken to generate the icon: 8 | - choose the 'App Icon Layout' scheme and run. 9 | - take screenshot on a retina screen 10 | - press cmd-shift-4 11 | - press and release the space bar to switch to object mode 12 | - click on the window 13 | - the rulers are set as child windows, so they'll be included in the screenshot 14 | - open Pixelmator file in this repo 15 | - paste into new layer (red squares should position it correctly for you) 16 | - move mask from old layer to new layer (to hide red squares) 17 | - move shadow from old layer to new layer 18 | - delete old layer 19 | - export 1024x1024 png 20 | - convert to ICNS file with Image2Icon or similar 21 | 22 | */ 23 | 24 | let titlebarHeight = CGFloat(30) 25 | 26 | let renderWidth = CGFloat(211) 27 | let targetWidth = CGFloat(680) 28 | let targetHeight = CGFloat(816) 29 | let aspect = targetWidth / targetHeight 30 | let boundsMultiplier = CGFloat(3.3) // used to render the layout at a larger scale 31 | 32 | class AppIconLayout: NSObject { 33 | 34 | func show() { 35 | let frame = NSRect(x: 100, y: 100, width: 1024, height: 1024 ) 36 | let window = NSWindow(contentRect: frame, styleMask: [], backing: .buffered, defer: false) 37 | window.isOpaque = false 38 | window.backgroundColor = NSColor(white: 1, alpha: 0) 39 | 40 | window.isMovableByWindowBackground = true 41 | window.contentView = ScaleView(frame: frame) 42 | window.orderFront(nil) 43 | } 44 | 45 | } 46 | 47 | class ScaleView: NSView { 48 | 49 | required init?(coder decoder: NSCoder) { 50 | fatalError() 51 | } 52 | 53 | override init(frame: NSRect) { 54 | super.init(frame: frame) 55 | 56 | // border 57 | // self.wantsLayer = true 58 | // self.layer?.borderColor = CGColor(gray: 0, alpha: 0.5) 59 | // self.layer?.borderWidth = 1.0 60 | 61 | let layoutRect = NSRect( 62 | x: 237, 63 | y: 54, 64 | width: frame.width, 65 | height: frame.height 66 | ) 67 | let layout = RulerLayoutView(frame: layoutRect) 68 | layout.setBoundsSize(NSSize(width: frame.width / boundsMultiplier, height: frame.height / boundsMultiplier)) 69 | layout.rotate(byDegrees: 9) 70 | 71 | self.addSubview(layout) 72 | 73 | let squareSize = CGFloat(10) 74 | let red = CGColor(red: 1, green: 0, blue: 0, alpha: 1) 75 | self.addSubview( 76 | ColorSquare( 77 | frame: NSRect(x: 0, y: frame.height - squareSize, width: squareSize, height: squareSize), 78 | color: red 79 | ) 80 | ) 81 | self.addSubview( 82 | ColorSquare( 83 | frame: NSRect(x: frame.width - squareSize, y: 0, width: squareSize, height: squareSize), 84 | color: red 85 | ) 86 | ) 87 | } 88 | 89 | } 90 | 91 | class RulerLayoutView: NSView { 92 | 93 | required init?(coder decoder: NSCoder) { 94 | super.init(coder: decoder) 95 | } 96 | 97 | override init(frame: NSRect) { 98 | super.init(frame: frame) 99 | 100 | let width = renderWidth 101 | let height = width / aspect 102 | 103 | let topRect = NSRect( 104 | x: Ruler.thickness, 105 | y: height - Ruler.thickness - 1, 106 | width: width - Ruler.thickness, 107 | height: Ruler.thickness 108 | ) 109 | let topRuler = Ruler(.horizontal, frame: topRect) 110 | let top = getRulerView(ruler: topRuler) 111 | top.wantsLayer = true 112 | top.layer?.borderColor = CGColor(gray: 0, alpha: 0.5) 113 | top.layer?.borderWidth = 1.0 114 | 115 | let leftRect = NSRect( 116 | x: 1, 117 | y: 0, 118 | width: Ruler.thickness, 119 | height: height - Ruler.thickness 120 | ) 121 | let leftRuler = Ruler(.vertical, frame: leftRect) 122 | let left = getRulerView(ruler: leftRuler) 123 | left.wantsLayer = true 124 | left.layer?.borderColor = CGColor(gray: 0, alpha: 0.5) 125 | left.layer?.borderWidth = 1.0 126 | 127 | let paperRect = NSRect( 128 | x: Ruler.thickness, 129 | y: 0, 130 | width: topRect.width, 131 | height: leftRect.height 132 | ) 133 | let paperView = NSView(frame: paperRect) 134 | paperView.wantsLayer = true 135 | paperView.layer?.borderColor = CGColor(gray: 0, alpha: 0.5) 136 | paperView.layer?.borderWidth = 1.0 137 | paperView.layer?.backgroundColor = CGColor(gray: 1, alpha: 1) 138 | paperView.layer?.cornerRadius = 10.0 139 | 140 | let toolbarRect = NSRect( 141 | x: -10, 142 | y: leftRect.height - titlebarHeight + 1, 143 | width: topRect.width + 20, 144 | height: titlebarHeight 145 | ) 146 | let toolbar = NSView(frame: toolbarRect) 147 | toolbar.wantsLayer = true 148 | toolbar.layer?.backgroundColor = CGColor(gray: 0.9, alpha: 1) 149 | toolbar.layer?.borderColor = CGColor(gray: 0, alpha: 0.2) 150 | toolbar.layer?.borderWidth = 1.0 151 | 152 | let buttonFrame = NSRect(x: 0, y: 0, width: 12, height: 12) 153 | let buttonsX = CGFloat(20) 154 | let buttonsY = CGFloat(8.5) 155 | let buttonSpace = CGFloat(18) 156 | 157 | let closeColor = CGColor(red: 250.0/255.0, green: 97.0/255.0, blue: 92.0/255.0, alpha: 255.0/255.0) 158 | let closeButton = ButtonView(frame: buttonFrame, color: closeColor) 159 | closeButton.setFrameOrigin(NSPoint(x: buttonsX, y: buttonsY)) 160 | toolbar.addSubview(closeButton) 161 | 162 | let minimizeColor = CGColor(red: 252.0/255.0, green: 188.0/255.0, blue: 63.0/255.0, alpha: 255.0/255.0) 163 | let minimizeButton = ButtonView(frame: buttonFrame, color: minimizeColor) 164 | minimizeButton.setFrameOrigin(NSPoint(x: buttonsX + buttonSpace, y: buttonsY)) 165 | toolbar.addSubview(minimizeButton) 166 | 167 | let zoomColor = CGColor(red: 59.0/255.0, green: 200.0/255.0, blue: 73.0/255.0, alpha: 255.0/255.0) 168 | let zoomButton = ButtonView(frame: buttonFrame, color: zoomColor) 169 | zoomButton.setFrameOrigin(NSPoint(x: buttonsX + buttonSpace + buttonSpace, y: buttonsY)) 170 | toolbar.addSubview(zoomButton) 171 | 172 | paperView.addSubview(toolbar) 173 | 174 | self.addSubview(paperView) 175 | self.addSubview(top) 176 | self.addSubview(left) 177 | 178 | } 179 | 180 | } 181 | 182 | class ButtonView: NSView { 183 | 184 | required init?(coder decoder: NSCoder) { 185 | fatalError() 186 | } 187 | 188 | init(frame frameRect: NSRect, color: CGColor) { 189 | super.init(frame: frameRect) 190 | self.wantsLayer = true 191 | self.layer?.backgroundColor = color 192 | self.layer?.cornerRadius = frame.width / 2 193 | self.layer?.borderColor = CGColor(gray: 0, alpha: 0.2) 194 | self.layer?.borderWidth = 1.0 195 | } 196 | 197 | } 198 | 199 | class ColorSquare: NSView { 200 | 201 | required init?(coder decoder: NSCoder) { 202 | fatalError() 203 | } 204 | 205 | init(frame frameRect: NSRect, color: CGColor) { 206 | super.init(frame: frameRect) 207 | self.wantsLayer = true 208 | self.layer?.backgroundColor = color 209 | } 210 | 211 | } 212 | -------------------------------------------------------------------------------- /Free Ruler/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 | -------------------------------------------------------------------------------- /Free Ruler/Base.lproj/PreferencesController.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 | 86 | 96 | 106 | 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 | -------------------------------------------------------------------------------- /Free Ruler/FreeRuler.help/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en_US 7 | CFBundleIdentifier 8 | com.pascal.freeruler.help 9 | CFBundleInfoDictionaryVersion 10 | 6.0 11 | CFBundleName 12 | Free Ruler 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1 17 | CFBundleSignature 18 | hbwr 19 | CFBundleVersion 20 | 1 21 | HPDBookAccessPath 22 | FreeRuler.html 23 | HPDBookIconPath 24 | 25 | HPDBookIndexPath 26 | English.lproj.helpindex 27 | HPDBookKBProduct 28 | freeruler1 29 | HPDBookTitle 30 | Free Ruler Help 31 | HPDBookType 32 | 3 33 | 34 | 35 | -------------------------------------------------------------------------------- /Free Ruler/FreeRuler.help/Contents/Resources/English.lproj/English.lproj.helpindex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pascalpp/FreeRuler/886780dab939e082a37c7401f632a27e980394f4/Free Ruler/FreeRuler.help/Contents/Resources/English.lproj/English.lproj.helpindex -------------------------------------------------------------------------------- /Free Ruler/FreeRuler.help/Contents/Resources/English.lproj/FreeRuler.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Free Ruler Help 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |

17 | Free Ruler Help 18 |

19 | 20 |

21 | Keyboard Shortcuts 22 |

23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
FFloat/unfloat rulers above other windows
GGroup/ungroup rulers
SShow/hide ruler shadows
OOrient rulers at mouse location
UCycle units: pixels, millimeters, and inches
RReset ruler positions to default
,Open Preferences
35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /Free Ruler/FreeRuler.help/Contents/Resources/shrd/freeruler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pascalpp/FreeRuler/886780dab939e082a37c7401f632a27e980394f4/Free Ruler/FreeRuler.help/Contents/Resources/shrd/freeruler.png -------------------------------------------------------------------------------- /Free Ruler/FreeRuler.help/Contents/Resources/shrd/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 40px; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", Arial, sans-serif; 4 | } 5 | 6 | .title-page-header { 7 | background-image: url(./freeruler.png); 8 | background-size: 200px 200px; 9 | background-repeat: no-repeat; 10 | padding-top: 220px; 11 | margin-bottom: 40px; 12 | } 13 | 14 | .keyboard-shortcuts th { 15 | text-align: center; 16 | padding-right: 5px; 17 | } 18 | 19 | .keyboard-shortcuts td { 20 | text-align: left; 21 | padding-left: 10px; 22 | } 23 | -------------------------------------------------------------------------------- /Free Ruler/Free_Ruler.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Free Ruler/HorizontalRule.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | class HorizontalRule: RuleView { 4 | 5 | let transformer = AffineTransform(translationByX: 0.5, byY: 0) 6 | 7 | var mouseTickX: CGFloat = 0 { 8 | didSet { 9 | if mouseTickX != oldValue { 10 | needsDisplay = true 11 | } 12 | } 13 | } 14 | 15 | override func draw(_ dirtyRect: NSRect) { 16 | super.draw(dirtyRect) 17 | 18 | // Drawing code here. 19 | color.fill.setFill() 20 | dirtyRect.fill() 21 | 22 | let paragraphStyle = NSMutableParagraphStyle() 23 | paragraphStyle.alignment = .center 24 | let attrs: [NSAttributedString.Key: Any] = [ 25 | .font: NSFont(name: "HelveticaNeue", size: 10)!, 26 | .paragraphStyle: paragraphStyle, 27 | .foregroundColor: color.numbers 28 | ] 29 | 30 | let width = dirtyRect.width 31 | let path = NSBezierPath() 32 | let tickScale: CGFloat 33 | let textScale: Int 34 | let largeTicks: Int 35 | let mediumTicks: Int 36 | let smallTicks: Int 37 | let tinyTicks: Int? 38 | 39 | switch prefs.unit { 40 | case .millimeters: 41 | tickScale = screen?.dpmm.width ?? NSScreen.defaultDpmm 42 | textScale = 1 43 | largeTicks = 10 44 | mediumTicks = 5 45 | smallTicks = 1 46 | tinyTicks = nil 47 | case .inches: 48 | tickScale = (screen?.dpi.width ?? NSScreen.defaultDpi) / 16 49 | textScale = 16 50 | largeTicks = 16 51 | mediumTicks = 8 52 | smallTicks = 4 53 | tinyTicks = 1 54 | default: 55 | tickScale = 1 56 | textScale = 1 57 | largeTicks = 50 58 | mediumTicks = 10 59 | smallTicks = 2 60 | tinyTicks = nil 61 | } 62 | 63 | let labelWidth: CGFloat = 50 64 | let labelHeight: CGFloat = 20 65 | let labelOffset: CGFloat = 13 // offset of label from bottom edge of ruler 66 | // TODO: refactor this to use label.size() logic (see func drawUnitLabel) 67 | 68 | // substract two so ticks don't overlap with border 69 | // subtract from this range so width var is accurate 70 | for i in 1...Int((width - 2) / tickScale) { 71 | let pos = CGFloat(i) * tickScale 72 | if i.isMultiple(of: largeTicks) { 73 | path.move(to: CGPoint(x: pos, y: 1)) 74 | path.line(to: CGPoint(x: pos, y: 10)) 75 | 76 | let label = String(i / textScale) 77 | let labelX: CGFloat = pos - (labelWidth / 2) + 0.5 // half-pixel nudge /shrug 78 | let labelY: CGFloat = labelOffset 79 | let labelRect = CGRect(x: labelX, y: labelY, width: labelWidth, height: labelHeight) 80 | 81 | label.draw( 82 | with: labelRect, 83 | attributes: attrs, 84 | context: nil 85 | ) 86 | 87 | } 88 | else if i.isMultiple(of: mediumTicks) { 89 | path.move(to: CGPoint(x: pos, y: 1)) 90 | path.line(to: CGPoint(x: pos, y: 8)) 91 | } 92 | else if i.isMultiple(of: smallTicks) { 93 | path.move(to: CGPoint(x: pos, y: 1)) 94 | path.line(to: CGPoint(x: pos, y: 5)) 95 | } 96 | else if let tinyTicks = tinyTicks, i.isMultiple(of: tinyTicks) { 97 | path.move(to: CGPoint(x: pos, y: 1)) 98 | path.line(to: CGPoint(x: pos, y: 3)) 99 | } 100 | } 101 | 102 | path.transform(using: transformer) 103 | 104 | color.ticks.setStroke() 105 | path.stroke() 106 | 107 | if !showMouseTick || mouseTickX < 0 || mouseTickX > 26 { 108 | drawUnitLabel() 109 | } 110 | 111 | // Draw the MouseTick & number 112 | if showMouseTick && mouseTickX > 0 && mouseTickX < self.windowWidth { 113 | drawMouseTick(mouseTickX) 114 | drawMouseNumber(mouseTickX) 115 | } 116 | 117 | } 118 | 119 | override func drawMouseTick(at mouseLoc: NSPoint) { 120 | let windowX = self.window?.frame.origin.x ?? 0 121 | let mouseX = mouseLoc.x 122 | self.mouseTickX = mouseX - windowX 123 | } 124 | 125 | func drawMouseTick(_ mouseTickX: CGFloat) { 126 | let mouseTick = NSBezierPath() 127 | let height: CGFloat = 40 128 | 129 | mouseTick.move(to: CGPoint(x: mouseTickX, y: 0)) 130 | mouseTick.line(to: CGPoint(x: mouseTickX, y: height)) 131 | 132 | mouseTick.transform(using: transformer) 133 | 134 | color.mouseTick.setStroke() 135 | mouseTick.stroke() 136 | } 137 | 138 | func drawMouseNumber(_ mouseTickX: CGFloat) { 139 | let number = mouseTickX 140 | let width = self.frame.width 141 | let height = self.frame.height 142 | let labelOffset: CGFloat = 5 143 | 144 | let paragraphStyle = NSMutableParagraphStyle() 145 | paragraphStyle.alignment = .center 146 | 147 | let attributes = [ 148 | NSAttributedString.Key.font: NSFont(name: "HelveticaNeue", size: 10)!, 149 | NSAttributedString.Key.paragraphStyle: paragraphStyle, 150 | NSAttributedString.Key.foregroundColor: color.mouseNumber, 151 | ] 152 | 153 | let mouseNumber = self.getMouseNumberLabel(number) 154 | let label = NSAttributedString(string: mouseNumber, attributes: attributes) 155 | let labelSize = label.size() 156 | 157 | let rightPosition = number + labelOffset; 158 | let leftPosition = number - labelOffset - labelSize.width 159 | let enoughRoomToTheRight = rightPosition + labelSize.width < width - labelOffset 160 | let labelX = enoughRoomToTheRight ? rightPosition : leftPosition 161 | 162 | let labelRect = CGRect(x: labelX, y: height - labelSize.height, width: labelSize.width, height: labelSize.height) 163 | 164 | label.draw( 165 | with: labelRect, 166 | context: nil 167 | ) 168 | } 169 | 170 | func drawUnitLabel() { 171 | let paragraphStyle = NSMutableParagraphStyle() 172 | paragraphStyle.alignment = .left 173 | 174 | let attributes = [ 175 | NSAttributedString.Key.font: NSFont(name: "HelveticaNeue", size: 10)!, 176 | NSAttributedString.Key.paragraphStyle: paragraphStyle, 177 | NSAttributedString.Key.foregroundColor: color.ticks, 178 | ] 179 | 180 | let unitlabel = self.getUnitLabel() 181 | let label = NSAttributedString(string: unitlabel, attributes: attributes) 182 | let height = self.frame.height 183 | let labelSize = label.size() 184 | let labelRect = CGRect(x: 10, y: height - labelSize.height, width: labelSize.width, height: labelSize.height) 185 | 186 | label.draw( 187 | with: labelRect, 188 | context: nil 189 | ) 190 | } 191 | 192 | } 193 | -------------------------------------------------------------------------------- /Free Ruler/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "icon_16x16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "icon_16x16@2x.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "icon_32x32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "icon_32x32@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "icon_128x128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "icon_128x128@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "icon_256x256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "icon_256x256@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "icon_512x512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "icon_512x512@2x.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Free Ruler/Images.xcassets/AppIcon.appiconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pascalpp/FreeRuler/886780dab939e082a37c7401f632a27e980394f4/Free Ruler/Images.xcassets/AppIcon.appiconset/icon_128x128.png -------------------------------------------------------------------------------- /Free Ruler/Images.xcassets/AppIcon.appiconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pascalpp/FreeRuler/886780dab939e082a37c7401f632a27e980394f4/Free Ruler/Images.xcassets/AppIcon.appiconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /Free Ruler/Images.xcassets/AppIcon.appiconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pascalpp/FreeRuler/886780dab939e082a37c7401f632a27e980394f4/Free Ruler/Images.xcassets/AppIcon.appiconset/icon_16x16.png -------------------------------------------------------------------------------- /Free Ruler/Images.xcassets/AppIcon.appiconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pascalpp/FreeRuler/886780dab939e082a37c7401f632a27e980394f4/Free Ruler/Images.xcassets/AppIcon.appiconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /Free Ruler/Images.xcassets/AppIcon.appiconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pascalpp/FreeRuler/886780dab939e082a37c7401f632a27e980394f4/Free Ruler/Images.xcassets/AppIcon.appiconset/icon_256x256.png -------------------------------------------------------------------------------- /Free Ruler/Images.xcassets/AppIcon.appiconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pascalpp/FreeRuler/886780dab939e082a37c7401f632a27e980394f4/Free Ruler/Images.xcassets/AppIcon.appiconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /Free Ruler/Images.xcassets/AppIcon.appiconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pascalpp/FreeRuler/886780dab939e082a37c7401f632a27e980394f4/Free Ruler/Images.xcassets/AppIcon.appiconset/icon_32x32.png -------------------------------------------------------------------------------- /Free Ruler/Images.xcassets/AppIcon.appiconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pascalpp/FreeRuler/886780dab939e082a37c7401f632a27e980394f4/Free Ruler/Images.xcassets/AppIcon.appiconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /Free Ruler/Images.xcassets/AppIcon.appiconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pascalpp/FreeRuler/886780dab939e082a37c7401f632a27e980394f4/Free Ruler/Images.xcassets/AppIcon.appiconset/icon_512x512.png -------------------------------------------------------------------------------- /Free Ruler/Images.xcassets/AppIcon.appiconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pascalpp/FreeRuler/886780dab939e082a37c7401f632a27e980394f4/Free Ruler/Images.xcassets/AppIcon.appiconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /Free Ruler/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleHelpBookFolder 10 | FreeRuler.help 11 | CFBundleHelpBookName 12 | com.pascal.freeruler.help 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | $(PRODUCT_NAME) 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | $(MARKETING_VERSION) 23 | CFBundleVersion 24 | $(CURRENT_PROJECT_VERSION) 25 | LSApplicationCategoryType 26 | public.app-category.utilities 27 | LSMinimumSystemVersion 28 | $(MACOSX_DEPLOYMENT_TARGET) 29 | NSHumanReadableCopyright 30 | 31 | NSMainNibFile 32 | MainMenu 33 | NSPrincipalClass 34 | NSApplication 35 | 36 | 37 | -------------------------------------------------------------------------------- /Free Ruler/Notifications.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // add enums for custom event names 4 | extension Notification.Name { 5 | 6 | static let preferencesWindowOpened = Notification.Name("preferencesWindowOpened") 7 | static let preferencesWindowClosed = Notification.Name("preferencesWindowClosed") 8 | 9 | } 10 | 11 | protocol NotificationPoster {} 12 | extension NotificationPoster { 13 | 14 | func post(_ name: Notification.Name) { 15 | NotificationCenter.default.post(name: name, object: self) 16 | } 17 | 18 | } 19 | 20 | protocol NotificationObserver {} 21 | extension NotificationObserver { 22 | 23 | func addObserver(_ forName: Notification.Name, using: @escaping (Notification) -> Void) { 24 | NotificationCenter.default.addObserver(forName: forName, object: nil, queue: nil, using: using) 25 | } 26 | 27 | // call removeObserver in your class deinit 28 | // deinit { 29 | // removeObserver() 30 | // } 31 | func removeObserver() { 32 | NotificationCenter.default.removeObserver(self) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Free Ruler/PreferencesController.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | class PreferencesController: NSWindowController, NSWindowDelegate, NotificationPoster { 4 | 5 | var observers: [NSKeyValueObservation] = [] 6 | 7 | @IBOutlet weak var foregroundOpacitySlider: NSSlider! 8 | @IBOutlet weak var backgroundOpacitySlider: NSSlider! 9 | 10 | @IBOutlet weak var foregroundOpacityLabel: NSTextField! 11 | @IBOutlet weak var backgroundOpacityLabel: NSTextField! 12 | 13 | @IBOutlet weak var floatRulersCheckbox: NSButton! 14 | @IBOutlet weak var groupRulersCheckbox: NSButton! 15 | @IBOutlet weak var rulerShadowCheckbox: NSButton! 16 | 17 | override var windowNibName: String { 18 | return "PreferencesController" 19 | } 20 | 21 | override func windowDidLoad() { 22 | super.windowDidLoad() 23 | 24 | window?.isMovableByWindowBackground = true 25 | 26 | subscribeToPrefs() 27 | updateView() 28 | } 29 | 30 | override func showWindow(_ sender: Any?) { 31 | 32 | // send opened notification 33 | post(.preferencesWindowOpened) 34 | 35 | window?.makeKeyAndOrderFront(sender) 36 | window?.center() 37 | } 38 | 39 | func windowWillClose(_ notification: Notification) { 40 | // send closed notification 41 | post(.preferencesWindowClosed) 42 | } 43 | 44 | func subscribeToPrefs() { 45 | observers = [ 46 | prefs.observe(\Prefs.foregroundOpacity, options: .new) { prefs, changed in 47 | self.updateForegroundSlider() 48 | }, 49 | prefs.observe(\Prefs.backgroundOpacity, options: .new) { prefs, changed in 50 | self.updateBackgroundSlider() 51 | }, 52 | prefs.observe(\Prefs.floatRulers, options: .new) { prefs, changed in 53 | self.updateFloatRulersCheckbox() 54 | }, 55 | prefs.observe(\Prefs.groupRulers, options: .new) { prefs, changed in 56 | self.updateGroupRulersCheckbox() 57 | }, 58 | prefs.observe(\Prefs.rulerShadow, options: .new) { prefs, changed in 59 | self.updateRulerShadowCheckbox() 60 | }, 61 | ] 62 | } 63 | 64 | @IBAction func setForegroundOpacity(_ sender: Any) { 65 | prefs.foregroundOpacity = foregroundOpacitySlider.integerValue 66 | } 67 | @IBAction func setBackgroundOpacity(_ sender: Any) { 68 | prefs.backgroundOpacity = backgroundOpacitySlider.integerValue 69 | } 70 | @IBAction func setFloatRulers(_ sender: Any) { 71 | prefs.floatRulers = floatRulersCheckbox.state == .on 72 | } 73 | @IBAction func setGroupRulers(_ sender: Any) { 74 | prefs.groupRulers = groupRulersCheckbox.state == .on 75 | } 76 | @IBAction func setRulerShadow(_ sender: Any) { 77 | prefs.rulerShadow = rulerShadowCheckbox.state == .on 78 | } 79 | 80 | func updateView() { 81 | updateForegroundSlider() 82 | updateBackgroundSlider() 83 | updateFloatRulersCheckbox() 84 | updateGroupRulersCheckbox() 85 | updateRulerShadowCheckbox() 86 | } 87 | 88 | func updateForegroundSlider() { 89 | foregroundOpacitySlider.integerValue = prefs.foregroundOpacity 90 | foregroundOpacityLabel.stringValue = "\(prefs.foregroundOpacity)%" 91 | } 92 | 93 | func updateBackgroundSlider() { 94 | backgroundOpacitySlider.integerValue = prefs.backgroundOpacity 95 | backgroundOpacityLabel.stringValue = "\(prefs.backgroundOpacity)%" 96 | } 97 | 98 | func updateFloatRulersCheckbox() { 99 | floatRulersCheckbox.state = prefs.floatRulers ? .on : .off 100 | } 101 | 102 | func updateGroupRulersCheckbox() { 103 | groupRulersCheckbox.state = prefs.groupRulers ? .on : .off 104 | } 105 | 106 | func updateRulerShadowCheckbox() { 107 | rulerShadowCheckbox.state = prefs.rulerShadow ? .on : .off 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /Free Ruler/Prefs.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // Prefs 4 | // a KVO bridge for UserDefaults 5 | // - registers default values 6 | // - exposes defaults on the prefs instance for key-value observation 7 | // - listens for changes and persists new values to UserDefaults 8 | // - provides save method to synchronize UserDefaults on applicationWillTerminate 9 | 10 | // TODO: there's a lot of boilerplate in here, not sure if we can reduce it 11 | // TODO: figure out how avoid saving values that haven't changed from the default 12 | 13 | // MARK: - global shortcut to shared prefs instance 14 | let prefs = Prefs.shared 15 | 16 | @objc enum Unit: Int { 17 | case pixels 18 | case millimeters 19 | case inches 20 | } 21 | 22 | class Prefs: NSObject { 23 | 24 | // MARK: - shared singleton instance 25 | static let shared = Prefs() 26 | 27 | // MARK: - public properties 28 | @objc dynamic var floatRulers : Bool 29 | @objc dynamic var groupRulers : Bool 30 | @objc dynamic var rulerShadow : Bool 31 | @objc dynamic var foregroundOpacity : Int 32 | @objc dynamic var backgroundOpacity : Int 33 | @objc dynamic var unit : Unit 34 | 35 | // MARK: - public save method 36 | func save() { 37 | defaults.synchronize() 38 | } 39 | 40 | // MARK: - private implementation 41 | 42 | private let defaults = UserDefaults.standard 43 | 44 | private var defaultValues: [String: Any] = [ 45 | "groupRulers": true, 46 | "floatRulers": true, 47 | "rulerShadow": false, 48 | "foregroundOpacity": 90, 49 | "backgroundOpacity": 50, 50 | "unit": Unit.pixels.rawValue 51 | ] 52 | 53 | private override init() { 54 | defaults.register(defaults: defaultValues) 55 | 56 | floatRulers = defaults.bool(forKey: "floatRulers") 57 | groupRulers = defaults.bool(forKey: "groupRulers") 58 | rulerShadow = defaults.bool(forKey: "rulerShadow") 59 | foregroundOpacity = defaults.integer(forKey: "foregroundOpacity") 60 | backgroundOpacity = defaults.integer(forKey: "backgroundOpacity") 61 | unit = Unit(rawValue: defaults.integer(forKey: "unit")) ?? .pixels 62 | 63 | super.init() 64 | 65 | addObservers() 66 | } 67 | 68 | private var observers: [NSKeyValueObservation] = [] 69 | 70 | private func addObservers() { 71 | observers = [ 72 | observe(\Prefs.floatRulers, options: .new) { prefs, changed in 73 | self.defaults.set(changed.newValue, forKey: "floatRulers") 74 | }, 75 | observe(\Prefs.groupRulers, options: .new) { prefs, changed in 76 | self.defaults.set(changed.newValue, forKey: "groupRulers") 77 | }, 78 | observe(\Prefs.rulerShadow, options: .new) { prefs, changed in 79 | self.defaults.set(changed.newValue, forKey: "rulerShadow") 80 | }, 81 | observe(\Prefs.foregroundOpacity, options: .new) { prefs, changed in 82 | self.defaults.set(changed.newValue, forKey: "foregroundOpacity") 83 | }, 84 | observe(\Prefs.backgroundOpacity, options: .new) { prefs, changed in 85 | self.defaults.set(changed.newValue, forKey: "backgroundOpacity") 86 | }, 87 | observe(\Prefs.unit, options: .new) { prefs, changed in 88 | self.defaults.set(prefs.unit.rawValue, forKey: "unit") 89 | }, 90 | ] 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /Free Ruler/RuleView.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | struct RulerColors { 4 | let fill = #colorLiteral(red: 0.9764705896, green: 0.850980401, blue: 0.5490196347, alpha: 1) 5 | let numbers = #colorLiteral(red: 0.6829560399, green: 0.4503545761, blue: 0.09706548601, alpha: 1) 6 | let ticks = #colorLiteral(red: 0.7254902124, green: 0.4784313738, blue: 0.09803921729, alpha: 1) 7 | let mouseTick = #colorLiteral(red: 0.3098039329, green: 0.2039215714, blue: 0.03921568766, alpha: 0.75) 8 | let mouseNumber = #colorLiteral(red: 0.3098039329, green: 0.2039215714, blue: 0.03921568766, alpha: 1) 9 | } 10 | 11 | class RuleView: NSView { 12 | 13 | let color = RulerColors() 14 | 15 | var trackingArea: NSTrackingArea? 16 | let trackingAreaOptions: NSTrackingArea.Options = [ 17 | .mouseMoved, 18 | .mouseEnteredAndExited, 19 | .activeAlways, 20 | .inVisibleRect, 21 | ] 22 | 23 | override func updateTrackingAreas() { 24 | if trackingArea != nil { 25 | removeTrackingArea(trackingArea!) 26 | } 27 | 28 | trackingArea = NSTrackingArea( 29 | rect: self.bounds, 30 | options: trackingAreaOptions, 31 | owner: self, 32 | userInfo: nil 33 | ) 34 | addTrackingArea(trackingArea!) 35 | } 36 | 37 | func drawMouseTick(at mouseLoc: NSPoint) { 38 | // required override 39 | // TODO: is there a better way to do this, maybe via a protocol? 40 | // AppDelegate needs to be able to infer that any RulerView has this method 41 | fatalError("RuleView subclass must override drawMouseTick method.") 42 | } 43 | 44 | var windowWidth: CGFloat { 45 | return self.window?.frame.width ?? 0 46 | } 47 | 48 | var windowHeight: CGFloat { 49 | return self.window?.frame.height ?? 0 50 | } 51 | 52 | var showMouseTick: Bool = true { 53 | didSet { 54 | if showMouseTick != oldValue { 55 | needsDisplay = true 56 | } 57 | } 58 | } 59 | 60 | var screen: NSScreen? { 61 | guard let window = window else { 62 | return nil 63 | } 64 | return NSScreen.screens.first { $0.frame.intersects(window.convertToScreen(frame)) } 65 | } 66 | 67 | func getUnitLabel() -> String { 68 | switch prefs.unit { 69 | case .pixels: 70 | return "px" 71 | case .millimeters: 72 | return "mm" 73 | case .inches: 74 | return "in" 75 | } 76 | } 77 | 78 | func getMouseNumberLabel(_ number: CGFloat) -> String { 79 | switch prefs.unit { 80 | case .pixels: 81 | return String(format: "%d", Int(number)) 82 | case .millimeters: 83 | return String(format: "%.1f", number / (screen?.dpmm.width ?? NSScreen.defaultDpmm)) 84 | case .inches: 85 | return String(format: "%.3f", number / (screen?.dpi.width ?? NSScreen.defaultDpi)) 86 | } 87 | } 88 | 89 | } 90 | 91 | fileprivate let mmPerIn: CGFloat = 25.4 92 | 93 | public extension NSScreen { 94 | 95 | // This is the same as what CoreGraphics assumes if no EDID data is available from the display device 96 | // https://developer.apple.com/documentation/coregraphics/1456599-cgdisplayscreensize 97 | static let defaultDpi: CGFloat = 72.0 98 | static let defaultDpmm: CGFloat = defaultDpi / mmPerIn 99 | 100 | var dpmm: CGSize { 101 | if let resolution = (deviceDescription[.size] as? NSValue)?.sizeValue, 102 | let screenNumber = (deviceDescription[NSDeviceDescriptionKey("NSScreenNumber")] as? NSNumber)?.uint32Value { 103 | let physicalSize = CGDisplayScreenSize(screenNumber) 104 | return CGSize(width: resolution.width / physicalSize.width, 105 | height: resolution.height / physicalSize.height) 106 | } else { 107 | return CGSize(width: NSScreen.defaultDpmm, height: NSScreen.defaultDpmm) 108 | } 109 | } 110 | 111 | var dpi: CGSize { 112 | return CGSize(width: mmPerIn * dpmm.width, 113 | height: mmPerIn * dpmm.height) 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /Free Ruler/Ruler.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | enum Orientation: String { 4 | case horizontal 5 | case vertical 6 | } 7 | 8 | class Ruler { 9 | static let thickness: CGFloat = 40 10 | 11 | let orientation: Orientation 12 | let frame: NSRect 13 | let name: String? // used for frameAutosaveName 14 | 15 | init(orientation: Orientation, frame: NSRect?, name: String?) { 16 | self.orientation = orientation 17 | self.name = name 18 | self.frame = frame ?? getDefaultContentRect(orientation: orientation) 19 | } 20 | 21 | convenience init(_ orientation: Orientation, frame: NSRect?, name: String?) { 22 | self.init(orientation: orientation, frame: frame, name: name) 23 | } 24 | 25 | convenience init(_ orientation: Orientation, name: String) { 26 | self.init(orientation, frame: nil, name: name) 27 | } 28 | 29 | convenience init(_ orientation: Orientation, frame: NSRect) { 30 | self.init(orientation, frame: frame, name: nil) 31 | } 32 | 33 | convenience init(_ orientation: Orientation) { 34 | self.init(orientation, frame: nil, name: nil) 35 | } 36 | 37 | } 38 | 39 | // MARK: - Ruler size helpers 40 | 41 | func getDefaultContentRect(orientation: Orientation) -> NSRect { 42 | var screenWidth: CGFloat = 1000 43 | var screenHeight: CGFloat = 800 44 | if let screen = NSScreen.main?.frame { 45 | screenWidth = screen.width 46 | screenHeight = screen.height 47 | } 48 | 49 | let aspectRatio = screenWidth / screenHeight 50 | let xOffset: CGFloat = 30 51 | let yOffset: CGFloat = 50 52 | let rulerThickness: CGFloat = 40 53 | 54 | let horizontalLength = screenWidth / 2 55 | let verticalLength = horizontalLength / aspectRatio 56 | 57 | switch orientation { 58 | case .horizontal: 59 | return NSRect( 60 | // offset horizontal by 1px leftward to compensate for ruler border 61 | x: xOffset + rulerThickness - 1.0, 62 | y: screenHeight - yOffset - rulerThickness, 63 | width: horizontalLength, 64 | height: rulerThickness 65 | ) 66 | case .vertical: 67 | return NSRect( 68 | // offset vertical by 1px upward to compensate for ruler border 69 | x: xOffset, 70 | y: screenHeight - yOffset - rulerThickness - verticalLength + 1.0, 71 | width: rulerThickness, 72 | height: verticalLength 73 | ) 74 | } 75 | 76 | } 77 | 78 | func getMinSize(ruler: Ruler) -> NSSize { 79 | switch ruler.orientation { 80 | case .horizontal: 81 | return NSSize(width: 200, height: 40) 82 | case .vertical: 83 | return NSSize(width: 40, height: 200) 84 | } 85 | } 86 | 87 | func getMaxSize(ruler: Ruler) -> NSSize { 88 | switch ruler.orientation { 89 | case .horizontal: 90 | return NSSize(width: 4000, height: 40) 91 | case .vertical: 92 | return NSSize(width: 40, height: 4000) 93 | } 94 | } 95 | 96 | func getRulerView(ruler: Ruler) -> RuleView { 97 | switch ruler.orientation { 98 | case .horizontal: 99 | return HorizontalRule(frame: ruler.frame) 100 | case .vertical: 101 | return VerticalRule(frame: ruler.frame) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Free Ruler/RulerController.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import Carbon.HIToolbox // For key constants 3 | 4 | 5 | class RulerController: NSWindowController, NSWindowDelegate, NotificationObserver { 6 | 7 | var observers: [NSKeyValueObservation] = [] 8 | 9 | let ruler: Ruler 10 | 11 | let rulerWindow: RulerWindow 12 | var otherWindow: RulerWindow? 13 | 14 | var keyListener: Any? 15 | 16 | let openHand = NSCursor.openHand 17 | let closedHand = NSCursor.closedHand 18 | let crosshair = NSCursor.crosshair 19 | 20 | var preferencesWindowOpen = false { 21 | didSet { 22 | updateIsFloatingPanel() 23 | // reset opacity to foreground in case they modified background opacity last 24 | if !preferencesWindowOpen { 25 | opacity = prefs.foregroundOpacity 26 | } 27 | } 28 | } 29 | 30 | var opacity = prefs.foregroundOpacity { 31 | didSet { 32 | rulerWindow.alphaValue = windowAlphaValue(opacity) 33 | } 34 | } 35 | 36 | convenience init(_ ruler: Ruler) { 37 | self.init(ruler: ruler) 38 | } 39 | 40 | init(ruler: Ruler) { 41 | self.ruler = ruler 42 | self.rulerWindow = RulerWindow(ruler) 43 | 44 | super.init(window: self.rulerWindow) 45 | 46 | createObservers() 47 | subscribeToPrefs() 48 | 49 | rulerWindow.delegate = self 50 | rulerWindow.nextResponder = self 51 | 52 | if let windowFrameAutosaveName = ruler.name { 53 | self.windowFrameAutosaveName = windowFrameAutosaveName 54 | } 55 | 56 | } 57 | 58 | required init?(coder: NSCoder) { 59 | fatalError("init(coder:) has not been implemented. Use init(ruler: Ruler)") 60 | } 61 | 62 | deinit { 63 | removeObserver() 64 | } 65 | 66 | func createObservers() { 67 | addObserver(.preferencesWindowOpened) { _ in self.preferencesWindowOpen = true } 68 | addObserver(.preferencesWindowClosed) { _ in self.preferencesWindowOpen = false } 69 | } 70 | 71 | func windowWillStartLiveResize(_ notification: Notification) { 72 | disableMouseTicks() 73 | } 74 | 75 | func windowDidEndLiveResize(_ notification: Notification) { 76 | enableMouseTicks() 77 | } 78 | 79 | func windowWillMove(_ notification: Notification) { 80 | disableMouseTicks() 81 | } 82 | 83 | func windowDidMove(_ notification: Notification) { 84 | rulerWindow.invalidateShadow() 85 | } 86 | 87 | func windowDidBecomeKey(_ notification: Notification) { 88 | updateChildWindow() 89 | startKeyListener() 90 | } 91 | 92 | func windowDidResignKey(_ notification: Notification) { 93 | updateChildWindow() 94 | stopKeyListener() 95 | } 96 | 97 | override func mouseEntered(with event: NSEvent) { 98 | openHand.push() 99 | } 100 | 101 | override func mouseExited(with event: NSEvent) { 102 | openHand.pop() 103 | crosshair.push() 104 | } 105 | 106 | override func mouseDown(with event: NSEvent) { 107 | closedHand.push() 108 | } 109 | 110 | override func mouseUp(with event: NSEvent) { 111 | closedHand.pop() 112 | } 113 | 114 | override func mouseMoved(with event: NSEvent) { 115 | enableMouseTicks() 116 | } 117 | 118 | func disableMouseTicks() { 119 | rulerWindow.rule.showMouseTick = false 120 | otherWindow?.rule.showMouseTick = false 121 | } 122 | 123 | func enableMouseTicks() { 124 | rulerWindow.rule.showMouseTick = true 125 | otherWindow?.rule.showMouseTick = true 126 | } 127 | 128 | func onChangeGrouped() { 129 | updateChildWindow() 130 | } 131 | 132 | func updateChildWindow() { 133 | guard let otherWindow = otherWindow else { return } 134 | 135 | if prefs.groupRulers && rulerWindow.isKeyWindow { 136 | rulerWindow.addChildWindow(otherWindow, ordered: .below) 137 | } else { 138 | rulerWindow.removeChildWindow(otherWindow) 139 | } 140 | } 141 | 142 | func updateIsFloatingPanel() { 143 | // never float while preferences window is open 144 | if preferencesWindowOpen { 145 | rulerWindow.isFloatingPanel = false 146 | } else { 147 | rulerWindow.isFloatingPanel = prefs.floatRulers 148 | } 149 | } 150 | 151 | func foreground() { 152 | opacity = prefs.foregroundOpacity 153 | } 154 | func background() { 155 | opacity = prefs.backgroundOpacity 156 | } 157 | 158 | func subscribeToPrefs() { 159 | observers = [ 160 | prefs.observe(\Prefs.foregroundOpacity, options: .new) { prefs, changed in 161 | self.opacity = prefs.foregroundOpacity 162 | }, 163 | prefs.observe(\Prefs.backgroundOpacity, options: .new) { prefs, changed in 164 | self.opacity = prefs.backgroundOpacity 165 | }, 166 | prefs.observe(\Prefs.floatRulers, options: .new) { prefs, changed in 167 | self.updateIsFloatingPanel() 168 | }, 169 | prefs.observe(\Prefs.groupRulers, options: .new) { prefs, changed in 170 | self.updateChildWindow() 171 | }, 172 | prefs.observe(\Prefs.rulerShadow, options: .new) { prefs, changed in 173 | self.rulerWindow.hasShadow = prefs.rulerShadow 174 | }, 175 | ] 176 | } 177 | 178 | func alignRuler(at point: NSPoint) { 179 | // only key window controller should respond to this command 180 | guard rulerWindow.isKeyWindow else { return } 181 | 182 | if prefs.groupRulers { 183 | // if grouped, ungroup rulers, move both, regroup 184 | prefs.groupRulers = false 185 | alignRuler(window: rulerWindow, at: point) 186 | alignRuler(window: otherWindow, at: point) 187 | prefs.groupRulers = true 188 | } else { 189 | // if not groups, just move key window 190 | alignRuler(window: rulerWindow, at: point) 191 | } 192 | } 193 | 194 | private func alignRuler(window: RulerWindow?, at point: NSPoint) { 195 | guard let window = window else { return } 196 | 197 | let frame = window.frame 198 | var x: CGFloat 199 | var y: CGFloat 200 | 201 | switch window.ruler.orientation { 202 | case .horizontal: 203 | // offset horizontal by 1px downward to compensate for ruler border 204 | x = point.x 205 | y = point.y - 1.0 206 | case .vertical: 207 | // offset vertical by 1px rightward to compensate for ruler border 208 | x = point.x - frame.width + 1.0 209 | y = point.y - frame.height 210 | } 211 | 212 | let rect = NSRect( 213 | x: x, 214 | y: y, 215 | width: frame.width, 216 | height: frame.height 217 | ) 218 | 219 | window.setFrame(rect, display: false) 220 | } 221 | 222 | func resetPosition() { 223 | let frame = getDefaultContentRect(orientation: ruler.orientation) 224 | rulerWindow.setFrame(frame, display: true) 225 | } 226 | 227 | } 228 | 229 | // MARK: KeyListener 230 | 231 | extension RulerController { 232 | 233 | func startKeyListener() { 234 | self.keyListener = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] in 235 | guard let self = self else { return $0 } 236 | return self.onKeyDown(with: $0) 237 | } 238 | } 239 | 240 | func stopKeyListener() { 241 | if let keyListener = self.keyListener { 242 | NSEvent.removeMonitor(keyListener) 243 | self.keyListener = nil 244 | } 245 | } 246 | 247 | // Return nil if the event was handled here. 248 | func onKeyDown(with event: NSEvent) -> NSEvent? { 249 | // print(ruler.orientation, "onKeyDown") 250 | 251 | let shift = event.modifierFlags.contains(.shift) 252 | 253 | switch Int(event.keyCode) { 254 | case kVK_LeftArrow: 255 | rulerWindow.nudgeLeft(withShift: shift) 256 | return nil 257 | case kVK_RightArrow: 258 | rulerWindow.nudgeRight(withShift: shift) 259 | return nil 260 | case kVK_UpArrow: 261 | rulerWindow.nudgeUp(withShift: shift) 262 | return nil 263 | case kVK_DownArrow: 264 | rulerWindow.nudgeDown(withShift: shift) 265 | return nil 266 | default: 267 | return event 268 | } 269 | } 270 | 271 | } 272 | 273 | // helper to convert opacity Int to window.alphaValue 274 | func windowAlphaValue(_ value: Int) -> CGFloat { 275 | return CGFloat(value) / 100.0 276 | } 277 | -------------------------------------------------------------------------------- /Free Ruler/RulerWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | class RulerWindow: NSPanel { 4 | 5 | var ruler: Ruler 6 | var rule: RuleView 7 | 8 | convenience init(_ ruler: Ruler) { 9 | self.init(ruler: ruler) 10 | } 11 | 12 | init(ruler: Ruler) { 13 | self.ruler = ruler 14 | self.rule = getRulerView(ruler: ruler) 15 | 16 | let styleMask: NSWindow.StyleMask = [ 17 | .borderless, 18 | .resizable, 19 | .fullSizeContentView, 20 | ] 21 | 22 | super.init( 23 | contentRect: ruler.frame, 24 | styleMask: styleMask, 25 | backing: .buffered, 26 | defer: false 27 | ) 28 | 29 | self.alphaValue = windowAlphaValue(prefs.foregroundOpacity) 30 | self.minSize = getMinSize(ruler: ruler) 31 | self.maxSize = getMaxSize(ruler: ruler) 32 | 33 | self.isFloatingPanel = prefs.floatRulers 34 | self.hidesOnDeactivate = false 35 | self.isMovableByWindowBackground = true 36 | self.hasShadow = prefs.rulerShadow 37 | 38 | rule.wantsLayer = true 39 | rule.layer?.borderColor = CGColor(gray: 0, alpha: 0.5) 40 | rule.layer?.borderWidth = 1.0 41 | 42 | rule.nextResponder = self 43 | self.contentView = rule 44 | } 45 | 46 | override var canBecomeKey: Bool { 47 | return true 48 | } 49 | 50 | override var acceptsMouseMovedEvents: Bool { 51 | get { return true } 52 | set {} 53 | } 54 | 55 | } 56 | 57 | extension RulerWindow { 58 | private enum Distance: CGFloat { 59 | case aLittle = 1 60 | case aLot = 10 61 | } 62 | 63 | func moveHorizontally(by pixels: CGFloat) { 64 | var position = frame.origin 65 | position.x = position.x + pixels 66 | setFrameOrigin(position) 67 | } 68 | 69 | func moveVertically(by pixels: CGFloat) { 70 | var position = frame.origin 71 | position.y = position.y + pixels 72 | setFrameOrigin(position) 73 | } 74 | 75 | private func distance(withShift: Bool) -> CGFloat { 76 | let dist = withShift ? Distance.aLot : Distance.aLittle 77 | return dist.rawValue 78 | } 79 | 80 | func nudgeLeft(withShift shiftPressed: Bool) { 81 | let dist = distance(withShift: shiftPressed) 82 | moveHorizontally(by: dist * -1) 83 | } 84 | 85 | func nudgeRight(withShift shiftPressed: Bool) { 86 | let dist = distance(withShift: shiftPressed) 87 | moveHorizontally(by: dist) 88 | } 89 | 90 | func nudgeDown(withShift shiftPressed: Bool) { 91 | let dist = distance(withShift: shiftPressed) 92 | moveVertically(by: dist * -1) 93 | } 94 | 95 | func nudgeUp(withShift shiftPressed: Bool) { 96 | let dist = distance(withShift: shiftPressed) 97 | moveVertically(by: dist) 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /Free Ruler/VerticalRule.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | class VerticalRule: RuleView { 4 | 5 | let transformer = AffineTransform(translationByX: 0, byY: -0.5) 6 | 7 | var mouseTickY: CGFloat = 0 { 8 | didSet { 9 | if mouseTickY != oldValue { 10 | needsDisplay = true 11 | } 12 | } 13 | } 14 | 15 | override func draw(_ dirtyRect: NSRect) { 16 | super.draw(dirtyRect) 17 | 18 | // Drawing code here. 19 | color.fill.setFill() 20 | dirtyRect.fill() 21 | 22 | let paragraphStyle = NSMutableParagraphStyle() 23 | paragraphStyle.alignment = .right 24 | let attrs: [NSAttributedString.Key: Any] = [ 25 | .font: NSFont(name: "HelveticaNeue", size: 10)!, 26 | .paragraphStyle: paragraphStyle, 27 | .foregroundColor: color.numbers 28 | ] 29 | 30 | let width = dirtyRect.width 31 | let height = dirtyRect.height 32 | let path = NSBezierPath() 33 | let tickScale: CGFloat 34 | let textScale: Int 35 | let largeTicks: Int 36 | let mediumTicks: Int 37 | let smallTicks: Int 38 | let tinyTicks: Int? 39 | 40 | switch prefs.unit { 41 | case .millimeters: 42 | tickScale = screen?.dpmm.width ?? NSScreen.defaultDpmm 43 | textScale = 1 44 | largeTicks = 10 45 | mediumTicks = 5 46 | smallTicks = 1 47 | tinyTicks = nil 48 | case .inches: 49 | tickScale = (screen?.dpi.width ?? NSScreen.defaultDpi) / 16 50 | textScale = 16 51 | largeTicks = 16 52 | mediumTicks = 8 53 | smallTicks = 4 54 | tinyTicks = 1 55 | default: 56 | tickScale = 1 57 | textScale = 1 58 | largeTicks = 50 59 | mediumTicks = 10 60 | smallTicks = 2 61 | tinyTicks = nil 62 | } 63 | 64 | let labelWidth: CGFloat = 50 65 | let labelHeight: CGFloat = 20 66 | let labelOffset: CGFloat = 13 // offset of label from right edge of ruler 67 | let textHeight: CGFloat = 8 // height of text, used to center the label next to the tick 68 | // TODO: refactor this to use label.size() logic (see func drawUnitLabel) 69 | 70 | // substract two so ticks don't overlap with border 71 | // substract from this range so we can use the height var for position calculations 72 | for i in 1...Int((height - 2) / tickScale) { 73 | let pos = CGFloat(i) * tickScale 74 | if i.isMultiple(of: largeTicks) { 75 | path.move(to: CGPoint(x: width - 1, y: height - pos)) 76 | path.line(to: CGPoint(x: width - 10, y: height - pos)) 77 | 78 | let label = String(i / textScale) 79 | let labelX = width - labelWidth - labelOffset 80 | let labelY = height - pos - (textHeight / 2) 81 | let labelRect = CGRect(x: labelX, y: labelY, width: labelWidth, height: labelHeight) 82 | 83 | label.draw( 84 | with: labelRect, 85 | attributes: attrs, 86 | context: nil 87 | ) 88 | 89 | } 90 | else if i.isMultiple(of: mediumTicks) { 91 | path.move(to: CGPoint(x: width - 1, y: height - pos)) 92 | path.line(to: CGPoint(x: width - 8, y: height - pos)) 93 | } 94 | else if i.isMultiple(of: smallTicks) { 95 | path.move(to: CGPoint(x: width - 1, y: height - pos)) 96 | path.line(to: CGPoint(x: width - 5, y: height - pos)) 97 | } 98 | else if let tinyTicks = tinyTicks, i.isMultiple(of: tinyTicks) { 99 | path.move(to: CGPoint(x: width - 1, y: height - pos)) 100 | path.line(to: CGPoint(x: width - 3, y: height - pos)) 101 | } 102 | } 103 | 104 | path.transform(using: transformer) 105 | 106 | color.ticks.setStroke() 107 | path.stroke() 108 | 109 | if !showMouseTick || self.windowHeight - mouseTickY < 0 || windowHeight - mouseTickY > 18 { 110 | drawUnitLabel() 111 | } 112 | 113 | // Draw the MouseTick & number 114 | if showMouseTick && mouseTickY >= 1 && mouseTickY < windowHeight { 115 | drawMouseTick(mouseTickY) 116 | drawMouseNumber(mouseTickY) 117 | } 118 | } 119 | 120 | override func drawMouseTick(at mouseLoc: NSPoint) { 121 | let windowY = self.window?.frame.origin.y ?? 0 122 | let mouseY = mouseLoc.y 123 | self.mouseTickY = mouseY - windowY 124 | } 125 | 126 | func drawMouseTick(_ mouseTickY: CGFloat) { 127 | let mouseTick = NSBezierPath() 128 | let width: CGFloat = 40 129 | let startX: CGFloat = 0 130 | 131 | mouseTick.move(to: CGPoint(x: startX, y: mouseTickY)) 132 | mouseTick.line(to: CGPoint(x: width, y: mouseTickY)) 133 | 134 | mouseTick.transform(using: transformer) 135 | 136 | color.mouseTick.setStroke() 137 | mouseTick.stroke() 138 | } 139 | 140 | func drawMouseNumber(_ mouseTickY: CGFloat) { 141 | let height = self.frame.height 142 | let number = height - mouseTickY 143 | let labelOffset: CGFloat = 2 144 | 145 | let paragraphStyle = NSMutableParagraphStyle() 146 | paragraphStyle.alignment = .left 147 | 148 | let attributes = [ 149 | NSAttributedString.Key.font: NSFont(name: "HelveticaNeue", size: 10)!, 150 | NSAttributedString.Key.paragraphStyle: paragraphStyle, 151 | NSAttributedString.Key.foregroundColor: color.mouseNumber, 152 | ] 153 | 154 | let mouseNumber = self.getMouseNumberLabel(number) 155 | let label = NSAttributedString(string: mouseNumber, attributes: attributes) 156 | let labelSize = label.size() 157 | 158 | // manually offsetting bottom position til i can figure out how to center text vertically in the label rect 159 | let bottomPosition = number + 7; 160 | let topPosition = number - labelOffset - labelSize.height 161 | let enoughRoomToTheBottom = bottomPosition + labelSize.height < height - labelOffset 162 | let labelY = enoughRoomToTheBottom ? bottomPosition : topPosition 163 | 164 | let labelRect = CGRect(x: 7, y: height - (labelY + labelSize.height), width: 22, height: 15) 165 | color.fill.setFill() 166 | labelRect.fill() 167 | 168 | label.draw( 169 | with: labelRect, 170 | options: .usesLineFragmentOrigin, 171 | context: nil 172 | ) 173 | } 174 | 175 | func drawUnitLabel() { 176 | let paragraphStyle = NSMutableParagraphStyle() 177 | paragraphStyle.alignment = .left 178 | 179 | let attributes = [ 180 | NSAttributedString.Key.font: NSFont(name: "HelveticaNeue", size: 10)!, 181 | NSAttributedString.Key.paragraphStyle: paragraphStyle, 182 | NSAttributedString.Key.foregroundColor: color.ticks, 183 | ] 184 | 185 | let unitlabel = self.getUnitLabel() 186 | let label = NSAttributedString(string: unitlabel, attributes: attributes) 187 | let height = self.frame.height 188 | let labelSize = label.size() 189 | let labelRect = CGRect(x: 8, y: height - labelSize.height - 2, width: labelSize.width, height: labelSize.height) 190 | 191 | label.draw( 192 | with: labelRect, 193 | context: nil 194 | ) 195 | } 196 | 197 | 198 | } 199 | -------------------------------------------------------------------------------- /Free Ruler/de.lproj/MainMenu.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "NSMenuItem"; title = "Free Ruler"; ObjectID = "1Xt-HY-uBw"; */ 3 | "1Xt-HY-uBw.title" = "Free Ruler"; 4 | 5 | /* Class = "NSMenuItem"; title = "Quit Free Ruler"; ObjectID = "4sb-4s-VLi"; */ 6 | "4sb-4s-VLi.title" = "Free Ruler beenden"; 7 | 8 | /* Class = "NSMenuItem"; title = "Edit"; ObjectID = "5QF-Oa-p0T"; */ 9 | "5QF-Oa-p0T.title" = "Bearbeiten"; 10 | 11 | /* Class = "NSMenuItem"; title = "About Free Ruler"; ObjectID = "5kV-Vb-QxS"; */ 12 | "5kV-Vb-QxS.title" = "Über Free Ruler"; 13 | 14 | /* Class = "NSMenuItem"; title = "Redo"; ObjectID = "6dh-zS-Vam"; */ 15 | "6dh-zS-Vam.title" = "Wiederholen"; 16 | 17 | /* Class = "NSMenuItem"; title = "Reset Ruler Positions"; ObjectID = "6ph-5N-O9R"; */ 18 | "6ph-5N-O9R.title" = "Linealposition zurücksetzen"; 19 | 20 | /* Class = "NSMenuItem"; title = "Group Rulers"; ObjectID = "7Ga-Fb-LLc"; */ 21 | "7Ga-Fb-LLc.title" = "Lineale gruppieren"; 22 | 23 | /* Class = "NSMenuItem"; title = "Group Rulers"; ObjectID = "7SO-Lm-Ylr"; */ 24 | "7SO-Lm-Ylr.title" = "Lineale gruppieren"; 25 | 26 | /* Class = "NSMenu"; title = "Main Menu"; ObjectID = "AYu-sK-qS6"; */ 27 | "AYu-sK-qS6.title" = "Main Menu"; 28 | 29 | /* Class = "NSMenuItem"; title = "Preferences…"; ObjectID = "BOF-NM-1cW"; */ 30 | "BOF-NM-1cW.title" = "Einstellungen …"; 31 | 32 | /* Class = "NSMenuItem"; title = "Close"; ObjectID = "DVo-aG-piG"; */ 33 | "DVo-aG-piG.title" = "Schließen"; 34 | 35 | /* Class = "NSMenu"; title = "Help"; ObjectID = "F2S-fz-NVQ"; */ 36 | "F2S-fz-NVQ.title" = "Hilfe"; 37 | 38 | /* Class = "NSMenuItem"; title = "Free Ruler Help"; ObjectID = "FKE-Sm-Kum"; */ 39 | "FKE-Sm-Kum.title" = "„Free Ruler“-Hilfe"; 40 | 41 | /* Class = "NSMenuItem"; title = "Float Rulers"; ObjectID = "GDK-AC-uC8"; */ 42 | "GDK-AC-uC8.title" = "Oben schweben"; 43 | 44 | /* Class = "NSMenuItem"; title = "Options"; ObjectID = "H8h-7b-M4v"; */ 45 | "H8h-7b-M4v.title" = "Optionen"; 46 | 47 | /* Class = "NSMenu"; title = "Options"; ObjectID = "HyV-fh-RgO"; */ 48 | "HyV-fh-RgO.title" = "Optionen"; 49 | 50 | /* Class = "NSMenuItem"; title = "Show All"; ObjectID = "Kd2-mp-pUS"; */ 51 | "Kd2-mp-pUS.title" = "Alle einblenden"; 52 | 53 | /* Class = "NSMenuItem"; title = "Bring All to Front"; ObjectID = "LE2-aR-0XJ"; */ 54 | "LE2-aR-0XJ.title" = "Alle nach vorne bringen"; 55 | 56 | /* Class = "NSMenuItem"; title = "Services"; ObjectID = "NMo-om-nkz"; */ 57 | "NMo-om-nkz.title" = "Dienste"; 58 | 59 | /* Class = "NSMenuItem"; title = "Minimize"; ObjectID = "OY7-WF-poV"; */ 60 | "OY7-WF-poV.title" = "Im Dock ablegen"; 61 | 62 | /* Class = "NSMenuItem"; title = "Hide Free Ruler"; ObjectID = "Olw-nP-bQN"; */ 63 | "Olw-nP-bQN.title" = "Free Ruler ausblenden"; 64 | 65 | /* Class = "NSMenuItem"; title = "Zoom"; ObjectID = "R4o-n2-Eq4"; */ 66 | "R4o-n2-Eq4.title" = "Zoomen"; 67 | 68 | /* Class = "NSMenuItem"; title = "Select All"; ObjectID = "Ruw-6m-B2m"; */ 69 | "Ruw-6m-B2m.title" = "Alles auswählen"; 70 | 71 | /* Class = "NSMenu"; title = "Window"; ObjectID = "Td7-aD-5lo"; */ 72 | "Td7-aD-5lo.title" = "Fenster"; 73 | 74 | /* Class = "NSMenuItem"; title = "Hide Others"; ObjectID = "Vdr-fp-XzO"; */ 75 | "Vdr-fp-XzO.title" = "Andere ausblenden"; 76 | 77 | /* Class = "NSMenu"; title = "Edit"; ObjectID = "W48-6f-4Dl"; */ 78 | "W48-6f-4Dl.title" = "Bearbeiten"; 79 | 80 | /* Class = "NSMenuItem"; title = "Paste and Match Style"; ObjectID = "WeT-3V-zwk"; */ 81 | "WeT-3V-zwk.title" = "Einsetzen und Stil anpassen"; 82 | 83 | /* Class = "NSMenuItem"; title = "Show Ruler Shadow"; ObjectID = "a8D-hN-A59"; */ 84 | "a8D-hN-A59.title" = "Linealschatten anzeigen"; 85 | 86 | /* Class = "NSMenuItem"; title = "Window"; ObjectID = "aUF-d1-5bR"; */ 87 | "aUF-d1-5bR.title" = "Fenster"; 88 | 89 | /* Class = "NSMenu"; title = "Ruler"; ObjectID = "bib-Uj-vzu"; */ 90 | "bib-Uj-vzu.title" = "Ablage"; 91 | 92 | /* Class = "NSMenuItem"; title = "Ruler"; ObjectID = "dMs-cI-mzQ"; */ 93 | "dMs-cI-mzQ.title" = "Ablage"; 94 | 95 | /* Class = "NSMenuItem"; title = "Undo"; ObjectID = "dRJ-4n-Yzg"; */ 96 | "dRJ-4n-Yzg.title" = "Widerrufen"; 97 | 98 | /* Class = "NSMenuItem"; title = "Paste"; ObjectID = "gVA-U4-sdL"; */ 99 | "gVA-U4-sdL.title" = "Einsetzen"; 100 | 101 | /* Class = "NSMenu"; title = "Services"; ObjectID = "hz9-B4-Xy5"; */ 102 | "hz9-B4-Xy5.title" = "Dienste"; 103 | 104 | /* Class = "NSMenuItem"; title = "Align Rulers at Mouse Location"; ObjectID = "iKV-uW-hwy"; */ 105 | "iKV-uW-hwy.title" = "Lineale an Zeigerposition ausrichten"; 106 | 107 | /* Class = "NSMenuItem"; title = "Delete"; ObjectID = "pa3-QI-u2k"; */ 108 | "pa3-QI-u2k.title" = "Löschen"; 109 | 110 | /* Class = "NSMenu"; title = "Free Ruler"; ObjectID = "uQy-DD-JDr"; */ 111 | "uQy-DD-JDr.title" = "Free Ruler"; 112 | 113 | /* Class = "NSMenuItem"; title = "Cut"; ObjectID = "uRl-iY-unG"; */ 114 | "uRl-iY-unG.title" = "Ausschneiden"; 115 | 116 | /* Class = "NSMenuItem"; title = "Help"; ObjectID = "wpr-3q-Mcd"; */ 117 | "wpr-3q-Mcd.title" = "Hilfe"; 118 | 119 | /* Class = "NSMenuItem"; title = "Copy"; ObjectID = "x3v-GG-iWU"; */ 120 | "x3v-GG-iWU.title" = "Kopieren"; 121 | 122 | /* Class = "NSMenu"; title = "Unit"; ObjectID = "z2p-dA-zcS"; */ 123 | "z2p-dA-zcS.title" = "Einheit"; 124 | 125 | /* Class = "NSMenuItem"; title = "Unit"; ObjectID = "iDP-2z-irv"; */ 126 | "iDP-2z-irv.title" = "Einheit"; 127 | 128 | /* Class = "NSMenuItem"; title = "Pixels"; ObjectID = "pYR-Ba-kKi"; */ 129 | "pYR-Ba-kKi.title" = "Pixel"; 130 | 131 | /* Class = "NSMenuItem"; title = "Millimeters"; ObjectID = "B6Y-Hi-AkN"; */ 132 | "B6Y-Hi-AkN.title" = "Millimeter"; 133 | 134 | /* Class = "NSMenuItem"; title = "Inches"; ObjectID = "lt1-Hj-2TR"; */ 135 | "lt1-Hj-2TR.title" = "Zoll"; 136 | 137 | /* Class = "NSMenuItem"; title = "Cycle Units"; ObjectID = "2nm-aL-kZd"; */ 138 | "2nm-aL-kZd.title" = "Nächste Einheit auswählen"; 139 | -------------------------------------------------------------------------------- /Free Ruler/de.lproj/PreferencesController.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "NSButtonCell"; title = "Reset Ruler Positions"; ObjectID = "10f-9L-qca"; */ 3 | "10f-9L-qca.title" = "Linealposition zurücksetzen"; 4 | 5 | /* Class = "NSTextFieldCell"; title = "Foreground Opacity"; ObjectID = "BgV-9N-IVn"; */ 6 | "BgV-9N-IVn.title" = "Deckkraft im Vordergrund"; 7 | 8 | /* Class = "NSWindow"; title = "Free Ruler Preferences"; ObjectID = "F0z-JX-Cv5"; */ 9 | "F0z-JX-Cv5.title" = "„Free Ruler“-Einstellungen"; 10 | 11 | /* Class = "NSButtonCell"; title = "Group rulers"; ObjectID = "N2Y-8B-L9c"; */ 12 | "N2Y-8B-L9c.title" = "Lineale gruppieren"; 13 | 14 | /* Class = "NSTextFieldCell"; title = "Label"; ObjectID = "VXi-Ch-Jf5"; */ 15 | "VXi-Ch-Jf5.title" = "Label"; 16 | 17 | /* Class = "NSTextFieldCell"; title = "Label"; ObjectID = "Vqd-CI-vmd"; */ 18 | "Vqd-CI-vmd.title" = "Label"; 19 | 20 | /* Class = "NSTextFieldCell"; title = "Background Opacity"; ObjectID = "cRb-8z-VZj"; */ 21 | "cRb-8z-VZj.title" = "Deckkraft im Hintergrund"; 22 | 23 | /* Class = "NSButtonCell"; title = "Show ruler shadow"; ObjectID = "l53-85-hoA"; */ 24 | "l53-85-hoA.title" = "Linealschatten anzeigen"; 25 | 26 | /* Class = "NSButtonCell"; title = "Float rulers above other applications"; ObjectID = "yPM-Cw-Qsi"; */ 27 | "yPM-Cw-Qsi.title" = "Lineale schweben über anderen Programmen"; 28 | -------------------------------------------------------------------------------- /Free Ruler/fi.lproj/MainMenu.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "NSMenuItem"; title = "Free Ruler"; ObjectID = "1Xt-HY-uBw"; */ 3 | "1Xt-HY-uBw.title" = "Free Ruler"; 4 | 5 | /* Class = "NSMenuItem"; title = "Quit Free Ruler"; ObjectID = "4sb-4s-VLi"; */ 6 | "4sb-4s-VLi.title" = "Lopeta Free Ruler"; 7 | 8 | /* Class = "NSMenuItem"; title = "Edit"; ObjectID = "5QF-Oa-p0T"; */ 9 | "5QF-Oa-p0T.title" = "Muokkaa"; 10 | 11 | /* Class = "NSMenuItem"; title = "About Free Ruler"; ObjectID = "5kV-Vb-QxS"; */ 12 | "5kV-Vb-QxS.title" = "Tietoja: Free Ruler"; 13 | 14 | /* Class = "NSMenuItem"; title = "Redo"; ObjectID = "6dh-zS-Vam"; */ 15 | "6dh-zS-Vam.title" = "Tee sittenkin"; 16 | 17 | /* Class = "NSMenuItem"; title = "Reset Ruler Positions"; ObjectID = "6ph-5N-O9R"; */ 18 | "6ph-5N-O9R.title" = "Nollaa viivainten sijainnit"; 19 | 20 | /* Class = "NSMenuItem"; title = "Group Rulers"; ObjectID = "7Ga-Fb-LLc"; */ 21 | "7Ga-Fb-LLc.title" = "Ryhmitä viivaimet"; 22 | 23 | /* Class = "NSMenuItem"; title = "Group Rulers"; ObjectID = "7SO-Lm-Ylr"; */ 24 | "7SO-Lm-Ylr.title" = "Ryhmitä viivaimet"; 25 | 26 | /* Class = "NSMenu"; title = "Main Menu"; ObjectID = "AYu-sK-qS6"; */ 27 | "AYu-sK-qS6.title" = "Päävalikko"; 28 | 29 | /* Class = "NSMenuItem"; title = "Preferences…"; ObjectID = "BOF-NM-1cW"; */ 30 | "BOF-NM-1cW.title" = "Asetukset…"; 31 | 32 | /* Class = "NSMenuItem"; title = "Close"; ObjectID = "DVo-aG-piG"; */ 33 | "DVo-aG-piG.title" = "Sulje"; 34 | 35 | /* Class = "NSMenu"; title = "Help"; ObjectID = "F2S-fz-NVQ"; */ 36 | "F2S-fz-NVQ.title" = "Ohje"; 37 | 38 | /* Class = "NSMenuItem"; title = "Free Ruler Help"; ObjectID = "FKE-Sm-Kum"; */ 39 | "FKE-Sm-Kum.title" = "Free Rulerin ohje"; 40 | 41 | /* Class = "NSMenuItem"; title = "Float Rulers"; ObjectID = "GDK-AC-uC8"; */ 42 | "GDK-AC-uC8.title" = "Kelluta viivaimet"; 43 | 44 | /* Class = "NSMenuItem"; title = "Options"; ObjectID = "H8h-7b-M4v"; */ 45 | "H8h-7b-M4v.title" = "Valinnat"; 46 | 47 | /* Class = "NSMenu"; title = "Options"; ObjectID = "HyV-fh-RgO"; */ 48 | "HyV-fh-RgO.title" = "Valinnat"; 49 | 50 | /* Class = "NSMenuItem"; title = "Show All"; ObjectID = "Kd2-mp-pUS"; */ 51 | "Kd2-mp-pUS.title" = "Näytä kaikki"; 52 | 53 | /* Class = "NSMenuItem"; title = "Bring All to Front"; ObjectID = "LE2-aR-0XJ"; */ 54 | "LE2-aR-0XJ.title" = "Tuo kaikki eteen"; 55 | 56 | /* Class = "NSMenuItem"; title = "Services"; ObjectID = "NMo-om-nkz"; */ 57 | "NMo-om-nkz.title" = "Palvelut"; 58 | 59 | /* Class = "NSMenuItem"; title = "Minimize"; ObjectID = "OY7-WF-poV"; */ 60 | "OY7-WF-poV.title" = "Pienennä"; 61 | 62 | /* Class = "NSMenuItem"; title = "Hide Free Ruler"; ObjectID = "Olw-nP-bQN"; */ 63 | "Olw-nP-bQN.title" = "Kätke Free Ruler"; 64 | 65 | /* Class = "NSMenuItem"; title = "Zoom"; ObjectID = "R4o-n2-Eq4"; */ 66 | "R4o-n2-Eq4.title" = "Zoomaa"; 67 | 68 | /* Class = "NSMenuItem"; title = "Select All"; ObjectID = "Ruw-6m-B2m"; */ 69 | "Ruw-6m-B2m.title" = "Valitse kaikki"; 70 | 71 | /* Class = "NSMenu"; title = "Window"; ObjectID = "Td7-aD-5lo"; */ 72 | "Td7-aD-5lo.title" = "Ikkuna"; 73 | 74 | /* Class = "NSMenuItem"; title = "Hide Others"; ObjectID = "Vdr-fp-XzO"; */ 75 | "Vdr-fp-XzO.title" = "Kätke muut"; 76 | 77 | /* Class = "NSMenu"; title = "Edit"; ObjectID = "W48-6f-4Dl"; */ 78 | "W48-6f-4Dl.title" = "Muokkaa"; 79 | 80 | /* Class = "NSMenuItem"; title = "Paste and Match Style"; ObjectID = "WeT-3V-zwk"; */ 81 | "WeT-3V-zwk.title" = "Paste and Match Style"; 82 | 83 | /* Class = "NSMenuItem"; title = "Show Ruler Shadow"; ObjectID = "a8D-hN-A59"; */ 84 | "a8D-hN-A59.title" = "Näytä viivainten varjot"; 85 | 86 | /* Class = "NSMenuItem"; title = "Window"; ObjectID = "aUF-d1-5bR"; */ 87 | "aUF-d1-5bR.title" = "Ikkuna"; 88 | 89 | /* Class = "NSMenu"; title = "Ruler"; ObjectID = "bib-Uj-vzu"; */ 90 | "bib-Uj-vzu.title" = "Viivain"; 91 | 92 | /* Class = "NSMenuItem"; title = "Ruler"; ObjectID = "dMs-cI-mzQ"; */ 93 | "dMs-cI-mzQ.title" = "Viivain"; 94 | 95 | /* Class = "NSMenuItem"; title = "Undo"; ObjectID = "dRJ-4n-Yzg"; */ 96 | "dRJ-4n-Yzg.title" = "Peru"; 97 | 98 | /* Class = "NSMenuItem"; title = "Paste"; ObjectID = "gVA-U4-sdL"; */ 99 | "gVA-U4-sdL.title" = "Sijoita"; 100 | 101 | /* Class = "NSMenu"; title = "Services"; ObjectID = "hz9-B4-Xy5"; */ 102 | "hz9-B4-Xy5.title" = "Palvelut"; 103 | 104 | /* Class = "NSMenuItem"; title = "Align Rulers at Mouse Location"; ObjectID = "iKV-uW-hwy"; */ 105 | "iKV-uW-hwy.title" = "Kohdista viivaimet hiiren sijaintiin"; 106 | 107 | /* Class = "NSMenuItem"; title = "Delete"; ObjectID = "pa3-QI-u2k"; */ 108 | "pa3-QI-u2k.title" = "Poista"; 109 | 110 | /* Class = "NSMenu"; title = "Free Ruler"; ObjectID = "uQy-DD-JDr"; */ 111 | "uQy-DD-JDr.title" = "Free Ruler"; 112 | 113 | /* Class = "NSMenuItem"; title = "Cut"; ObjectID = "uRl-iY-unG"; */ 114 | "uRl-iY-unG.title" = "Leikkaa"; 115 | 116 | /* Class = "NSMenuItem"; title = "Help"; ObjectID = "wpr-3q-Mcd"; */ 117 | "wpr-3q-Mcd.title" = "Ohje"; 118 | 119 | /* Class = "NSMenuItem"; title = "Copy"; ObjectID = "x3v-GG-iWU"; */ 120 | "x3v-GG-iWU.title" = "Kopioi"; 121 | 122 | /* Class = "NSMenu"; title = "Unit"; ObjectID = "z2p-dA-zcS"; */ 123 | "z2p-dA-zcS.title" = "Yksikkö"; 124 | 125 | /* Class = "NSMenuItem"; title = "Unit"; ObjectID = "iDP-2z-irv"; */ 126 | "iDP-2z-irv.title" = "Yksikkö"; 127 | 128 | /* Class = "NSMenuItem"; title = "Pixels"; ObjectID = "pYR-Ba-kKi"; */ 129 | "pYR-Ba-kKi.title" = "Pikselit"; 130 | 131 | /* Class = "NSMenuItem"; title = "Millimeters"; ObjectID = "B6Y-Hi-AkN"; */ 132 | "B6Y-Hi-AkN.title" = "Millimetrit"; 133 | 134 | /* Class = "NSMenuItem"; title = "Inches"; ObjectID = "lt1-Hj-2TR"; */ 135 | "lt1-Hj-2TR.title" = "Tuumat"; 136 | 137 | /* Class = "NSMenuItem"; title = "Cycle Units"; ObjectID = "2nm-aL-kZd"; */ 138 | "2nm-aL-kZd.title" = "Vaihda yksikköä"; 139 | -------------------------------------------------------------------------------- /Free Ruler/fi.lproj/PreferencesController.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "NSButtonCell"; title = "Reset Ruler Positions"; ObjectID = "10f-9L-qca"; */ 3 | "10f-9L-qca.title" = "Nollaa viivainten sijainnit"; 4 | 5 | /* Class = "NSTextFieldCell"; title = "Foreground Opacity"; ObjectID = "BgV-9N-IVn"; */ 6 | "BgV-9N-IVn.title" = "Peittävyys edustalla"; 7 | 8 | /* Class = "NSWindow"; title = "Free Ruler Preferences"; ObjectID = "F0z-JX-Cv5"; */ 9 | "F0z-JX-Cv5.title" = "Free Rulerin asetukset"; 10 | 11 | /* Class = "NSButtonCell"; title = "Group rulers"; ObjectID = "N2Y-8B-L9c"; */ 12 | "N2Y-8B-L9c.title" = "Ryhmitä viivaimet"; 13 | 14 | /* Class = "NSTextFieldCell"; title = "Label"; ObjectID = "VXi-Ch-Jf5"; */ 15 | "VXi-Ch-Jf5.title" = "Label"; 16 | 17 | /* Class = "NSTextFieldCell"; title = "Label"; ObjectID = "Vqd-CI-vmd"; */ 18 | "Vqd-CI-vmd.title" = "Label"; 19 | 20 | /* Class = "NSTextFieldCell"; title = "Background Opacity"; ObjectID = "cRb-8z-VZj"; */ 21 | "cRb-8z-VZj.title" = "Peittävyys taustalla"; 22 | 23 | /* Class = "NSButtonCell"; title = "Show ruler shadow"; ObjectID = "l53-85-hoA"; */ 24 | "l53-85-hoA.title" = "Näytä viivainten varjot"; 25 | 26 | /* Class = "NSButtonCell"; title = "Float rulers above other applications"; ObjectID = "yPM-Cw-Qsi"; */ 27 | "yPM-Cw-Qsi.title" = "Kelluta viivaimia muiden sovellusten päällä"; 28 | -------------------------------------------------------------------------------- /Free Ruler/ja.lproj/MainMenu.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "NSMenuItem"; title = "Free Ruler"; ObjectID = "1Xt-HY-uBw"; */ 3 | "1Xt-HY-uBw.title" = "Free Ruler"; 4 | 5 | /* Class = "NSMenuItem"; title = "Cycle Units"; ObjectID = "2nm-aL-kZd"; */ 6 | "2nm-aL-kZd.title" = "単位を切替"; 7 | 8 | /* Class = "NSMenuItem"; title = "Quit Free Ruler"; ObjectID = "4sb-4s-VLi"; */ 9 | "4sb-4s-VLi.title" = "Free Rulerを終了"; 10 | 11 | /* Class = "NSMenuItem"; title = "Edit"; ObjectID = "5QF-Oa-p0T"; */ 12 | "5QF-Oa-p0T.title" = "編集"; 13 | 14 | /* Class = "NSMenuItem"; title = "About Free Ruler"; ObjectID = "5kV-Vb-QxS"; */ 15 | "5kV-Vb-QxS.title" = "Free Rulerについて"; 16 | 17 | /* Class = "NSMenuItem"; title = "Redo"; ObjectID = "6dh-zS-Vam"; */ 18 | "6dh-zS-Vam.title" = "やり直し"; 19 | 20 | /* Class = "NSMenuItem"; title = "Reset Ruler Positions"; ObjectID = "6ph-5N-O9R"; */ 21 | "6ph-5N-O9R.title" = "定規の位置を元に戻す"; 22 | 23 | /* Class = "NSMenuItem"; title = "Group Rulers"; ObjectID = "7Ga-Fb-LLc"; */ 24 | "7Ga-Fb-LLc.title" = "定規をグループ化"; 25 | 26 | /* Class = "NSMenu"; title = "Main Menu"; ObjectID = "AYu-sK-qS6"; */ 27 | "AYu-sK-qS6.title" = "メインメニュー"; 28 | 29 | /* Class = "NSMenuItem"; title = "Millimeters"; ObjectID = "B6Y-Hi-AkN"; */ 30 | "B6Y-Hi-AkN.title" = "ミリメートル"; 31 | 32 | /* Class = "NSMenuItem"; title = "Preferences…"; ObjectID = "BOF-NM-1cW"; */ 33 | "BOF-NM-1cW.title" = "環境設定..."; 34 | 35 | /* Class = "NSMenuItem"; title = "Close"; ObjectID = "DVo-aG-piG"; */ 36 | "DVo-aG-piG.title" = "閉じる"; 37 | 38 | /* Class = "NSMenu"; title = "Help"; ObjectID = "F2S-fz-NVQ"; */ 39 | "F2S-fz-NVQ.title" = "ヘルプ"; 40 | 41 | /* Class = "NSMenuItem"; title = "Free Ruler Help"; ObjectID = "FKE-Sm-Kum"; */ 42 | "FKE-Sm-Kum.title" = "Free Rulerヘルプ"; 43 | 44 | /* Class = "NSMenuItem"; title = "Float Rulers"; ObjectID = "GDK-AC-uC8"; */ 45 | "GDK-AC-uC8.title" = "定規を常に手前に表示"; 46 | 47 | /* Class = "NSMenuItem"; title = "Options"; ObjectID = "H8h-7b-M4v"; */ 48 | "H8h-7b-M4v.title" = "オプション"; 49 | 50 | /* Class = "NSMenu"; title = "Options"; ObjectID = "HyV-fh-RgO"; */ 51 | "HyV-fh-RgO.title" = "オプション"; 52 | 53 | /* Class = "NSMenuItem"; title = "Show All"; ObjectID = "Kd2-mp-pUS"; */ 54 | "Kd2-mp-pUS.title" = "すべてを表示"; 55 | 56 | /* Class = "NSMenuItem"; title = "Bring All to Front"; ObjectID = "LE2-aR-0XJ"; */ 57 | "LE2-aR-0XJ.title" = "すべてを手前に移動"; 58 | 59 | /* Class = "NSMenuItem"; title = "Services"; ObjectID = "NMo-om-nkz"; */ 60 | "NMo-om-nkz.title" = "サービス"; 61 | 62 | /* Class = "NSMenuItem"; title = "Minimize"; ObjectID = "OY7-WF-poV"; */ 63 | "OY7-WF-poV.title" = "しまう"; 64 | 65 | /* Class = "NSMenuItem"; title = "Hide Free Ruler"; ObjectID = "Olw-nP-bQN"; */ 66 | "Olw-nP-bQN.title" = "Free Rulerを非表示"; 67 | 68 | /* Class = "NSMenuItem"; title = "Zoom"; ObjectID = "R4o-n2-Eq4"; */ 69 | "R4o-n2-Eq4.title" = "拡大"; 70 | 71 | /* Class = "NSMenuItem"; title = "Select All"; ObjectID = "Ruw-6m-B2m"; */ 72 | "Ruw-6m-B2m.title" = "すべてを選択"; 73 | 74 | /* Class = "NSMenu"; title = "Window"; ObjectID = "Td7-aD-5lo"; */ 75 | "Td7-aD-5lo.title" = "ウィンドウ"; 76 | 77 | /* Class = "NSMenuItem"; title = "Hide Others"; ObjectID = "Vdr-fp-XzO"; */ 78 | "Vdr-fp-XzO.title" = "ほかを非表示"; 79 | 80 | /* Class = "NSMenu"; title = "Edit"; ObjectID = "W48-6f-4Dl"; */ 81 | "W48-6f-4Dl.title" = "編集"; 82 | 83 | /* Class = "NSMenuItem"; title = "Paste and Match Style"; ObjectID = "WeT-3V-zwk"; */ 84 | "WeT-3V-zwk.title" = "ペーストしてスタイルを合わせる"; 85 | 86 | /* Class = "NSMenuItem"; title = "Show Ruler Shadow"; ObjectID = "a8D-hN-A59"; */ 87 | "a8D-hN-A59.title" = "定規の影を表示"; 88 | 89 | /* Class = "NSMenuItem"; title = "Window"; ObjectID = "aUF-d1-5bR"; */ 90 | "aUF-d1-5bR.title" = "ウィンドウ"; 91 | 92 | /* Class = "NSMenu"; title = "Ruler"; ObjectID = "bib-Uj-vzu"; */ 93 | "bib-Uj-vzu.title" = "定規"; 94 | 95 | /* Class = "NSMenuItem"; title = "Ruler"; ObjectID = "dMs-cI-mzQ"; */ 96 | "dMs-cI-mzQ.title" = "定規"; 97 | 98 | /* Class = "NSMenuItem"; title = "Undo"; ObjectID = "dRJ-4n-Yzg"; */ 99 | "dRJ-4n-Yzg.title" = "取り消す"; 100 | 101 | /* Class = "NSMenuItem"; title = "Paste"; ObjectID = "gVA-U4-sdL"; */ 102 | "gVA-U4-sdL.title" = "ペースト"; 103 | 104 | /* Class = "NSMenu"; title = "Services"; ObjectID = "hz9-B4-Xy5"; */ 105 | "hz9-B4-Xy5.title" = "サービス"; 106 | 107 | /* Class = "NSMenuItem"; title = "Unit"; ObjectID = "iDP-2z-irv"; */ 108 | "iDP-2z-irv.title" = "単位"; 109 | 110 | /* Class = "NSMenuItem"; title = "Align Rulers at Mouse Location"; ObjectID = "iKV-uW-hwy"; */ 111 | "iKV-uW-hwy.title" = "マウスの位置に定規を移動"; 112 | 113 | /* Class = "NSMenuItem"; title = "Inches"; ObjectID = "lt1-Hj-2TR"; */ 114 | "lt1-Hj-2TR.title" = "インチ"; 115 | 116 | /* Class = "NSMenuItem"; title = "Pixels"; ObjectID = "pYR-Ba-kKi"; */ 117 | "pYR-Ba-kKi.title" = "ピクセル"; 118 | 119 | /* Class = "NSMenuItem"; title = "Delete"; ObjectID = "pa3-QI-u2k"; */ 120 | "pa3-QI-u2k.title" = "削除"; 121 | 122 | /* Class = "NSMenu"; title = "Free Ruler"; ObjectID = "uQy-DD-JDr"; */ 123 | "uQy-DD-JDr.title" = "Free Ruler"; 124 | 125 | /* Class = "NSMenuItem"; title = "Cut"; ObjectID = "uRl-iY-unG"; */ 126 | "uRl-iY-unG.title" = "カット"; 127 | 128 | /* Class = "NSMenuItem"; title = "Help"; ObjectID = "wpr-3q-Mcd"; */ 129 | "wpr-3q-Mcd.title" = "ヘルプ"; 130 | 131 | /* Class = "NSMenuItem"; title = "Copy"; ObjectID = "x3v-GG-iWU"; */ 132 | "x3v-GG-iWU.title" = "コピー"; 133 | 134 | /* Class = "NSMenu"; title = "Unit"; ObjectID = "z2p-dA-zcS"; */ 135 | "z2p-dA-zcS.title" = "単位"; 136 | -------------------------------------------------------------------------------- /Free Ruler/ja.lproj/PreferencesController.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "NSButtonCell"; title = "Reset Ruler Positions"; ObjectID = "10f-9L-qca"; */ 3 | "10f-9L-qca.title" = "定規の位置を元に戻す"; 4 | 5 | /* Class = "NSTextFieldCell"; title = "Foreground Opacity"; ObjectID = "BgV-9N-IVn"; */ 6 | "BgV-9N-IVn.title" = "前景の不透明度"; 7 | 8 | /* Class = "NSWindow"; title = "Free Ruler Preferences"; ObjectID = "F0z-JX-Cv5"; */ 9 | "F0z-JX-Cv5.title" = "Free Rulerの環境設定"; 10 | 11 | /* Class = "NSButtonCell"; title = "Group rulers"; ObjectID = "N2Y-8B-L9c"; */ 12 | "N2Y-8B-L9c.title" = "定規をグループ化"; 13 | 14 | /* Class = "NSTextFieldCell"; title = "Label"; ObjectID = "VXi-Ch-Jf5"; */ 15 | "VXi-Ch-Jf5.title" = "ラベル"; 16 | 17 | /* Class = "NSTextFieldCell"; title = "Label"; ObjectID = "Vqd-CI-vmd"; */ 18 | "Vqd-CI-vmd.title" = "ラベル"; 19 | 20 | /* Class = "NSTextFieldCell"; title = "Background Opacity"; ObjectID = "cRb-8z-VZj"; */ 21 | "cRb-8z-VZj.title" = "背景の不透明度"; 22 | 23 | /* Class = "NSButtonCell"; title = "Show ruler shadow"; ObjectID = "l53-85-hoA"; */ 24 | "l53-85-hoA.title" = "定規の影を表示"; 25 | 26 | /* Class = "NSButtonCell"; title = "Float rulers above other applications"; ObjectID = "yPM-Cw-Qsi"; */ 27 | "yPM-Cw-Qsi.title" = "定規を常に手前に表示"; 28 | -------------------------------------------------------------------------------- /Free Ruler/zh-hans.lproj/MainMenu.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "NSMenuItem"; title = "Free Ruler"; ObjectID = "1Xt-HY-uBw"; */ 3 | "1Xt-HY-uBw.title" = "Free Ruler"; 4 | 5 | /* Class = "NSMenuItem"; title = "Quit Free Ruler"; ObjectID = "4sb-4s-VLi"; */ 6 | "4sb-4s-VLi.title" = "退出 Free Ruler"; 7 | 8 | /* Class = "NSMenuItem"; title = "Edit"; ObjectID = "5QF-Oa-p0T"; */ 9 | "5QF-Oa-p0T.title" = "编辑"; 10 | 11 | /* Class = "NSMenuItem"; title = "About Free Ruler"; ObjectID = "5kV-Vb-QxS"; */ 12 | "5kV-Vb-QxS.title" = "关于 Free Ruler"; 13 | 14 | /* Class = "NSMenuItem"; title = "Redo"; ObjectID = "6dh-zS-Vam"; */ 15 | "6dh-zS-Vam.title" = "重做"; 16 | 17 | /* Class = "NSMenuItem"; title = "Reset Ruler Positions"; ObjectID = "6ph-5N-O9R"; */ 18 | "6ph-5N-O9R.title" = "重置尺子位置"; 19 | 20 | /* Class = "NSMenuItem"; title = "Group Rulers"; ObjectID = "7Ga-Fb-LLc"; */ 21 | "7Ga-Fb-LLc.title" = "组合尺子"; 22 | 23 | /* Class = "NSMenuItem"; title = "Group Rulers"; ObjectID = "7SO-Lm-Ylr"; */ 24 | "7SO-Lm-Ylr.title" = "组合尺子"; 25 | 26 | /* Class = "NSMenu"; title = "Main Menu"; ObjectID = "AYu-sK-qS6"; */ 27 | "AYu-sK-qS6.title" = "Main Menu"; 28 | 29 | /* Class = "NSMenuItem"; title = "Preferences…"; ObjectID = "BOF-NM-1cW"; */ 30 | "BOF-NM-1cW.title" = "偏好设置…"; 31 | 32 | /* Class = "NSMenuItem"; title = "Close"; ObjectID = "DVo-aG-piG"; */ 33 | "DVo-aG-piG.title" = "关闭"; 34 | 35 | /* Class = "NSMenu"; title = "Help"; ObjectID = "F2S-fz-NVQ"; */ 36 | "F2S-fz-NVQ.title" = "帮助"; 37 | 38 | /* Class = "NSMenuItem"; title = "Free Ruler Help"; ObjectID = "FKE-Sm-Kum"; */ 39 | "FKE-Sm-Kum.title" = "Free Ruler 帮助"; 40 | 41 | /* Class = "NSMenuItem"; title = "Float Rulers"; ObjectID = "GDK-AC-uC8"; */ 42 | "GDK-AC-uC8.title" = "悬浮尺子"; 43 | 44 | /* Class = "NSMenuItem"; title = "Options"; ObjectID = "H8h-7b-M4v"; */ 45 | "H8h-7b-M4v.title" = "选项"; 46 | 47 | /* Class = "NSMenu"; title = "Options"; ObjectID = "HyV-fh-RgO"; */ 48 | "HyV-fh-RgO.title" = "选项"; 49 | 50 | /* Class = "NSMenuItem"; title = "Show All"; ObjectID = "Kd2-mp-pUS"; */ 51 | "Kd2-mp-pUS.title" = "显示全部"; 52 | 53 | /* Class = "NSMenuItem"; title = "Bring All to Front"; ObjectID = "LE2-aR-0XJ"; */ 54 | "LE2-aR-0XJ.title" = "全部置于顶层"; 55 | 56 | /* Class = "NSMenuItem"; title = "Services"; ObjectID = "NMo-om-nkz"; */ 57 | "NMo-om-nkz.title" = "服务"; 58 | 59 | /* Class = "NSMenuItem"; title = "Minimize"; ObjectID = "OY7-WF-poV"; */ 60 | "OY7-WF-poV.title" = "最小化"; 61 | 62 | /* Class = "NSMenuItem"; title = "Hide Free Ruler"; ObjectID = "Olw-nP-bQN"; */ 63 | "Olw-nP-bQN.title" = "隐藏 Free Ruler"; 64 | 65 | /* Class = "NSMenuItem"; title = "Zoom"; ObjectID = "R4o-n2-Eq4"; */ 66 | "R4o-n2-Eq4.title" = "缩放"; 67 | 68 | /* Class = "NSMenuItem"; title = "Select All"; ObjectID = "Ruw-6m-B2m"; */ 69 | "Ruw-6m-B2m.title" = "选择全部"; 70 | 71 | /* Class = "NSMenu"; title = "Window"; ObjectID = "Td7-aD-5lo"; */ 72 | "Td7-aD-5lo.title" = "窗口"; 73 | 74 | /* Class = "NSMenuItem"; title = "Hide Others"; ObjectID = "Vdr-fp-XzO"; */ 75 | "Vdr-fp-XzO.title" = "隐藏其他"; 76 | 77 | /* Class = "NSMenu"; title = "Edit"; ObjectID = "W48-6f-4Dl"; */ 78 | "W48-6f-4Dl.title" = "编辑"; 79 | 80 | /* Class = "NSMenuItem"; title = "Paste and Match Style"; ObjectID = "WeT-3V-zwk"; */ 81 | "WeT-3V-zwk.title" = "粘贴并匹配样式"; 82 | 83 | /* Class = "NSMenuItem"; title = "Show Ruler Shadow"; ObjectID = "a8D-hN-A59"; */ 84 | "a8D-hN-A59.title" = "显示尺子阴影"; 85 | 86 | /* Class = "NSMenuItem"; title = "Window"; ObjectID = "aUF-d1-5bR"; */ 87 | "aUF-d1-5bR.title" = "窗口"; 88 | 89 | /* Class = "NSMenu"; title = "Ruler"; ObjectID = "bib-Uj-vzu"; */ 90 | "bib-Uj-vzu.title" = "尺子"; 91 | 92 | /* Class = "NSMenuItem"; title = "Ruler"; ObjectID = "dMs-cI-mzQ"; */ 93 | "dMs-cI-mzQ.title" = "尺子"; 94 | 95 | /* Class = "NSMenuItem"; title = "Undo"; ObjectID = "dRJ-4n-Yzg"; */ 96 | "dRJ-4n-Yzg.title" = "撤销"; 97 | 98 | /* Class = "NSMenuItem"; title = "Paste"; ObjectID = "gVA-U4-sdL"; */ 99 | "gVA-U4-sdL.title" = "粘贴"; 100 | 101 | /* Class = "NSMenu"; title = "Services"; ObjectID = "hz9-B4-Xy5"; */ 102 | "hz9-B4-Xy5.title" = "服务"; 103 | 104 | /* Class = "NSMenuItem"; title = "Align Rulers at Mouse Location"; ObjectID = "iKV-uW-hwy"; */ 105 | "iKV-uW-hwy.title" = "将尺子与鼠标位置对齐"; 106 | 107 | /* Class = "NSMenuItem"; title = "Delete"; ObjectID = "pa3-QI-u2k"; */ 108 | "pa3-QI-u2k.title" = "删除"; 109 | 110 | /* Class = "NSMenu"; title = "Free Ruler"; ObjectID = "uQy-DD-JDr"; */ 111 | "uQy-DD-JDr.title" = "Free Ruler"; 112 | 113 | /* Class = "NSMenuItem"; title = "Cut"; ObjectID = "uRl-iY-unG"; */ 114 | "uRl-iY-unG.title" = "剪切"; 115 | 116 | /* Class = "NSMenuItem"; title = "Help"; ObjectID = "wpr-3q-Mcd"; */ 117 | "wpr-3q-Mcd.title" = "帮助"; 118 | 119 | /* Class = "NSMenuItem"; title = "Copy"; ObjectID = "x3v-GG-iWU"; */ 120 | "x3v-GG-iWU.title" = "拷贝"; 121 | 122 | /* Class = "NSMenu"; title = "Unit"; ObjectID = "z2p-dA-zcS"; */ 123 | "z2p-dA-zcS.title" = "单位"; 124 | 125 | /* Class = "NSMenuItem"; title = "Unit"; ObjectID = "iDP-2z-irv"; */ 126 | "iDP-2z-irv.title" = "单位"; 127 | 128 | /* Class = "NSMenuItem"; title = "Pixels"; ObjectID = "pYR-Ba-kKi"; */ 129 | "pYR-Ba-kKi.title" = "像素"; 130 | 131 | /* Class = "NSMenuItem"; title = "Millimeters"; ObjectID = "B6Y-Hi-AkN"; */ 132 | "B6Y-Hi-AkN.title" = "毫米"; 133 | 134 | /* Class = "NSMenuItem"; title = "Inches"; ObjectID = "lt1-Hj-2TR"; */ 135 | "lt1-Hj-2TR.title" = "英寸"; 136 | 137 | /* Class = "NSMenuItem"; title = "Cycle Units"; ObjectID = "2nm-aL-kZd"; */ 138 | "2nm-aL-kZd.title" = "循环单位"; 139 | -------------------------------------------------------------------------------- /Free Ruler/zh-hans.lproj/PreferencesController.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "NSButtonCell"; title = "Reset Ruler Positions"; ObjectID = "10f-9L-qca"; */ 3 | "10f-9L-qca.title" = "重置尺子位置"; 4 | 5 | /* Class = "NSTextFieldCell"; title = "Foreground Opacity"; ObjectID = "BgV-9N-IVn"; */ 6 | "BgV-9N-IVn.title" = "前景不透明度"; 7 | 8 | /* Class = "NSWindow"; title = "Free Ruler Preferences"; ObjectID = "F0z-JX-Cv5"; */ 9 | "F0z-JX-Cv5.title" = "Free Ruler 偏好设置"; 10 | 11 | /* Class = "NSButtonCell"; title = "Group rulers"; ObjectID = "N2Y-8B-L9c"; */ 12 | "N2Y-8B-L9c.title" = "组合尺子"; 13 | 14 | /* Class = "NSTextFieldCell"; title = "Label"; ObjectID = "VXi-Ch-Jf5"; */ 15 | "VXi-Ch-Jf5.title" = "标签"; 16 | 17 | /* Class = "NSTextFieldCell"; title = "Label"; ObjectID = "Vqd-CI-vmd"; */ 18 | "Vqd-CI-vmd.title" = "标签"; 19 | 20 | /* Class = "NSTextFieldCell"; title = "Background Opacity"; ObjectID = "cRb-8z-VZj"; */ 21 | "cRb-8z-VZj.title" = "背景不透明度"; 22 | 23 | /* Class = "NSButtonCell"; title = "Show ruler shadow"; ObjectID = "l53-85-hoA"; */ 24 | "l53-85-hoA.title" = "显示尺子阴影"; 25 | 26 | /* Class = "NSButtonCell"; title = "Float rulers above other applications"; ObjectID = "yPM-Cw-Qsi"; */ 27 | "yPM-Cw-Qsi.title" = "让标尺窗口悬浮在其他应用程序之上"; 28 | -------------------------------------------------------------------------------- /HOW-TO-RELEASE.md: -------------------------------------------------------------------------------- 1 | # Free Ruler Release Process 2 | 3 | Mostly notes for myself. 4 | 5 | - Merge any PRs to be included in the release. 6 | - Read the current version: `npm get:version` 7 | - Create a new branch from main named vX.X.X 8 | - Get the number of commits: `npm get:commits` 9 | - Bump the version number in package.json, using `npm version patch` (or minor, or major) 10 | 11 | - In XCode, bump the version and build number, using the number of commits as the build number. Example https://github.com/pascalpp/FreeRuler/commit/ed9f8db186b00125a78629a5e5569dfda9dd6285 12 | - In Xcode, choose Product > Archive 13 | - In the Archives window, click Validate App. May require visiting https://developer.apple.com to accept updated licensing agreements, etc. 14 | 15 | ## Github Release 16 | 17 | - In the XCode Archives window, click Distribute App > Direct Distribution > Distribute. 18 | - Wait for notification from Apple notary service, usually less than a minute. 19 | - Export the build to `dist/Free Ruler.app`. Compress Free Ruler.app as free-ruler-X.X.X.zip. `npm run build:zip` 20 | - Delete the app and any extra folders created by the build process. (Should be handled by above command.) 21 | - Commit the modified XCode project and the new zip file with the commit message 'Build vX.X.X' `npm run build:commit` 22 | - Create a PR for the branch back to main. `npm run build:pr` 23 | - Review and merge the PR. 24 | - Switch to main and pull latest. `git checkout main && git pull` 25 | - Create a tag for the release. `git build:tag` 26 | - Create a draft release: `git build:release` 27 | - Update the release notes. Describe changes with #XX references to closed tickets. 28 | - Publish the release. 29 | 30 | ## App Store Release 31 | 32 | - In the XCode Archives window, click Distribute App > App Store Connect > Upload. 33 | - Visit https://appstoreconnect.apple.com and submit the new build for review. 34 | - Create a new version (green plus button). 35 | - Add the new build. 36 | - Update release information with new features, etc. 37 | - Save 38 | - Submit for Review 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Pascal Balthrop 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 | # Free Ruler 2 | 3 | A ruler application for Mac OS 4 | 5 | ![icon_512x512](https://user-images.githubusercontent.com/1355312/57780292-f0c4b880-76f5-11e9-8933-36ab3bfccb14.png) 6 | 7 | ### Features 8 | 9 | - Horizontal and vertical rulers. 10 | - Choose from units: pixels, millimeters, or inches (press U to cycle). 11 | - Float rulers above other applications (press F to toggle). 12 | - Move rulers independently or as a group (press G to toggle). 13 | - Drag the end of each ruler to resize. 14 | - Show or hide the shadow behind rulers (press S to toggle). 15 | - Align rulers at mouse location (press O for origin). 16 | - Reset ruler locations with Command-R. 17 | 18 | ### Download 19 | 20 | See the [releases page](https://github.com/pascalpp/FreeRuler/releases). 21 | 22 | ### Todo 23 | 24 | See [the list of open issues](https://github.com/pascalpp/FreeRuler/issues). For feature requests, [open a new issue](https://github.com/pascalpp/FreeRuler/issues/new). 25 | 26 | ### Contributing 27 | 28 | As this is my first Swift application, any help is appreciated! 29 | 30 | If you have experience with Mac app development in Swift, please look at the [help wanted issues](https://github.com/pascalpp/FreeRuler/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22+sort%3Aupdated-desc). 31 | 32 | Or you can [make a donation](https://www.paypal.me/pascalpp). 33 | -------------------------------------------------------------------------------- /dist/free-ruler-2.0.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pascalpp/FreeRuler/886780dab939e082a37c7401f632a27e980394f4/dist/free-ruler-2.0.0.zip -------------------------------------------------------------------------------- /dist/free-ruler-2.0.1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pascalpp/FreeRuler/886780dab939e082a37c7401f632a27e980394f4/dist/free-ruler-2.0.1.zip -------------------------------------------------------------------------------- /dist/free-ruler-2.0.2.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pascalpp/FreeRuler/886780dab939e082a37c7401f632a27e980394f4/dist/free-ruler-2.0.2.zip -------------------------------------------------------------------------------- /dist/free-ruler-2.0.3.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pascalpp/FreeRuler/886780dab939e082a37c7401f632a27e980394f4/dist/free-ruler-2.0.3.zip -------------------------------------------------------------------------------- /dist/free-ruler-2.0.4.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pascalpp/FreeRuler/886780dab939e082a37c7401f632a27e980394f4/dist/free-ruler-2.0.4.zip -------------------------------------------------------------------------------- /dist/free-ruler-2.0.5.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pascalpp/FreeRuler/886780dab939e082a37c7401f632a27e980394f4/dist/free-ruler-2.0.5.zip -------------------------------------------------------------------------------- /dist/free-ruler-2.0.6.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pascalpp/FreeRuler/886780dab939e082a37c7401f632a27e980394f4/dist/free-ruler-2.0.6.zip -------------------------------------------------------------------------------- /dist/free-ruler-2.0.7.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pascalpp/FreeRuler/886780dab939e082a37c7401f632a27e980394f4/dist/free-ruler-2.0.7.zip -------------------------------------------------------------------------------- /dist/free-ruler-2.0.8.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pascalpp/FreeRuler/886780dab939e082a37c7401f632a27e980394f4/dist/free-ruler-2.0.8.zip -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "freeruler", 3 | "version": "2.0.8", 4 | "description": "A free ruler for macOS", 5 | "main": "none", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "get:version": "cat package.json | jq -r '.version'", 9 | "get:commits": "git log --pretty=oneline | wc -l", 10 | "bump:version": "npm version patch", 11 | "build:zip": "cd dist && zip -r free-ruler-$(npm run --silent get:version).zip 'Free Ruler.app' && rm -rf 'Free Ruler.app' && cd ..", 12 | "build:commit": "git add --all && git commit -am \"Build v$(npm run --silent get:version)\" && git push --set-upstream origin $(currentbranch)", 13 | "build:pr": "gh pr create --title \"Build v$(npm run --silent get:version)\" --body \"Build v$(npm run --silent get:version)\" --base main --web", 14 | "build:tag": "git tag -a v$(npm run --silent get:version) -m \"v$(npm run --silent get:version)\" && git push origin v$(npm run --silent get:version)", 15 | "build:release": "gh release create v$(npm run --silent get:version) dist/free-ruler-$(npm run --silent get:version).zip --title \"v$(npm run --silent get:version)\" --notes \"v$(npm run --silent get:version)\" --draft" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/pascalpp/FreeRuler.git" 20 | }, 21 | "author": "Pascal ", 22 | "license": "ISC", 23 | "bugs": { 24 | "url": "https://github.com/pascalpp/FreeRuler/issues" 25 | }, 26 | "homepage": "https://github.com/pascalpp/FreeRuler#readme" 27 | } 28 | --------------------------------------------------------------------------------