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.

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