menu.mm 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  1. #include "common.h"
  2. #include "menu.h"
  3. #include "window.h"
  4. #include "KeyTransform.h"
  5. #include <CoreFoundation/CoreFoundation.h>
  6. #include <Carbon/Carbon.h> /* For kVK_ constants, and TIS functions. */
  7. @implementation AvnMenu
  8. {
  9. bool _isReparented;
  10. NSObject<NSMenuDelegate>* _wtf;
  11. }
  12. - (id) initWithDelegate: (NSObject<NSMenuDelegate>*)del
  13. {
  14. self = [super init];
  15. self.delegate = del;
  16. _wtf = del;
  17. _isReparented = false;
  18. return self;
  19. }
  20. - (bool)hasGlobalMenuItem
  21. {
  22. return _isReparented;
  23. }
  24. - (void)setHasGlobalMenuItem:(bool)value
  25. {
  26. _isReparented = value;
  27. }
  28. @end
  29. @implementation AvnMenuItem
  30. {
  31. AvnAppMenuItem* _item;
  32. }
  33. - (id) initWithAvnAppMenuItem: (AvnAppMenuItem*)menuItem
  34. {
  35. if(self != nil)
  36. {
  37. _item = menuItem;
  38. self = [super initWithTitle:@""
  39. action:@selector(didSelectItem:)
  40. keyEquivalent:@""];
  41. [self setEnabled:YES];
  42. [self setTarget:self];
  43. }
  44. return self;
  45. }
  46. - (BOOL)validateMenuItem:(NSMenuItem *)menuItem
  47. {
  48. if([self submenu] != nil)
  49. {
  50. return YES;
  51. }
  52. return _item->EvaluateItemEnabled();
  53. }
  54. - (void)didSelectItem:(nullable id)sender
  55. {
  56. _item->RaiseOnClicked();
  57. }
  58. @end
  59. AvnAppMenuItem::AvnAppMenuItem(bool isSeparator)
  60. {
  61. _isCheckable = false;
  62. _isSeparator = isSeparator;
  63. if(isSeparator)
  64. {
  65. _native = [NSMenuItem separatorItem];
  66. }
  67. else
  68. {
  69. _native = [[AvnMenuItem alloc] initWithAvnAppMenuItem: this];
  70. }
  71. _callback = nullptr;
  72. }
  73. NSMenuItem* AvnAppMenuItem::GetNative()
  74. {
  75. return _native;
  76. }
  77. HRESULT AvnAppMenuItem::SetSubMenu (IAvnMenu* menu)
  78. {
  79. @autoreleasepool
  80. {
  81. if(menu != nullptr)
  82. {
  83. auto nsMenu = dynamic_cast<AvnAppMenu*>(menu)->GetNative();
  84. [_native setSubmenu: nsMenu];
  85. }
  86. else
  87. {
  88. [_native setSubmenu: nullptr];
  89. }
  90. return S_OK;
  91. }
  92. }
  93. HRESULT AvnAppMenuItem::SetTitle (char* utf8String)
  94. {
  95. @autoreleasepool
  96. {
  97. if (utf8String != nullptr)
  98. {
  99. [_native setTitle:[NSString stringWithUTF8String:(const char*)utf8String]];
  100. }
  101. return S_OK;
  102. }
  103. }
  104. HRESULT AvnAppMenuItem::SetGesture (AvnKey key, AvnInputModifiers modifiers)
  105. {
  106. @autoreleasepool
  107. {
  108. if(key != AvnKeyNone)
  109. {
  110. NSEventModifierFlags flags = 0;
  111. if (modifiers & Control)
  112. flags |= NSEventModifierFlagControl;
  113. if (modifiers & Shift)
  114. flags |= NSEventModifierFlagShift;
  115. if (modifiers & Alt)
  116. flags |= NSEventModifierFlagOption;
  117. if (modifiers & Windows)
  118. flags |= NSEventModifierFlagCommand;
  119. auto it = s_UnicodeKeyMap.find(key);
  120. if(it != s_UnicodeKeyMap.end())
  121. {
  122. auto keyString= [NSString stringWithFormat:@"%C", (unsigned short)it->second];
  123. [_native setKeyEquivalent: keyString];
  124. [_native setKeyEquivalentModifierMask:flags];
  125. return S_OK;
  126. }
  127. else
  128. {
  129. auto it = s_AvnKeyMap.find(key); // check if a virtual key is mapped.
  130. if(it != s_AvnKeyMap.end())
  131. {
  132. auto it1 = s_QwertyKeyMap.find(it->second); // convert virtual key to qwerty string.
  133. if(it1 != s_QwertyKeyMap.end())
  134. {
  135. [_native setKeyEquivalent: [NSString stringWithUTF8String: it1->second]];
  136. [_native setKeyEquivalentModifierMask:flags];
  137. return S_OK;
  138. }
  139. }
  140. }
  141. }
  142. // Nothing matched... clear.
  143. [_native setKeyEquivalent: @""];
  144. [_native setKeyEquivalentModifierMask: 0];
  145. return S_OK;
  146. }
  147. }
  148. HRESULT AvnAppMenuItem::SetAction (IAvnPredicateCallback* predicate, IAvnActionCallback* callback)
  149. {
  150. @autoreleasepool
  151. {
  152. _predicate = predicate;
  153. _callback = callback;
  154. return S_OK;
  155. }
  156. }
  157. HRESULT AvnAppMenuItem::SetIsChecked (bool isChecked)
  158. {
  159. @autoreleasepool
  160. {
  161. [_native setState:(isChecked && _isCheckable ? NSOnState : NSOffState)];
  162. return S_OK;
  163. }
  164. }
  165. HRESULT AvnAppMenuItem::SetToggleType(AvnMenuItemToggleType toggleType)
  166. {
  167. @autoreleasepool
  168. {
  169. switch(toggleType)
  170. {
  171. case AvnMenuItemToggleType::None:
  172. [_native setOnStateImage: [NSImage imageNamed:@"NSMenuCheckmark"]];
  173. _isCheckable = false;
  174. break;
  175. case AvnMenuItemToggleType::CheckMark:
  176. [_native setOnStateImage: [NSImage imageNamed:@"NSMenuCheckmark"]];
  177. _isCheckable = true;
  178. break;
  179. case AvnMenuItemToggleType::Radio:
  180. [_native setOnStateImage: [NSImage imageNamed:@"NSMenuItemBullet"]];
  181. _isCheckable = true;
  182. break;
  183. }
  184. return S_OK;
  185. }
  186. }
  187. HRESULT AvnAppMenuItem::SetIcon(void *data, size_t length)
  188. {
  189. @autoreleasepool
  190. {
  191. if(data != nullptr)
  192. {
  193. NSData *imageData = [NSData dataWithBytes:data length:length];
  194. NSImage *image = [[NSImage alloc] initWithData:imageData];
  195. NSSize originalSize = [image size];
  196. NSSize size;
  197. size.height = [[NSFont menuFontOfSize:0] pointSize] * 1.333333;
  198. auto scaleFactor = size.height / originalSize.height;
  199. size.width = originalSize.width * scaleFactor;
  200. [image setSize: size];
  201. [_native setImage:image];
  202. }
  203. else
  204. {
  205. [_native setImage:nullptr];
  206. }
  207. return S_OK;
  208. }
  209. }
  210. bool AvnAppMenuItem::EvaluateItemEnabled()
  211. {
  212. if(_predicate != nullptr)
  213. {
  214. auto result = _predicate->Evaluate ();
  215. return result;
  216. }
  217. return false;
  218. }
  219. void AvnAppMenuItem::RaiseOnClicked()
  220. {
  221. if(_callback != nullptr)
  222. {
  223. _callback->Run();
  224. }
  225. }
  226. AvnAppMenu::AvnAppMenu(IAvnMenuEvents* events)
  227. {
  228. _baseEvents = events;
  229. id del = [[AvnMenuDelegate alloc] initWithParent: this];
  230. _native = [[AvnMenu alloc] initWithDelegate: del];
  231. }
  232. AvnMenu* AvnAppMenu::GetNative()
  233. {
  234. return _native;
  235. }
  236. void AvnAppMenu::RaiseNeedsUpdate()
  237. {
  238. if(_baseEvents != nullptr)
  239. {
  240. _baseEvents->NeedsUpdate();
  241. }
  242. }
  243. void AvnAppMenu::RaiseOpening()
  244. {
  245. if(_baseEvents != nullptr)
  246. {
  247. _baseEvents->Opening();
  248. }
  249. }
  250. void AvnAppMenu::RaiseClosed()
  251. {
  252. if(_baseEvents != nullptr)
  253. {
  254. _baseEvents->Closed();
  255. }
  256. }
  257. HRESULT AvnAppMenu::InsertItem(int index, IAvnMenuItem *item)
  258. {
  259. @autoreleasepool
  260. {
  261. if([_native hasGlobalMenuItem])
  262. {
  263. index++;
  264. }
  265. auto avnMenuItem = dynamic_cast<AvnAppMenuItem*>(item);
  266. if(avnMenuItem != nullptr)
  267. {
  268. [_native insertItem: avnMenuItem->GetNative() atIndex:index];
  269. }
  270. return S_OK;
  271. }
  272. }
  273. HRESULT AvnAppMenu::RemoveItem (IAvnMenuItem* item)
  274. {
  275. @autoreleasepool
  276. {
  277. auto avnMenuItem = dynamic_cast<AvnAppMenuItem*>(item);
  278. if(avnMenuItem != nullptr)
  279. {
  280. [_native removeItem:avnMenuItem->GetNative()];
  281. }
  282. return S_OK;
  283. }
  284. }
  285. HRESULT AvnAppMenu::SetTitle (char* utf8String)
  286. {
  287. @autoreleasepool
  288. {
  289. if (utf8String != nullptr)
  290. {
  291. [_native setTitle:[NSString stringWithUTF8String:(const char*)utf8String]];
  292. }
  293. return S_OK;
  294. }
  295. }
  296. HRESULT AvnAppMenu::Clear()
  297. {
  298. @autoreleasepool
  299. {
  300. [_native removeAllItems];
  301. return S_OK;
  302. }
  303. }
  304. @implementation AvnMenuDelegate
  305. {
  306. ComPtr<AvnAppMenu> _parent;
  307. }
  308. - (id) initWithParent:(AvnAppMenu *)parent
  309. {
  310. self = [super init];
  311. _parent = parent;
  312. return self;
  313. }
  314. - (BOOL)menu:(NSMenu *)menu updateItem:(NSMenuItem *)item atIndex:(NSInteger)index shouldCancel:(BOOL)shouldCancel
  315. {
  316. if(shouldCancel)
  317. return NO;
  318. return YES;
  319. }
  320. - (NSInteger)numberOfItemsInMenu:(NSMenu *)menu
  321. {
  322. return [menu numberOfItems];
  323. }
  324. - (void)menuNeedsUpdate:(NSMenu *)menu
  325. {
  326. _parent->RaiseNeedsUpdate();
  327. }
  328. - (void)menuWillOpen:(NSMenu *)menu
  329. {
  330. _parent->RaiseOpening();
  331. }
  332. - (void)menuDidClose:(NSMenu *)menu
  333. {
  334. _parent->RaiseClosed();
  335. }
  336. @end
  337. extern IAvnMenu* CreateAppMenu(IAvnMenuEvents* cb)
  338. {
  339. @autoreleasepool
  340. {
  341. return new AvnAppMenu(cb);
  342. }
  343. }
  344. extern IAvnMenuItem* CreateAppMenuItem()
  345. {
  346. @autoreleasepool
  347. {
  348. return new AvnAppMenuItem(false);
  349. }
  350. }
  351. extern IAvnMenuItem* CreateAppMenuItemSeparator()
  352. {
  353. @autoreleasepool
  354. {
  355. return new AvnAppMenuItem(true);
  356. }
  357. }
  358. static IAvnMenu* s_appMenu = nullptr;
  359. static NSMenuItem* s_appMenuItem = nullptr;
  360. extern void SetAppMenu (NSString* appName, IAvnMenu* menu)
  361. {
  362. s_appMenu = menu;
  363. if(s_appMenu != nullptr)
  364. {
  365. auto nativeMenu = dynamic_cast<AvnAppMenu*>(s_appMenu);
  366. auto currentMenu = [s_appMenuItem menu];
  367. if (currentMenu != nullptr)
  368. {
  369. [currentMenu removeItem:s_appMenuItem];
  370. }
  371. s_appMenuItem = [nativeMenu->GetNative() itemAtIndex:0];
  372. if (currentMenu == nullptr)
  373. {
  374. currentMenu = [s_appMenuItem menu];
  375. }
  376. [[s_appMenuItem menu] removeItem:s_appMenuItem];
  377. [currentMenu insertItem:s_appMenuItem atIndex:0];
  378. if([s_appMenuItem submenu] == nullptr)
  379. {
  380. [s_appMenuItem setSubmenu:[NSMenu new]];
  381. }
  382. auto appMenu = [s_appMenuItem submenu];
  383. if(GetAutoGenerateDefaultAppMenuItems())
  384. {
  385. [appMenu addItem:[NSMenuItem separatorItem]];
  386. // Services item and menu
  387. auto servicesItem = [[NSMenuItem alloc] init];
  388. servicesItem.title = @"Services";
  389. NSMenu *servicesMenu = [[NSMenu alloc] initWithTitle:@"Services"];
  390. servicesItem.submenu = servicesMenu;
  391. [NSApplication sharedApplication].servicesMenu = servicesMenu;
  392. [appMenu addItem:servicesItem];
  393. [appMenu addItem:[NSMenuItem separatorItem]];
  394. // Hide Application
  395. auto hideItem = [[NSMenuItem alloc] initWithTitle:[@"Hide " stringByAppendingString:appName] action:@selector(hide:) keyEquivalent:@"h"];
  396. [appMenu addItem:hideItem];
  397. // Hide Others
  398. auto hideAllOthersItem = [[NSMenuItem alloc] initWithTitle:@"Hide Others"
  399. action:@selector(hideOtherApplications:)
  400. keyEquivalent:@"h"];
  401. hideAllOthersItem.keyEquivalentModifierMask = NSEventModifierFlagCommand | NSEventModifierFlagOption;
  402. [appMenu addItem:hideAllOthersItem];
  403. // Show All
  404. auto showAllItem = [[NSMenuItem alloc] initWithTitle:@"Show All"
  405. action:@selector(unhideAllApplications:)
  406. keyEquivalent:@""];
  407. [appMenu addItem:showAllItem];
  408. [appMenu addItem:[NSMenuItem separatorItem]];
  409. // Quit Application
  410. auto quitItem = [[NSMenuItem alloc] init];
  411. quitItem.title = [@"Quit " stringByAppendingString:appName];
  412. quitItem.keyEquivalent = @"q";
  413. quitItem.target = [AvnWindow class];
  414. quitItem.action = @selector(closeAll);
  415. [appMenu addItem:quitItem];
  416. }
  417. }
  418. else
  419. {
  420. s_appMenuItem = nullptr;
  421. }
  422. }
  423. extern IAvnMenu* GetAppMenu ()
  424. {
  425. return s_appMenu;
  426. }
  427. extern NSMenuItem* GetAppMenuItem ()
  428. {
  429. return s_appMenuItem;
  430. }