The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

445 lines
18KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2020 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 6 End-User License
  8. Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
  9. End User License Agreement: www.juce.com/juce-6-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. namespace juce
  19. {
  20. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunguarded-availability", "-Wdeprecated-declarations")
  21. extern NSMenu* createNSMenu (const PopupMenu&, const String& name, int topLevelMenuId,
  22. int topLevelIndex, bool addDelegate);
  23. //==============================================================================
  24. struct StatusItemContainer : public Timer
  25. {
  26. //==============================================================================
  27. StatusItemContainer (SystemTrayIconComponent& iconComp, const Image& im)
  28. : owner (iconComp), statusIcon (imageToNSImage (im))
  29. {
  30. }
  31. virtual void configureIcon() = 0;
  32. virtual void setHighlighted (bool shouldHighlight) = 0;
  33. //==============================================================================
  34. void setIconSize()
  35. {
  36. [statusIcon.get() setSize: NSMakeSize (20.0f, 20.0f)];
  37. }
  38. void updateIcon (const Image& newImage)
  39. {
  40. statusIcon.reset (imageToNSImage (newImage));
  41. setIconSize();
  42. configureIcon();
  43. }
  44. void showMenu (const PopupMenu& menu)
  45. {
  46. if (NSMenu* m = createNSMenu (menu, "MenuBarItem", -2, -3, true))
  47. {
  48. setHighlighted (true);
  49. stopTimer();
  50. // There's currently no good alternative to this.
  51. [statusItem.get() popUpStatusItemMenu: m];
  52. startTimer (1);
  53. }
  54. }
  55. //==============================================================================
  56. void timerCallback() override
  57. {
  58. stopTimer();
  59. setHighlighted (false);
  60. }
  61. //==============================================================================
  62. SystemTrayIconComponent& owner;
  63. NSUniquePtr<NSStatusItem> statusItem;
  64. NSUniquePtr<NSImage> statusIcon;
  65. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StatusItemContainer)
  66. };
  67. //==============================================================================
  68. struct ButtonBasedStatusItem : public StatusItemContainer
  69. {
  70. //==============================================================================
  71. ButtonBasedStatusItem (SystemTrayIconComponent& iconComp, const Image& im)
  72. : StatusItemContainer (iconComp, im)
  73. {
  74. static ButtonEventForwarderClass cls;
  75. eventForwarder.reset ([cls.createInstance() init]);
  76. ButtonEventForwarderClass::setOwner (eventForwarder.get(), this);
  77. setIconSize();
  78. configureIcon();
  79. statusItem.reset ([[[NSStatusBar systemStatusBar] statusItemWithLength: NSSquareStatusItemLength] retain]);
  80. auto button = [statusItem.get() button];
  81. button.image = statusIcon.get();
  82. button.target = eventForwarder.get();
  83. button.action = @selector (handleEvent:);
  84. #if defined (MAC_OS_X_VERSION_10_12) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12
  85. [button sendActionOn: NSEventMaskLeftMouseDown | NSEventMaskRightMouseDown | NSEventMaskScrollWheel];
  86. #else
  87. [button sendActionOn: NSLeftMouseDownMask | NSRightMouseDownMask | NSScrollWheelMask];
  88. #endif
  89. }
  90. void configureIcon() override
  91. {
  92. [statusIcon.get() setTemplate: true];
  93. [statusItem.get() button].image = statusIcon.get();
  94. }
  95. void setHighlighted (bool shouldHighlight) override
  96. {
  97. [[statusItem.get() button] setHighlighted: shouldHighlight];
  98. }
  99. //==============================================================================
  100. void handleEvent()
  101. {
  102. auto e = [NSApp currentEvent];
  103. NSEventType type = [e type];
  104. const bool isLeft = (type == NSEventTypeLeftMouseDown);
  105. const bool isRight = (type == NSEventTypeRightMouseDown);
  106. if (owner.isCurrentlyBlockedByAnotherModalComponent())
  107. {
  108. if (isLeft || isRight)
  109. if (auto* current = Component::getCurrentlyModalComponent())
  110. current->inputAttemptWhenModal();
  111. }
  112. else
  113. {
  114. auto eventMods = ComponentPeer::getCurrentModifiersRealtime();
  115. if (([e modifierFlags] & NSEventModifierFlagCommand) != 0)
  116. eventMods = eventMods.withFlags (ModifierKeys::commandModifier);
  117. auto now = Time::getCurrentTime();
  118. auto mouseSource = Desktop::getInstance().getMainMouseSource();
  119. auto pressure = (float) e.pressure;
  120. if (isLeft || isRight)
  121. {
  122. owner.mouseDown ({ mouseSource, {},
  123. eventMods.withFlags (isLeft ? ModifierKeys::leftButtonModifier
  124. : ModifierKeys::rightButtonModifier),
  125. pressure,
  126. MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation,
  127. MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY,
  128. &owner, &owner, now, {}, now, 1, false });
  129. owner.mouseUp ({ mouseSource, {},
  130. eventMods.withoutMouseButtons(),
  131. pressure,
  132. MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation,
  133. MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY,
  134. &owner, &owner, now, {}, now, 1, false });
  135. }
  136. else if (type == NSEventTypeMouseMoved)
  137. {
  138. owner.mouseMove (MouseEvent (mouseSource, {}, eventMods, pressure,
  139. MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation,
  140. MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY,
  141. &owner, &owner, now, {}, now, 1, false));
  142. }
  143. }
  144. }
  145. //==============================================================================
  146. class ButtonEventForwarderClass : public ObjCClass<NSObject>
  147. {
  148. public:
  149. ButtonEventForwarderClass() : ObjCClass<NSObject> ("JUCEButtonEventForwarderClass_")
  150. {
  151. addIvar<ButtonBasedStatusItem*> ("owner");
  152. addMethod (@selector (handleEvent:), handleEvent, "v@:@");
  153. registerClass();
  154. }
  155. static ButtonBasedStatusItem* getOwner (id self) { return getIvar<ButtonBasedStatusItem*> (self, "owner"); }
  156. static void setOwner (id self, ButtonBasedStatusItem* owner) { object_setInstanceVariable (self, "owner", owner); }
  157. private:
  158. static void handleEvent (id self, SEL, id)
  159. {
  160. if (auto* owner = getOwner (self))
  161. owner->handleEvent();
  162. }
  163. };
  164. //==============================================================================
  165. NSUniquePtr<NSObject> eventForwarder;
  166. };
  167. //==============================================================================
  168. struct ViewBasedStatusItem : public StatusItemContainer
  169. {
  170. //==============================================================================
  171. ViewBasedStatusItem (SystemTrayIconComponent& iconComp, const Image& im)
  172. : StatusItemContainer (iconComp, im)
  173. {
  174. static SystemTrayViewClass cls;
  175. view.reset ([cls.createInstance() init]);
  176. SystemTrayViewClass::setOwner (view.get(), this);
  177. SystemTrayViewClass::setImage (view.get(), statusIcon.get());
  178. setIconSize();
  179. statusItem.reset ([[[NSStatusBar systemStatusBar] statusItemWithLength: NSSquareStatusItemLength] retain]);
  180. [statusItem.get() setView: view.get()];
  181. SystemTrayViewClass::frameChanged (view.get(), SEL(), nullptr);
  182. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
  183. [[NSNotificationCenter defaultCenter] addObserver: view.get()
  184. selector: @selector (frameChanged:)
  185. name: NSWindowDidMoveNotification
  186. object: nil];
  187. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  188. }
  189. ~ViewBasedStatusItem() override
  190. {
  191. [[NSNotificationCenter defaultCenter] removeObserver: view.get()];
  192. [[NSStatusBar systemStatusBar] removeStatusItem: statusItem.get()];
  193. SystemTrayViewClass::setOwner (view.get(), nullptr);
  194. SystemTrayViewClass::setImage (view.get(), nil);
  195. }
  196. void configureIcon() override
  197. {
  198. SystemTrayViewClass::setImage (view.get(), statusIcon.get());
  199. [statusItem.get() setView: view.get()];
  200. }
  201. void setHighlighted (bool shouldHighlight) override
  202. {
  203. isHighlighted = shouldHighlight;
  204. [view.get() setNeedsDisplay: true];
  205. }
  206. //==============================================================================
  207. void handleStatusItemAction (NSEvent* e)
  208. {
  209. NSEventType type = [e type];
  210. const bool isLeft = (type == NSEventTypeLeftMouseDown || type == NSEventTypeLeftMouseUp);
  211. const bool isRight = (type == NSEventTypeRightMouseDown || type == NSEventTypeRightMouseUp);
  212. if (owner.isCurrentlyBlockedByAnotherModalComponent())
  213. {
  214. if (isLeft || isRight)
  215. if (auto* current = Component::getCurrentlyModalComponent())
  216. current->inputAttemptWhenModal();
  217. }
  218. else
  219. {
  220. auto eventMods = ComponentPeer::getCurrentModifiersRealtime();
  221. if (([e modifierFlags] & NSEventModifierFlagCommand) != 0)
  222. eventMods = eventMods.withFlags (ModifierKeys::commandModifier);
  223. auto now = Time::getCurrentTime();
  224. auto mouseSource = Desktop::getInstance().getMainMouseSource();
  225. auto pressure = (float) e.pressure;
  226. if (isLeft || isRight) // Only mouse up is sent by the OS, so simulate a down/up
  227. {
  228. setHighlighted (true);
  229. startTimer (150);
  230. owner.mouseDown (MouseEvent (mouseSource, {},
  231. eventMods.withFlags (isLeft ? ModifierKeys::leftButtonModifier
  232. : ModifierKeys::rightButtonModifier),
  233. pressure, MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation,
  234. MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY,
  235. &owner, &owner, now, {}, now, 1, false));
  236. owner.mouseUp (MouseEvent (mouseSource, {}, eventMods.withoutMouseButtons(), pressure,
  237. MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation,
  238. MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY,
  239. &owner, &owner, now, {}, now, 1, false));
  240. }
  241. else if (type == NSEventTypeMouseMoved)
  242. {
  243. owner.mouseMove (MouseEvent (mouseSource, {}, eventMods, pressure,
  244. MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation,
  245. MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY,
  246. &owner, &owner, now, {}, now, 1, false));
  247. }
  248. }
  249. }
  250. //==============================================================================
  251. struct SystemTrayViewClass : public ObjCClass<NSControl>
  252. {
  253. SystemTrayViewClass() : ObjCClass<NSControl> ("JUCESystemTrayView_")
  254. {
  255. addIvar<ViewBasedStatusItem*> ("owner");
  256. addIvar<NSImage*> ("image");
  257. addMethod (@selector (mouseDown:), handleEventDown, "v@:@");
  258. addMethod (@selector (rightMouseDown:), handleEventDown, "v@:@");
  259. addMethod (@selector (drawRect:), drawRect, "v@:@");
  260. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
  261. addMethod (@selector (frameChanged:), frameChanged, "v@:@");
  262. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  263. registerClass();
  264. }
  265. static ViewBasedStatusItem* getOwner (id self) { return getIvar<ViewBasedStatusItem*> (self, "owner"); }
  266. static NSImage* getImage (id self) { return getIvar<NSImage*> (self, "image"); }
  267. static void setOwner (id self, ViewBasedStatusItem* owner) { object_setInstanceVariable (self, "owner", owner); }
  268. static void setImage (id self, NSImage* image) { object_setInstanceVariable (self, "image", image); }
  269. static void frameChanged (id self, SEL, NSNotification*)
  270. {
  271. if (auto* owner = getOwner (self))
  272. {
  273. NSRect r = [[[owner->statusItem.get() view] window] frame];
  274. NSRect sr = [[[NSScreen screens] objectAtIndex: 0] frame];
  275. r.origin.y = sr.size.height - r.origin.y - r.size.height;
  276. owner->owner.setBounds (convertToRectInt (r));
  277. }
  278. }
  279. private:
  280. static void handleEventDown (id self, SEL, NSEvent* e)
  281. {
  282. if (auto* owner = getOwner (self))
  283. owner->handleStatusItemAction (e);
  284. }
  285. static void drawRect (id self, SEL, NSRect)
  286. {
  287. NSRect bounds = [self bounds];
  288. if (auto* owner = getOwner (self))
  289. [owner->statusItem.get() drawStatusBarBackgroundInRect: bounds
  290. withHighlight: owner->isHighlighted];
  291. if (NSImage* const im = getImage (self))
  292. {
  293. NSSize imageSize = [im size];
  294. [im drawInRect: NSMakeRect (bounds.origin.x + ((bounds.size.width - imageSize.width) / 2.0f),
  295. bounds.origin.y + ((bounds.size.height - imageSize.height) / 2.0f),
  296. imageSize.width, imageSize.height)
  297. fromRect: NSZeroRect
  298. operation: NSCompositingOperationSourceOver
  299. fraction: 1.0f];
  300. }
  301. }
  302. };
  303. //==============================================================================
  304. NSUniquePtr<NSControl> view;
  305. bool isHighlighted = false;
  306. };
  307. //==============================================================================
  308. class SystemTrayIconComponent::Pimpl
  309. {
  310. public:
  311. //==============================================================================
  312. Pimpl (SystemTrayIconComponent& iconComp, const Image& im)
  313. {
  314. if (std::floor (NSFoundationVersionNumber) > NSFoundationVersionNumber10_10)
  315. statusItemHolder = std::make_unique<ButtonBasedStatusItem> (iconComp, im);
  316. else
  317. statusItemHolder = std::make_unique<ViewBasedStatusItem> (iconComp, im);
  318. }
  319. //==============================================================================
  320. std::unique_ptr<StatusItemContainer> statusItemHolder;
  321. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
  322. };
  323. //==============================================================================
  324. void SystemTrayIconComponent::setIconImage (const Image&, const Image& templateImage)
  325. {
  326. if (templateImage.isValid())
  327. {
  328. if (pimpl == nullptr)
  329. pimpl.reset (new Pimpl (*this, templateImage));
  330. else
  331. pimpl->statusItemHolder->updateIcon (templateImage);
  332. }
  333. else
  334. {
  335. pimpl.reset();
  336. }
  337. }
  338. void SystemTrayIconComponent::setIconTooltip (const String&)
  339. {
  340. // xxx not yet implemented!
  341. }
  342. void SystemTrayIconComponent::setHighlighted (bool shouldHighlight)
  343. {
  344. if (pimpl != nullptr)
  345. pimpl->statusItemHolder->setHighlighted (shouldHighlight);
  346. }
  347. void SystemTrayIconComponent::showInfoBubble (const String& /*title*/, const String& /*content*/)
  348. {
  349. // xxx Not implemented!
  350. }
  351. void SystemTrayIconComponent::hideInfoBubble()
  352. {
  353. // xxx Not implemented!
  354. }
  355. void* SystemTrayIconComponent::getNativeHandle() const
  356. {
  357. return pimpl != nullptr ? pimpl->statusItemHolder->statusItem.get() : nullptr;
  358. }
  359. void SystemTrayIconComponent::showDropdownMenu (const PopupMenu& menu)
  360. {
  361. if (pimpl != nullptr)
  362. pimpl->statusItemHolder->showMenu (menu);
  363. }
  364. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  365. } // namespace juce