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.

344 lines
11KB

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