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.8KB

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