diff --git a/.gitignore b/.gitignore index e9f3e7b..051b3c1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .DS_Store xcuserdata/ +GMailinator.xcodeproj/project.xcworkspace/xcshareddata/ \ No newline at end of file diff --git a/GMailinator.mailbundle.zip b/GMailinator.mailbundle.zip new file mode 100644 index 0000000..8d732dd Binary files /dev/null and b/GMailinator.mailbundle.zip differ diff --git a/GMailinator.xcodeproj/project.pbxproj b/GMailinator.xcodeproj/project.pbxproj index 42fe4dc..bd27f59 100644 --- a/GMailinator.xcodeproj/project.pbxproj +++ b/GMailinator.xcodeproj/project.pbxproj @@ -75,6 +75,7 @@ 499015DB1615FE5300991F6C /* Products */, ); sourceTree = ""; + wrapsLines = 0; }; 499015DB1615FE5300991F6C /* Products */ = { isa = PBXGroup; @@ -130,6 +131,7 @@ isa = PBXNativeTarget; buildConfigurationList = 499015E81615FE5300991F6C /* Build configuration list for PBXNativeTarget "GMailinator" */; buildPhases = ( + A718F5961A681B6700545966 /* Save current UUID to Info.plist */, 499015D61615FE5300991F6C /* Sources */, 499015D71615FE5300991F6C /* Frameworks */, 499015D81615FE5300991F6C /* Resources */, @@ -150,7 +152,7 @@ 499015D11615FE5300991F6C /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0450; + LastUpgradeCheck = 0610; ORGANIZATIONNAME = nompute; }; buildConfigurationList = 499015D41615FE5300991F6C /* Build configuration list for PBXProject "GMailinator" */; @@ -181,6 +183,23 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + A718F5961A681B6700545966 /* Save current UUID to Info.plist */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Save current UUID to Info.plist"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"Grabbing the mail app uuid for your version....\"\necho \"the Source_ROOT ${SOURCE_ROOT}\"\n\ndefaults write ${SOURCE_ROOT}/GMailinator/Info.plist SupportedPluginCompatibilityUUIDs -array-add \"`${SOURCE_ROOT}/find_uuid.sh`\""; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ 499015D61615FE5300991F6C /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -201,13 +220,13 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ARCHS = "$(ARCHS_STANDARD_64_BIT)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; @@ -232,7 +251,6 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ARCHS = "$(ARCHS_STANDARD_64_BIT)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_OBJC_ARC = YES; @@ -261,6 +279,7 @@ GCC_PREFIX_HEADER = "GMailinator/GMailinator-Prefix.pch"; INFOPLIST_FILE = GMailinator/Info.plist; INSTALL_PATH = "$(HOME)/Library/Mail/Bundles"; + PRODUCT_BUNDLE_IDENTIFIER = "com.nompute.gmailinator.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = GMailinator; WRAPPER_EXTENSION = mailbundle; }; @@ -275,6 +294,7 @@ GCC_PREFIX_HEADER = "GMailinator/GMailinator-Prefix.pch"; INFOPLIST_FILE = GMailinator/Info.plist; INSTALL_PATH = "$(HOME)/Library/Mail/Bundles"; + PRODUCT_BUNDLE_IDENTIFIER = "com.nompute.gmailinator.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = GMailinator; WRAPPER_EXTENSION = mailbundle; }; diff --git a/GMailinator/GMailinator.m b/GMailinator/GMailinator.m index d6fd5af..71cbae1 100644 --- a/GMailinator/GMailinator.m +++ b/GMailinator/GMailinator.m @@ -1,5 +1,6 @@ #import "GMailinator.h" #import +#import #import NSBundle *GetGMailinatorBundle(void) @@ -13,180 +14,243 @@ + (void)initialize { [GMailinator registerBundle]; SearchManager* sm = [[SearchManager alloc] init]; [sm setContextMenu: nil]; - objc_setAssociatedObject(GetGMailinatorBundle(), @"searchManager", sm, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + objc_setAssociatedObject(GetGMailinatorBundle(), + @"searchManager", + sm, + OBJC_ASSOCIATION_RETAIN_NONATOMIC); } -+ (void)load { - // Add shortcuts to the mailbox list - Class c = NSClassFromString(@"MailTableView"); - SEL originalSelector = @selector(keyDown:); - SEL overrideSelector = @selector(overrideMailKeyDown:); - Method originalMethod = class_getInstanceMethod(c, originalSelector); - Method overrideMethod = class_getInstanceMethod(self, overrideSelector); ++ (void)logAllSelectorsFromClass:(Class)cls { + unsigned int methodCount = 0; + Method * methodlist = class_copyMethodList(cls, &methodCount); - class_addMethod(c, overrideSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); - class_replaceMethod(c, originalSelector, method_getImplementation(overrideMethod), method_getTypeEncoding(overrideMethod)); + NSLog(@"Class '%s' has %d methods", class_getName(cls), methodCount); + for(int i = 0; i < methodCount; ++i) { + NSLog(@"Method no #%d: %s", i, sel_getName(method_getName(methodlist[i]))); + } +} - // Add shortcuts to the messages list - c = NSClassFromString(@"MessagesTableView"); - originalSelector = @selector(keyDown:); - overrideSelector = @selector(overrideMessagesKeyDown:); - originalMethod = class_getInstanceMethod(c, originalSelector); - overrideMethod = class_getInstanceMethod(self, overrideSelector); +/** + * Helper method to setup a class from Mail to use our custom methods instead of they common + * keyDown:. + */ ++ (void)setupClass:(Class)cls swappingKeyDownWith:(SEL)overrideSelector { + if (DEBUG) { + [self logAllSelectorsFromClass:cls]; + } + + if (cls == nil) return; - class_addMethod(c, overrideSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); - class_replaceMethod(c, originalSelector, method_getImplementation(overrideMethod), method_getTypeEncoding(overrideMethod)); + // Helper methods + SEL performSelector = @selector(performSelectorOnMessageViewer:basedOnEvent:); + SEL getShortcutSelector = @selector(getShortcutRemappedEventFor:); + Method performMethod = class_getInstanceMethod(self, performSelector); + Method getShortcutMethod = class_getInstanceMethod(self, getShortcutSelector); - // Add shortcuts to the messages list - c = NSClassFromString(@"MessageViewer"); - originalSelector = @selector(keyDown:); - overrideSelector = @selector(overrideMessagesKeyDown:); - originalMethod = class_getInstanceMethod(c, originalSelector); - overrideMethod = class_getInstanceMethod(self, overrideSelector); + // Swapped methods + SEL originalSelector = @selector(keyDown:); + Method originalMethod = class_getInstanceMethod(cls, originalSelector); + Method overrideMethod = class_getInstanceMethod(self, overrideSelector); - class_addMethod(c, overrideSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); - class_replaceMethod(c, originalSelector, method_getImplementation(overrideMethod), method_getTypeEncoding(overrideMethod)); + // Swap keyDow with the given method + class_addMethod(cls, + overrideSelector, + method_getImplementation(originalMethod), + method_getTypeEncoding(originalMethod)); + class_replaceMethod(cls, + originalSelector, + method_getImplementation(overrideMethod), + method_getTypeEncoding(overrideMethod)); + // Add helper methods + class_addMethod(cls, + performSelector, + method_getImplementation(performMethod), + method_getTypeEncoding(performMethod)); + class_addMethod(cls, + getShortcutSelector, + method_getImplementation(getShortcutMethod), + method_getTypeEncoding(getShortcutMethod)); } -+ (void)registerBundle -{ - if(class_getClassMethod(NSClassFromString(@"MVMailBundle"), @selector(registerBundle))) - [NSClassFromString(@"MVMailBundle") performSelector:@selector(registerBundle)]; ++ (void)load { + [self setupClass:NSClassFromString(@"MailTableView") + swappingKeyDownWith:@selector(overrideMailKeyDown:)]; + + // this class does not exist on newer versions of Mail +// [self setupClass:NSClassFromString(@"MessagesTableView") +// swappingKeyDownWith:@selector(overrideMessagesKeyDown:)]; - //[[self class] load]; + [self setupClass:NSClassFromString(@"MessageViewer") + swappingKeyDownWith:@selector(overrideMessagesKeyDown:)]; } ++ (void)registerBundle { + Class mailBundleClass = NSClassFromString(@"MVMailBundle"); + if(class_getClassMethod(mailBundleClass, @selector(registerBundle))) + [mailBundleClass performSelector:@selector(registerBundle)]; +} -- (void)overrideMailKeyDown:(NSEvent*)event { +/** + * This method is where we perform known selectors on the message viewer. This is prefferable + * over the shortcut proxy since a user could change their shortcuts, unfortunately there is no + * documentation on which selectors the MessageViewer on Mail we could use. + */ +- (BOOL)performSelectorOnMessageViewer:(id)messageViewer basedOnEvent:(NSEvent*)event { unichar key = [[event characters] characterAtIndex:0]; - id messageViewer = [[self performSelector:@selector(delegate)] performSelector:@selector(delegate)]; + BOOL performed = YES; switch (key) { - case 'e': - case 'y': { - [messageViewer performSelector:@selector(archiveMessages:) withObject:nil]; + case '#': { + [messageViewer performSelector:@selector(deleteMessages:) withObject:nil]; break; } -// case 'h': { -// NSEvent *newEvent = [NSEvent eventWithCGEvent: CGEventCreateKeyboardEvent(NULL, 115, true)]; -// [self overrideMailKeyDown: newEvent]; -// break; -// } -// case 'l': { -// NSEvent *newEvent = [NSEvent eventWithCGEvent: CGEventCreateKeyboardEvent(NULL, 119, true)]; -// [self overrideMailKeyDown: newEvent]; -// break; -// } - case 'k': { - NSEvent *newEvent = [NSEvent eventWithCGEvent: CGEventCreateKeyboardEvent(NULL, 126, true)]; - [self overrideMailKeyDown: newEvent]; + case 'a': { + [messageViewer performSelector:@selector(replyAllMessage:) withObject:nil]; break; } - case 'K': { - CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 126, true); - CGEventSetFlags(cgEvent, kCGEventFlagMaskShift); - NSEvent *newEvent = [NSEvent eventWithCGEvent: cgEvent]; - [self overrideMailKeyDown: newEvent]; + case 'c': { + [messageViewer performSelector:@selector(showComposeWindow:) withObject:nil]; break; } - case 'j': { - NSEvent *newEvent = [NSEvent eventWithCGEvent: CGEventCreateKeyboardEvent(NULL, 125, true)]; - [self overrideMailKeyDown: newEvent]; + case 'e': + case 'y': { + [messageViewer performSelector:@selector(archiveMessages:) withObject:nil]; break; } - case 'J': { - CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 125, true); - CGEventSetFlags(cgEvent, kCGEventFlagMaskShift); - NSEvent *newEvent = [NSEvent eventWithCGEvent: cgEvent]; - [self overrideMailKeyDown: newEvent]; + case 'f': { + [messageViewer performSelector:@selector(forwardMessage:) withObject:nil]; break; } - case '#': { - [messageViewer performSelector:@selector(deleteMessages:) withObject:nil]; + case 'o': { + [messageViewer performSelector:@selector(openMessages:) withObject:nil]; break; } - case 'c': { - [messageViewer performSelector:@selector(showComposeWindow:) withObject:nil]; + case 'R': { + [messageViewer performSelector:@selector(checkNewMail:) withObject:nil]; break; } case 'r': { [messageViewer performSelector:@selector(replyMessage:) withObject:nil]; break; } - case 'f': { - [messageViewer performSelector:@selector(forwardMessage:) withObject:nil]; + case 's': { + [messageViewer performSelector:@selector(toggleFlaggedStatus:) withObject:nil]; break; } - case 'a': { - [messageViewer performSelector:@selector(replyAllMessage:) withObject:nil]; + case 'u': { + [messageViewer performSelector:@selector(markAsRead:) withObject:nil]; break; } - case '/': { - CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 3, true); - CGEventSetFlags(cgEvent, kCGEventFlagMaskCommand | kCGEventFlagMaskAlternate); - NSEvent *newEvent = [NSEvent eventWithCGEvent: cgEvent]; - [self overrideMailKeyDown: newEvent]; + case 'U': { + [messageViewer performSelector:@selector(markAsUnread:) withObject:nil]; break; } default: - [self overrideMailKeyDown:event]; - break; - + performed = NO; } + return performed; } -- (void)overrideMessagesKeyDown:(NSEvent*)event { +/** + * This method is a proxy for shortcuts. We receive the Gmail key presses and translate it to normal + * Mail shortcuts. Althoug this is the easiest way to remap shortcuts it shouldn't be the primary + * way since a user could remap the entire set of shortcuts and have weird behavior using this + * plugin. Also there is the fact that some modifiers cannot be remapped, for instanse Alt+Up/Down + * can be used to go to next and previous message on a thread, but when we remap them here, the + * generated shortcut is the same as going to the beginning or end of the message list. + */ +-(NSEvent*)getShortcutRemappedEventFor:(NSEvent*)event { unichar key = [[event characters] characterAtIndex:0]; + NSEvent *newEvent = event; + CGEventRef cgEvent = NULL; switch (key) { - case 'e': - case 'y': { - [self performSelector:@selector(archiveMessages:) withObject:nil]; - break; - } - // These don't work so well, but it looks like this is a Mail bug; the - // menu option for Select next/previous message in conversation just jumps - // to the next/previous thread instead. Also, it looks like capturing left/ - // right doesn't work for MessageViewer for some reason. -// case 'k': { -// [self performSelector:@selector(selectNextInThread:) withObject:nil]; -// break; -// } -// case 'j': { -// [self performSelector:@selector(selectPreviousInThread:) withObject:nil]; -// break; -// } - case '#': { - [self performSelector:@selector(deleteMessages:) withObject:nil]; + case '!': { // mark message as Spam + cgEvent = CGEventCreateKeyboardEvent(NULL, kVK_ANSI_J, true); + CGEventSetFlags(cgEvent, kCGEventFlagMaskCommand | kCGEventFlagMaskShift); + newEvent = [NSEvent eventWithCGEvent: cgEvent]; break; } - case 'c': { - [self performSelector:@selector(showComposeWindow:) withObject:nil]; + case '/': { // go to search field + cgEvent = CGEventCreateKeyboardEvent(NULL, kVK_ANSI_F, true); + CGEventSetFlags(cgEvent, kCGEventFlagMaskCommand | kCGEventFlagMaskAlternate); + newEvent = [NSEvent eventWithCGEvent: cgEvent]; break; } - case 'r': { - [self performSelector:@selector(replyMessage:) withObject:nil]; + case 'g': { // go to the beginning of the list + cgEvent = CGEventCreateKeyboardEvent(NULL, kVK_UpArrow, true); + CGEventSetFlags(cgEvent, kCGEventFlagMaskAlternate | kCGEventFlagMaskControl); + newEvent = [NSEvent eventWithCGEvent: cgEvent]; break; } - case 'f': { - [self performSelector:@selector(forwardMessage:) withObject:nil]; + case 'G': { // go to the end of the list + cgEvent = CGEventCreateKeyboardEvent(NULL, kVK_DownArrow, true); + CGEventSetFlags(cgEvent, kCGEventFlagMaskAlternate | kCGEventFlagMaskControl); + newEvent = [NSEvent eventWithCGEvent: cgEvent]; break; } - case 'a': { - [self performSelector:@selector(replyAllMessage:) withObject:nil]; + case 'j': { // next message (down) + cgEvent = CGEventCreateKeyboardEvent(NULL, kVK_DownArrow, true); + newEvent = [NSEvent eventWithCGEvent: cgEvent]; break; } - case '/': { - CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 3, true); + case 'J': { // expand selection to next message (down) + cgEvent = CGEventCreateKeyboardEvent(NULL, kVK_DownArrow, true); + CGEventSetFlags(cgEvent, kCGEventFlagMaskShift); + newEvent = [NSEvent eventWithCGEvent: cgEvent]; + break; + } + case 'k': { // previous message (up) + cgEvent = CGEventCreateKeyboardEvent(NULL, kVK_UpArrow, true); + newEvent = [NSEvent eventWithCGEvent: cgEvent]; + break; + } + case 'K': { // expand selection to previous message (up) + cgEvent = CGEventCreateKeyboardEvent(NULL, kVK_UpArrow, true); + CGEventSetFlags(cgEvent, kCGEventFlagMaskShift); + newEvent = [NSEvent eventWithCGEvent: cgEvent]; + break; + } + case 'v': { // view raw message + cgEvent = CGEventCreateKeyboardEvent(NULL, kVK_ANSI_U, true); CGEventSetFlags(cgEvent, kCGEventFlagMaskCommand | kCGEventFlagMaskAlternate); - NSEvent *newEvent = [NSEvent eventWithCGEvent: cgEvent]; - [self overrideMessagesKeyDown: newEvent]; + newEvent = [NSEvent eventWithCGEvent: cgEvent]; break; } - default: - [self overrideMessagesKeyDown:event]; + case 'z': { // undo + cgEvent = CGEventCreateKeyboardEvent(NULL, kVK_ANSI_Z, true); + CGEventSetFlags(cgEvent, kCGEventFlagMaskCommand); + newEvent = [NSEvent eventWithCGEvent: cgEvent]; break; + } + } + + if (cgEvent != NULL) { + // prevent memory leak from the temporary CGEvent + CFRelease(cgEvent); + } + return newEvent; +} + +- (void)overrideMailKeyDown:(NSEvent*)event { + id tableViewManager = [self performSelector:@selector(delegate)]; + id messageListViewController = [tableViewManager performSelector:@selector(delegate)]; + + // NOTE: backwards compatibility. In 10.11 and earlier, tableViewManager.delegate.delegate was already the message viewer. + id messageViewer + = [messageListViewController respondsToSelector:@selector(messageViewer)] + ? [messageListViewController performSelector:@selector(messageViewer)] + : messageListViewController; + + if (! [self performSelectorOnMessageViewer:messageViewer basedOnEvent:event]) { + [self overrideMailKeyDown:[self getShortcutRemappedEventFor:event]]; + } +} + +- (void)overrideMessagesKeyDown:(NSEvent*)event { + if (! [self performSelectorOnMessageViewer:self basedOnEvent:event]) { + [self overrideMessagesKeyDown:[self getShortcutRemappedEventFor:event]]; } } diff --git a/GMailinator/Info.plist b/GMailinator/Info.plist index cb7fb11..7f796fd 100644 Binary files a/GMailinator/Info.plist and b/GMailinator/Info.plist differ diff --git a/GMailinator/SearchManager.m b/GMailinator/SearchManager.m index f7747fd..360051e 100644 --- a/GMailinator/SearchManager.m +++ b/GMailinator/SearchManager.m @@ -57,6 +57,12 @@ - (id)popupWithSubmenu:(NSMenu *)submenu } [sp setSubmenu: submenu]; + + // The call bellow, should be replace by: + // + // [[NSBundle mainBundle] loadNibNamed:@"SearchPopup" owner:sp topLevelObjects:nil]; + // + // But for reasons I still don't know, the proper way to load the xib makes the popup to not show! [NSBundle loadNibNamed: @"SearchPopup" owner:sp]; return sp; @@ -95,10 +101,17 @@ - (NSMenuItem *) newMenuItemWithTitle:(NSString *)title action:(SEL)action andKe - (void) setContextMenu:(NSMenu *)menu { - NSMenuItem* moveFolderItem = [self newMenuItemWithTitle: @"Move to Folder..." action: @selector(moveToFolder:) andKeyEquivalent: @"l" inMenu: [[NSApplication sharedApplication] mainMenu] withTitle: @"Move Again" offset: 1]; + // create "Move to Folder..." menu item just below the existing "Move Again" menuitem + NSMenuItem* moveFolderItem = [self newMenuItemWithTitle: @"Move to Folder..." + action: @selector(moveToFolder:) + andKeyEquivalent: @"l" + inMenu: [[NSApplication sharedApplication] mainMenu] + withTitle: @"Move Again" + offset: 1]; [moveFolderItem setTarget: self]; [moveFolderItem setKeyEquivalentModifierMask: 0]; + // lookup existing "Move to" and "Copy to" menuitems NSMenu* messagesMenu = [moveFolderItem menu]; NSArray *items = [messagesMenu itemArray]; for (int iI = 0; iI < [items count]; iI++) { diff --git a/GMailinator/SearchPopup.m b/GMailinator/SearchPopup.m index 6e87912..e214da4 100644 --- a/GMailinator/SearchPopup.m +++ b/GMailinator/SearchPopup.m @@ -18,7 +18,6 @@ - (void)setSubmenu:(NSMenu*)sm { - (void)addMenu:(NSMenu *)menu toDictionary:(NSMutableDictionary *)dict withPath:(NSMutableArray *)path atLevel:(int)depth { NSArray *items = [menu itemArray]; - for (int i = 0; i < [items count]; ++i) { NSMenuItem *menuItem = [items objectAtIndex:i]; @@ -39,7 +38,7 @@ - (void)addMenu:(NSMenu *)menu toDictionary:(NSMutableDictionary *)dict withPath [self addMenu:[menuItem submenu] toDictionary:dict withPath:path - atLevel:level + 1]; + atLevel:(int)level + 1]; } } @@ -57,9 +56,13 @@ - (void)showWithSender: sender andTitle: (NSString *)title { // update menu items [[submenu delegate] menuNeedsUpdate: submenu ]; + // set message handling to copy / move - //[submenu _sendMenuOpeningNotification]; - [submenu performSelector:@selector(_sendMenuOpeningNotification)]; + if ([submenu respondsToSelector:@selector(_sendMenuOpeningNotification:)]) { // Yosemite 10.10.2 + [submenu performSelector:@selector(_sendMenuOpeningNotification:)]; + } else if ([submenu respondsToSelector:@selector(_sendMenuOpeningNotification)]) { + [submenu performSelector:@selector(_sendMenuOpeningNotification)]; + } // if ([p lastFolder] != nil) // { @@ -137,7 +140,9 @@ - (BOOL)control:(NSControl*)control textView:(NSTextView*)textView doCommandBySe { // [parent setLastFolder: selectedResult objectAtIndex:0]]; NSMenuItem *menuItem = [selectedResult objectForKey:@"menuItem"]; - [[menuItem menu] performActionForItemAtIndex:[[menuItem menu] indexOfItem:menuItem]]; + NSMenu *menu = [menuItem menu]; + NSInteger index = [menu indexOfItem:menuItem]; + [menu performActionForItemAtIndex:index]; } [searchWindow orderOut:nil]; diff --git a/GMailinator/SearchPopup.xib b/GMailinator/SearchPopup.xib index fa0c190..204f88a 100644 --- a/GMailinator/SearchPopup.xib +++ b/GMailinator/SearchPopup.xib @@ -1,680 +1,94 @@ - - - - 1050 - 12E55 - 3084 - 1187.39 - 626.00 - - com.apple.InterfaceBuilder.CocoaPlugin - 3084 - - - YES - NSCustomObject - NSImageCell - NSScrollView - NSScroller - NSSearchField - NSSearchFieldCell - NSTableColumn - NSTableView - NSTextFieldCell - NSView - NSWindowTemplate - - - YES - com.apple.InterfaceBuilder.CocoaPlugin - - - PluginDependencyRecalculationVersion - - - - YES - - SearchPopup - - - FirstResponder - - - NSApplication - - - 81 - 2 - {{673, 431}, {249, 307}} - 1685586944 - Search Mailbox - NSPanel - - - - - 256 - - YES - - - 266 - {{20, 265}, {209, 22}} - - - - YES - - 342884417 - 268436480 - - - LucidaGrande - 13 - 1044 - - - YES - 1 - - 6 - System - textBackgroundColor - - 3 - MQA - - - - 6 - System - controlTextColor - - 3 - MAA - - - - 0 - 0 - search - - _searchFieldSearch: - - 138690560 - 0 - - 400 - 75 - - - 0 - 0 - clear - - YES - - YES - - YES - AXDescription - NSAccessibilityEncodedAttributesValueType - - - YES - cancel - - - - - - _searchFieldCancel: - - 138690560 - 0 - - 400 - 75 - - 255 - CAAAAA - - NO - - - - 274 - - YES - - - 2304 - - YES - - - 256 - {207, 228} - - - - YES - NO - YES - - - -2147483392 - {{209, 0}, {16, 17}} - - - YES - - image - 40 - 40 - 1000 - - 75497536 - 201328640 - Image - - LucidaGrande - 11 - 3100 - - - 3 - MC4zMzMzMzI5OQA - - - 6 - System - headerTextColor - - - - - 134217728 - 33685504 - 0 - 0 - 0 - NO - - - - - title - 161 - 40 - 1000 - - 75497536 - 2048 - Title - - - - - - 337641536 - 2048 - Text Cell - - - - 6 - System - controlBackgroundColor - - 3 - MC42NjY2NjY2NjY3AA - - - - - 1 - YES - - - - 3 - 2 - - - 6 - System - gridColor - - 3 - MC41AA - - - 17 - 37748736 - - - 4 - 15 - 0 - NO - 0 - 1 - - - {{1, 1}, {207, 228}} - - - - - - 4 - - - - -2147483392 - {{209, 1}, {15, 126}} - - - NO - - _doScroller: - 0.99212599999999995 - - - - -2147483392 - {{-100, -100}, {208, 15}} - - - - NO - 1 - - _doScroller: - 0.81632660000000001 - - - {{20, 20}, {209, 230}} - - - - 133650 - - - - QSAAAEEgAABBmAAAQZgAAA - 0.25 - 4 - 1 - - - {249, 307} - - - - - {{0, 0}, {1920, 1178}} - {10000000000000, 10000000000000} - YES - - - - - YES - - - searchField - - - - 7 - - - - searchWindow - - - - 8 - - - - doSearch: - - - - 9 - - - - resultViewer - - - - 33 - - - - changeSelection: - - - - 34 - - - - delegate - - - - 10 - - - - dataSource - - - - 32 - - - - - YES - - 0 - - YES - - - - - - -2 - - - File's Owner - - - -1 - - - First Responder - - - -3 - - - Application - - - 1 - - - YES - - - - - - 2 - - - YES - - - - - - - 4 - - - YES - - - - - - 5 - - - - - 20 - - - YES - - - - - - - - 21 - - - - - 22 - - - - - 23 - - - YES - - - - - - - 25 - - - YES - - - - - - 26 - - - YES - - - - - - 27 - - - - - 29 - - - - - - - YES - - YES - -1.IBPluginDependency - -2.IBPluginDependency - -3.IBPluginDependency - 1.IBNSWindowAutoPositionCentersHorizontal - 1.IBNSWindowAutoPositionCentersVertical - 1.IBPluginDependency - 1.IBWindowTemplateEditedContentRect - 1.NSWindowTemplate.visibleAtLaunch - 2.IBPluginDependency - 2.IBViewIntegration.shadowBlurRadius - 2.IBViewIntegration.shadowColor - 2.IBViewIntegration.shadowOffsetHeight - 2.IBViewIntegration.shadowOffsetWidth - 20.IBPluginDependency - 21.IBPluginDependency - 22.IBPluginDependency - 23.IBPluginDependency - 23.IBViewIntegration.shadowBlurRadius - 23.IBViewIntegration.shadowColor - 23.IBViewIntegration.shadowOffsetHeight - 23.IBViewIntegration.shadowOffsetWidth - 25.IBPluginDependency - 26.IBPluginDependency - 27.IBPluginDependency - 29.IBPluginDependency - 4.IBPluginDependency - 5.IBPluginDependency - - - YES - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - - - com.apple.InterfaceBuilder.CocoaPlugin - {{703, 547}, {265, 198}} - - com.apple.InterfaceBuilder.CocoaPlugin - - - - - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - - - - - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - - - - YES - - - - - - YES - - - - - 36 - - - - YES - - SearchPopup - NSObject - - YES - - YES - changeSelection: - doSearch: - - - YES - id - id - - - - YES - - YES - changeSelection: - doSearch: - - - YES - - changeSelection: - id - - - doSearch: - id - - - - - YES - - YES - resultViewer - searchField - searchWindow - - - YES - NSTableView - NSSearchField - NSWindow - - - - YES - - YES - resultViewer - searchField - searchWindow - - - YES - - resultViewer - NSTableView - - - searchField - NSSearchField - - - searchWindow - NSWindow - - - - - IBProjectSource - ./Classes/SearchPopup.h - - - - - 0 - IBCocoaFramework - - com.apple.InterfaceBuilder.CocoaPlugin.macosx - - - - com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3 - - - YES - 3 - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/README.md b/README.md index 76fe2da..72526c9 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,32 @@ # GMailinator Adds Gmail-esque keyboard shorcuts to Mail.app. This is still very much a work -in progress. Tested with Mail for OS X 10.8.4. +in progress. Tested with Mail for OS X Sierra. ## Supported Shortcuts - - - - - - - - - - - -
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)
+| Key | Action | +| :----: | ------------------------------ | +| # | Delete | +| / | Mailbox search | +| ! | Toggle message as Junk | +| a | Reply All | +| c | Compose new message | +| e, y | Archive | +| f | Forward message | +| G | Go to the last message | +| g | Go to the first message | +| j | Go to next message/thread | +| k | Go to previous message/thread | +| l | Move to folder (opens dialog) | +| o | Open selected message | +| R | Get new mail (Refresh) | +| r | Reply | +| s | Flag | +| u | Mark message as read | +| U | Mark message as unread | +| v | View raw message dialog | +| z | Undo | ## How to install @@ -34,7 +44,7 @@ in progress. Tested with Mail for OS X 10.8.4. ## Credits -A lot of this was built with heavy use of of the +A lot of this was built with heavy use of the [BindDeleteKeyToArchive](https://github.com/benlenarts/BindDeleteKeyToArchive) project by Ben Lenarts. The Xcode project and interface skeleton were all from that project, and for the most part, renamed. I added the keybinding code.