MenuProxy.m 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. //
  2. // MenuProxy.m
  3. // MacGap
  4. //
  5. // Created by Joe Hildebrand on 1/14/12.
  6. // Copyright (c) 2012 Twitter. All rights reserved.
  7. //
  8. #import <objc/runtime.h>
  9. #import <JavaScriptCore/JavaScript.h>
  10. #import "MenuProxy.h"
  11. #import "MenuItemProxy.h"
  12. static char REPRESENTED_OBJECT;
  13. @interface NSMenu (represented)
  14. @property (strong) id representedObject;
  15. @end
  16. @implementation NSMenu (represented)
  17. - (id) representedObject
  18. {
  19. return objc_getAssociatedObject(self, &REPRESENTED_OBJECT);
  20. }
  21. - (void) setRepresentedObject:(id)representedObject
  22. {
  23. objc_setAssociatedObject(self,
  24. &REPRESENTED_OBJECT,
  25. representedObject,
  26. OBJC_ASSOCIATION_RETAIN);
  27. }
  28. @end
  29. @implementation MenuProxy
  30. - (id) initWithContext:(JSContextRef)aContext andMenu:(NSMenu*)aMenu
  31. {
  32. self = [super initWithContext:aContext];
  33. if (!self)
  34. return nil;
  35. menu = aMenu;
  36. menu.representedObject = self;
  37. return self;
  38. }
  39. + (MenuProxy*)proxyWithContext:(JSContextRef)aContext andMenu:(NSMenu*)aMenu
  40. {
  41. // singleton-ish.
  42. MenuProxy *ret = [aMenu representedObject];
  43. if (ret)
  44. {
  45. NSLog(@"MP cache hit");
  46. return ret;
  47. }
  48. return [[MenuProxy alloc] initWithContext:aContext andMenu:aMenu];
  49. }
  50. - (void) dealloc
  51. {
  52. menu.representedObject = nil;
  53. }
  54. - (NSString*) description
  55. {
  56. return [menu description];
  57. }
  58. static BOOL isNullish(id o)
  59. {
  60. if (!o)
  61. return YES;
  62. if ([o isKindOfClass:[WebUndefined class]])
  63. return YES;
  64. return NO;
  65. }
  66. - (MenuItemProxy*)addItemWithTitle:(NSString*)title
  67. keyEquivalent:(NSString*)keyCommand
  68. callback:(WebScriptObject*)aCallback
  69. atIndex:(NSInteger)index
  70. {
  71. if (isNullish(title))
  72. title = @"";
  73. NSString *aKey = [MenuProxy getKeyFromString:keyCommand];
  74. NSMenuItem *item = nil;
  75. if(index) {
  76. item = [menu insertItemWithTitle:title action:nil keyEquivalent:aKey atIndex:index ];
  77. } else {
  78. item = [menu addItemWithTitle:title action:nil keyEquivalent:aKey ];
  79. }
  80. // Set the modifiers.
  81. NSUInteger modifiers = [MenuProxy getModifiersFromString:keyCommand];
  82. [item setKeyEquivalentModifierMask:modifiers];
  83. if(!menu.supermenu) {
  84. NSMenu *s = [[NSMenu alloc] initWithTitle:title];
  85. [item setSubmenu:s];
  86. }
  87. MenuItemProxy *mip = [MenuItemProxy proxyWithContext:context andMenuItem:item];
  88. if (!isNullish(aCallback))
  89. [mip setCallback:aCallback];
  90. return mip;
  91. }
  92. + (NSString*)getKeyFromString:(NSString*)keyCommand {
  93. if (isNullish(keyCommand))
  94. keyCommand = @"";
  95. // Obtain the key (if there are modifiers, it will be the last character).
  96. NSString *aKey = @"";
  97. if ([keyCommand length] > 0) {
  98. aKey = [keyCommand substringFromIndex:[keyCommand length] - 1];
  99. }
  100. return aKey;
  101. }
  102. + (NSUInteger*)getModifiersFromString:(NSString*)keyCommand {
  103. // aKeys may optionally specify one or more modifiers.
  104. NSUInteger modifiers = 0;
  105. if ([keyCommand rangeOfString:@"caps"].location != NSNotFound) modifiers += NSAlphaShiftKeyMask;
  106. if ([keyCommand rangeOfString:@"shift"].location != NSNotFound) modifiers += NSShiftKeyMask;
  107. if ([keyCommand rangeOfString:@"cmd"].location != NSNotFound) modifiers += NSCommandKeyMask;
  108. if ([keyCommand rangeOfString:@"ctrl"].location != NSNotFound) modifiers += NSControlKeyMask;
  109. if ([keyCommand rangeOfString:@"opt"].location != NSNotFound) modifiers += NSAlternateKeyMask;
  110. if ([keyCommand rangeOfString:@"alt"].location != NSNotFound) modifiers += NSAlternateKeyMask;
  111. return modifiers;
  112. }
  113. - (MenuItemProxy*)addSeparator
  114. {
  115. NSMenuItem *sep = [NSMenuItem separatorItem];
  116. [menu addItem:sep];
  117. return [MenuItemProxy proxyWithContext:context andMenuItem:sep];
  118. }
  119. - (MenuItemProxy*)itemForKey:(id)key
  120. {
  121. if (isNullish(key))
  122. return nil;
  123. NSMenuItem *item = nil;
  124. if ([key isKindOfClass:[NSNumber class]])
  125. {
  126. item = [menu itemAtIndex:[key intValue]];
  127. }
  128. else if ([key isKindOfClass:[NSString class]])
  129. {
  130. item = [menu itemWithTitle:key];
  131. if (!item)
  132. {
  133. // Try again, with ... appended. e.g. "Save..."
  134. item = [menu itemWithTitle:
  135. [key stringByAppendingString:@"\u2026"]];
  136. }
  137. }
  138. if (!item)
  139. return nil;
  140. return [MenuItemProxy proxyWithContext:context andMenuItem:item];
  141. }
  142. - (MenuProxy*)removeItem:(id)key
  143. {
  144. if (isNullish(key))
  145. return nil;
  146. NSMenuItem *item = nil;
  147. if ([key isKindOfClass:[NSNumber class]])
  148. {
  149. item = [menu itemAtIndex:[key intValue]];
  150. }
  151. else if ([key isKindOfClass:[NSString class]])
  152. {
  153. item = [menu itemWithTitle:key];
  154. if (!item)
  155. {
  156. // Try again, with ... appended. e.g. "Save..."
  157. item = [menu itemWithTitle:
  158. [key stringByAppendingString:@"\u2026"]];
  159. }
  160. }
  161. if (!item)
  162. return nil;
  163. [menu removeItem:item];
  164. return [MenuProxy proxyWithContext:context andMenu:menu];
  165. }
  166. + (BOOL) isSelectorExcludedFromWebScript:(SEL)selector
  167. {
  168. return [self webScriptNameForSelector:selector] == nil;
  169. }
  170. + (BOOL) isKeyExcludedFromWebScript:(const char*)name
  171. {
  172. return YES;
  173. }
  174. + (NSString*) webScriptNameForSelector:(SEL)selector
  175. {
  176. id result = nil;
  177. if (selector == @selector(addItemWithTitle:keyEquivalent:callback:atIndex:)) {
  178. result = @"addItem";
  179. }
  180. else if (selector == @selector(addSeparator)) {
  181. result = @"addSeparator";
  182. }
  183. else if (selector == @selector(itemForKey:)) {
  184. result = @"getItem";
  185. }
  186. else if (selector == @selector(removeItem:)) {
  187. result = @"removeMenu";
  188. }
  189. return result;
  190. }
  191. @end