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.

252 lines
8.9KB

  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. extern NSMenu* createNSMenu (const PopupMenu&, const String& name, int topLevelMenuId,
  22. int topLevelIndex, bool addDelegate);
  23. class SystemTrayIconComponent::Pimpl : private Timer
  24. {
  25. public:
  26. //==============================================================================
  27. Pimpl (SystemTrayIconComponent& iconComp, const Image& im)
  28. : owner (iconComp), statusIcon (imageToNSImage (im))
  29. {
  30. static ButtonEventForwarderClass cls;
  31. eventForwarder.reset ([cls.createInstance() init]);
  32. ButtonEventForwarderClass::setOwner (eventForwarder.get(), this);
  33. configureIcon();
  34. statusItem.reset ([[[NSStatusBar systemStatusBar] statusItemWithLength: NSSquareStatusItemLength] retain]);
  35. auto button = [statusItem.get() button];
  36. button.image = statusIcon.get();
  37. button.target = eventForwarder.get();
  38. button.action = @selector (handleEvent:);
  39. #if defined (MAC_OS_X_VERSION_10_12) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12
  40. [button sendActionOn: NSEventMaskLeftMouseDown | NSEventMaskRightMouseDown | NSEventMaskScrollWheel];
  41. #else
  42. [button sendActionOn: NSLeftMouseDownMask | NSRightMouseDownMask | NSScrollWheelMask];
  43. #endif
  44. }
  45. //==============================================================================
  46. void updateIcon (const Image& newImage)
  47. {
  48. statusIcon.reset (imageToNSImage (newImage));
  49. configureIcon();
  50. [statusItem.get() button].image = statusIcon.get();
  51. }
  52. void setHighlighted (bool shouldHighlight)
  53. {
  54. [[statusItem.get() button] setHighlighted: shouldHighlight];
  55. }
  56. void showMenu (const PopupMenu& menu)
  57. {
  58. if (NSMenu* m = createNSMenu (menu, "MenuBarItem", -2, -3, true))
  59. {
  60. setHighlighted (true);
  61. stopTimer();
  62. // There's currently no good alternative to this...
  63. #if JUCE_CLANG && ! (defined (MAC_OS_X_VERSION_10_16) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_16)
  64. #pragma clang diagnostic push
  65. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  66. #define JUCE_DEPRECATION_IGNORED 1
  67. #endif
  68. [statusItem.get() popUpStatusItemMenu: m];
  69. #if JUCE_DEPRECATION_IGNORED
  70. #pragma clang diagnostic pop
  71. #undef JUCE_DEPRECATION_IGNORED
  72. #endif
  73. startTimer (1);
  74. }
  75. }
  76. //==============================================================================
  77. NSStatusItem* getStatusItem()
  78. {
  79. return statusItem.get();
  80. }
  81. //==============================================================================
  82. void handleEvent()
  83. {
  84. auto e = [NSApp currentEvent];
  85. NSEventType type = [e type];
  86. const bool isLeft = (type == NSEventTypeLeftMouseDown);
  87. const bool isRight = (type == NSEventTypeRightMouseDown);
  88. if (owner.isCurrentlyBlockedByAnotherModalComponent())
  89. {
  90. if (isLeft || isRight)
  91. if (auto* current = Component::getCurrentlyModalComponent())
  92. current->inputAttemptWhenModal();
  93. }
  94. else
  95. {
  96. auto eventMods = ComponentPeer::getCurrentModifiersRealtime();
  97. if (([e modifierFlags] & NSEventModifierFlagCommand) != 0)
  98. eventMods = eventMods.withFlags (ModifierKeys::commandModifier);
  99. auto now = Time::getCurrentTime();
  100. auto mouseSource = Desktop::getInstance().getMainMouseSource();
  101. auto pressure = (float) e.pressure;
  102. if (isLeft || isRight)
  103. {
  104. owner.mouseDown ({ mouseSource, {},
  105. eventMods.withFlags (isLeft ? ModifierKeys::leftButtonModifier
  106. : ModifierKeys::rightButtonModifier),
  107. pressure,
  108. MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation,
  109. MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY,
  110. &owner, &owner, now, {}, now, 1, false });
  111. owner.mouseUp ({ mouseSource, {},
  112. eventMods.withoutMouseButtons(),
  113. pressure,
  114. MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation,
  115. MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY,
  116. &owner, &owner, now, {}, now, 1, false });
  117. }
  118. else if (type == NSEventTypeMouseMoved)
  119. {
  120. owner.mouseMove (MouseEvent (mouseSource, {}, eventMods, pressure,
  121. MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation,
  122. MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY,
  123. &owner, &owner, now, {}, now, 1, false));
  124. }
  125. }
  126. }
  127. private:
  128. //==============================================================================
  129. void configureIcon()
  130. {
  131. [statusIcon.get() setSize: NSMakeSize (20.0f, 20.0f)];
  132. [statusIcon.get() setTemplate: true];
  133. }
  134. void timerCallback() override
  135. {
  136. stopTimer();
  137. setHighlighted (false);
  138. }
  139. //==============================================================================
  140. class ButtonEventForwarderClass : public ObjCClass<NSObject>
  141. {
  142. public:
  143. ButtonEventForwarderClass() : ObjCClass<NSObject> ("JUCEButtonEventForwarderClass_")
  144. {
  145. addIvar<Pimpl*> ("owner");
  146. addMethod (@selector (handleEvent:), handleEvent, "v@:@");
  147. registerClass();
  148. }
  149. static Pimpl* getOwner (id self) { return getIvar<Pimpl*> (self, "owner"); }
  150. static void setOwner (id self, Pimpl* owner) { object_setInstanceVariable (self, "owner", owner); }
  151. private:
  152. static void handleEvent (id self, SEL, id)
  153. {
  154. if (auto* owner = getOwner (self))
  155. owner->handleEvent();
  156. }
  157. };
  158. //==============================================================================
  159. SystemTrayIconComponent& owner;
  160. std::unique_ptr<NSStatusItem, NSObjectDeleter> statusItem;
  161. std::unique_ptr<NSObject, NSObjectDeleter> eventForwarder;
  162. std::unique_ptr<NSImage, NSObjectDeleter> statusIcon;
  163. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
  164. };
  165. //==============================================================================
  166. void SystemTrayIconComponent::setIconImage (const Image&, const Image& templateImage)
  167. {
  168. if (templateImage.isValid())
  169. {
  170. if (pimpl == nullptr)
  171. pimpl.reset (new Pimpl (*this, templateImage));
  172. else
  173. pimpl->updateIcon (templateImage);
  174. }
  175. else
  176. {
  177. pimpl.reset();
  178. }
  179. }
  180. void SystemTrayIconComponent::setIconTooltip (const String&)
  181. {
  182. // xxx not yet implemented!
  183. }
  184. void SystemTrayIconComponent::setHighlighted (bool highlight)
  185. {
  186. if (pimpl != nullptr)
  187. pimpl->setHighlighted (highlight);
  188. }
  189. void SystemTrayIconComponent::showInfoBubble (const String& /*title*/, const String& /*content*/)
  190. {
  191. // xxx Not implemented!
  192. }
  193. void SystemTrayIconComponent::hideInfoBubble()
  194. {
  195. // xxx Not implemented!
  196. }
  197. void* SystemTrayIconComponent::getNativeHandle() const
  198. {
  199. return pimpl != nullptr ? pimpl->getStatusItem() : nullptr;
  200. }
  201. void SystemTrayIconComponent::showDropdownMenu (const PopupMenu& menu)
  202. {
  203. if (pimpl != nullptr)
  204. pimpl->showMenu (menu);
  205. }
  206. } // namespace juce