├── .gitignore ├── GMailinator.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── GMailinator ├── GMailinator-Prefix.pch ├── GMailinator.h ├── GMailinator.m ├── Info.plist ├── Mail.h ├── NSArray+reverse.h ├── NSArray+reverse.m ├── Ranker.h ├── Ranker.mm ├── SearchManager.h ├── SearchManager.m ├── SearchPopup.h ├── SearchPopup.m └── SearchPopup.xib ├── README.md └── find_uuid.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | xcuserdata/ 3 | -------------------------------------------------------------------------------- /GMailinator.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 2352AA2217BDE44B001006D0 /* NSArray+reverse.m in Sources */ = {isa = PBXBuildFile; fileRef = 2352AA1817BDE44B001006D0 /* NSArray+reverse.m */; }; 11 | 2352AA2317BDE44B001006D0 /* Ranker.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2352AA1A17BDE44B001006D0 /* Ranker.mm */; }; 12 | 2352AA2417BDE44B001006D0 /* SearchManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 2352AA1C17BDE44B001006D0 /* SearchManager.m */; }; 13 | 2352AA2617BDE44B001006D0 /* SearchPopup.m in Sources */ = {isa = PBXBuildFile; fileRef = 2352AA1F17BDE44B001006D0 /* SearchPopup.m */; }; 14 | 2352AA2717BDE44B001006D0 /* SearchPopup.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2352AA2017BDE44B001006D0 /* SearchPopup.xib */; }; 15 | 2352AA2917BDE624001006D0 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2352AA2817BDE624001006D0 /* Cocoa.framework */; }; 16 | 2352AA2D17BE0F25001006D0 /* GMailinator.m in Sources */ = {isa = PBXBuildFile; fileRef = 2352AA2C17BE0F25001006D0 /* GMailinator.m */; }; 17 | 239B1F2317B6FD2F002CEA1B /* GMailinator.mailbundle in CopyFiles */ = {isa = PBXBuildFile; fileRef = 499015DA1615FE5300991F6C /* GMailinator.mailbundle */; }; 18 | 499015DE1615FE5300991F6C /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 499015DD1615FE5300991F6C /* CoreFoundation.framework */; }; 19 | B54609F916F9E3D000101CB2 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B54609F816F9E3D000101CB2 /* AppKit.framework */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXCopyFilesBuildPhase section */ 23 | 239B1F2217B6FD16002CEA1B /* CopyFiles */ = { 24 | isa = PBXCopyFilesBuildPhase; 25 | buildActionMask = 2147483647; 26 | dstPath = "~/Library/Mail/Bundles"; 27 | dstSubfolderSpec = 0; 28 | files = ( 29 | 239B1F2317B6FD2F002CEA1B /* GMailinator.mailbundle in CopyFiles */, 30 | ); 31 | runOnlyForDeploymentPostprocessing = 0; 32 | }; 33 | /* End PBXCopyFilesBuildPhase section */ 34 | 35 | /* Begin PBXFileReference section */ 36 | 2352AA1617BDE44B001006D0 /* Mail.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Mail.h; sourceTree = ""; }; 37 | 2352AA1717BDE44B001006D0 /* NSArray+reverse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+reverse.h"; sourceTree = ""; }; 38 | 2352AA1817BDE44B001006D0 /* NSArray+reverse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+reverse.m"; sourceTree = ""; }; 39 | 2352AA1917BDE44B001006D0 /* Ranker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Ranker.h; sourceTree = ""; }; 40 | 2352AA1A17BDE44B001006D0 /* Ranker.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = Ranker.mm; sourceTree = ""; }; 41 | 2352AA1B17BDE44B001006D0 /* SearchManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SearchManager.h; sourceTree = ""; }; 42 | 2352AA1C17BDE44B001006D0 /* SearchManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SearchManager.m; sourceTree = ""; }; 43 | 2352AA1E17BDE44B001006D0 /* SearchPopup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SearchPopup.h; sourceTree = ""; }; 44 | 2352AA1F17BDE44B001006D0 /* SearchPopup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SearchPopup.m; sourceTree = ""; }; 45 | 2352AA2017BDE44B001006D0 /* SearchPopup.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SearchPopup.xib; sourceTree = ""; }; 46 | 2352AA2817BDE624001006D0 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 47 | 2352AA2B17BE0F25001006D0 /* GMailinator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GMailinator.h; sourceTree = ""; }; 48 | 2352AA2C17BE0F25001006D0 /* GMailinator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GMailinator.m; sourceTree = ""; }; 49 | 499015DA1615FE5300991F6C /* GMailinator.mailbundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GMailinator.mailbundle; sourceTree = BUILT_PRODUCTS_DIR; }; 50 | 499015DD1615FE5300991F6C /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; 51 | 499015E11615FE5300991F6C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 52 | 499015E51615FE5300991F6C /* GMailinator-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "GMailinator-Prefix.pch"; sourceTree = ""; }; 53 | B54609F816F9E3D000101CB2 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 54 | /* End PBXFileReference section */ 55 | 56 | /* Begin PBXFrameworksBuildPhase section */ 57 | 499015D71615FE5300991F6C /* Frameworks */ = { 58 | isa = PBXFrameworksBuildPhase; 59 | buildActionMask = 2147483647; 60 | files = ( 61 | 2352AA2917BDE624001006D0 /* Cocoa.framework in Frameworks */, 62 | B54609F916F9E3D000101CB2 /* AppKit.framework in Frameworks */, 63 | 499015DE1615FE5300991F6C /* CoreFoundation.framework in Frameworks */, 64 | ); 65 | runOnlyForDeploymentPostprocessing = 0; 66 | }; 67 | /* End PBXFrameworksBuildPhase section */ 68 | 69 | /* Begin PBXGroup section */ 70 | 499015CF1615FE5300991F6C = { 71 | isa = PBXGroup; 72 | children = ( 73 | 499015DF1615FE5300991F6C /* GMailinator */, 74 | 499015DC1615FE5300991F6C /* Frameworks */, 75 | 499015DB1615FE5300991F6C /* Products */, 76 | ); 77 | sourceTree = ""; 78 | }; 79 | 499015DB1615FE5300991F6C /* Products */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | 499015DA1615FE5300991F6C /* GMailinator.mailbundle */, 83 | ); 84 | name = Products; 85 | sourceTree = ""; 86 | }; 87 | 499015DC1615FE5300991F6C /* Frameworks */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | 2352AA2817BDE624001006D0 /* Cocoa.framework */, 91 | B54609F816F9E3D000101CB2 /* AppKit.framework */, 92 | 499015DD1615FE5300991F6C /* CoreFoundation.framework */, 93 | ); 94 | name = Frameworks; 95 | sourceTree = ""; 96 | }; 97 | 499015DF1615FE5300991F6C /* GMailinator */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | 2352AA2B17BE0F25001006D0 /* GMailinator.h */, 101 | 2352AA2C17BE0F25001006D0 /* GMailinator.m */, 102 | 2352AA1617BDE44B001006D0 /* Mail.h */, 103 | 2352AA1717BDE44B001006D0 /* NSArray+reverse.h */, 104 | 2352AA1817BDE44B001006D0 /* NSArray+reverse.m */, 105 | 2352AA1917BDE44B001006D0 /* Ranker.h */, 106 | 2352AA1A17BDE44B001006D0 /* Ranker.mm */, 107 | 2352AA1B17BDE44B001006D0 /* SearchManager.h */, 108 | 2352AA1C17BDE44B001006D0 /* SearchManager.m */, 109 | 2352AA1E17BDE44B001006D0 /* SearchPopup.h */, 110 | 2352AA1F17BDE44B001006D0 /* SearchPopup.m */, 111 | 2352AA2017BDE44B001006D0 /* SearchPopup.xib */, 112 | 499015E01615FE5300991F6C /* Supporting Files */, 113 | ); 114 | path = GMailinator; 115 | sourceTree = ""; 116 | }; 117 | 499015E01615FE5300991F6C /* Supporting Files */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | 499015E11615FE5300991F6C /* Info.plist */, 121 | 499015E51615FE5300991F6C /* GMailinator-Prefix.pch */, 122 | ); 123 | name = "Supporting Files"; 124 | sourceTree = ""; 125 | }; 126 | /* End PBXGroup section */ 127 | 128 | /* Begin PBXNativeTarget section */ 129 | 499015D91615FE5300991F6C /* GMailinator */ = { 130 | isa = PBXNativeTarget; 131 | buildConfigurationList = 499015E81615FE5300991F6C /* Build configuration list for PBXNativeTarget "GMailinator" */; 132 | buildPhases = ( 133 | 499015D61615FE5300991F6C /* Sources */, 134 | 499015D71615FE5300991F6C /* Frameworks */, 135 | 499015D81615FE5300991F6C /* Resources */, 136 | 239B1F2217B6FD16002CEA1B /* CopyFiles */, 137 | ); 138 | buildRules = ( 139 | ); 140 | dependencies = ( 141 | ); 142 | name = GMailinator; 143 | productName = NoFlaggedMailbox; 144 | productReference = 499015DA1615FE5300991F6C /* GMailinator.mailbundle */; 145 | productType = "com.apple.product-type.bundle"; 146 | }; 147 | /* End PBXNativeTarget section */ 148 | 149 | /* Begin PBXProject section */ 150 | 499015D11615FE5300991F6C /* Project object */ = { 151 | isa = PBXProject; 152 | attributes = { 153 | LastUpgradeCheck = 0450; 154 | ORGANIZATIONNAME = nompute; 155 | }; 156 | buildConfigurationList = 499015D41615FE5300991F6C /* Build configuration list for PBXProject "GMailinator" */; 157 | compatibilityVersion = "Xcode 3.2"; 158 | developmentRegion = English; 159 | hasScannedForEncodings = 0; 160 | knownRegions = ( 161 | en, 162 | ); 163 | mainGroup = 499015CF1615FE5300991F6C; 164 | productRefGroup = 499015DB1615FE5300991F6C /* Products */; 165 | projectDirPath = ""; 166 | projectRoot = ""; 167 | targets = ( 168 | 499015D91615FE5300991F6C /* GMailinator */, 169 | ); 170 | }; 171 | /* End PBXProject section */ 172 | 173 | /* Begin PBXResourcesBuildPhase section */ 174 | 499015D81615FE5300991F6C /* Resources */ = { 175 | isa = PBXResourcesBuildPhase; 176 | buildActionMask = 2147483647; 177 | files = ( 178 | 2352AA2717BDE44B001006D0 /* SearchPopup.xib in Resources */, 179 | ); 180 | runOnlyForDeploymentPostprocessing = 0; 181 | }; 182 | /* End PBXResourcesBuildPhase section */ 183 | 184 | /* Begin PBXSourcesBuildPhase section */ 185 | 499015D61615FE5300991F6C /* Sources */ = { 186 | isa = PBXSourcesBuildPhase; 187 | buildActionMask = 2147483647; 188 | files = ( 189 | 2352AA2217BDE44B001006D0 /* NSArray+reverse.m in Sources */, 190 | 2352AA2317BDE44B001006D0 /* Ranker.mm in Sources */, 191 | 2352AA2417BDE44B001006D0 /* SearchManager.m in Sources */, 192 | 2352AA2617BDE44B001006D0 /* SearchPopup.m in Sources */, 193 | 2352AA2D17BE0F25001006D0 /* GMailinator.m in Sources */, 194 | ); 195 | runOnlyForDeploymentPostprocessing = 0; 196 | }; 197 | /* End PBXSourcesBuildPhase section */ 198 | 199 | /* Begin XCBuildConfiguration section */ 200 | 499015E61615FE5300991F6C /* Debug */ = { 201 | isa = XCBuildConfiguration; 202 | buildSettings = { 203 | ALWAYS_SEARCH_USER_PATHS = NO; 204 | ARCHS = "$(ARCHS_STANDARD_64_BIT)"; 205 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 206 | CLANG_CXX_LIBRARY = "libc++"; 207 | CLANG_ENABLE_OBJC_ARC = YES; 208 | CLANG_WARN_EMPTY_BODY = YES; 209 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 210 | COPY_PHASE_STRIP = NO; 211 | GCC_C_LANGUAGE_STANDARD = gnu99; 212 | GCC_DYNAMIC_NO_PIC = NO; 213 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 214 | GCC_OPTIMIZATION_LEVEL = 0; 215 | GCC_PREPROCESSOR_DEFINITIONS = ( 216 | "DEBUG=1", 217 | "$(inherited)", 218 | ); 219 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 220 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 221 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 222 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 223 | GCC_WARN_UNUSED_VARIABLE = YES; 224 | INSTALL_PATH = "~/Library/Mail/Bundles"; 225 | MACOSX_DEPLOYMENT_TARGET = 10.8; 226 | ONLY_ACTIVE_ARCH = YES; 227 | SDKROOT = macosx; 228 | }; 229 | name = Debug; 230 | }; 231 | 499015E71615FE5300991F6C /* Release */ = { 232 | isa = XCBuildConfiguration; 233 | buildSettings = { 234 | ALWAYS_SEARCH_USER_PATHS = NO; 235 | ARCHS = "$(ARCHS_STANDARD_64_BIT)"; 236 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 237 | CLANG_CXX_LIBRARY = "libc++"; 238 | CLANG_ENABLE_OBJC_ARC = YES; 239 | CLANG_WARN_EMPTY_BODY = YES; 240 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 241 | COPY_PHASE_STRIP = YES; 242 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 243 | GCC_C_LANGUAGE_STANDARD = gnu99; 244 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 245 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 246 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 247 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 248 | GCC_WARN_UNUSED_VARIABLE = YES; 249 | INSTALL_PATH = "~/Library/Mail/Bundles"; 250 | MACOSX_DEPLOYMENT_TARGET = 10.8; 251 | SDKROOT = macosx; 252 | }; 253 | name = Release; 254 | }; 255 | 499015E91615FE5300991F6C /* Debug */ = { 256 | isa = XCBuildConfiguration; 257 | buildSettings = { 258 | CLANG_ENABLE_OBJC_ARC = YES; 259 | COMBINE_HIDPI_IMAGES = YES; 260 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 261 | GCC_PREFIX_HEADER = "GMailinator/GMailinator-Prefix.pch"; 262 | INFOPLIST_FILE = GMailinator/Info.plist; 263 | INSTALL_PATH = "$(HOME)/Library/Mail/Bundles"; 264 | PRODUCT_NAME = GMailinator; 265 | WRAPPER_EXTENSION = mailbundle; 266 | }; 267 | name = Debug; 268 | }; 269 | 499015EA1615FE5300991F6C /* Release */ = { 270 | isa = XCBuildConfiguration; 271 | buildSettings = { 272 | CLANG_ENABLE_OBJC_ARC = YES; 273 | COMBINE_HIDPI_IMAGES = YES; 274 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 275 | GCC_PREFIX_HEADER = "GMailinator/GMailinator-Prefix.pch"; 276 | INFOPLIST_FILE = GMailinator/Info.plist; 277 | INSTALL_PATH = "$(HOME)/Library/Mail/Bundles"; 278 | PRODUCT_NAME = GMailinator; 279 | WRAPPER_EXTENSION = mailbundle; 280 | }; 281 | name = Release; 282 | }; 283 | /* End XCBuildConfiguration section */ 284 | 285 | /* Begin XCConfigurationList section */ 286 | 499015D41615FE5300991F6C /* Build configuration list for PBXProject "GMailinator" */ = { 287 | isa = XCConfigurationList; 288 | buildConfigurations = ( 289 | 499015E61615FE5300991F6C /* Debug */, 290 | 499015E71615FE5300991F6C /* Release */, 291 | ); 292 | defaultConfigurationIsVisible = 0; 293 | defaultConfigurationName = Release; 294 | }; 295 | 499015E81615FE5300991F6C /* Build configuration list for PBXNativeTarget "GMailinator" */ = { 296 | isa = XCConfigurationList; 297 | buildConfigurations = ( 298 | 499015E91615FE5300991F6C /* Debug */, 299 | 499015EA1615FE5300991F6C /* Release */, 300 | ); 301 | defaultConfigurationIsVisible = 0; 302 | defaultConfigurationName = Release; 303 | }; 304 | /* End XCConfigurationList section */ 305 | }; 306 | rootObject = 499015D11615FE5300991F6C /* Project object */; 307 | } 308 | -------------------------------------------------------------------------------- /GMailinator.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /GMailinator/GMailinator-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'GMailinator' target in the 'GMailinator' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /GMailinator/GMailinator.h: -------------------------------------------------------------------------------- 1 | // 2 | // NoFlaggedMailbox.h 3 | // NoFlaggedMailbox 4 | // 5 | // Created by Eelco Lempsink on 28-09-12. 6 | // Copyright (c) 2012 Eelco Lempsink. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "SearchManager.h" 11 | 12 | @interface GMailinator : NSObject { 13 | SearchManager* sm; 14 | } 15 | 16 | + (void)registerBundle; 17 | @end 18 | 19 | NSBundle *GetGMailinatorBundle(void); -------------------------------------------------------------------------------- /GMailinator/GMailinator.m: -------------------------------------------------------------------------------- 1 | #import "GMailinator.h" 2 | #import 3 | #import 4 | 5 | NSBundle *GetGMailinatorBundle(void) 6 | { 7 | return [NSBundle bundleForClass:[GMailinator class]]; 8 | } 9 | 10 | @implementation GMailinator 11 | 12 | + (void)initialize { 13 | [GMailinator registerBundle]; 14 | SearchManager* sm = [[SearchManager alloc] init]; 15 | [sm setContextMenu: nil]; 16 | objc_setAssociatedObject(GetGMailinatorBundle(), @"searchManager", sm, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 17 | } 18 | 19 | + (void)load { 20 | // Add shortcuts to the mailbox list 21 | Class c = NSClassFromString(@"MailTableView"); 22 | SEL originalSelector = @selector(keyDown:); 23 | SEL overrideSelector = @selector(overrideMailKeyDown:); 24 | Method originalMethod = class_getInstanceMethod(c, originalSelector); 25 | Method overrideMethod = class_getInstanceMethod(self, overrideSelector); 26 | 27 | class_addMethod(c, overrideSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); 28 | class_replaceMethod(c, originalSelector, method_getImplementation(overrideMethod), method_getTypeEncoding(overrideMethod)); 29 | 30 | // Add shortcuts to the messages list 31 | c = NSClassFromString(@"MessagesTableView"); 32 | originalSelector = @selector(keyDown:); 33 | overrideSelector = @selector(overrideMessagesKeyDown:); 34 | originalMethod = class_getInstanceMethod(c, originalSelector); 35 | overrideMethod = class_getInstanceMethod(self, overrideSelector); 36 | 37 | class_addMethod(c, overrideSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); 38 | class_replaceMethod(c, originalSelector, method_getImplementation(overrideMethod), method_getTypeEncoding(overrideMethod)); 39 | 40 | // Add shortcuts to the messages list 41 | c = NSClassFromString(@"MessageViewer"); 42 | originalSelector = @selector(keyDown:); 43 | overrideSelector = @selector(overrideMessagesKeyDown:); 44 | originalMethod = class_getInstanceMethod(c, originalSelector); 45 | overrideMethod = class_getInstanceMethod(self, overrideSelector); 46 | 47 | class_addMethod(c, overrideSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); 48 | class_replaceMethod(c, originalSelector, method_getImplementation(overrideMethod), method_getTypeEncoding(overrideMethod)); 49 | } 50 | 51 | + (void)registerBundle 52 | { 53 | if(class_getClassMethod(NSClassFromString(@"MVMailBundle"), @selector(registerBundle))) 54 | [NSClassFromString(@"MVMailBundle") performSelector:@selector(registerBundle)]; 55 | 56 | //[[self class] load]; 57 | } 58 | 59 | 60 | - (void)overrideMailKeyDown:(NSEvent*)event { 61 | unichar key = [[event characters] characterAtIndex:0]; 62 | id messageViewer = [[self performSelector:@selector(delegate)] performSelector:@selector(delegate)]; 63 | 64 | switch (key) { 65 | case 'e': 66 | case 'y': { 67 | [messageViewer performSelector:@selector(archiveMessages:) withObject:nil]; 68 | break; 69 | } 70 | // case 'h': { 71 | // NSEvent *newEvent = [NSEvent eventWithCGEvent: CGEventCreateKeyboardEvent(NULL, 115, true)]; 72 | // [self overrideMailKeyDown: newEvent]; 73 | // break; 74 | // } 75 | // case 'l': { 76 | // NSEvent *newEvent = [NSEvent eventWithCGEvent: CGEventCreateKeyboardEvent(NULL, 119, true)]; 77 | // [self overrideMailKeyDown: newEvent]; 78 | // break; 79 | // } 80 | case 'k': { 81 | NSEvent *newEvent = [NSEvent eventWithCGEvent: CGEventCreateKeyboardEvent(NULL, 126, true)]; 82 | [self overrideMailKeyDown: newEvent]; 83 | break; 84 | } 85 | case 'K': { 86 | CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 126, true); 87 | CGEventSetFlags(cgEvent, kCGEventFlagMaskShift); 88 | NSEvent *newEvent = [NSEvent eventWithCGEvent: cgEvent]; 89 | [self overrideMailKeyDown: newEvent]; 90 | break; 91 | } 92 | case 'j': { 93 | NSEvent *newEvent = [NSEvent eventWithCGEvent: CGEventCreateKeyboardEvent(NULL, 125, true)]; 94 | [self overrideMailKeyDown: newEvent]; 95 | break; 96 | } 97 | case 'J': { 98 | CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 125, true); 99 | CGEventSetFlags(cgEvent, kCGEventFlagMaskShift); 100 | NSEvent *newEvent = [NSEvent eventWithCGEvent: cgEvent]; 101 | [self overrideMailKeyDown: newEvent]; 102 | break; 103 | } 104 | case '#': { 105 | [messageViewer performSelector:@selector(deleteMessages:) withObject:nil]; 106 | break; 107 | } 108 | case 'c': { 109 | [messageViewer performSelector:@selector(showComposeWindow:) withObject:nil]; 110 | break; 111 | } 112 | case 'r': { 113 | [messageViewer performSelector:@selector(replyMessage:) withObject:nil]; 114 | break; 115 | } 116 | case 'f': { 117 | [messageViewer performSelector:@selector(forwardMessage:) withObject:nil]; 118 | break; 119 | } 120 | case 'a': { 121 | [messageViewer performSelector:@selector(replyAllMessage:) withObject:nil]; 122 | break; 123 | } 124 | case '/': { 125 | CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 3, true); 126 | CGEventSetFlags(cgEvent, kCGEventFlagMaskCommand | kCGEventFlagMaskAlternate); 127 | NSEvent *newEvent = [NSEvent eventWithCGEvent: cgEvent]; 128 | [self overrideMailKeyDown: newEvent]; 129 | break; 130 | } 131 | default: 132 | [self overrideMailKeyDown:event]; 133 | break; 134 | 135 | } 136 | } 137 | 138 | - (void)overrideMessagesKeyDown:(NSEvent*)event { 139 | unichar key = [[event characters] characterAtIndex:0]; 140 | 141 | switch (key) { 142 | case 'e': 143 | case 'y': { 144 | [self performSelector:@selector(archiveMessages:) withObject:nil]; 145 | break; 146 | } 147 | // These don't work so well, but it looks like this is a Mail bug; the 148 | // menu option for Select next/previous message in conversation just jumps 149 | // to the next/previous thread instead. Also, it looks like capturing left/ 150 | // right doesn't work for MessageViewer for some reason. 151 | // case 'k': { 152 | // [self performSelector:@selector(selectNextInThread:) withObject:nil]; 153 | // break; 154 | // } 155 | // case 'j': { 156 | // [self performSelector:@selector(selectPreviousInThread:) withObject:nil]; 157 | // break; 158 | // } 159 | case '#': { 160 | [self performSelector:@selector(deleteMessages:) withObject:nil]; 161 | break; 162 | } 163 | case 'c': { 164 | [self performSelector:@selector(showComposeWindow:) withObject:nil]; 165 | break; 166 | } 167 | case 'r': { 168 | [self performSelector:@selector(replyMessage:) withObject:nil]; 169 | break; 170 | } 171 | case 'f': { 172 | [self performSelector:@selector(forwardMessage:) withObject:nil]; 173 | break; 174 | } 175 | case 'a': { 176 | [self performSelector:@selector(replyAllMessage:) withObject:nil]; 177 | break; 178 | } 179 | case '/': { 180 | CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 3, true); 181 | CGEventSetFlags(cgEvent, kCGEventFlagMaskCommand | kCGEventFlagMaskAlternate); 182 | NSEvent *newEvent = [NSEvent eventWithCGEvent: cgEvent]; 183 | [self overrideMessagesKeyDown: newEvent]; 184 | break; 185 | } 186 | default: 187 | [self overrideMessagesKeyDown:event]; 188 | break; 189 | 190 | } 191 | } 192 | 193 | @end 194 | -------------------------------------------------------------------------------- /GMailinator/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.nompute.gmailinator.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | NSPrincipalClass 24 | GMailinator 25 | NSHumanReadableCopyright 26 | Copyright © 2013 Michael Lai. All rights reserved. 27 | SupportedPluginCompatibilityUUIDs 28 | 29 | B61772F2-9975-4EC0-B22F-9A277C46ADD2 30 | 0A13A9ED-4864-4F07-AE70-60FB2F7EA63D 31 | DAFFB2B4-77BC-4C25-8CE1-2405E652D54B 32 | 2B98D2DD-000B-4521-83EB-7BFCB9B161C8 33 | 608CE00F-4576-4CAD-B362-F3CCB7DE8D67 34 | 1146A009-E373-4DB6-AB4D-47E59A7E50FD 35 | 2183B2CD-BEDF-4AA6-AC18-A1BBED2E3354 36 | 19B53E95-0964-4AAB-88F9-6D2F8B7B6037 37 | 0941BB9F-231F-452D-A26F-47A43863C991 38 | 04D6EC0A-52FF-4BBE-9896-C0B5FB851BBA 39 | FBE5B158-5602-4A6D-9CC5-8461B9B7054E 40 | 1CD40D64-945D-4D50-B12D-9CD865533506 41 | 88ED2D4C-D384-4BF5-8E94-B533455E6AAF 42 | F4C26776-22B3-4A0A-96E1-EA8E4482E0B5 43 | D1EFE124-86FF-4751-BF00-80B2C0D6F2E4 44 | 800E5C92-87D3-429B-8740-5C6183CD13EA 45 | 7C051997-F45A-4523-B053-2D262F94C775 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /GMailinator/Mail.h: -------------------------------------------------------------------------------- 1 | @interface MailApp : NSApplication 2 | {} 3 | - (NSArray *)accounts; 4 | @end 5 | 6 | @interface MailAccount : NSObject 7 | {} 8 | - (bool) isActive; 9 | - (NSArray *)mailboxes; 10 | @end 11 | 12 | @interface MailboxUid : NSObject 13 | {} 14 | - (MailboxUid *)parent; 15 | - (NSString *)displayName; 16 | @end 17 | -------------------------------------------------------------------------------- /GMailinator/NSArray+reverse.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSArray+reverse.h 3 | // Nostalgy4MailApp 4 | // 5 | // Created by Jelmer van der Linde on 25-08-12. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @interface NSArray (reverse) 12 | - (NSArray *)reversed; 13 | @end 14 | -------------------------------------------------------------------------------- /GMailinator/NSArray+reverse.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSArray+reverse.m 3 | // Nostalgy4MailApp 4 | // 5 | // Created by Jelmer van der Linde on 25-08-12. 6 | // 7 | // 8 | 9 | #import "NSArray+reverse.h" 10 | 11 | @implementation NSArray (reverse) 12 | 13 | - (NSArray *)reversed 14 | { 15 | return [[self reverseObjectEnumerator] allObjects]; 16 | } 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /GMailinator/Ranker.h: -------------------------------------------------------------------------------- 1 | // 2 | // Ranker.h 3 | // Nostalgy4MailApp 4 | // 5 | // Created by Jelmer van der Linde on 23-08-12. 6 | // 7 | // 8 | 9 | #ifndef __Nostalgy4MailApp__Ranker__ 10 | #define __Nostalgy4MailApp__Ranker__ 11 | 12 | #ifdef __cplusplus 13 | extern "C" { 14 | #endif 15 | 16 | bool is_subset (NSString *needle, NSString *haystack); 17 | 18 | double calculate_rank (NSString *left, NSString *right); 19 | 20 | #ifdef __cplusplus 21 | } 22 | #endif 23 | 24 | #endif /* defined(__Nostalgy4MailApp__Ranker__) */ 25 | -------------------------------------------------------------------------------- /GMailinator/Ranker.mm: -------------------------------------------------------------------------------- 1 | // 2 | // Ranker.cpp 3 | // Nostalgy4MailApp 4 | // 5 | // Created by Jelmer van der Linde on 23-08-12. 6 | // Taken from https://github.com/textmate/textmate/blob/master/Frameworks/text/src/ranker.cc 7 | // 8 | // 9 | 10 | #include 11 | 12 | #include "Ranker.h" 13 | 14 | bool is_subset (NSString *needle, NSString *haystack) 15 | { 16 | NSInteger n = 0, m = 0; 17 | char const *needle_chars = [needle UTF8String]; 18 | char const *haystack_chars = [haystack UTF8String]; 19 | 20 | while (n < [needle length] && m < [haystack length]) 21 | { 22 | if(needle_chars[n] == haystack_chars[m] || toupper(needle_chars[n]) == haystack_chars[m]) 23 | ++n; 24 | ++m; 25 | } 26 | 27 | return n == [needle length]; 28 | } 29 | 30 | double calculate_rank (NSString *left, NSString *right) 31 | { 32 | size_t const n = [left length]; 33 | size_t const m = [right length]; 34 | 35 | char const *lhs = [left UTF8String]; 36 | char const *rhs = [right UTF8String]; 37 | 38 | size_t matrix[n][m], first[n], last[n]; 39 | bool capitals[m]; 40 | bzero(matrix, sizeof(matrix)); 41 | std::fill_n(&first[0], n, m); 42 | std::fill_n(&last[0], n, 0); 43 | 44 | bool at_bow = true; 45 | for(size_t j = 0; j < m; ++j) 46 | { 47 | char ch = rhs[j]; 48 | capitals[j] = (at_bow && isalnum(ch)) || isupper(ch); 49 | at_bow = !isalnum(ch) && ch != '\'' && ch != '.'; 50 | } 51 | 52 | for(size_t i = 0; i < n; ++i) 53 | { 54 | size_t j = i == 0 ? 0 : first[i-1] + 1; 55 | for(; j < m; ++j) 56 | { 57 | if(tolower(lhs[i]) == tolower(rhs[j])) 58 | { 59 | matrix[i][j] = i == 0 || j == 0 ? 1 : matrix[i-1][j-1] + 1; 60 | first[i] = std::min(j, first[i]); 61 | last[i] = std::max(j+1, last[i]); 62 | } 63 | } 64 | } 65 | 66 | for(ssize_t i = n-1; i > 0; --i) 67 | { 68 | size_t bound = last[i]-1; 69 | if(bound < last[i-1]) 70 | { 71 | while(first[i-1] < bound && matrix[i-1][bound-1] == 0) 72 | --bound; 73 | last[i-1] = bound; 74 | } 75 | } 76 | 77 | for(ssize_t i = n-1; i > 0; --i) 78 | { 79 | for(size_t j = first[i]; j < last[i]; ++j) 80 | { 81 | if(matrix[i][j] && matrix[i-1][j-1]) 82 | matrix[i-1][j-1] = matrix[i][j]; 83 | } 84 | } 85 | 86 | for(size_t i = 0; i < n; ++i) 87 | { 88 | for(size_t j = first[i]; j < last[i]; ++j) 89 | { 90 | if(matrix[i][j] > 1 && i+1 < n && j+1 < m) 91 | matrix[i+1][j+1] = matrix[i][j] - 1; 92 | } 93 | } 94 | 95 | // ========================= 96 | // = Greedy walk of Matrix = 97 | // ========================= 98 | 99 | size_t capitalsTouched = 0; // 0-n 100 | size_t substrings = 0; // 1-n 101 | size_t prefixSize = 0; // 0-m 102 | 103 | size_t i = 0; 104 | while(i < n) 105 | { 106 | size_t bestJIndex = 0; 107 | size_t bestJLength = 0; 108 | for(size_t j = first[i]; j < last[i]; ++j) 109 | { 110 | if(matrix[i][j] && capitals[j]) 111 | { 112 | bestJIndex = j; 113 | bestJLength = matrix[i][j]; 114 | 115 | for(size_t k = j; k < j + bestJLength; ++k) 116 | capitalsTouched += capitals[k] ? 1 : 0; 117 | 118 | break; 119 | } 120 | else if(bestJLength < matrix[i][j]) 121 | { 122 | bestJIndex = j; 123 | bestJLength = matrix[i][j]; 124 | } 125 | } 126 | 127 | if(i == 0) 128 | prefixSize = bestJIndex; 129 | 130 | size_t len = 0; 131 | bool foundCapital = false; 132 | do { 133 | 134 | ++i; ++len; 135 | first[i] = std::max(bestJIndex + len, first[i]); 136 | if(len < bestJLength && n < 4) 137 | { 138 | if(capitals[first[i]]) 139 | continue; 140 | 141 | for(size_t j = first[i]; j < last[i] && !foundCapital; ++j) 142 | { 143 | if(matrix[i][j] && capitals[j]) 144 | foundCapital = true; 145 | } 146 | } 147 | 148 | } while(len < bestJLength && !foundCapital); 149 | 150 | ++substrings; 151 | } 152 | 153 | // ================================ 154 | // = Calculate rank based on walk = 155 | // ================================ 156 | 157 | size_t totalCapitals = std::count(&capitals[0], &capitals[0] + m, true); 158 | double score = 0.0; 159 | double denom = n*(n+1) + 1; 160 | if(n == capitalsTouched) 161 | { 162 | score = (denom - 1) / denom; 163 | } 164 | else 165 | { 166 | double subtract = substrings * n + (n - capitalsTouched); 167 | score = (denom - subtract) / denom; 168 | } 169 | score += (m - prefixSize) / (double)m / (2*denom); 170 | score += capitalsTouched / (double)totalCapitals / (4*denom); 171 | score += n / (double)m / (8*denom); 172 | 173 | return score; 174 | } 175 | 176 | -------------------------------------------------------------------------------- /GMailinator/SearchManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // SearchManager.h 3 | // Nostalgy4MailApp 4 | // 5 | // Created by Hajo Nils Krabbenhöft on 30.05.09. 6 | // Copyright 2009 Hajo Nils KrabbenhöftHajo Nils Krabbenhöft. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "SearchPopup.h" 11 | 12 | @interface SearchManager : NSObject { 13 | #if 0 14 | IBOutlet NSMenu* contextMenu; 15 | #endif 16 | NSMenuItem* submenuMove; 17 | NSMenuItem* submenuCopy; 18 | NSString* lastFolder; 19 | SearchPopup* sp; 20 | 21 | IBOutlet NSMenuItem* menuitemLastMove; 22 | IBOutlet NSMenuItem* menuitemLastCopy; 23 | } 24 | 25 | - (void) setContextMenu:(NSMenu *)menu; 26 | 27 | - (IBAction)moveToFolder: sender; 28 | - (IBAction)copyToFolder: sender; 29 | - (IBAction)moveToLastFolder: sender; 30 | - (IBAction)copyToLastFolder: sender; 31 | 32 | - (void)invokeLastFolder:(NSMenu*) submenu; 33 | - (void) setLastFolder: (NSString*) folder; 34 | - (NSString*) lastFolder; 35 | 36 | - (NSMenuItem*) dbgSubmenuMove; 37 | - (NSMenuItem*) dbgSubmenuCopy; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /GMailinator/SearchManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // SearchManager.m 3 | // GMailinator 4 | // 5 | // Created by Hajo Nils Krabbenhöft on 30.05.09. 6 | // Copyright 2009 Hajo Nils Krabbenhöft. All rights reserved. 7 | // Modifications by Michael Lai, 2013 8 | // 9 | 10 | #import "SearchManager.h" 11 | 12 | @implementation SearchManager 13 | 14 | 15 | - (IBAction)moveToFolder: sender { 16 | NSMenu* menu = [submenuMove submenu]; 17 | [[self popupWithSubmenu: menu ] showWithSender:sender andTitle:@"Move to folder..."]; 18 | } 19 | 20 | - (IBAction)moveToLastFolder: sender { 21 | NSMenu* menu = [submenuMove submenu]; 22 | [self invokeLastFolder: menu]; 23 | } 24 | 25 | - (IBAction)copyToFolder: sender { 26 | NSMenu* menu = [submenuCopy submenu]; 27 | [[self popupWithSubmenu: menu ] showWithSender:sender andTitle:@"Copy to folder..."]; 28 | } 29 | 30 | - (IBAction)copyToLastFolder: sender { 31 | NSMenu* menu = [submenuCopy submenu]; 32 | [self invokeLastFolder: menu]; 33 | } 34 | 35 | - (void)invokeLastFolder:(NSMenu*) submenu { 36 | if( [submenu numberOfItems] == 0 ) [[submenu delegate] menuNeedsUpdate: submenu ]; 37 | [submenu update]; 38 | 39 | NSArray *items = [submenu itemArray]; 40 | for(int iI = 0; iI < [items count]; iI++){ 41 | NSMenuItem* menuItem = [items objectAtIndex:iI]; 42 | if(! [menuItem isEnabled] ) continue; 43 | 44 | NSString* title = [menuItem title]; 45 | if( [title isEqualToString: lastFolder] ) { 46 | [submenu performActionForItemAtIndex: iI ]; 47 | return; 48 | } 49 | } 50 | } 51 | 52 | - (id)popupWithSubmenu:(NSMenu *)submenu 53 | { 54 | if (!sp) { 55 | SearchPopup* popup = [[SearchPopup alloc] init]; 56 | sp = popup; 57 | } 58 | 59 | [sp setSubmenu: submenu]; 60 | [NSBundle loadNibNamed: @"SearchPopup" owner:sp]; 61 | 62 | return sp; 63 | } 64 | 65 | - (NSMenuItem *) newMenuItemWithTitle:(NSString *)title action:(SEL)action andKeyEquivalent:(NSString *)keyEquivalent inMenu:(NSMenu *)menu withTitle:(NSString*)searchTitle offset:(int)offset 66 | // Taken from /System/Developer/Examples/EnterpriseObjects/AppKit/ModelerBundle/EOUtil.m 67 | { 68 | // Simple utility category which adds a new menu item with title, action 69 | // and keyEquivalent to menu (or one of its submenus) under that item with 70 | // selector as its action. Returns the new addition or nil if no such 71 | // item could be found. 72 | 73 | NSMenuItem *menuItem; 74 | NSArray *items = [menu itemArray]; 75 | int iI; 76 | 77 | if(!keyEquivalent) { 78 | keyEquivalent = @""; 79 | } 80 | 81 | for (iI = 0; iI < [items count]; iI++) { 82 | menuItem = [items objectAtIndex:iI]; 83 | 84 | if ([[menuItem title] isEqualToString:searchTitle]) { 85 | return ([menu insertItemWithTitle:title action:action keyEquivalent:keyEquivalent atIndex:iI + offset]); 86 | } 87 | else if([[menuItem target] isKindOfClass:[NSMenu class]]) { 88 | menuItem = [self newMenuItemWithTitle:title action:action andKeyEquivalent:keyEquivalent inMenu:[menuItem target] withTitle:searchTitle offset:offset]; 89 | if (menuItem) return menuItem; 90 | } 91 | } 92 | 93 | return nil; 94 | } 95 | 96 | - (void) setContextMenu:(NSMenu *)menu { 97 | 98 | NSMenuItem* moveFolderItem = [self newMenuItemWithTitle: @"Move to Folder..." action: @selector(moveToFolder:) andKeyEquivalent: @"l" inMenu: [[NSApplication sharedApplication] mainMenu] withTitle: @"Move Again" offset: 1]; 99 | [moveFolderItem setTarget: self]; 100 | [moveFolderItem setKeyEquivalentModifierMask: 0]; 101 | 102 | NSMenu* messagesMenu = [moveFolderItem menu]; 103 | NSArray *items = [messagesMenu itemArray]; 104 | for (int iI = 0; iI < [items count]; iI++) { 105 | NSMenuItem* menuItem = [items objectAtIndex:iI]; 106 | 107 | if ([menuItem hasSubmenu]) { 108 | if([menuItem tag] == 105) submenuMove = menuItem; 109 | if([menuItem tag] == 106) submenuCopy = menuItem; 110 | } 111 | } 112 | } 113 | 114 | - (void) setLastFolder: (NSString*) folder { 115 | lastFolder = folder; 116 | [menuitemLastMove setHidden:FALSE]; 117 | [menuitemLastMove setTitle: [NSString stringWithFormat:@"Move selected messages to \"%@\"", folder]]; 118 | [menuitemLastCopy setHidden:FALSE]; 119 | [menuitemLastCopy setTitle: [NSString stringWithFormat:@"Copy selected messages to \"%@\"", folder]]; 120 | } 121 | 122 | - (NSString*) lastFolder { 123 | return lastFolder; 124 | } 125 | 126 | - (NSMenuItem*) dbgSubmenuMove { 127 | return submenuMove; 128 | } 129 | - (NSMenuItem*) dbgSubmenuCopy{ 130 | return submenuCopy; 131 | } 132 | 133 | 134 | @end 135 | -------------------------------------------------------------------------------- /GMailinator/SearchPopup.h: -------------------------------------------------------------------------------- 1 | // 2 | // SearchPopup.h 3 | // Nostalgy4MailApp 4 | // 5 | // Created by Hajo Nils Krabbenhöft on 30.05.09. 6 | // Copyright 2009 Hajo Nils Krabbenhöft. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SearchPopup : NSObject { 12 | IBOutlet NSSearchField* searchField; 13 | IBOutlet NSWindow* searchWindow; 14 | NSMenu* submenu; 15 | NSMutableArray* currentResults; 16 | NSDictionary* selectedResult; 17 | IBOutlet NSTableView* resultViewer; 18 | } 19 | 20 | //+ (id)popupWithSubmenu:(NSMenu *)submenu andParent:(SearchManager*) parent; 21 | - (id)init; 22 | - (void)setSubmenu:(NSMenu*) sm; 23 | - (void)addMenu:(NSMenu *)menu toDictionary:(NSMutableDictionary*)dict withPath:(NSMutableArray *)array atLevel:(int)level; 24 | - (void)showWithSender: sender andTitle:(NSString *)title; 25 | - (IBAction)doSearch: sender; 26 | - (IBAction)changeSelection: sender; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /GMailinator/SearchPopup.m: -------------------------------------------------------------------------------- 1 | // 2 | // SearchPopup.m 3 | // Nostalgy4MailApp 4 | // 5 | // Created by Hajo Nils Krabbenhöft on 30.05.09. 6 | // Copyright 2009 Hajo Nils Krabbenhöft. All rights reserved. 7 | // 8 | 9 | #import "SearchPopup.h" 10 | #import "Ranker.h" 11 | 12 | @implementation SearchPopup 13 | 14 | - (void)setSubmenu:(NSMenu*)sm { 15 | submenu = sm; 16 | } 17 | 18 | - (void)addMenu:(NSMenu *)menu toDictionary:(NSMutableDictionary *)dict withPath:(NSMutableArray *)path atLevel:(int)depth 19 | { 20 | NSArray *items = [menu itemArray]; 21 | 22 | for (int i = 0; i < [items count]; ++i) 23 | { 24 | NSMenuItem *menuItem = [items objectAtIndex:i]; 25 | long level = depth + [menuItem indentationLevel]; 26 | 27 | // Remove items below this item. 28 | for (long l = [path count] - 1; l > level; --l) 29 | [path removeObjectAtIndex:l]; 30 | 31 | // Add this menu item to the path at the appropriate level. 32 | [path setObject:[menuItem title] atIndexedSubscript:level]; 33 | NSString *key = [path componentsJoinedByString:@"/"]; 34 | 35 | if ([menuItem isEnabled]) 36 | [dict setObject:menuItem forKey:key]; 37 | 38 | if ([menuItem hasSubmenu]) 39 | [self addMenu:[menuItem submenu] 40 | toDictionary:dict 41 | withPath:path 42 | atLevel:level + 1]; 43 | } 44 | } 45 | 46 | - (id) init 47 | { 48 | self = [super init]; 49 | 50 | currentResults = [NSMutableArray array]; 51 | //[currentResults retain]; // Is to nessecary? 52 | 53 | return self; 54 | } 55 | 56 | - (void)showWithSender: sender andTitle: (NSString *)title { 57 | 58 | // update menu items 59 | [[submenu delegate] menuNeedsUpdate: submenu ]; 60 | // set message handling to copy / move 61 | //[submenu _sendMenuOpeningNotification]; 62 | [submenu performSelector:@selector(_sendMenuOpeningNotification)]; 63 | 64 | // if ([p lastFolder] != nil) 65 | // { 66 | // [searchField setStringValue: [p lastFolder]]; 67 | // [self doSearch: sender]; 68 | // [searchField selectText: sender]; 69 | // } 70 | 71 | [searchWindow setTitle:title]; 72 | [searchWindow makeKeyAndOrderFront: sender]; 73 | [searchField becomeFirstResponder]; 74 | } 75 | 76 | NSInteger compareMatch(id l_row, id r_row, void *query) 77 | { 78 | double l_rank = calculate_rank( 79 | (NSString *)[(NSDictionary*)l_row objectForKey:@"path"], 80 | (__bridge NSString *)query); 81 | 82 | double r_rank = calculate_rank( 83 | (NSString *)[(NSDictionary*)r_row objectForKey:@"path"], 84 | (__bridge NSString *)query); 85 | 86 | if (l_rank == r_rank) 87 | return NSOrderedSame; 88 | else 89 | return l_rank > r_rank 90 | ? NSOrderedAscending 91 | : NSOrderedDescending; 92 | } 93 | 94 | - (IBAction)doSearch: sender 95 | { 96 | NSString *searchString = [searchField stringValue]; 97 | 98 | // Clear all previous results. 99 | [currentResults removeAllObjects]; 100 | 101 | // All the available folders will be collected in here. 102 | NSMutableDictionary *folders = [NSMutableDictionary dictionary]; 103 | 104 | // And we will use this array as a sort of stack to keep track of the path 105 | // of each folder, which we use to generate the path string. 106 | NSMutableArray *path = [NSMutableArray array]; 107 | 108 | [submenu update]; 109 | [self addMenu:submenu toDictionary:folders withPath:path atLevel:0]; 110 | 111 | // Filter the folders to only include folders that somehow match. 112 | for (NSString *path in folders) 113 | { 114 | if ([searchString length] == 0 || is_subset(searchString, path)) 115 | [currentResults addObject:[NSDictionary dictionaryWithObjectsAndKeys: 116 | path, @"path", 117 | [folders objectForKey:path], @"menuItem", 118 | nil]]; 119 | } 120 | 121 | // Sort the folders according to how well they match with the search string. 122 | [currentResults sortUsingFunction:compareMatch context: (__bridge void *)(searchString)]; 123 | 124 | selectedResult = [currentResults count] > 0 125 | ? [currentResults objectAtIndex:0] 126 | : nil; 127 | 128 | [resultViewer reloadData]; 129 | } 130 | 131 | - (BOOL)control:(NSControl*)control textView:(NSTextView*)textView doCommandBySelector:(SEL)commandSelector 132 | { 133 | BOOL result = NO; 134 | if (commandSelector == @selector(insertNewline:)) 135 | { 136 | if (selectedResult != nil) 137 | { 138 | // [parent setLastFolder: selectedResult objectAtIndex:0]]; 139 | NSMenuItem *menuItem = [selectedResult objectForKey:@"menuItem"]; 140 | [[menuItem menu] performActionForItemAtIndex:[[menuItem menu] indexOfItem:menuItem]]; 141 | } 142 | 143 | [searchWindow orderOut:nil]; 144 | result = YES; 145 | } 146 | else if (commandSelector == @selector(cancelOperation:)) 147 | { 148 | [searchWindow orderOut:nil]; 149 | result = YES; 150 | } 151 | else if (commandSelector == @selector(moveUp:)) 152 | { 153 | if (selectedResult != nil) 154 | { 155 | long index = [currentResults indexOfObject:selectedResult] - 1; 156 | 157 | if (index < 0) 158 | index = 0; 159 | 160 | selectedResult = [currentResults objectAtIndex:index]; 161 | [resultViewer selectRowIndexes:[NSIndexSet indexSetWithIndex:index] byExtendingSelection:FALSE]; 162 | [resultViewer scrollRowToVisible:index]; 163 | } 164 | result = YES; 165 | } 166 | else if (commandSelector == @selector(moveDown:)) 167 | { 168 | if (selectedResult != nil) 169 | { 170 | long index = [currentResults indexOfObject: selectedResult] + 1; 171 | 172 | if (index >= [currentResults count]) 173 | index = [currentResults count] - 1; 174 | 175 | selectedResult = [currentResults objectAtIndex:index]; 176 | [resultViewer selectRowIndexes:[NSIndexSet indexSetWithIndex:index] byExtendingSelection:FALSE]; 177 | [resultViewer scrollRowToVisible:index]; 178 | } 179 | result = YES; 180 | } 181 | 182 | return result; 183 | } 184 | 185 | - (id)tableView:(NSTableView *)table objectValueForTableColumn:(NSTableColumn *)column row:(int)rowIndex 186 | { 187 | NSParameterAssert(rowIndex >= 0 && rowIndex < [currentResults count]); 188 | NSDictionary* row = [currentResults objectAtIndex:rowIndex]; 189 | 190 | if ([[column identifier] isEqualToString: @"image"]) 191 | return [[row objectForKey:@"menuItem"] image]; 192 | 193 | if ([[column identifier] isEqualToString: @"title"]) 194 | return [row objectForKey:@"path"]; 195 | 196 | return 0; 197 | } 198 | 199 | - (long)numberOfRowsInTableView:(NSTableView *)aTableView 200 | { 201 | return [currentResults count]; 202 | } 203 | 204 | - (IBAction)changeSelection:sender 205 | { 206 | long i = [resultViewer selectedRow]; 207 | 208 | if (i >= 0 && i < [currentResults count]) 209 | selectedResult = [currentResults objectAtIndex: i]; 210 | else 211 | selectedResult = nil; 212 | } 213 | 214 | @end 215 | -------------------------------------------------------------------------------- /GMailinator/SearchPopup.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1050 5 | 12E55 6 | 3084 7 | 1187.39 8 | 626.00 9 | 10 | com.apple.InterfaceBuilder.CocoaPlugin 11 | 3084 12 | 13 | 14 | YES 15 | NSCustomObject 16 | NSImageCell 17 | NSScrollView 18 | NSScroller 19 | NSSearchField 20 | NSSearchFieldCell 21 | NSTableColumn 22 | NSTableView 23 | NSTextFieldCell 24 | NSView 25 | NSWindowTemplate 26 | 27 | 28 | YES 29 | com.apple.InterfaceBuilder.CocoaPlugin 30 | 31 | 32 | PluginDependencyRecalculationVersion 33 | 34 | 35 | 36 | YES 37 | 38 | SearchPopup 39 | 40 | 41 | FirstResponder 42 | 43 | 44 | NSApplication 45 | 46 | 47 | 81 48 | 2 49 | {{673, 431}, {249, 307}} 50 | 1685586944 51 | Search Mailbox 52 | NSPanel 53 | 54 | 55 | 56 | 57 | 256 58 | 59 | YES 60 | 61 | 62 | 266 63 | {{20, 265}, {209, 22}} 64 | 65 | 66 | 67 | YES 68 | 69 | 342884417 70 | 268436480 71 | 72 | 73 | LucidaGrande 74 | 13 75 | 1044 76 | 77 | 78 | YES 79 | 1 80 | 81 | 6 82 | System 83 | textBackgroundColor 84 | 85 | 3 86 | MQA 87 | 88 | 89 | 90 | 6 91 | System 92 | controlTextColor 93 | 94 | 3 95 | MAA 96 | 97 | 98 | 99 | 0 100 | 0 101 | search 102 | 103 | _searchFieldSearch: 104 | 105 | 138690560 106 | 0 107 | 108 | 400 109 | 75 110 | 111 | 112 | 0 113 | 0 114 | clear 115 | 116 | YES 117 | 118 | YES 119 | 120 | YES 121 | AXDescription 122 | NSAccessibilityEncodedAttributesValueType 123 | 124 | 125 | YES 126 | cancel 127 | 128 | 129 | 130 | 131 | 132 | _searchFieldCancel: 133 | 134 | 138690560 135 | 0 136 | 137 | 400 138 | 75 139 | 140 | 255 141 | CAAAAA 142 | 143 | NO 144 | 145 | 146 | 147 | 274 148 | 149 | YES 150 | 151 | 152 | 2304 153 | 154 | YES 155 | 156 | 157 | 256 158 | {207, 228} 159 | 160 | 161 | 162 | YES 163 | NO 164 | YES 165 | 166 | 167 | -2147483392 168 | {{209, 0}, {16, 17}} 169 | 170 | 171 | YES 172 | 173 | image 174 | 40 175 | 40 176 | 1000 177 | 178 | 75497536 179 | 201328640 180 | Image 181 | 182 | LucidaGrande 183 | 11 184 | 3100 185 | 186 | 187 | 3 188 | MC4zMzMzMzI5OQA 189 | 190 | 191 | 6 192 | System 193 | headerTextColor 194 | 195 | 196 | 197 | 198 | 134217728 199 | 33685504 200 | 0 201 | 0 202 | 0 203 | NO 204 | 205 | 206 | 207 | 208 | title 209 | 161 210 | 40 211 | 1000 212 | 213 | 75497536 214 | 2048 215 | Title 216 | 217 | 218 | 219 | 220 | 221 | 337641536 222 | 2048 223 | Text Cell 224 | 225 | 226 | 227 | 6 228 | System 229 | controlBackgroundColor 230 | 231 | 3 232 | MC42NjY2NjY2NjY3AA 233 | 234 | 235 | 236 | 237 | 1 238 | YES 239 | 240 | 241 | 242 | 3 243 | 2 244 | 245 | 246 | 6 247 | System 248 | gridColor 249 | 250 | 3 251 | MC41AA 252 | 253 | 254 | 17 255 | 37748736 256 | 257 | 258 | 4 259 | 15 260 | 0 261 | NO 262 | 0 263 | 1 264 | 265 | 266 | {{1, 1}, {207, 228}} 267 | 268 | 269 | 270 | 271 | 272 | 4 273 | 274 | 275 | 276 | -2147483392 277 | {{209, 1}, {15, 126}} 278 | 279 | 280 | NO 281 | 282 | _doScroller: 283 | 0.99212599999999995 284 | 285 | 286 | 287 | -2147483392 288 | {{-100, -100}, {208, 15}} 289 | 290 | 291 | 292 | NO 293 | 1 294 | 295 | _doScroller: 296 | 0.81632660000000001 297 | 298 | 299 | {{20, 20}, {209, 230}} 300 | 301 | 302 | 303 | 133650 304 | 305 | 306 | 307 | QSAAAEEgAABBmAAAQZgAAA 308 | 0.25 309 | 4 310 | 1 311 | 312 | 313 | {249, 307} 314 | 315 | 316 | 317 | 318 | {{0, 0}, {1920, 1178}} 319 | {10000000000000, 10000000000000} 320 | YES 321 | 322 | 323 | 324 | 325 | YES 326 | 327 | 328 | searchField 329 | 330 | 331 | 332 | 7 333 | 334 | 335 | 336 | searchWindow 337 | 338 | 339 | 340 | 8 341 | 342 | 343 | 344 | doSearch: 345 | 346 | 347 | 348 | 9 349 | 350 | 351 | 352 | resultViewer 353 | 354 | 355 | 356 | 33 357 | 358 | 359 | 360 | changeSelection: 361 | 362 | 363 | 364 | 34 365 | 366 | 367 | 368 | delegate 369 | 370 | 371 | 372 | 10 373 | 374 | 375 | 376 | dataSource 377 | 378 | 379 | 380 | 32 381 | 382 | 383 | 384 | 385 | YES 386 | 387 | 0 388 | 389 | YES 390 | 391 | 392 | 393 | 394 | 395 | -2 396 | 397 | 398 | File's Owner 399 | 400 | 401 | -1 402 | 403 | 404 | First Responder 405 | 406 | 407 | -3 408 | 409 | 410 | Application 411 | 412 | 413 | 1 414 | 415 | 416 | YES 417 | 418 | 419 | 420 | 421 | 422 | 2 423 | 424 | 425 | YES 426 | 427 | 428 | 429 | 430 | 431 | 432 | 4 433 | 434 | 435 | YES 436 | 437 | 438 | 439 | 440 | 441 | 5 442 | 443 | 444 | 445 | 446 | 20 447 | 448 | 449 | YES 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 21 458 | 459 | 460 | 461 | 462 | 22 463 | 464 | 465 | 466 | 467 | 23 468 | 469 | 470 | YES 471 | 472 | 473 | 474 | 475 | 476 | 477 | 25 478 | 479 | 480 | YES 481 | 482 | 483 | 484 | 485 | 486 | 26 487 | 488 | 489 | YES 490 | 491 | 492 | 493 | 494 | 495 | 27 496 | 497 | 498 | 499 | 500 | 29 501 | 502 | 503 | 504 | 505 | 506 | 507 | YES 508 | 509 | YES 510 | -1.IBPluginDependency 511 | -2.IBPluginDependency 512 | -3.IBPluginDependency 513 | 1.IBNSWindowAutoPositionCentersHorizontal 514 | 1.IBNSWindowAutoPositionCentersVertical 515 | 1.IBPluginDependency 516 | 1.IBWindowTemplateEditedContentRect 517 | 1.NSWindowTemplate.visibleAtLaunch 518 | 2.IBPluginDependency 519 | 2.IBViewIntegration.shadowBlurRadius 520 | 2.IBViewIntegration.shadowColor 521 | 2.IBViewIntegration.shadowOffsetHeight 522 | 2.IBViewIntegration.shadowOffsetWidth 523 | 20.IBPluginDependency 524 | 21.IBPluginDependency 525 | 22.IBPluginDependency 526 | 23.IBPluginDependency 527 | 23.IBViewIntegration.shadowBlurRadius 528 | 23.IBViewIntegration.shadowColor 529 | 23.IBViewIntegration.shadowOffsetHeight 530 | 23.IBViewIntegration.shadowOffsetWidth 531 | 25.IBPluginDependency 532 | 26.IBPluginDependency 533 | 27.IBPluginDependency 534 | 29.IBPluginDependency 535 | 4.IBPluginDependency 536 | 5.IBPluginDependency 537 | 538 | 539 | YES 540 | com.apple.InterfaceBuilder.CocoaPlugin 541 | com.apple.InterfaceBuilder.CocoaPlugin 542 | com.apple.InterfaceBuilder.CocoaPlugin 543 | 544 | 545 | com.apple.InterfaceBuilder.CocoaPlugin 546 | {{703, 547}, {265, 198}} 547 | 548 | com.apple.InterfaceBuilder.CocoaPlugin 549 | 550 | 551 | 552 | 553 | com.apple.InterfaceBuilder.CocoaPlugin 554 | com.apple.InterfaceBuilder.CocoaPlugin 555 | com.apple.InterfaceBuilder.CocoaPlugin 556 | com.apple.InterfaceBuilder.CocoaPlugin 557 | 558 | 559 | 560 | 561 | com.apple.InterfaceBuilder.CocoaPlugin 562 | com.apple.InterfaceBuilder.CocoaPlugin 563 | com.apple.InterfaceBuilder.CocoaPlugin 564 | com.apple.InterfaceBuilder.CocoaPlugin 565 | com.apple.InterfaceBuilder.CocoaPlugin 566 | com.apple.InterfaceBuilder.CocoaPlugin 567 | 568 | 569 | 570 | YES 571 | 572 | 573 | 574 | 575 | 576 | YES 577 | 578 | 579 | 580 | 581 | 36 582 | 583 | 584 | 585 | YES 586 | 587 | SearchPopup 588 | NSObject 589 | 590 | YES 591 | 592 | YES 593 | changeSelection: 594 | doSearch: 595 | 596 | 597 | YES 598 | id 599 | id 600 | 601 | 602 | 603 | YES 604 | 605 | YES 606 | changeSelection: 607 | doSearch: 608 | 609 | 610 | YES 611 | 612 | changeSelection: 613 | id 614 | 615 | 616 | doSearch: 617 | id 618 | 619 | 620 | 621 | 622 | YES 623 | 624 | YES 625 | resultViewer 626 | searchField 627 | searchWindow 628 | 629 | 630 | YES 631 | NSTableView 632 | NSSearchField 633 | NSWindow 634 | 635 | 636 | 637 | YES 638 | 639 | YES 640 | resultViewer 641 | searchField 642 | searchWindow 643 | 644 | 645 | YES 646 | 647 | resultViewer 648 | NSTableView 649 | 650 | 651 | searchField 652 | NSSearchField 653 | 654 | 655 | searchWindow 656 | NSWindow 657 | 658 | 659 | 660 | 661 | IBProjectSource 662 | ./Classes/SearchPopup.h 663 | 664 | 665 | 666 | 667 | 0 668 | IBCocoaFramework 669 | 670 | com.apple.InterfaceBuilder.CocoaPlugin.macosx 671 | 672 | 673 | 674 | com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3 675 | 676 | 677 | YES 678 | 3 679 | 680 | 681 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GMailinator 2 | 3 | Adds Gmail-esque keyboard shorcuts to Mail.app. This is still very much a work 4 | in progress. Tested with Mail for OS X 10.8.4. 5 | 6 | ## Supported Shortcuts 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
KeyAction
cCompose new message
rReply
aReply All
y, eArchive
#Delete
jGo to previous message/thread
kGo to next message/thread
/Mailbox search
lMove to folder (opens dialog)
20 | 21 | ## How to install 22 | 23 | 1. Grab the latest build from the builds/ directory, and unzip to ~/Library/Mail/Bundles 24 | 2. Enable Mail.app plugins: 25 | `defaults write com.apple.mail EnableBundles -bool true` 26 | 27 | ## How to build 28 | 29 | 1. Load up the project in Xcode. 30 | 2. Run the build, this should automatically create ~/Library/Mail/Bundles (but you may need to create this). 31 | 3. Enable Mail.app plugins: 32 | `defaults write com.apple.mail EnableBundles -bool true` 33 | 4. Relaunch Mail. 34 | 35 | ## Credits 36 | 37 | A lot of this was built with heavy use of of the 38 | [BindDeleteKeyToArchive](https://github.com/benlenarts/BindDeleteKeyToArchive) 39 | project by Ben Lenarts. The Xcode project and interface skeleton were 40 | all from that project, and for the most part, renamed. I added the keybinding code. 41 | 42 | A lot of the code is also either copied in whole, or modified from the 43 | Nostalgy4Mail.app project, by [Hajo Nils 44 | Krabbenhöft](https://github.com/fxtentacle/Nostalgy-4-Mail.app), and 45 | subsequently by [Jelmer van der 46 | Linde](https://github.com/jelmervdl/Nostalgy-4-Mail.app). I've added support 47 | for ARC (turns out there were quite a few leaks), and prettied-up (imho) the 48 | move-to-folder dialog. 49 | 50 | Other references: 51 | 52 | - [Rui Carmo's PyObjC vim keybinding script](http://taoofmac.com/space/blog/2011/08/13/2110) 53 | -------------------------------------------------------------------------------- /find_uuid.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | defaults read /Applications/Mail.app/Contents/Info PluginCompatibilityUUID 3 | --------------------------------------------------------------------------------