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