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.

345 lines
12KB

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