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.

juce_mac_CarbonViewWrapperComponent.h 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  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. //==============================================================================
  22. /**
  23. Creates a floating carbon window that can be used to hold a carbon UI.
  24. This is a handy class that's designed to be inlined where needed, e.g.
  25. in the audio plugin hosting code.
  26. */
  27. class CarbonViewWrapperComponent : public Component,
  28. public ComponentMovementWatcher,
  29. public Timer
  30. {
  31. public:
  32. CarbonViewWrapperComponent()
  33. : ComponentMovementWatcher (this),
  34. carbonWindow (nil),
  35. keepPluginWindowWhenHidden (false),
  36. wrapperWindow (nil),
  37. embeddedView (0),
  38. recursiveResize (false),
  39. repaintChildOnCreation (true)
  40. {
  41. }
  42. ~CarbonViewWrapperComponent()
  43. {
  44. jassert (embeddedView == 0); // must call deleteWindow() in the subclass's destructor!
  45. }
  46. virtual HIViewRef attachView (WindowRef windowRef, HIViewRef rootView) = 0;
  47. virtual void removeView (HIViewRef embeddedView) = 0;
  48. virtual void handleMouseDown (int, int) {}
  49. virtual void handlePaint() {}
  50. virtual bool getEmbeddedViewSize (int& w, int& h)
  51. {
  52. if (embeddedView == 0)
  53. return false;
  54. HIRect bounds;
  55. HIViewGetBounds (embeddedView, &bounds);
  56. w = jmax (1, roundToInt (bounds.size.width));
  57. h = jmax (1, roundToInt (bounds.size.height));
  58. return true;
  59. }
  60. void createWindow()
  61. {
  62. if (wrapperWindow == nil)
  63. {
  64. Rect r;
  65. r.left = (short) getScreenX();
  66. r.top = (short) getScreenY();
  67. r.right = (short) (r.left + getWidth());
  68. r.bottom = (short) (r.top + getHeight());
  69. CreateNewWindow (kDocumentWindowClass,
  70. (WindowAttributes) (kWindowStandardHandlerAttribute | kWindowCompositingAttribute
  71. | kWindowNoShadowAttribute | kWindowNoTitleBarAttribute),
  72. &r, &wrapperWindow);
  73. jassert (wrapperWindow != 0);
  74. if (wrapperWindow == 0)
  75. return;
  76. carbonWindow = [[NSWindow alloc] initWithWindowRef: wrapperWindow];
  77. [getOwnerWindow() addChildWindow: carbonWindow
  78. ordered: NSWindowAbove];
  79. embeddedView = attachView (wrapperWindow, HIViewGetRoot (wrapperWindow));
  80. // Check for the plugin creating its own floating window, and if there is one,
  81. // we need to reparent it to make it visible..
  82. if (carbonWindow.childWindows.count > 0)
  83. if (NSWindow* floatingChildWindow = [[carbonWindow childWindows] objectAtIndex: 0])
  84. [getOwnerWindow() addChildWindow: floatingChildWindow
  85. ordered: NSWindowAbove];
  86. EventTypeSpec windowEventTypes[] =
  87. {
  88. { kEventClassWindow, kEventWindowGetClickActivation },
  89. { kEventClassWindow, kEventWindowHandleDeactivate },
  90. { kEventClassWindow, kEventWindowBoundsChanging },
  91. { kEventClassMouse, kEventMouseDown },
  92. { kEventClassMouse, kEventMouseMoved },
  93. { kEventClassMouse, kEventMouseDragged },
  94. { kEventClassMouse, kEventMouseUp },
  95. { kEventClassWindow, kEventWindowDrawContent },
  96. { kEventClassWindow, kEventWindowShown },
  97. { kEventClassWindow, kEventWindowHidden }
  98. };
  99. EventHandlerUPP upp = NewEventHandlerUPP (carbonEventCallback);
  100. InstallWindowEventHandler (wrapperWindow, upp,
  101. sizeof (windowEventTypes) / sizeof (EventTypeSpec),
  102. windowEventTypes, this, &eventHandlerRef);
  103. setOurSizeToEmbeddedViewSize();
  104. setEmbeddedWindowToOurSize();
  105. creationTime = Time::getCurrentTime();
  106. }
  107. }
  108. void deleteWindow()
  109. {
  110. removeView (embeddedView);
  111. embeddedView = 0;
  112. if (wrapperWindow != nil)
  113. {
  114. NSWindow* ownerWindow = getOwnerWindow();
  115. if ([[ownerWindow childWindows] count] > 0)
  116. {
  117. [ownerWindow removeChildWindow: carbonWindow];
  118. [carbonWindow close];
  119. }
  120. RemoveEventHandler (eventHandlerRef);
  121. DisposeWindow (wrapperWindow);
  122. wrapperWindow = nil;
  123. }
  124. }
  125. //==============================================================================
  126. void setOurSizeToEmbeddedViewSize()
  127. {
  128. int w, h;
  129. if (getEmbeddedViewSize (w, h))
  130. {
  131. if (w != getWidth() || h != getHeight())
  132. {
  133. startTimer (50);
  134. setSize (w, h);
  135. if (Component* p = getParentComponent())
  136. p->setSize (w, h);
  137. }
  138. else
  139. {
  140. startTimer (jlimit (50, 500, getTimerInterval() + 20));
  141. }
  142. }
  143. else
  144. {
  145. stopTimer();
  146. }
  147. }
  148. void setEmbeddedWindowToOurSize()
  149. {
  150. if (! recursiveResize)
  151. {
  152. recursiveResize = true;
  153. if (embeddedView != 0)
  154. {
  155. HIRect r;
  156. r.origin.x = 0;
  157. r.origin.y = 0;
  158. r.size.width = (float) getWidth();
  159. r.size.height = (float) getHeight();
  160. HIViewSetFrame (embeddedView, &r);
  161. }
  162. if (wrapperWindow != nil)
  163. {
  164. jassert (getTopLevelComponent()->getDesktopScaleFactor() == 1.0f);
  165. Rectangle<int> screenBounds (getScreenBounds() * Desktop::getInstance().getGlobalScaleFactor());
  166. Rect wr;
  167. wr.left = (short) screenBounds.getX();
  168. wr.top = (short) screenBounds.getY();
  169. wr.right = (short) screenBounds.getRight();
  170. wr.bottom = (short) screenBounds.getBottom();
  171. SetWindowBounds (wrapperWindow, kWindowContentRgn, &wr);
  172. // This group stuff is mainly a workaround for Mackie plugins like FinalMix..
  173. WindowGroupRef group = GetWindowGroup (wrapperWindow);
  174. WindowRef attachedWindow;
  175. if (GetIndexedWindow (group, 2, kWindowGroupContentsReturnWindows, &attachedWindow) == noErr)
  176. {
  177. SelectWindow (attachedWindow);
  178. ActivateWindow (attachedWindow, TRUE);
  179. HideWindow (wrapperWindow);
  180. }
  181. ShowWindow (wrapperWindow);
  182. }
  183. recursiveResize = false;
  184. }
  185. }
  186. void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override
  187. {
  188. setEmbeddedWindowToOurSize();
  189. }
  190. // (overridden to intercept movements of the top-level window)
  191. void componentMovedOrResized (Component& component, bool wasMoved, bool wasResized) override
  192. {
  193. ComponentMovementWatcher::componentMovedOrResized (component, wasMoved, wasResized);
  194. if (&component == getTopLevelComponent())
  195. setEmbeddedWindowToOurSize();
  196. }
  197. void componentPeerChanged() override
  198. {
  199. deleteWindow();
  200. createWindow();
  201. }
  202. void componentVisibilityChanged() override
  203. {
  204. if (isShowing())
  205. createWindow();
  206. else if (! keepPluginWindowWhenHidden)
  207. deleteWindow();
  208. setEmbeddedWindowToOurSize();
  209. }
  210. static void recursiveHIViewRepaint (HIViewRef view)
  211. {
  212. HIViewSetNeedsDisplay (view, true);
  213. HIViewRef child = HIViewGetFirstSubview (view);
  214. while (child != 0)
  215. {
  216. recursiveHIViewRepaint (child);
  217. child = HIViewGetNextView (child);
  218. }
  219. }
  220. void timerCallback() override
  221. {
  222. if (isShowing())
  223. {
  224. setOurSizeToEmbeddedViewSize();
  225. // To avoid strange overpainting problems when the UI is first opened, we'll
  226. // repaint it a few times during the first second that it's on-screen..
  227. if (repaintChildOnCreation && (Time::getCurrentTime() - creationTime).inMilliseconds() < 1000)
  228. recursiveHIViewRepaint (HIViewGetRoot (wrapperWindow));
  229. }
  230. }
  231. void setRepaintsChildHIViewWhenCreated (bool b) noexcept
  232. {
  233. repaintChildOnCreation = b;
  234. }
  235. OSStatus carbonEventHandler (EventHandlerCallRef /*nextHandlerRef*/, EventRef event)
  236. {
  237. switch (GetEventKind (event))
  238. {
  239. case kEventWindowHandleDeactivate:
  240. ActivateWindow (wrapperWindow, TRUE);
  241. return noErr;
  242. case kEventWindowGetClickActivation:
  243. {
  244. getTopLevelComponent()->toFront (false);
  245. [carbonWindow makeKeyAndOrderFront: nil];
  246. ClickActivationResult howToHandleClick = kActivateAndHandleClick;
  247. SetEventParameter (event, kEventParamClickActivation, typeClickActivationResult,
  248. sizeof (ClickActivationResult), &howToHandleClick);
  249. if (embeddedView != 0)
  250. HIViewSetNeedsDisplay (embeddedView, true);
  251. return noErr;
  252. }
  253. }
  254. return eventNotHandledErr;
  255. }
  256. static pascal OSStatus carbonEventCallback (EventHandlerCallRef nextHandlerRef, EventRef event, void* userData)
  257. {
  258. return ((CarbonViewWrapperComponent*) userData)->carbonEventHandler (nextHandlerRef, event);
  259. }
  260. NSWindow* carbonWindow;
  261. bool keepPluginWindowWhenHidden;
  262. protected:
  263. WindowRef wrapperWindow;
  264. HIViewRef embeddedView;
  265. bool recursiveResize, repaintChildOnCreation;
  266. Time creationTime;
  267. EventHandlerRef eventHandlerRef;
  268. NSWindow* getOwnerWindow() const { return [((NSView*) getWindowHandle()) window]; }
  269. };
  270. //==============================================================================
  271. // Non-public utility function that hosts can use if they need to get hold of the
  272. // internals of a carbon wrapper window..
  273. void* getCarbonWindow (Component* possibleCarbonComponent)
  274. {
  275. if (CarbonViewWrapperComponent* cv = dynamic_cast<CarbonViewWrapperComponent*> (possibleCarbonComponent))
  276. return cv->carbonWindow;
  277. return nullptr;
  278. }
  279. } // namespace juce