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.

348 lines
12KB

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