/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-11 by Raw Material Software Ltd. ------------------------------------------------------------------------------ JUCE can be redistributed and/or modified under the terms of the GNU General Public License (Version 2), as published by the Free Software Foundation. A copy of the license is included in the JUCE distribution, or can be found online at www.gnu.org/licenses. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.rawmaterialsoftware.com/juce for more information. ============================================================================== */ #ifndef __JUCE_MAC_CARBONVIEWWRAPPERCOMPONENT_JUCEHEADER__ #define __JUCE_MAC_CARBONVIEWWRAPPERCOMPONENT_JUCEHEADER__ //============================================================================== /** Creates a floating carbon window that can be used to hold a carbon UI. This is a handy class that's designed to be inlined where needed, e.g. in the audio plugin hosting code. */ class CarbonViewWrapperComponent : public Component, public ComponentMovementWatcher, public Timer { public: CarbonViewWrapperComponent() : ComponentMovementWatcher (this), wrapperWindow (0), carbonWindow (0), embeddedView (0), recursiveResize (false) { } virtual ~CarbonViewWrapperComponent() { jassert (embeddedView == 0); // must call deleteWindow() in the subclass's destructor! } virtual HIViewRef attachView (WindowRef windowRef, HIViewRef rootView) = 0; virtual void removeView (HIViewRef embeddedView) = 0; virtual void mouseDown (int, int) {} virtual void paint() {} virtual bool getEmbeddedViewSize (int& w, int& h) { if (embeddedView == 0) return false; HIRect bounds; HIViewGetBounds (embeddedView, &bounds); w = jmax (1, roundToInt (bounds.size.width)); h = jmax (1, roundToInt (bounds.size.height)); return true; } void createWindow() { if (wrapperWindow == 0) { Rect r; r.left = getScreenX(); r.top = getScreenY(); r.right = r.left + getWidth(); r.bottom = r.top + getHeight(); CreateNewWindow (kDocumentWindowClass, (WindowAttributes) (kWindowStandardHandlerAttribute | kWindowCompositingAttribute | kWindowNoShadowAttribute | kWindowNoTitleBarAttribute), &r, &wrapperWindow); jassert (wrapperWindow != 0); if (wrapperWindow == 0) return; carbonWindow = [[NSWindow alloc] initWithWindowRef: wrapperWindow]; [getOwnerWindow() addChildWindow: carbonWindow ordered: NSWindowAbove]; embeddedView = attachView (wrapperWindow, HIViewGetRoot (wrapperWindow)); EventTypeSpec windowEventTypes[] = { { kEventClassWindow, kEventWindowGetClickActivation }, { kEventClassWindow, kEventWindowHandleDeactivate }, { kEventClassWindow, kEventWindowBoundsChanging }, { kEventClassMouse, kEventMouseDown }, { kEventClassMouse, kEventMouseMoved }, { kEventClassMouse, kEventMouseDragged }, { kEventClassMouse, kEventMouseUp}, { kEventClassWindow, kEventWindowDrawContent }, { kEventClassWindow, kEventWindowShown }, { kEventClassWindow, kEventWindowHidden } }; EventHandlerUPP upp = NewEventHandlerUPP (carbonEventCallback); InstallWindowEventHandler (wrapperWindow, upp, sizeof (windowEventTypes) / sizeof (EventTypeSpec), windowEventTypes, this, &eventHandlerRef); setOurSizeToEmbeddedViewSize(); setEmbeddedWindowToOurSize(); creationTime = Time::getCurrentTime(); } } void deleteWindow() { removeView (embeddedView); embeddedView = 0; if (wrapperWindow != 0) { NSWindow* ownerWindow = getOwnerWindow(); if ([[ownerWindow childWindows] count] > 0) { [ownerWindow removeChildWindow: carbonWindow]; [carbonWindow close]; } RemoveEventHandler (eventHandlerRef); DisposeWindow (wrapperWindow); wrapperWindow = 0; } } //============================================================================== void setOurSizeToEmbeddedViewSize() { int w, h; if (getEmbeddedViewSize (w, h)) { if (w != getWidth() || h != getHeight()) { startTimer (50); setSize (w, h); if (getParentComponent() != nullptr) getParentComponent()->setSize (w, h); } else { startTimer (jlimit (50, 500, getTimerInterval() + 20)); } } else { stopTimer(); } } void setEmbeddedWindowToOurSize() { if (! recursiveResize) { recursiveResize = true; if (embeddedView != 0) { HIRect r; r.origin.x = 0; r.origin.y = 0; r.size.width = (float) getWidth(); r.size.height = (float) getHeight(); HIViewSetFrame (embeddedView, &r); } if (wrapperWindow != 0) { Rect wr; wr.left = getScreenX(); wr.top = getScreenY(); wr.right = wr.left + getWidth(); wr.bottom = wr.top + getHeight(); SetWindowBounds (wrapperWindow, kWindowContentRgn, &wr); ShowWindow (wrapperWindow); } recursiveResize = false; } } void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) { setEmbeddedWindowToOurSize(); } void componentPeerChanged() { deleteWindow(); createWindow(); } void componentVisibilityChanged() { if (isShowing()) createWindow(); else deleteWindow(); setEmbeddedWindowToOurSize(); } static void recursiveHIViewRepaint (HIViewRef view) { HIViewSetNeedsDisplay (view, true); HIViewRef child = HIViewGetFirstSubview (view); while (child != 0) { recursiveHIViewRepaint (child); child = HIViewGetNextView (child); } } void timerCallback() { setOurSizeToEmbeddedViewSize(); // To avoid strange overpainting problems when the UI is first opened, we'll // repaint it a few times during the first second that it's on-screen.. if ((Time::getCurrentTime() - creationTime).inMilliseconds() < 1000) recursiveHIViewRepaint (HIViewGetRoot (wrapperWindow)); } OSStatus carbonEventHandler (EventHandlerCallRef /*nextHandlerRef*/, EventRef event) { switch (GetEventKind (event)) { case kEventWindowHandleDeactivate: ActivateWindow (wrapperWindow, TRUE); return noErr; case kEventWindowGetClickActivation: { getTopLevelComponent()->toFront (false); [carbonWindow makeKeyAndOrderFront: nil]; ClickActivationResult howToHandleClick = kActivateAndHandleClick; SetEventParameter (event, kEventParamClickActivation, typeClickActivationResult, sizeof (ClickActivationResult), &howToHandleClick); HIViewSetNeedsDisplay (embeddedView, true); return noErr; } } return eventNotHandledErr; } static pascal OSStatus carbonEventCallback (EventHandlerCallRef nextHandlerRef, EventRef event, void* userData) { return ((CarbonViewWrapperComponent*) userData)->carbonEventHandler (nextHandlerRef, event); } protected: WindowRef wrapperWindow; NSWindow* carbonWindow; HIViewRef embeddedView; bool recursiveResize; Time creationTime; EventHandlerRef eventHandlerRef; NSWindow* getOwnerWindow() const { return [((NSView*) getWindowHandle()) window]; } }; #endif // __JUCE_MAC_CARBONVIEWWRAPPERCOMPONENT_JUCEHEADER__