Audio plugin host https://kx.studio/carla
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.

282 lines
10.0KB

  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. namespace MouseCursorHelpers
  22. {
  23. extern NSImage* createNSImage (const Image&, float scaleFactor = 1.f);
  24. }
  25. extern NSMenu* createNSMenu (const PopupMenu&, const String& name, int topLevelMenuId,
  26. int topLevelIndex, bool addDelegate);
  27. class SystemTrayIconComponent::Pimpl : private Timer
  28. {
  29. public:
  30. Pimpl (SystemTrayIconComponent& iconComp, const Image& im)
  31. : owner (iconComp), statusIcon (MouseCursorHelpers::createNSImage (im))
  32. {
  33. static SystemTrayViewClass cls;
  34. view = [cls.createInstance() init];
  35. SystemTrayViewClass::setOwner (view, this);
  36. SystemTrayViewClass::setImage (view, statusIcon);
  37. setIconSize();
  38. statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength: NSSquareStatusItemLength] retain];
  39. [statusItem setView: view];
  40. SystemTrayViewClass::frameChanged (view, SEL(), nullptr);
  41. [[NSNotificationCenter defaultCenter] addObserver: view
  42. selector: @selector (frameChanged:)
  43. name: NSWindowDidMoveNotification
  44. object: nil];
  45. }
  46. ~Pimpl()
  47. {
  48. [[NSNotificationCenter defaultCenter] removeObserver: view];
  49. [[NSStatusBar systemStatusBar] removeStatusItem: statusItem];
  50. SystemTrayViewClass::setOwner (view, nullptr);
  51. SystemTrayViewClass::setImage (view, nil);
  52. [statusItem release];
  53. [view release];
  54. [statusIcon release];
  55. }
  56. void updateIcon (const Image& newImage)
  57. {
  58. [statusIcon release];
  59. statusIcon = MouseCursorHelpers::createNSImage (newImage);
  60. setIconSize();
  61. SystemTrayViewClass::setImage (view, statusIcon);
  62. [statusItem setView: view];
  63. }
  64. void setHighlighted (bool shouldHighlight)
  65. {
  66. isHighlighted = shouldHighlight;
  67. [view setNeedsDisplay: true];
  68. }
  69. void handleStatusItemAction (NSEvent* e)
  70. {
  71. NSEventType type = [e type];
  72. const bool isLeft = (type == NSEventTypeLeftMouseDown || type == NSEventTypeLeftMouseUp);
  73. const bool isRight = (type == NSEventTypeRightMouseDown || type == NSEventTypeRightMouseUp);
  74. if (owner.isCurrentlyBlockedByAnotherModalComponent())
  75. {
  76. if (isLeft || isRight)
  77. if (auto* current = Component::getCurrentlyModalComponent())
  78. current->inputAttemptWhenModal();
  79. }
  80. else
  81. {
  82. auto eventMods = ModifierKeys::getCurrentModifiersRealtime();
  83. if (([e modifierFlags] & NSEventModifierFlagCommand) != 0)
  84. eventMods = eventMods.withFlags (ModifierKeys::commandModifier);
  85. auto now = Time::getCurrentTime();
  86. auto mouseSource = Desktop::getInstance().getMainMouseSource();
  87. auto pressure = (float) e.pressure;
  88. if (isLeft || isRight) // Only mouse up is sent by the OS, so simulate a down/up
  89. {
  90. setHighlighted (true);
  91. startTimer (150);
  92. owner.mouseDown (MouseEvent (mouseSource, {},
  93. eventMods.withFlags (isLeft ? ModifierKeys::leftButtonModifier
  94. : ModifierKeys::rightButtonModifier),
  95. pressure, MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation,
  96. MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY,
  97. &owner, &owner, now, {}, now, 1, false));
  98. owner.mouseUp (MouseEvent (mouseSource, {}, eventMods.withoutMouseButtons(), pressure,
  99. MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation,
  100. MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY,
  101. &owner, &owner, now, {}, now, 1, false));
  102. }
  103. else if (type == NSEventTypeMouseMoved)
  104. {
  105. owner.mouseMove (MouseEvent (mouseSource, {}, eventMods, pressure,
  106. MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation,
  107. MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY,
  108. &owner, &owner, now, {}, now, 1, false));
  109. }
  110. }
  111. }
  112. void showMenu (const PopupMenu& menu)
  113. {
  114. if (NSMenu* m = createNSMenu (menu, "MenuBarItem", -2, -3, true))
  115. {
  116. setHighlighted (true);
  117. stopTimer();
  118. [statusItem popUpStatusItemMenu: m];
  119. startTimer (1);
  120. }
  121. }
  122. SystemTrayIconComponent& owner;
  123. NSStatusItem* statusItem = nil;
  124. private:
  125. NSImage* statusIcon = nil;
  126. NSControl* view = nil;
  127. bool isHighlighted = false;
  128. void setIconSize()
  129. {
  130. [statusIcon setSize: NSMakeSize (20.0f, 20.0f)];
  131. }
  132. void timerCallback() override
  133. {
  134. stopTimer();
  135. setHighlighted (false);
  136. }
  137. struct SystemTrayViewClass : public ObjCClass<NSControl>
  138. {
  139. SystemTrayViewClass() : ObjCClass<NSControl> ("JUCESystemTrayView_")
  140. {
  141. addIvar<Pimpl*> ("owner");
  142. addIvar<NSImage*> ("image");
  143. addMethod (@selector (mouseDown:), handleEventDown, "v@:@");
  144. addMethod (@selector (rightMouseDown:), handleEventDown, "v@:@");
  145. addMethod (@selector (drawRect:), drawRect, "v@:@");
  146. addMethod (@selector (frameChanged:), frameChanged, "v@:@");
  147. registerClass();
  148. }
  149. static Pimpl* getOwner (id self) { return getIvar<Pimpl*> (self, "owner"); }
  150. static NSImage* getImage (id self) { return getIvar<NSImage*> (self, "image"); }
  151. static void setOwner (id self, Pimpl* owner) { object_setInstanceVariable (self, "owner", owner); }
  152. static void setImage (id self, NSImage* image) { object_setInstanceVariable (self, "image", image); }
  153. static void frameChanged (id self, SEL, NSNotification*)
  154. {
  155. if (auto* owner = getOwner (self))
  156. {
  157. NSRect r = [[[owner->statusItem view] window] frame];
  158. NSRect sr = [[[NSScreen screens] objectAtIndex: 0] frame];
  159. r.origin.y = sr.size.height - r.origin.y - r.size.height;
  160. owner->owner.setBounds (convertToRectInt (r));
  161. }
  162. }
  163. private:
  164. static void handleEventDown (id self, SEL, NSEvent* e)
  165. {
  166. if (auto* owner = getOwner (self))
  167. owner->handleStatusItemAction (e);
  168. }
  169. static void drawRect (id self, SEL, NSRect)
  170. {
  171. NSRect bounds = [self bounds];
  172. if (auto* owner = getOwner (self))
  173. [owner->statusItem drawStatusBarBackgroundInRect: bounds
  174. withHighlight: owner->isHighlighted];
  175. if (NSImage* const im = getImage (self))
  176. {
  177. NSSize imageSize = [im size];
  178. [im drawInRect: NSMakeRect (bounds.origin.x + ((bounds.size.width - imageSize.width) / 2.0f),
  179. bounds.origin.y + ((bounds.size.height - imageSize.height) / 2.0f),
  180. imageSize.width, imageSize.height)
  181. fromRect: NSZeroRect
  182. operation: NSCompositingOperationSourceOver
  183. fraction: 1.0f];
  184. }
  185. }
  186. };
  187. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
  188. };
  189. //==============================================================================
  190. void SystemTrayIconComponent::setIconImage (const Image& newImage)
  191. {
  192. if (newImage.isValid())
  193. {
  194. if (pimpl == nullptr)
  195. pimpl = new Pimpl (*this, newImage);
  196. else
  197. pimpl->updateIcon (newImage);
  198. }
  199. else
  200. {
  201. pimpl = nullptr;
  202. }
  203. }
  204. void SystemTrayIconComponent::setIconTooltip (const String&)
  205. {
  206. // xxx not yet implemented!
  207. }
  208. void SystemTrayIconComponent::setHighlighted (bool highlight)
  209. {
  210. if (pimpl != nullptr)
  211. pimpl->setHighlighted (highlight);
  212. }
  213. void SystemTrayIconComponent::showInfoBubble (const String& /*title*/, const String& /*content*/)
  214. {
  215. // xxx Not implemented!
  216. }
  217. void SystemTrayIconComponent::hideInfoBubble()
  218. {
  219. // xxx Not implemented!
  220. }
  221. void* SystemTrayIconComponent::getNativeHandle() const
  222. {
  223. return pimpl != nullptr ? pimpl->statusItem : nullptr;
  224. }
  225. void SystemTrayIconComponent::showDropdownMenu (const PopupMenu& menu)
  226. {
  227. if (pimpl != nullptr)
  228. pimpl->showMenu (menu);
  229. }
  230. } // namespace juce