348 lines
12 KiB
C
348 lines
12 KiB
C
|
/*
|
||
|
==============================================================================
|
||
|
|
||
|
This file is part of the JUCE library.
|
||
|
Copyright (c) 2017 - ROLI Ltd.
|
||
|
|
||
|
JUCE is an open source library subject to commercial or open-source
|
||
|
licensing.
|
||
|
|
||
|
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||
|
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||
|
27th April 2017).
|
||
|
|
||
|
End User License Agreement: www.juce.com/juce-5-licence
|
||
|
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||
|
|
||
|
Or: You may also use this code under the terms of the GPL v3 (see
|
||
|
www.gnu.org/licenses).
|
||
|
|
||
|
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||
|
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||
|
DISCLAIMED.
|
||
|
|
||
|
==============================================================================
|
||
|
*/
|
||
|
|
||
|
namespace juce
|
||
|
{
|
||
|
|
||
|
//==============================================================================
|
||
|
/**
|
||
|
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.
|
||
|
|
||
|
@tags{GUI}
|
||
|
*/
|
||
|
class CarbonViewWrapperComponent : public Component,
|
||
|
public ComponentMovementWatcher,
|
||
|
public Timer
|
||
|
{
|
||
|
public:
|
||
|
CarbonViewWrapperComponent()
|
||
|
: ComponentMovementWatcher (this),
|
||
|
carbonWindow (nil),
|
||
|
keepPluginWindowWhenHidden (false),
|
||
|
wrapperWindow (nil),
|
||
|
embeddedView (0),
|
||
|
recursiveResize (false),
|
||
|
repaintChildOnCreation (true)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
~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 handleMouseDown (int, int) {}
|
||
|
virtual void handlePaint() {}
|
||
|
|
||
|
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 == nil)
|
||
|
{
|
||
|
Rect r;
|
||
|
r.left = (short) getScreenX();
|
||
|
r.top = (short) getScreenY();
|
||
|
r.right = (short) (r.left + getWidth());
|
||
|
r.bottom = (short) (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));
|
||
|
|
||
|
// Check for the plugin creating its own floating window, and if there is one,
|
||
|
// we need to reparent it to make it visible..
|
||
|
if (carbonWindow.childWindows.count > 0)
|
||
|
if (NSWindow* floatingChildWindow = [[carbonWindow childWindows] objectAtIndex: 0])
|
||
|
[getOwnerWindow() addChildWindow: floatingChildWindow
|
||
|
ordered: NSWindowAbove];
|
||
|
|
||
|
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 != nil)
|
||
|
{
|
||
|
NSWindow* ownerWindow = getOwnerWindow();
|
||
|
|
||
|
if ([[ownerWindow childWindows] count] > 0)
|
||
|
{
|
||
|
[ownerWindow removeChildWindow: carbonWindow];
|
||
|
[carbonWindow close];
|
||
|
}
|
||
|
|
||
|
RemoveEventHandler (eventHandlerRef);
|
||
|
DisposeWindow (wrapperWindow);
|
||
|
wrapperWindow = nil;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
void setOurSizeToEmbeddedViewSize()
|
||
|
{
|
||
|
int w, h;
|
||
|
if (getEmbeddedViewSize (w, h))
|
||
|
{
|
||
|
if (w != getWidth() || h != getHeight())
|
||
|
{
|
||
|
startTimer (50);
|
||
|
setSize (w, h);
|
||
|
|
||
|
if (Component* p = getParentComponent())
|
||
|
p->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 != nil)
|
||
|
{
|
||
|
jassert (getTopLevelComponent()->getDesktopScaleFactor() == 1.0f);
|
||
|
Rectangle<int> screenBounds (getScreenBounds() * Desktop::getInstance().getGlobalScaleFactor());
|
||
|
|
||
|
Rect wr;
|
||
|
wr.left = (short) screenBounds.getX();
|
||
|
wr.top = (short) screenBounds.getY();
|
||
|
wr.right = (short) screenBounds.getRight();
|
||
|
wr.bottom = (short) screenBounds.getBottom();
|
||
|
|
||
|
SetWindowBounds (wrapperWindow, kWindowContentRgn, &wr);
|
||
|
|
||
|
// This group stuff is mainly a workaround for Mackie plugins like FinalMix..
|
||
|
WindowGroupRef group = GetWindowGroup (wrapperWindow);
|
||
|
WindowRef attachedWindow;
|
||
|
|
||
|
if (GetIndexedWindow (group, 2, kWindowGroupContentsReturnWindows, &attachedWindow) == noErr)
|
||
|
{
|
||
|
SelectWindow (attachedWindow);
|
||
|
ActivateWindow (attachedWindow, TRUE);
|
||
|
HideWindow (wrapperWindow);
|
||
|
}
|
||
|
|
||
|
ShowWindow (wrapperWindow);
|
||
|
}
|
||
|
|
||
|
recursiveResize = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override
|
||
|
{
|
||
|
setEmbeddedWindowToOurSize();
|
||
|
}
|
||
|
|
||
|
// (overridden to intercept movements of the top-level window)
|
||
|
void componentMovedOrResized (Component& component, bool wasMoved, bool wasResized) override
|
||
|
{
|
||
|
ComponentMovementWatcher::componentMovedOrResized (component, wasMoved, wasResized);
|
||
|
|
||
|
if (&component == getTopLevelComponent())
|
||
|
setEmbeddedWindowToOurSize();
|
||
|
}
|
||
|
|
||
|
void componentPeerChanged() override
|
||
|
{
|
||
|
deleteWindow();
|
||
|
createWindow();
|
||
|
}
|
||
|
|
||
|
void componentVisibilityChanged() override
|
||
|
{
|
||
|
if (isShowing())
|
||
|
createWindow();
|
||
|
else if (! keepPluginWindowWhenHidden)
|
||
|
deleteWindow();
|
||
|
|
||
|
setEmbeddedWindowToOurSize();
|
||
|
}
|
||
|
|
||
|
static void recursiveHIViewRepaint (HIViewRef view)
|
||
|
{
|
||
|
HIViewSetNeedsDisplay (view, true);
|
||
|
HIViewRef child = HIViewGetFirstSubview (view);
|
||
|
|
||
|
while (child != 0)
|
||
|
{
|
||
|
recursiveHIViewRepaint (child);
|
||
|
child = HIViewGetNextView (child);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void timerCallback() override
|
||
|
{
|
||
|
if (isShowing())
|
||
|
{
|
||
|
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 (repaintChildOnCreation && (Time::getCurrentTime() - creationTime).inMilliseconds() < 1000)
|
||
|
recursiveHIViewRepaint (HIViewGetRoot (wrapperWindow));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void setRepaintsChildHIViewWhenCreated (bool b) noexcept
|
||
|
{
|
||
|
repaintChildOnCreation = b;
|
||
|
}
|
||
|
|
||
|
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);
|
||
|
|
||
|
if (embeddedView != 0)
|
||
|
HIViewSetNeedsDisplay (embeddedView, true);
|
||
|
|
||
|
return noErr;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return eventNotHandledErr;
|
||
|
}
|
||
|
|
||
|
static pascal OSStatus carbonEventCallback (EventHandlerCallRef nextHandlerRef, EventRef event, void* userData)
|
||
|
{
|
||
|
return ((CarbonViewWrapperComponent*) userData)->carbonEventHandler (nextHandlerRef, event);
|
||
|
}
|
||
|
|
||
|
NSWindow* carbonWindow;
|
||
|
bool keepPluginWindowWhenHidden;
|
||
|
|
||
|
protected:
|
||
|
WindowRef wrapperWindow;
|
||
|
HIViewRef embeddedView;
|
||
|
bool recursiveResize, repaintChildOnCreation;
|
||
|
Time creationTime;
|
||
|
|
||
|
EventHandlerRef eventHandlerRef;
|
||
|
|
||
|
NSWindow* getOwnerWindow() const { return [((NSView*) getWindowHandle()) window]; }
|
||
|
};
|
||
|
|
||
|
//==============================================================================
|
||
|
// Non-public utility function that hosts can use if they need to get hold of the
|
||
|
// internals of a carbon wrapper window..
|
||
|
void* getCarbonWindow (Component* possibleCarbonComponent)
|
||
|
{
|
||
|
if (CarbonViewWrapperComponent* cv = dynamic_cast<CarbonViewWrapperComponent*> (possibleCarbonComponent))
|
||
|
return cv->carbonWindow;
|
||
|
|
||
|
return nullptr;
|
||
|
}
|
||
|
|
||
|
} // namespace juce
|