migrating to the latest JUCE version

This commit is contained in:
2022-11-04 23:11:33 +01:00
committed by Nikolai Rodionov
parent 4257a0f8ba
commit faf8f18333
2796 changed files with 888518 additions and 784244 deletions

View File

@ -2,15 +2,15 @@
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
Copyright (c) 2022 - Raw Material Software Limited
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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-6-licence
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see

View File

@ -2,15 +2,15 @@
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
Copyright (c) 2022 - Raw Material Software Limited
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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-6-licence
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see

View File

@ -2,15 +2,15 @@
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
Copyright (c) 2022 - Raw Material Software Limited
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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-6-licence
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see

View File

@ -2,15 +2,15 @@
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
Copyright (c) 2022 - Raw Material Software Limited
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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-6-licence
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see

View File

@ -1,176 +1,176 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-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
{
class AndroidViewComponent::Pimpl : public ComponentMovementWatcher
{
public:
Pimpl (const LocalRef<jobject>& v, Component& comp)
: ComponentMovementWatcher (&comp),
view (v),
owner (comp)
{
if (owner.isShowing())
componentPeerChanged();
}
~Pimpl() override
{
removeFromParent();
}
void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override
{
auto* topComp = owner.getTopLevelComponent();
if (topComp->getPeer() != nullptr)
{
auto pos = topComp->getLocalPoint (&owner, Point<int>());
Rectangle<int> r (pos.x, pos.y, owner.getWidth(), owner.getHeight());
r *= Desktop::getInstance().getDisplays().getPrimaryDisplay()->scale;
getEnv()->CallVoidMethod (view, AndroidView.layout, r.getX(), r.getY(),
r.getRight(), r.getBottom());
}
}
void componentPeerChanged() override
{
auto* peer = owner.getPeer();
if (currentPeer != peer)
{
removeFromParent();
currentPeer = peer;
addToParent();
}
enum
{
VISIBLE = 0,
INVISIBLE = 4
};
getEnv()->CallVoidMethod (view, AndroidView.setVisibility, owner.isShowing() ? VISIBLE : INVISIBLE);
}
void componentVisibilityChanged() override
{
componentPeerChanged();
}
void componentBroughtToFront (Component& comp) override
{
ComponentMovementWatcher::componentBroughtToFront (comp);
}
Rectangle<int> getViewBounds() const
{
auto* env = getEnv();
int width = env->CallIntMethod (view, AndroidView.getWidth);
int height = env->CallIntMethod (view, AndroidView.getHeight);
return Rectangle<int> (width, height);
}
GlobalRef view;
private:
void addToParent()
{
if (currentPeer != nullptr)
{
jobject peerView = (jobject) currentPeer->getNativeHandle();
// NB: Assuming a parent is always of ViewGroup type
auto* env = getEnv();
env->CallVoidMethod (peerView, AndroidViewGroup.addView, view.get());
componentMovedOrResized (false, false);
}
}
void removeFromParent()
{
auto* env = getEnv();
auto parentView = env->CallObjectMethod (view, AndroidView.getParent);
if (parentView != nullptr)
{
// Assuming a parent is always of ViewGroup type
env->CallVoidMethod (parentView, AndroidViewGroup.removeView, view.get());
}
}
Component& owner;
ComponentPeer* currentPeer = nullptr;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
};
//==============================================================================
AndroidViewComponent::AndroidViewComponent()
{
}
AndroidViewComponent::~AndroidViewComponent() {}
void AndroidViewComponent::setView (void* view)
{
if (view != getView())
{
pimpl.reset();
if (view != nullptr)
{
// explicitly create a new local ref here so that we don't
// delete the users pointer
auto* env = getEnv();
auto localref = LocalRef<jobject>(env->NewLocalRef((jobject) view));
pimpl.reset (new Pimpl (localref, *this));
}
}
}
void* AndroidViewComponent::getView() const
{
return pimpl == nullptr ? nullptr : (void*) pimpl->view;
}
void AndroidViewComponent::resizeToFitView()
{
if (pimpl != nullptr)
setBounds (pimpl->getViewBounds());
}
void AndroidViewComponent::paint (Graphics&) {}
} // namespace juce
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-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
{
class AndroidViewComponent::Pimpl : public ComponentMovementWatcher
{
public:
Pimpl (const LocalRef<jobject>& v, Component& comp)
: ComponentMovementWatcher (&comp),
view (v),
owner (comp)
{
if (owner.isShowing())
componentPeerChanged();
}
~Pimpl() override
{
removeFromParent();
}
void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override
{
auto* topComp = owner.getTopLevelComponent();
if (topComp->getPeer() != nullptr)
{
auto pos = topComp->getLocalPoint (&owner, Point<int>());
Rectangle<int> r (pos.x, pos.y, owner.getWidth(), owner.getHeight());
r *= Desktop::getInstance().getDisplays().getPrimaryDisplay()->scale;
getEnv()->CallVoidMethod (view, AndroidView.layout, r.getX(), r.getY(),
r.getRight(), r.getBottom());
}
}
void componentPeerChanged() override
{
auto* peer = owner.getPeer();
if (currentPeer != peer)
{
removeFromParent();
currentPeer = peer;
addToParent();
}
enum
{
VISIBLE = 0,
INVISIBLE = 4
};
getEnv()->CallVoidMethod (view, AndroidView.setVisibility, owner.isShowing() ? VISIBLE : INVISIBLE);
}
void componentVisibilityChanged() override
{
componentPeerChanged();
}
void componentBroughtToFront (Component& comp) override
{
ComponentMovementWatcher::componentBroughtToFront (comp);
}
Rectangle<int> getViewBounds() const
{
auto* env = getEnv();
int width = env->CallIntMethod (view, AndroidView.getWidth);
int height = env->CallIntMethod (view, AndroidView.getHeight);
return Rectangle<int> (width, height);
}
GlobalRef view;
private:
void addToParent()
{
if (currentPeer != nullptr)
{
jobject peerView = (jobject) currentPeer->getNativeHandle();
// NB: Assuming a parent is always of ViewGroup type
auto* env = getEnv();
env->CallVoidMethod (peerView, AndroidViewGroup.addView, view.get());
componentMovedOrResized (false, false);
}
}
void removeFromParent()
{
auto* env = getEnv();
auto parentView = env->CallObjectMethod (view, AndroidView.getParent);
if (parentView != nullptr)
{
// Assuming a parent is always of ViewGroup type
env->CallVoidMethod (parentView, AndroidViewGroup.removeView, view.get());
}
}
Component& owner;
ComponentPeer* currentPeer = nullptr;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
};
//==============================================================================
AndroidViewComponent::AndroidViewComponent()
{
}
AndroidViewComponent::~AndroidViewComponent() {}
void AndroidViewComponent::setView (void* view)
{
if (view != getView())
{
pimpl.reset();
if (view != nullptr)
{
// explicitly create a new local ref here so that we don't
// delete the users pointer
auto* env = getEnv();
auto localref = LocalRef<jobject>(env->NewLocalRef((jobject) view));
pimpl.reset (new Pimpl (localref, *this));
}
}
}
void* AndroidViewComponent::getView() const
{
return pimpl == nullptr ? nullptr : (void*) pimpl->view;
}
void AndroidViewComponent::resizeToFitView()
{
if (pimpl != nullptr)
setBounds (pimpl->getViewBounds());
}
void AndroidViewComponent::paint (Graphics&) {}
} // namespace juce

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -2,15 +2,15 @@
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
Copyright (c) 2022 - Raw Material Software Limited
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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-6-licence
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see

View File

@ -1,151 +1,151 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-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
{
class SystemTrayIconComponent::Pimpl
{
public:
Pimpl (const Image& im, Window windowH) : image (im)
{
XWindowSystemUtilities::ScopedXLock xLock;
auto* display = XWindowSystem::getInstance()->getDisplay();
auto* screen = X11Symbols::getInstance()->xDefaultScreenOfDisplay (display);
auto screenNumber = X11Symbols::getInstance()->xScreenNumberOfScreen (screen);
String screenAtom ("_NET_SYSTEM_TRAY_S");
screenAtom << screenNumber;
Atom selectionAtom = XWindowSystemUtilities::Atoms::getCreating (display, screenAtom.toUTF8());
X11Symbols::getInstance()->xGrabServer (display);
auto managerWin = X11Symbols::getInstance()->xGetSelectionOwner (display, selectionAtom);
if (managerWin != None)
X11Symbols::getInstance()->xSelectInput (display, managerWin, StructureNotifyMask);
X11Symbols::getInstance()->xUngrabServer (display);
X11Symbols::getInstance()->xFlush (display);
if (managerWin != None)
{
XEvent ev = { 0 };
ev.xclient.type = ClientMessage;
ev.xclient.window = managerWin;
ev.xclient.message_type = XWindowSystemUtilities::Atoms::getCreating (display, "_NET_SYSTEM_TRAY_OPCODE");
ev.xclient.format = 32;
ev.xclient.data.l[0] = CurrentTime;
ev.xclient.data.l[1] = 0 /*SYSTEM_TRAY_REQUEST_DOCK*/;
ev.xclient.data.l[2] = (long) windowH;
ev.xclient.data.l[3] = 0;
ev.xclient.data.l[4] = 0;
X11Symbols::getInstance()->xSendEvent (display, managerWin, False, NoEventMask, &ev);
X11Symbols::getInstance()->xSync (display, False);
}
// For older KDE's ...
long atomData = 1;
Atom trayAtom = XWindowSystemUtilities::Atoms::getCreating (display, "KWM_DOCKWINDOW");
X11Symbols::getInstance()->xChangeProperty (display, windowH, trayAtom, trayAtom,
32, PropModeReplace, (unsigned char*) &atomData, 1);
// For more recent KDE's...
trayAtom = XWindowSystemUtilities::Atoms::getCreating (display, "_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR");
X11Symbols::getInstance()->xChangeProperty (display, windowH, trayAtom, XA_WINDOW,
32, PropModeReplace, (unsigned char*) &windowH, 1);
// A minimum size must be specified for GNOME and Xfce, otherwise the icon is displayed with a width of 1
if (auto* hints = X11Symbols::getInstance()->xAllocSizeHints())
{
hints->flags = PMinSize;
hints->min_width = 22;
hints->min_height = 22;
X11Symbols::getInstance()->xSetWMNormalHints (display, windowH, hints);
X11Symbols::getInstance()->xFree (hints);
}
}
Image image;
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
};
//==============================================================================
void SystemTrayIconComponent::setIconImage (const Image& colourImage, const Image&)
{
pimpl.reset();
if (colourImage.isValid())
{
if (! isOnDesktop())
addToDesktop (0);
pimpl.reset (new Pimpl (colourImage, (Window) getWindowHandle()));
setVisible (true);
toFront (false);
}
repaint();
}
void SystemTrayIconComponent::paint (Graphics& g)
{
if (pimpl != nullptr)
g.drawImage (pimpl->image, getLocalBounds().toFloat(),
RectanglePlacement::xLeft | RectanglePlacement::yTop | RectanglePlacement::onlyReduceInSize);
}
void SystemTrayIconComponent::setIconTooltip (const String& /*tooltip*/)
{
// xxx Not implemented!
}
void SystemTrayIconComponent::setHighlighted (bool)
{
// xxx Not implemented!
}
void SystemTrayIconComponent::showInfoBubble (const String& /*title*/, const String& /*content*/)
{
// xxx Not implemented!
}
void SystemTrayIconComponent::hideInfoBubble()
{
// xxx Not implemented!
}
void* SystemTrayIconComponent::getNativeHandle() const
{
return getWindowHandle();
}
} // namespace juce
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-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
{
class SystemTrayIconComponent::Pimpl
{
public:
Pimpl (const Image& im, Window windowH) : image (im)
{
XWindowSystemUtilities::ScopedXLock xLock;
auto* display = XWindowSystem::getInstance()->getDisplay();
auto* screen = X11Symbols::getInstance()->xDefaultScreenOfDisplay (display);
auto screenNumber = X11Symbols::getInstance()->xScreenNumberOfScreen (screen);
String screenAtom ("_NET_SYSTEM_TRAY_S");
screenAtom << screenNumber;
Atom selectionAtom = XWindowSystemUtilities::Atoms::getCreating (display, screenAtom.toUTF8());
X11Symbols::getInstance()->xGrabServer (display);
auto managerWin = X11Symbols::getInstance()->xGetSelectionOwner (display, selectionAtom);
if (managerWin != None)
X11Symbols::getInstance()->xSelectInput (display, managerWin, StructureNotifyMask);
X11Symbols::getInstance()->xUngrabServer (display);
X11Symbols::getInstance()->xFlush (display);
if (managerWin != None)
{
XEvent ev = { 0 };
ev.xclient.type = ClientMessage;
ev.xclient.window = managerWin;
ev.xclient.message_type = XWindowSystemUtilities::Atoms::getCreating (display, "_NET_SYSTEM_TRAY_OPCODE");
ev.xclient.format = 32;
ev.xclient.data.l[0] = CurrentTime;
ev.xclient.data.l[1] = 0 /*SYSTEM_TRAY_REQUEST_DOCK*/;
ev.xclient.data.l[2] = (long) windowH;
ev.xclient.data.l[3] = 0;
ev.xclient.data.l[4] = 0;
X11Symbols::getInstance()->xSendEvent (display, managerWin, False, NoEventMask, &ev);
X11Symbols::getInstance()->xSync (display, False);
}
// For older KDE's ...
long atomData = 1;
Atom trayAtom = XWindowSystemUtilities::Atoms::getCreating (display, "KWM_DOCKWINDOW");
X11Symbols::getInstance()->xChangeProperty (display, windowH, trayAtom, trayAtom,
32, PropModeReplace, (unsigned char*) &atomData, 1);
// For more recent KDE's...
trayAtom = XWindowSystemUtilities::Atoms::getCreating (display, "_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR");
X11Symbols::getInstance()->xChangeProperty (display, windowH, trayAtom, XA_WINDOW,
32, PropModeReplace, (unsigned char*) &windowH, 1);
// A minimum size must be specified for GNOME and Xfce, otherwise the icon is displayed with a width of 1
if (auto* hints = X11Symbols::getInstance()->xAllocSizeHints())
{
hints->flags = PMinSize;
hints->min_width = 22;
hints->min_height = 22;
X11Symbols::getInstance()->xSetWMNormalHints (display, windowH, hints);
X11Symbols::getInstance()->xFree (hints);
}
}
Image image;
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
};
//==============================================================================
void SystemTrayIconComponent::setIconImage (const Image& colourImage, const Image&)
{
pimpl.reset();
if (colourImage.isValid())
{
if (! isOnDesktop())
addToDesktop (0);
pimpl.reset (new Pimpl (colourImage, (Window) getWindowHandle()));
setVisible (true);
toFront (false);
}
repaint();
}
void SystemTrayIconComponent::paint (Graphics& g)
{
if (pimpl != nullptr)
g.drawImage (pimpl->image, getLocalBounds().toFloat(),
RectanglePlacement::xLeft | RectanglePlacement::yTop | RectanglePlacement::onlyReduceInSize);
}
void SystemTrayIconComponent::setIconTooltip (const String& /*tooltip*/)
{
// xxx Not implemented!
}
void SystemTrayIconComponent::setHighlighted (bool)
{
// xxx Not implemented!
}
void SystemTrayIconComponent::showInfoBubble (const String& /*title*/, const String& /*content*/)
{
// xxx Not implemented!
}
void SystemTrayIconComponent::hideInfoBubble()
{
// xxx Not implemented!
}
void* SystemTrayIconComponent::getNativeHandle() const
{
return getWindowHandle();
}
} // namespace juce

File diff suppressed because it is too large Load Diff

View File

@ -2,15 +2,15 @@
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
Copyright (c) 2022 - Raw Material Software Limited
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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-6-licence
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
@ -47,12 +47,17 @@ namespace
io_iterator_t iter = 0;
io_object_t iod = 0;
const auto defaultPort =
#if defined (MAC_OS_VERSION_12_0) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_VERSION_12_0
kIOMainPortDefault;
#else
kIOMasterPortDefault;
#endif
const auto defaultPort = []
{
#if defined (MAC_OS_VERSION_12_0) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_12_0
if (@available (macOS 12.0, *))
return kIOMainPortDefault;
#endif
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
return kIOMasterPortDefault;
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}();
if (IOServiceGetMatchingServices (defaultPort, dict, &iter) == kIOReturnSuccess
&& iter != 0)

View File

@ -1,346 +0,0 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-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

View File

@ -2,15 +2,15 @@
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
Copyright (c) 2022 - Raw Material Software Limited
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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-6-licence
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
@ -26,85 +26,6 @@
namespace juce
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
static const auto nsViewFrameChangedSelector = @selector (frameChanged:);
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
struct NSViewCallbackInterface
{
virtual ~NSViewCallbackInterface() = default;
virtual void frameChanged() = 0;
};
//==============================================================================
struct NSViewFrameChangeCallbackClass : public ObjCClass<NSObject>
{
NSViewFrameChangeCallbackClass()
: ObjCClass ("JUCE_NSViewCallback_")
{
addIvar<NSViewCallbackInterface*> ("target");
addMethod (nsViewFrameChangedSelector, frameChanged, "v@:@");
registerClass();
}
static void setTarget (id self, NSViewCallbackInterface* c)
{
object_setInstanceVariable (self, "target", c);
}
private:
static void frameChanged (id self, SEL, NSNotification*)
{
if (auto* target = getIvar<NSViewCallbackInterface*> (self, "target"))
target->frameChanged();
}
JUCE_DECLARE_NON_COPYABLE (NSViewFrameChangeCallbackClass)
};
//==============================================================================
class NSViewFrameWatcher : private NSViewCallbackInterface
{
public:
NSViewFrameWatcher (NSView* viewToWatch, std::function<void()> viewResizedIn)
: viewResized (std::move (viewResizedIn)), callback (makeCallbackForView (viewToWatch))
{
}
~NSViewFrameWatcher() override
{
[[NSNotificationCenter defaultCenter] removeObserver: callback];
[callback release];
callback = nil;
}
JUCE_DECLARE_NON_COPYABLE (NSViewFrameWatcher)
JUCE_DECLARE_NON_MOVEABLE (NSViewFrameWatcher)
private:
id makeCallbackForView (NSView* view)
{
static NSViewFrameChangeCallbackClass cls;
auto* result = [cls.createInstance() init];
NSViewFrameChangeCallbackClass::setTarget (result, this);
[[NSNotificationCenter defaultCenter] addObserver: result
selector: nsViewFrameChangedSelector
name: NSViewFrameDidChangeNotification
object: view];
return result;
}
void frameChanged() override { viewResized(); }
std::function<void()> viewResized;
id callback;
};
//==============================================================================
class NSViewAttachment : public ReferenceCountedObject,
public ComponentMovementWatcher
{

View File

@ -0,0 +1,111 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-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.
==============================================================================
*/
#if JUCE_MAC
namespace juce
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
const auto nsViewFrameChangedSelector = @selector (frameChanged:);
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
struct NSViewCallbackInterface
{
virtual ~NSViewCallbackInterface() = default;
virtual void frameChanged() = 0;
};
//==============================================================================
struct NSViewFrameChangeCallbackClass : public ObjCClass<NSObject>
{
NSViewFrameChangeCallbackClass()
: ObjCClass ("JUCE_NSViewCallback_")
{
addIvar<NSViewCallbackInterface*> ("target");
addMethod (nsViewFrameChangedSelector, frameChanged);
registerClass();
}
static void setTarget (id self, NSViewCallbackInterface* c)
{
object_setInstanceVariable (self, "target", c);
}
private:
static void frameChanged (id self, SEL, NSNotification*)
{
if (auto* target = getIvar<NSViewCallbackInterface*> (self, "target"))
target->frameChanged();
}
JUCE_DECLARE_NON_COPYABLE (NSViewFrameChangeCallbackClass)
};
//==============================================================================
class NSViewFrameWatcher : private NSViewCallbackInterface
{
public:
NSViewFrameWatcher (NSView* viewToWatch, std::function<void()> viewResizedIn)
: viewResized (std::move (viewResizedIn)), callback (makeCallbackForView (viewToWatch))
{
}
~NSViewFrameWatcher() override
{
[[NSNotificationCenter defaultCenter] removeObserver: callback];
[callback release];
callback = nil;
}
JUCE_DECLARE_NON_COPYABLE (NSViewFrameWatcher)
JUCE_DECLARE_NON_MOVEABLE (NSViewFrameWatcher)
private:
id makeCallbackForView (NSView* view)
{
static NSViewFrameChangeCallbackClass cls;
auto* result = [cls.createInstance() init];
NSViewFrameChangeCallbackClass::setTarget (result, this);
[[NSNotificationCenter defaultCenter] addObserver: result
selector: nsViewFrameChangedSelector
name: NSViewFrameDidChangeNotification
object: view];
return result;
}
void frameChanged() override { viewResized(); }
std::function<void()> viewResized;
id callback;
};
} // namespace juce
#endif

File diff suppressed because it is too large Load Diff

View File

@ -1,444 +1,445 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-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
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunguarded-availability", "-Wdeprecated-declarations")
extern NSMenu* createNSMenu (const PopupMenu&, const String& name, int topLevelMenuId,
int topLevelIndex, bool addDelegate);
//==============================================================================
struct StatusItemContainer : public Timer
{
//==============================================================================
StatusItemContainer (SystemTrayIconComponent& iconComp, const Image& im)
: owner (iconComp), statusIcon (imageToNSImage (im))
{
}
virtual void configureIcon() = 0;
virtual void setHighlighted (bool shouldHighlight) = 0;
//==============================================================================
void setIconSize()
{
[statusIcon.get() setSize: NSMakeSize (20.0f, 20.0f)];
}
void updateIcon (const Image& newImage)
{
statusIcon.reset (imageToNSImage (newImage));
setIconSize();
configureIcon();
}
void showMenu (const PopupMenu& menu)
{
if (NSMenu* m = createNSMenu (menu, "MenuBarItem", -2, -3, true))
{
setHighlighted (true);
stopTimer();
// There's currently no good alternative to this.
[statusItem.get() popUpStatusItemMenu: m];
startTimer (1);
}
}
//==============================================================================
void timerCallback() override
{
stopTimer();
setHighlighted (false);
}
//==============================================================================
SystemTrayIconComponent& owner;
NSUniquePtr<NSStatusItem> statusItem;
NSUniquePtr<NSImage> statusIcon;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StatusItemContainer)
};
//==============================================================================
struct ButtonBasedStatusItem : public StatusItemContainer
{
//==============================================================================
ButtonBasedStatusItem (SystemTrayIconComponent& iconComp, const Image& im)
: StatusItemContainer (iconComp, im)
{
static ButtonEventForwarderClass cls;
eventForwarder.reset ([cls.createInstance() init]);
ButtonEventForwarderClass::setOwner (eventForwarder.get(), this);
setIconSize();
configureIcon();
statusItem.reset ([[[NSStatusBar systemStatusBar] statusItemWithLength: NSSquareStatusItemLength] retain]);
auto button = [statusItem.get() button];
button.image = statusIcon.get();
button.target = eventForwarder.get();
button.action = @selector (handleEvent:);
#if defined (MAC_OS_X_VERSION_10_12) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12
[button sendActionOn: NSEventMaskLeftMouseDown | NSEventMaskRightMouseDown | NSEventMaskScrollWheel];
#else
[button sendActionOn: NSLeftMouseDownMask | NSRightMouseDownMask | NSScrollWheelMask];
#endif
}
void configureIcon() override
{
[statusIcon.get() setTemplate: true];
[statusItem.get() button].image = statusIcon.get();
}
void setHighlighted (bool shouldHighlight) override
{
[[statusItem.get() button] setHighlighted: shouldHighlight];
}
//==============================================================================
void handleEvent()
{
auto e = [NSApp currentEvent];
NSEventType type = [e type];
const bool isLeft = (type == NSEventTypeLeftMouseDown);
const bool isRight = (type == NSEventTypeRightMouseDown);
if (owner.isCurrentlyBlockedByAnotherModalComponent())
{
if (isLeft || isRight)
if (auto* current = Component::getCurrentlyModalComponent())
current->inputAttemptWhenModal();
}
else
{
auto eventMods = ComponentPeer::getCurrentModifiersRealtime();
if (([e modifierFlags] & NSEventModifierFlagCommand) != 0)
eventMods = eventMods.withFlags (ModifierKeys::commandModifier);
auto now = Time::getCurrentTime();
auto mouseSource = Desktop::getInstance().getMainMouseSource();
auto pressure = (float) e.pressure;
if (isLeft || isRight)
{
owner.mouseDown ({ mouseSource, {},
eventMods.withFlags (isLeft ? ModifierKeys::leftButtonModifier
: ModifierKeys::rightButtonModifier),
pressure,
MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation,
MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY,
&owner, &owner, now, {}, now, 1, false });
owner.mouseUp ({ mouseSource, {},
eventMods.withoutMouseButtons(),
pressure,
MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation,
MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY,
&owner, &owner, now, {}, now, 1, false });
}
else if (type == NSEventTypeMouseMoved)
{
owner.mouseMove (MouseEvent (mouseSource, {}, eventMods, pressure,
MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation,
MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY,
&owner, &owner, now, {}, now, 1, false));
}
}
}
//==============================================================================
class ButtonEventForwarderClass : public ObjCClass<NSObject>
{
public:
ButtonEventForwarderClass() : ObjCClass<NSObject> ("JUCEButtonEventForwarderClass_")
{
addIvar<ButtonBasedStatusItem*> ("owner");
addMethod (@selector (handleEvent:), handleEvent, "v@:@");
registerClass();
}
static ButtonBasedStatusItem* getOwner (id self) { return getIvar<ButtonBasedStatusItem*> (self, "owner"); }
static void setOwner (id self, ButtonBasedStatusItem* owner) { object_setInstanceVariable (self, "owner", owner); }
private:
static void handleEvent (id self, SEL, id)
{
if (auto* owner = getOwner (self))
owner->handleEvent();
}
};
//==============================================================================
NSUniquePtr<NSObject> eventForwarder;
};
//==============================================================================
struct ViewBasedStatusItem : public StatusItemContainer
{
//==============================================================================
ViewBasedStatusItem (SystemTrayIconComponent& iconComp, const Image& im)
: StatusItemContainer (iconComp, im)
{
static SystemTrayViewClass cls;
view.reset ([cls.createInstance() init]);
SystemTrayViewClass::setOwner (view.get(), this);
SystemTrayViewClass::setImage (view.get(), statusIcon.get());
setIconSize();
statusItem.reset ([[[NSStatusBar systemStatusBar] statusItemWithLength: NSSquareStatusItemLength] retain]);
[statusItem.get() setView: view.get()];
SystemTrayViewClass::frameChanged (view.get(), SEL(), nullptr);
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
[[NSNotificationCenter defaultCenter] addObserver: view.get()
selector: @selector (frameChanged:)
name: NSWindowDidMoveNotification
object: nil];
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}
~ViewBasedStatusItem() override
{
[[NSNotificationCenter defaultCenter] removeObserver: view.get()];
[[NSStatusBar systemStatusBar] removeStatusItem: statusItem.get()];
SystemTrayViewClass::setOwner (view.get(), nullptr);
SystemTrayViewClass::setImage (view.get(), nil);
}
void configureIcon() override
{
SystemTrayViewClass::setImage (view.get(), statusIcon.get());
[statusItem.get() setView: view.get()];
}
void setHighlighted (bool shouldHighlight) override
{
isHighlighted = shouldHighlight;
[view.get() setNeedsDisplay: true];
}
//==============================================================================
void handleStatusItemAction (NSEvent* e)
{
NSEventType type = [e type];
const bool isLeft = (type == NSEventTypeLeftMouseDown || type == NSEventTypeLeftMouseUp);
const bool isRight = (type == NSEventTypeRightMouseDown || type == NSEventTypeRightMouseUp);
if (owner.isCurrentlyBlockedByAnotherModalComponent())
{
if (isLeft || isRight)
if (auto* current = Component::getCurrentlyModalComponent())
current->inputAttemptWhenModal();
}
else
{
auto eventMods = ComponentPeer::getCurrentModifiersRealtime();
if (([e modifierFlags] & NSEventModifierFlagCommand) != 0)
eventMods = eventMods.withFlags (ModifierKeys::commandModifier);
auto now = Time::getCurrentTime();
auto mouseSource = Desktop::getInstance().getMainMouseSource();
auto pressure = (float) e.pressure;
if (isLeft || isRight) // Only mouse up is sent by the OS, so simulate a down/up
{
setHighlighted (true);
startTimer (150);
owner.mouseDown (MouseEvent (mouseSource, {},
eventMods.withFlags (isLeft ? ModifierKeys::leftButtonModifier
: ModifierKeys::rightButtonModifier),
pressure, MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation,
MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY,
&owner, &owner, now, {}, now, 1, false));
owner.mouseUp (MouseEvent (mouseSource, {}, eventMods.withoutMouseButtons(), pressure,
MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation,
MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY,
&owner, &owner, now, {}, now, 1, false));
}
else if (type == NSEventTypeMouseMoved)
{
owner.mouseMove (MouseEvent (mouseSource, {}, eventMods, pressure,
MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation,
MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY,
&owner, &owner, now, {}, now, 1, false));
}
}
}
//==============================================================================
struct SystemTrayViewClass : public ObjCClass<NSControl>
{
SystemTrayViewClass() : ObjCClass<NSControl> ("JUCESystemTrayView_")
{
addIvar<ViewBasedStatusItem*> ("owner");
addIvar<NSImage*> ("image");
addMethod (@selector (mouseDown:), handleEventDown, "v@:@");
addMethod (@selector (rightMouseDown:), handleEventDown, "v@:@");
addMethod (@selector (drawRect:), drawRect, "v@:@");
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
addMethod (@selector (frameChanged:), frameChanged, "v@:@");
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
registerClass();
}
static ViewBasedStatusItem* getOwner (id self) { return getIvar<ViewBasedStatusItem*> (self, "owner"); }
static NSImage* getImage (id self) { return getIvar<NSImage*> (self, "image"); }
static void setOwner (id self, ViewBasedStatusItem* owner) { object_setInstanceVariable (self, "owner", owner); }
static void setImage (id self, NSImage* image) { object_setInstanceVariable (self, "image", image); }
static void frameChanged (id self, SEL, NSNotification*)
{
if (auto* owner = getOwner (self))
{
NSRect r = [[[owner->statusItem.get() view] window] frame];
NSRect sr = [[[NSScreen screens] objectAtIndex: 0] frame];
r.origin.y = sr.size.height - r.origin.y - r.size.height;
owner->owner.setBounds (convertToRectInt (r));
}
}
private:
static void handleEventDown (id self, SEL, NSEvent* e)
{
if (auto* owner = getOwner (self))
owner->handleStatusItemAction (e);
}
static void drawRect (id self, SEL, NSRect)
{
NSRect bounds = [self bounds];
if (auto* owner = getOwner (self))
[owner->statusItem.get() drawStatusBarBackgroundInRect: bounds
withHighlight: owner->isHighlighted];
if (NSImage* const im = getImage (self))
{
NSSize imageSize = [im size];
[im drawInRect: NSMakeRect (bounds.origin.x + ((bounds.size.width - imageSize.width) / 2.0f),
bounds.origin.y + ((bounds.size.height - imageSize.height) / 2.0f),
imageSize.width, imageSize.height)
fromRect: NSZeroRect
operation: NSCompositingOperationSourceOver
fraction: 1.0f];
}
}
};
//==============================================================================
NSUniquePtr<NSControl> view;
bool isHighlighted = false;
};
//==============================================================================
class SystemTrayIconComponent::Pimpl
{
public:
//==============================================================================
Pimpl (SystemTrayIconComponent& iconComp, const Image& im)
{
if (std::floor (NSFoundationVersionNumber) > NSFoundationVersionNumber10_10)
statusItemHolder = std::make_unique<ButtonBasedStatusItem> (iconComp, im);
else
statusItemHolder = std::make_unique<ViewBasedStatusItem> (iconComp, im);
}
//==============================================================================
std::unique_ptr<StatusItemContainer> statusItemHolder;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
};
//==============================================================================
void SystemTrayIconComponent::setIconImage (const Image&, const Image& templateImage)
{
if (templateImage.isValid())
{
if (pimpl == nullptr)
pimpl.reset (new Pimpl (*this, templateImage));
else
pimpl->statusItemHolder->updateIcon (templateImage);
}
else
{
pimpl.reset();
}
}
void SystemTrayIconComponent::setIconTooltip (const String&)
{
// xxx not yet implemented!
}
void SystemTrayIconComponent::setHighlighted (bool shouldHighlight)
{
if (pimpl != nullptr)
pimpl->statusItemHolder->setHighlighted (shouldHighlight);
}
void SystemTrayIconComponent::showInfoBubble (const String& /*title*/, const String& /*content*/)
{
// xxx Not implemented!
}
void SystemTrayIconComponent::hideInfoBubble()
{
// xxx Not implemented!
}
void* SystemTrayIconComponent::getNativeHandle() const
{
return pimpl != nullptr ? pimpl->statusItemHolder->statusItem.get() : nullptr;
}
void SystemTrayIconComponent::showDropdownMenu (const PopupMenu& menu)
{
if (pimpl != nullptr)
pimpl->statusItemHolder->showMenu (menu);
}
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
} // namespace juce
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-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
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
extern NSMenu* createNSMenu (const PopupMenu&, const String& name, int topLevelMenuId,
int topLevelIndex, bool addDelegate);
//==============================================================================
struct StatusItemContainer : public Timer
{
//==============================================================================
StatusItemContainer (SystemTrayIconComponent& iconComp, const Image& im)
: owner (iconComp), statusIcon (imageToNSImage (ScaledImage (im)))
{
}
virtual void configureIcon() = 0;
virtual void setHighlighted (bool shouldHighlight) = 0;
//==============================================================================
void setIconSize()
{
[statusIcon.get() setSize: NSMakeSize (20.0f, 20.0f)];
}
void updateIcon (const Image& newImage)
{
statusIcon.reset (imageToNSImage (ScaledImage (newImage)));
setIconSize();
configureIcon();
}
void showMenu (const PopupMenu& menu)
{
if (NSMenu* m = createNSMenu (menu, "MenuBarItem", -2, -3, true))
{
setHighlighted (true);
stopTimer();
// There's currently no good alternative to this.
[statusItem.get() popUpStatusItemMenu: m];
startTimer (1);
}
}
//==============================================================================
void timerCallback() override
{
stopTimer();
setHighlighted (false);
}
//==============================================================================
SystemTrayIconComponent& owner;
NSUniquePtr<NSStatusItem> statusItem;
NSUniquePtr<NSImage> statusIcon;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StatusItemContainer)
};
//==============================================================================
struct API_AVAILABLE (macos (10.10)) ButtonBasedStatusItem : public StatusItemContainer
{
//==============================================================================
ButtonBasedStatusItem (SystemTrayIconComponent& iconComp, const Image& im)
: StatusItemContainer (iconComp, im)
{
static ButtonEventForwarderClass cls;
eventForwarder.reset ([cls.createInstance() init]);
ButtonEventForwarderClass::setOwner (eventForwarder.get(), this);
setIconSize();
configureIcon();
statusItem.reset ([[[NSStatusBar systemStatusBar] statusItemWithLength: NSSquareStatusItemLength] retain]);
auto button = [statusItem.get() button];
button.image = statusIcon.get();
button.target = eventForwarder.get();
button.action = @selector (handleEvent:);
[button sendActionOn: NSEventMaskLeftMouseDown | NSEventMaskRightMouseDown | NSEventMaskScrollWheel];
}
~ButtonBasedStatusItem() override
{
[statusItem.get() button].image = nullptr;
}
void configureIcon() override
{
[statusIcon.get() setTemplate: true];
[statusItem.get() button].image = statusIcon.get();
}
void setHighlighted (bool shouldHighlight) override
{
[[statusItem.get() button] setHighlighted: shouldHighlight];
}
//==============================================================================
void handleEvent()
{
auto e = [NSApp currentEvent];
NSEventType type = [e type];
const bool isLeft = (type == NSEventTypeLeftMouseDown);
const bool isRight = (type == NSEventTypeRightMouseDown);
if (owner.isCurrentlyBlockedByAnotherModalComponent())
{
if (isLeft || isRight)
if (auto* current = Component::getCurrentlyModalComponent())
current->inputAttemptWhenModal();
}
else
{
auto eventMods = ComponentPeer::getCurrentModifiersRealtime();
if (([e modifierFlags] & NSEventModifierFlagCommand) != 0)
eventMods = eventMods.withFlags (ModifierKeys::commandModifier);
auto now = Time::getCurrentTime();
auto mouseSource = Desktop::getInstance().getMainMouseSource();
auto pressure = (float) e.pressure;
if (isLeft || isRight)
{
owner.mouseDown ({ mouseSource, {},
eventMods.withFlags (isLeft ? ModifierKeys::leftButtonModifier
: ModifierKeys::rightButtonModifier),
pressure,
MouseInputSource::defaultOrientation, MouseInputSource::defaultRotation,
MouseInputSource::defaultTiltX, MouseInputSource::defaultTiltY,
&owner, &owner, now, {}, now, 1, false });
owner.mouseUp ({ mouseSource, {},
eventMods.withoutMouseButtons(),
pressure,
MouseInputSource::defaultOrientation, MouseInputSource::defaultRotation,
MouseInputSource::defaultTiltX, MouseInputSource::defaultTiltY,
&owner, &owner, now, {}, now, 1, false });
}
else if (type == NSEventTypeMouseMoved)
{
owner.mouseMove (MouseEvent (mouseSource, {}, eventMods, pressure,
MouseInputSource::defaultOrientation, MouseInputSource::defaultRotation,
MouseInputSource::defaultTiltX, MouseInputSource::defaultTiltY,
&owner, &owner, now, {}, now, 1, false));
}
}
}
//==============================================================================
class ButtonEventForwarderClass : public ObjCClass<NSObject>
{
public:
ButtonEventForwarderClass() : ObjCClass<NSObject> ("JUCEButtonEventForwarderClass_")
{
addIvar<ButtonBasedStatusItem*> ("owner");
addMethod (@selector (handleEvent:), handleEvent);
registerClass();
}
static ButtonBasedStatusItem* getOwner (id self) { return getIvar<ButtonBasedStatusItem*> (self, "owner"); }
static void setOwner (id self, ButtonBasedStatusItem* owner) { object_setInstanceVariable (self, "owner", owner); }
private:
static void handleEvent (id self, SEL, id)
{
if (auto* owner = getOwner (self))
owner->handleEvent();
}
};
//==============================================================================
NSUniquePtr<NSObject> eventForwarder;
};
//==============================================================================
struct ViewBasedStatusItem : public StatusItemContainer
{
//==============================================================================
ViewBasedStatusItem (SystemTrayIconComponent& iconComp, const Image& im)
: StatusItemContainer (iconComp, im)
{
static SystemTrayViewClass cls;
view.reset ([cls.createInstance() init]);
SystemTrayViewClass::setOwner (view.get(), this);
SystemTrayViewClass::setImage (view.get(), statusIcon.get());
setIconSize();
statusItem.reset ([[[NSStatusBar systemStatusBar] statusItemWithLength: NSSquareStatusItemLength] retain]);
[statusItem.get() setView: view.get()];
SystemTrayViewClass::frameChanged (view.get(), SEL(), nullptr);
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
[[NSNotificationCenter defaultCenter] addObserver: view.get()
selector: @selector (frameChanged:)
name: NSWindowDidMoveNotification
object: nil];
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}
~ViewBasedStatusItem() override
{
[[NSNotificationCenter defaultCenter] removeObserver: view.get()];
[[NSStatusBar systemStatusBar] removeStatusItem: statusItem.get()];
SystemTrayViewClass::setOwner (view.get(), nullptr);
SystemTrayViewClass::setImage (view.get(), nil);
}
void configureIcon() override
{
SystemTrayViewClass::setImage (view.get(), statusIcon.get());
[statusItem.get() setView: view.get()];
}
void setHighlighted (bool shouldHighlight) override
{
isHighlighted = shouldHighlight;
[view.get() setNeedsDisplay: true];
}
//==============================================================================
void handleStatusItemAction (NSEvent* e)
{
NSEventType type = [e type];
const bool isLeft = (type == NSEventTypeLeftMouseDown || type == NSEventTypeLeftMouseUp);
const bool isRight = (type == NSEventTypeRightMouseDown || type == NSEventTypeRightMouseUp);
if (owner.isCurrentlyBlockedByAnotherModalComponent())
{
if (isLeft || isRight)
if (auto* current = Component::getCurrentlyModalComponent())
current->inputAttemptWhenModal();
}
else
{
auto eventMods = ComponentPeer::getCurrentModifiersRealtime();
if (([e modifierFlags] & NSEventModifierFlagCommand) != 0)
eventMods = eventMods.withFlags (ModifierKeys::commandModifier);
auto now = Time::getCurrentTime();
auto mouseSource = Desktop::getInstance().getMainMouseSource();
auto pressure = (float) e.pressure;
if (isLeft || isRight) // Only mouse up is sent by the OS, so simulate a down/up
{
setHighlighted (true);
startTimer (150);
owner.mouseDown (MouseEvent (mouseSource, {},
eventMods.withFlags (isLeft ? ModifierKeys::leftButtonModifier
: ModifierKeys::rightButtonModifier),
pressure, MouseInputSource::defaultOrientation, MouseInputSource::defaultRotation,
MouseInputSource::defaultTiltX, MouseInputSource::defaultTiltY,
&owner, &owner, now, {}, now, 1, false));
owner.mouseUp (MouseEvent (mouseSource, {}, eventMods.withoutMouseButtons(), pressure,
MouseInputSource::defaultOrientation, MouseInputSource::defaultRotation,
MouseInputSource::defaultTiltX, MouseInputSource::defaultTiltY,
&owner, &owner, now, {}, now, 1, false));
}
else if (type == NSEventTypeMouseMoved)
{
owner.mouseMove (MouseEvent (mouseSource, {}, eventMods, pressure,
MouseInputSource::defaultOrientation, MouseInputSource::defaultRotation,
MouseInputSource::defaultTiltX, MouseInputSource::defaultTiltY,
&owner, &owner, now, {}, now, 1, false));
}
}
}
//==============================================================================
struct SystemTrayViewClass : public ObjCClass<NSControl>
{
SystemTrayViewClass() : ObjCClass<NSControl> ("JUCESystemTrayView_")
{
addIvar<ViewBasedStatusItem*> ("owner");
addIvar<NSImage*> ("image");
addMethod (@selector (mouseDown:), handleEventDown);
addMethod (@selector (rightMouseDown:), handleEventDown);
addMethod (@selector (drawRect:), drawRect);
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
addMethod (@selector (frameChanged:), frameChanged);
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
registerClass();
}
static ViewBasedStatusItem* getOwner (id self) { return getIvar<ViewBasedStatusItem*> (self, "owner"); }
static NSImage* getImage (id self) { return getIvar<NSImage*> (self, "image"); }
static void setOwner (id self, ViewBasedStatusItem* owner) { object_setInstanceVariable (self, "owner", owner); }
static void setImage (id self, NSImage* image) { object_setInstanceVariable (self, "image", image); }
static void frameChanged (id self, SEL, NSNotification*)
{
if (auto* owner = getOwner (self))
{
NSRect r = [[[owner->statusItem.get() view] window] frame];
NSRect sr = [[[NSScreen screens] objectAtIndex: 0] frame];
r.origin.y = sr.size.height - r.origin.y - r.size.height;
owner->owner.setBounds (convertToRectInt (r));
}
}
private:
static void handleEventDown (id self, SEL, NSEvent* e)
{
if (auto* owner = getOwner (self))
owner->handleStatusItemAction (e);
}
static void drawRect (id self, SEL, NSRect)
{
NSRect bounds = [self bounds];
if (auto* owner = getOwner (self))
[owner->statusItem.get() drawStatusBarBackgroundInRect: bounds
withHighlight: owner->isHighlighted];
if (NSImage* const im = getImage (self))
{
NSSize imageSize = [im size];
[im drawInRect: NSMakeRect (bounds.origin.x + ((bounds.size.width - imageSize.width) / 2.0f),
bounds.origin.y + ((bounds.size.height - imageSize.height) / 2.0f),
imageSize.width, imageSize.height)
fromRect: NSZeroRect
operation: NSCompositingOperationSourceOver
fraction: 1.0f];
}
}
};
//==============================================================================
NSUniquePtr<NSControl> view;
bool isHighlighted = false;
};
//==============================================================================
class SystemTrayIconComponent::Pimpl
{
public:
//==============================================================================
Pimpl (SystemTrayIconComponent& iconComp, const Image& im)
{
if (@available (macOS 10.10, *))
statusItemHolder = std::make_unique<ButtonBasedStatusItem> (iconComp, im);
else
statusItemHolder = std::make_unique<ViewBasedStatusItem> (iconComp, im);
}
//==============================================================================
std::unique_ptr<StatusItemContainer> statusItemHolder;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
};
//==============================================================================
void SystemTrayIconComponent::setIconImage (const Image&, const Image& templateImage)
{
if (templateImage.isValid())
{
if (pimpl == nullptr)
pimpl.reset (new Pimpl (*this, templateImage));
else
pimpl->statusItemHolder->updateIcon (templateImage);
}
else
{
pimpl.reset();
}
}
void SystemTrayIconComponent::setIconTooltip (const String&)
{
// xxx not yet implemented!
}
void SystemTrayIconComponent::setHighlighted (bool shouldHighlight)
{
if (pimpl != nullptr)
pimpl->statusItemHolder->setHighlighted (shouldHighlight);
}
void SystemTrayIconComponent::showInfoBubble (const String& /*title*/, const String& /*content*/)
{
// xxx Not implemented!
}
void SystemTrayIconComponent::hideInfoBubble()
{
// xxx Not implemented!
}
void* SystemTrayIconComponent::getNativeHandle() const
{
return pimpl != nullptr ? pimpl->statusItemHolder->statusItem.get() : nullptr;
}
void SystemTrayIconComponent::showDropdownMenu (const PopupMenu& menu)
{
if (pimpl != nullptr)
pimpl->statusItemHolder->showMenu (menu);
}
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
} // namespace juce

View File

@ -2,15 +2,15 @@
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
Copyright (c) 2022 - Raw Material Software Limited
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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-6-licence
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
@ -26,17 +26,9 @@
namespace juce
{
#if JUCE_IOS || (defined (MAC_OS_X_VERSION_10_10) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_10)
#define JUCE_USE_WKWEBVIEW 1
#endif
#if (defined (MAC_OS_X_VERSION_10_12) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12)
#define WKWEBVIEW_OPENPANEL_SUPPORTED 1
#endif
static NSURL* appendParametersToFileURL (const URL& url, NSURL* fileUrl)
{
if (@available (macOS 10.9, *))
if (@available (macOS 10.10, *))
{
const auto parameterNames = url.getParameterNames();
const auto parameterValues = url.getParameterValues();
@ -115,295 +107,125 @@ static NSMutableURLRequest* getRequestForURL (const String& url, const StringArr
}
#if JUCE_MAC
#if JUCE_USE_WKWEBVIEW
using WebViewBase = ObjCClass<WKWebView>;
#else
using WebViewBase = ObjCClass<WebView>;
#endif
struct WebViewKeyEquivalentResponder : public WebViewBase
template <class WebViewClass>
struct WebViewKeyEquivalentResponder : public ObjCClass<WebViewClass>
{
WebViewKeyEquivalentResponder()
: WebViewBase ("WebViewKeyEquivalentResponder_")
: ObjCClass<WebViewClass> ("WebViewKeyEquivalentResponder_")
{
addMethod (@selector (performKeyEquivalent:), performKeyEquivalent, @encode (BOOL), "@:@");
registerClass();
}
this->addMethod (@selector (performKeyEquivalent:),
[] (id self, SEL selector, NSEvent* event)
{
const auto isCommandDown = [event]
{
const auto modifierFlags = [event modifierFlags];
private:
static BOOL performKeyEquivalent (id self, SEL selector, NSEvent* event)
{
NSResponder* first = [[self window] firstResponder];
if (@available (macOS 10.12, *))
return (modifierFlags & NSEventModifierFlagDeviceIndependentFlagsMask) == NSEventModifierFlagCommand;
#if (defined (MAC_OS_X_VERSION_10_12) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12)
constexpr auto mask = NSEventModifierFlagDeviceIndependentFlagsMask;
constexpr auto key = NSEventModifierFlagCommand;
#else
constexpr auto mask = NSDeviceIndependentModifierFlagsMask;
constexpr auto key = NSCommandKeyMask;
#endif
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
return (modifierFlags & NSDeviceIndependentModifierFlagsMask) == NSCommandKeyMask;
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}();
if (([event modifierFlags] & mask) == key)
{
auto sendAction = [&] (SEL actionSelector) -> BOOL
{
return [NSApp sendAction: actionSelector
to: first
from: self];
};
if (isCommandDown)
{
auto sendAction = [&] (SEL actionSelector) -> BOOL
{
return [NSApp sendAction:actionSelector
to:[[self window] firstResponder]
from:self];
};
if ([[event charactersIgnoringModifiers] isEqualToString: @"x"]) return sendAction (@selector (cut:));
if ([[event charactersIgnoringModifiers] isEqualToString: @"c"]) return sendAction (@selector (copy:));
if ([[event charactersIgnoringModifiers] isEqualToString: @"v"]) return sendAction (@selector (paste:));
if ([[event charactersIgnoringModifiers] isEqualToString: @"a"]) return sendAction (@selector (selectAll:));
}
if ([[event charactersIgnoringModifiers] isEqualToString:@"x"])
return sendAction (@selector (cut:));
if ([[event charactersIgnoringModifiers] isEqualToString:@"c"])
return sendAction (@selector (copy:));
if ([[event charactersIgnoringModifiers] isEqualToString:@"v"])
return sendAction (@selector (paste:));
if ([[event charactersIgnoringModifiers] isEqualToString:@"a"])
return sendAction (@selector (selectAll:));
}
return sendSuperclassMessage<BOOL> (self, selector, event);
return ObjCClass<WebViewClass>::template sendSuperclassMessage<BOOL> (self, selector, event);
});
this->registerClass();
}
};
#endif
#if JUCE_USE_WKWEBVIEW
struct WebViewDelegateClass : public ObjCClass<NSObject>
{
WebViewDelegateClass() : ObjCClass<NSObject> ("JUCEWebViewDelegate_")
{
addIvar<WebBrowserComponent*> ("owner");
addMethod (@selector (webView:decidePolicyForNavigationAction:decisionHandler:), decidePolicyForNavigationAction, "v@:@@@");
addMethod (@selector (webView:didFinishNavigation:), didFinishNavigation, "v@:@@");
addMethod (@selector (webView:didFailNavigation:withError:), didFailNavigation, "v@:@@@");
addMethod (@selector (webView:didFailProvisionalNavigation:withError:), didFailProvisionalNavigation, "v@:@@@");
addMethod (@selector (webViewDidClose:), webViewDidClose, "v@:@");
addMethod (@selector (webView:createWebViewWithConfiguration:forNavigationAction:
windowFeatures:), createWebView, "@@:@@@@");
#if WKWEBVIEW_OPENPANEL_SUPPORTED
addMethod (@selector (webView:runOpenPanelWithParameters:initiatedByFrame:completionHandler:), runOpenPanel, "v@:@@@@");
#endif
registerClass();
}
static void setOwner (id self, WebBrowserComponent* owner) { object_setInstanceVariable (self, "owner", owner); }
static WebBrowserComponent* getOwner (id self) { return getIvar<WebBrowserComponent*> (self, "owner"); }
private:
static void decidePolicyForNavigationAction (id self, SEL, WKWebView*, WKNavigationAction* navigationAction,
void (^decisionHandler)(WKNavigationActionPolicy))
{
if (getOwner (self)->pageAboutToLoad (nsStringToJuce ([[[navigationAction request] URL] absoluteString])))
decisionHandler (WKNavigationActionPolicyAllow);
else
decisionHandler (WKNavigationActionPolicyCancel);
}
static void didFinishNavigation (id self, SEL, WKWebView* webview, WKNavigation*)
{
getOwner (self)->pageFinishedLoading (nsStringToJuce ([[webview URL] absoluteString]));
}
static void displayError (WebBrowserComponent* owner, NSError* error)
{
if ([error code] != NSURLErrorCancelled)
{
auto errorString = nsStringToJuce ([error localizedDescription]);
bool proceedToErrorPage = owner->pageLoadHadNetworkError (errorString);
// WKWebView doesn't have an internal error page, so make a really simple one ourselves
if (proceedToErrorPage)
owner->goToURL ("data:text/plain;charset=UTF-8," + errorString);
}
}
static void didFailNavigation (id self, SEL, WKWebView*, WKNavigation*, NSError* error)
{
displayError (getOwner (self), error);
}
static void didFailProvisionalNavigation (id self, SEL, WKWebView*, WKNavigation*, NSError* error)
{
displayError (getOwner (self), error);
}
static void webViewDidClose (id self, SEL, WKWebView*)
{
getOwner (self)->windowCloseRequest();
}
static WKWebView* createWebView (id self, SEL, WKWebView*, WKWebViewConfiguration*,
WKNavigationAction* navigationAction, WKWindowFeatures*)
{
getOwner (self)->newWindowAttemptingToLoad (nsStringToJuce ([[[navigationAction request] URL] absoluteString]));
return nil;
}
#if WKWEBVIEW_OPENPANEL_SUPPORTED
static void runOpenPanel (id, SEL, WKWebView*, WKOpenPanelParameters* parameters, WKFrameInfo*,
void (^completionHandler)(NSArray<NSURL*>*))
{
using CompletionHandlerType = decltype (completionHandler);
class DeletedFileChooserWrapper : private DeletedAtShutdown
{
public:
DeletedFileChooserWrapper (std::unique_ptr<FileChooser> fc, CompletionHandlerType h)
: chooser (std::move (fc)), handler (h)
{
[handler retain];
}
~DeletedFileChooserWrapper()
{
callHandler (nullptr);
[handler release];
}
void callHandler (NSArray<NSURL*>* urls)
{
if (handlerCalled)
return;
handler (urls);
handlerCalled = true;
}
std::unique_ptr<FileChooser> chooser;
private:
CompletionHandlerType handler;
bool handlerCalled = false;
};
auto chooser = std::make_unique<FileChooser> (TRANS("Select the file you want to upload..."),
File::getSpecialLocation (File::userHomeDirectory), "*");
auto* wrapper = new DeletedFileChooserWrapper (std::move (chooser), completionHandler);
auto flags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles
| ([parameters allowsMultipleSelection] ? FileBrowserComponent::canSelectMultipleItems : 0);
#if (defined (MAC_OS_X_VERSION_10_14) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_14)
if ([parameters allowsDirectories])
flags |= FileBrowserComponent::canSelectDirectories;
#endif
wrapper->chooser->launchAsync (flags, [wrapper] (const FileChooser&)
{
auto results = wrapper->chooser->getResults();
auto urls = [NSMutableArray arrayWithCapacity: (NSUInteger) results.size()];
for (auto& f : results)
[urls addObject: [NSURL fileURLWithPath: juceStringToNS (f.getFullPathName())]];
wrapper->callHandler (urls);
delete wrapper;
});
}
#endif
};
//==============================================================================
class WebBrowserComponent::Pimpl
#if JUCE_MAC
: public NSViewComponent
#else
: public UIViewComponent
#endif
{
public:
Pimpl (WebBrowserComponent* owner)
{
#if JUCE_MAC
static WebViewKeyEquivalentResponder webviewClass;
webView = (WKWebView*) webviewClass.createInstance();
webView = [webView initWithFrame: NSMakeRect (0, 0, 100.0f, 100.0f)];
#else
webView = [[WKWebView alloc] initWithFrame: CGRectMake (0, 0, 100.0f, 100.0f)];
#endif
static WebViewDelegateClass cls;
webViewDelegate = [cls.createInstance() init];
WebViewDelegateClass::setOwner (webViewDelegate, owner);
[webView setNavigationDelegate: webViewDelegate];
[webView setUIDelegate: webViewDelegate];
setView (webView);
}
~Pimpl()
{
[webView setNavigationDelegate: nil];
[webView setUIDelegate: nil];
[webViewDelegate release];
setView (nil);
}
void goToURL (const String& url,
const StringArray* headers,
const MemoryBlock* postData)
{
auto trimmed = url.trimStart();
if (trimmed.startsWithIgnoreCase ("javascript:"))
{
[webView evaluateJavaScript: juceStringToNS (url.fromFirstOccurrenceOf (":", false, false))
completionHandler: nil];
return;
}
stop();
if (trimmed.startsWithIgnoreCase ("file:"))
{
auto file = URL (url).getLocalFile();
if (NSURL* nsUrl = [NSURL fileURLWithPath: juceStringToNS (file.getFullPathName())])
[webView loadFileURL: appendParametersToFileURL (url, nsUrl) allowingReadAccessToURL: nsUrl];
}
else if (NSMutableURLRequest* request = getRequestForURL (url, headers, postData))
{
[webView loadRequest: request];
}
}
void goBack() { [webView goBack]; }
void goForward() { [webView goForward]; }
void stop() { [webView stopLoading]; }
void refresh() { [webView reload]; }
private:
WKWebView* webView = nil;
id webViewDelegate;
};
#else
#if JUCE_MAC
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
struct DownloadClickDetectorClass : public ObjCClass<NSObject>
{
DownloadClickDetectorClass() : ObjCClass<NSObject> ("JUCEWebClickDetector_")
DownloadClickDetectorClass() : ObjCClass ("JUCEWebClickDetector_")
{
addIvar<WebBrowserComponent*> ("owner");
addMethod (@selector (webView:didFailLoadWithError:forFrame:), didFailLoadWithError);
addMethod (@selector (webView:didFailProvisionalLoadWithError:forFrame:), didFailLoadWithError);
addMethod (@selector (webView:decidePolicyForNavigationAction:request:frame:decisionListener:),
decidePolicyForNavigationAction, "v@:@@@@@");
[] (id self, SEL, WebView*, NSDictionary* actionInformation, NSURLRequest*, WebFrame*, id<WebPolicyDecisionListener> listener)
{
if (getOwner (self)->pageAboutToLoad (getOriginalURL (actionInformation)))
[listener use];
else
[listener ignore];
});
addMethod (@selector (webView:decidePolicyForNewWindowAction:request:newFrameName:decisionListener:),
decidePolicyForNewWindowAction, "v@:@@@@@");
addMethod (@selector (webView:didFinishLoadForFrame:), didFinishLoadForFrame, "v@:@@");
addMethod (@selector (webView:didFailLoadWithError:forFrame:), didFailLoadWithError, "v@:@@@");
addMethod (@selector (webView:didFailProvisionalLoadWithError:forFrame:), didFailLoadWithError, "v@:@@@");
addMethod (@selector (webView:willCloseFrame:), willCloseFrame, "v@:@@");
addMethod (@selector (webView:runOpenPanelForFileButtonWithResultListener:allowMultipleFiles:), runOpenPanel, "v@:@@", @encode (BOOL));
[] (id self, SEL, WebView*, NSDictionary* actionInformation, NSURLRequest*, NSString*, id<WebPolicyDecisionListener> listener)
{
getOwner (self)->newWindowAttemptingToLoad (getOriginalURL (actionInformation));
[listener ignore];
});
addMethod (@selector (webView:didFinishLoadForFrame:),
[] (id self, SEL, WebView* sender, WebFrame* frame)
{
if ([frame isEqual:[sender mainFrame]])
{
NSURL* url = [[[frame dataSource] request] URL];
getOwner (self)->pageFinishedLoading (nsStringToJuce ([url absoluteString]));
}
});
addMethod (@selector (webView:willCloseFrame:),
[] (id self, SEL, WebView*, WebFrame*)
{
getOwner (self)->windowCloseRequest();
});
addMethod (@selector (webView:runOpenPanelForFileButtonWithResultListener:allowMultipleFiles:),
[] (id, SEL, WebView*, id<WebOpenPanelResultListener> resultListener, BOOL allowMultipleFiles)
{
struct DeletedFileChooserWrapper : private DeletedAtShutdown
{
DeletedFileChooserWrapper (std::unique_ptr<FileChooser> fc, id<WebOpenPanelResultListener> rl)
: chooser (std::move (fc)), listener (rl)
{
[listener.get() retain];
}
std::unique_ptr<FileChooser> chooser;
ObjCObjectHandle<id<WebOpenPanelResultListener>> listener;
};
auto chooser = std::make_unique<FileChooser> (TRANS ("Select the file you want to upload..."),
File::getSpecialLocation (File::userHomeDirectory),
"*");
auto* wrapper = new DeletedFileChooserWrapper (std::move (chooser), resultListener);
auto flags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles
| (allowMultipleFiles ? FileBrowserComponent::canSelectMultipleItems : 0);
wrapper->chooser->launchAsync (flags, [wrapper] (const FileChooser&)
{
for (auto& f : wrapper->chooser->getResults())
[wrapper->listener.get() chooseFilename: juceStringToNS (f.getFullPathName())];
delete wrapper;
});
});
registerClass();
}
@ -420,31 +242,6 @@ private:
return {};
}
static void decidePolicyForNavigationAction (id self, SEL, WebView*, NSDictionary* actionInformation,
NSURLRequest*, WebFrame*, id<WebPolicyDecisionListener> listener)
{
if (getOwner (self)->pageAboutToLoad (getOriginalURL (actionInformation)))
[listener use];
else
[listener ignore];
}
static void decidePolicyForNewWindowAction (id self, SEL, WebView*, NSDictionary* actionInformation,
NSURLRequest*, NSString*, id<WebPolicyDecisionListener> listener)
{
getOwner (self)->newWindowAttemptingToLoad (getOriginalURL (actionInformation));
[listener ignore];
}
static void didFinishLoadForFrame (id self, SEL, WebView* sender, WebFrame* frame)
{
if ([frame isEqual: [sender mainFrame]])
{
NSURL* url = [[[frame dataSource] request] URL];
getOwner (self)->pageFinishedLoading (nsStringToJuce ([url absoluteString]));
}
}
static void didFailLoadWithError (id self, SEL, WebView* sender, NSError* error, WebFrame* frame)
{
if ([frame isEqual: [sender mainFrame]] && error != nullptr && [error code] != NSURLErrorCancelled)
@ -457,61 +254,123 @@ private:
getOwner (self)->goToURL ("data:text/plain;charset=UTF-8," + errorString);
}
}
static void willCloseFrame (id self, SEL, WebView*, WebFrame*)
{
getOwner (self)->windowCloseRequest();
}
static void runOpenPanel (id, SEL, WebView*, id<WebOpenPanelResultListener> resultListener, BOOL allowMultipleFiles)
{
struct DeletedFileChooserWrapper : private DeletedAtShutdown
{
DeletedFileChooserWrapper (std::unique_ptr<FileChooser> fc, id<WebOpenPanelResultListener> rl)
: chooser (std::move (fc)), listener (rl)
{
[listener retain];
}
~DeletedFileChooserWrapper()
{
[listener release];
}
std::unique_ptr<FileChooser> chooser;
id<WebOpenPanelResultListener> listener;
};
auto chooser = std::make_unique<FileChooser> (TRANS("Select the file you want to upload..."),
File::getSpecialLocation (File::userHomeDirectory), "*");
auto* wrapper = new DeletedFileChooserWrapper (std::move (chooser), resultListener);
auto flags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles
| (allowMultipleFiles ? FileBrowserComponent::canSelectMultipleItems : 0);
wrapper->chooser->launchAsync (flags, [wrapper] (const FileChooser&)
{
for (auto& f : wrapper->chooser->getResults())
[wrapper->listener chooseFilename: juceStringToNS (f.getFullPathName())];
delete wrapper;
});
}
};
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
#endif
#else
struct WebViewDelegateClass : public ObjCClass<NSObject>
struct API_AVAILABLE (macos (10.10)) WebViewDelegateClass : public ObjCClass<NSObject>
{
WebViewDelegateClass() : ObjCClass<NSObject> ("JUCEWebViewDelegate_")
WebViewDelegateClass() : ObjCClass ("JUCEWebViewDelegate_")
{
addIvar<WebBrowserComponent*> ("owner");
addMethod (@selector (gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:),
shouldRecognizeSimultaneouslyWithGestureRecognizer, "c@:@@");
addMethod (@selector (webView:decidePolicyForNavigationAction:decisionHandler:),
[] (id self, SEL, WKWebView*, WKNavigationAction* navigationAction, void (^decisionHandler) (WKNavigationActionPolicy))
{
if (getOwner (self)->pageAboutToLoad (nsStringToJuce ([[[navigationAction request] URL] absoluteString])))
decisionHandler (WKNavigationActionPolicyAllow);
else
decisionHandler (WKNavigationActionPolicyCancel);
});
addMethod (@selector (webView:shouldStartLoadWithRequest:navigationType:), shouldStartLoadWithRequest, "c@:@@@");
addMethod (@selector (webViewDidFinishLoad:), webViewDidFinishLoad, "v@:@");
addMethod (@selector (webView:didFinishNavigation:),
[] (id self, SEL, WKWebView* webview, WKNavigation*)
{
getOwner (self)->pageFinishedLoading (nsStringToJuce ([[webview URL] absoluteString]));
});
addMethod (@selector (webView:didFailNavigation:withError:),
[] (id self, SEL, WKWebView*, WKNavigation*, NSError* error)
{
displayError (getOwner (self), error);
});
addMethod (@selector (webView:didFailProvisionalNavigation:withError:),
[] (id self, SEL, WKWebView*, WKNavigation*, NSError* error)
{
displayError (getOwner (self), error);
});
addMethod (@selector (webViewDidClose:),
[] (id self, SEL, WKWebView*)
{
getOwner (self)->windowCloseRequest();
});
addMethod (@selector (webView:createWebViewWithConfiguration:forNavigationAction:windowFeatures:),
[] (id self, SEL, WKWebView*, WKWebViewConfiguration*, WKNavigationAction* navigationAction, WKWindowFeatures*)
{
getOwner (self)->newWindowAttemptingToLoad (nsStringToJuce ([[[navigationAction request] URL] absoluteString]));
return nil;
});
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
if (@available (macOS 10.12, *))
{
addMethod (@selector (webView:runOpenPanelWithParameters:initiatedByFrame:completionHandler:),
[] (id, SEL, WKWebView*, WKOpenPanelParameters* parameters, WKFrameInfo*, void (^completionHandler)(NSArray<NSURL*>*))
{
using CompletionHandlerType = decltype (completionHandler);
class DeletedFileChooserWrapper : private DeletedAtShutdown
{
public:
DeletedFileChooserWrapper (std::unique_ptr<FileChooser> fc, CompletionHandlerType h)
: chooser (std::move (fc)), handler (h)
{
[handler.get() retain];
}
~DeletedFileChooserWrapper()
{
callHandler (nullptr);
}
void callHandler (NSArray<NSURL*>* urls)
{
if (handlerCalled)
return;
handler.get() (urls);
handlerCalled = true;
}
std::unique_ptr<FileChooser> chooser;
private:
ObjCObjectHandle<CompletionHandlerType> handler;
bool handlerCalled = false;
};
auto chooser = std::make_unique<FileChooser> (TRANS("Select the file you want to upload..."),
File::getSpecialLocation (File::userHomeDirectory), "*");
auto* wrapper = new DeletedFileChooserWrapper (std::move (chooser), completionHandler);
auto flags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles
| ([parameters allowsMultipleSelection] ? FileBrowserComponent::canSelectMultipleItems : 0);
#if JUCE_MAC
if (@available (macOS 10.14, *))
{
if ([parameters allowsDirectories])
flags |= FileBrowserComponent::canSelectDirectories;
}
#endif
wrapper->chooser->launchAsync (flags, [wrapper] (const FileChooser&)
{
auto results = wrapper->chooser->getResults();
auto urls = [NSMutableArray arrayWithCapacity: (NSUInteger) results.size()];
for (auto& f : results)
[urls addObject: [NSURL fileURLWithPath: juceStringToNS (f.getFullPathName())]];
wrapper->callHandler (urls);
delete wrapper;
});
});
}
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
registerClass();
}
@ -520,86 +379,70 @@ struct WebViewDelegateClass : public ObjCClass<NSObject>
static WebBrowserComponent* getOwner (id self) { return getIvar<WebBrowserComponent*> (self, "owner"); }
private:
static BOOL shouldRecognizeSimultaneouslyWithGestureRecognizer (id, SEL, UIGestureRecognizer*, UIGestureRecognizer*)
static void displayError (WebBrowserComponent* owner, NSError* error)
{
return YES;
}
if ([error code] != NSURLErrorCancelled)
{
auto errorString = nsStringToJuce ([error localizedDescription]);
bool proceedToErrorPage = owner->pageLoadHadNetworkError (errorString);
static BOOL shouldStartLoadWithRequest (id self, SEL, UIWebView*, NSURLRequest* request, UIWebViewNavigationType)
{
return getOwner (self)->pageAboutToLoad (nsStringToJuce ([[request URL] absoluteString]));
}
static void webViewDidFinishLoad (id self, SEL, UIWebView* webView)
{
getOwner (self)->pageFinishedLoading (nsStringToJuce ([[[webView request] URL] absoluteString]));
// WKWebView doesn't have an internal error page, so make a really simple one ourselves
if (proceedToErrorPage)
owner->goToURL ("data:text/plain;charset=UTF-8," + errorString);
}
}
};
#endif
//==============================================================================
class WebBrowserComponent::Pimpl
#if JUCE_MAC
: public NSViewComponent
#else
: public UIViewComponent
#endif
struct WebViewBase
{
virtual ~WebViewBase() = default;
virtual void goToURL (const String&, const StringArray*, const MemoryBlock*) = 0;
virtual void goBack() = 0;
virtual void goForward() = 0;
virtual void stop() = 0;
virtual void refresh() = 0;
virtual id getWebView() = 0;
};
#if JUCE_MAC
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
class WebViewImpl : public WebViewBase
{
public:
Pimpl (WebBrowserComponent* owner)
WebViewImpl (WebBrowserComponent* owner)
{
#if JUCE_MAC
static WebViewKeyEquivalentResponder webviewClass;
webView = (WebView*) webviewClass.createInstance();
static WebViewKeyEquivalentResponder<WebView> webviewClass;
webView = [webView initWithFrame: NSMakeRect (0, 0, 100.0f, 100.0f)
frameName: nsEmptyString()
groupName: nsEmptyString()];
webView.reset ([webviewClass.createInstance() initWithFrame: NSMakeRect (0, 0, 100.0f, 100.0f)
frameName: nsEmptyString()
groupName: nsEmptyString()]);
static DownloadClickDetectorClass cls;
clickListener = [cls.createInstance() init];
DownloadClickDetectorClass::setOwner (clickListener, owner);
clickListener.reset ([cls.createInstance() init]);
DownloadClickDetectorClass::setOwner (clickListener.get(), owner);
[webView setPolicyDelegate: clickListener];
[webView setFrameLoadDelegate: clickListener];
[webView setUIDelegate: clickListener];
#else
webView = [[UIWebView alloc] initWithFrame: CGRectMake (0, 0, 1.0f, 1.0f)];
static WebViewDelegateClass cls;
webViewDelegate = [cls.createInstance() init];
WebViewDelegateClass::setOwner (webViewDelegate, owner);
[webView setDelegate: webViewDelegate];
#endif
setView (webView);
[webView.get() setPolicyDelegate: clickListener.get()];
[webView.get() setFrameLoadDelegate: clickListener.get()];
[webView.get() setUIDelegate: clickListener.get()];
}
~Pimpl()
~WebViewImpl() override
{
#if JUCE_MAC
[webView setPolicyDelegate: nil];
[webView setFrameLoadDelegate: nil];
[webView setUIDelegate: nil];
[clickListener release];
#else
[webView setDelegate: nil];
[webViewDelegate release];
#endif
setView (nil);
[webView.get() setPolicyDelegate: nil];
[webView.get() setFrameLoadDelegate: nil];
[webView.get() setUIDelegate: nil];
}
void goToURL (const String& url,
const StringArray* headers,
const MemoryBlock* postData)
const MemoryBlock* postData) override
{
if (url.trimStart().startsWithIgnoreCase ("javascript:"))
{
[webView stringByEvaluatingJavaScriptFromString: juceStringToNS (url.fromFirstOccurrenceOf (":", false, false))];
[webView.get() stringByEvaluatingJavaScriptFromString: juceStringToNS (url.fromFirstOccurrenceOf (":", false, false))];
return;
}
@ -623,51 +466,146 @@ public:
};
if (NSMutableURLRequest* request = getRequest())
{
#if JUCE_MAC
[[webView mainFrame] loadRequest: request];
#else
[webView loadRequest: request];
#endif
#if JUCE_IOS
[webView setScalesPageToFit: YES];
#endif
}
[[webView.get() mainFrame] loadRequest: request];
}
void goBack() { [webView goBack]; }
void goForward() { [webView goForward]; }
void goBack() override { [webView.get() goBack]; }
void goForward() override { [webView.get() goForward]; }
#if JUCE_MAC
void stop() { [webView stopLoading: nil]; }
void refresh() { [webView reload: nil]; }
#else
void stop() { [webView stopLoading]; }
void refresh() { [webView reload]; }
#endif
void stop() override { [webView.get() stopLoading: nil]; }
void refresh() override { [webView.get() reload: nil]; }
id getWebView() override { return webView.get(); }
void mouseMove (const MouseEvent&)
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
// WebKit doesn't capture mouse-moves itself, so it seems the only way to make
// them work is to push them via this non-public method..
if ([webView respondsToSelector: @selector (_updateMouseoverWithFakeEvent)])
[webView performSelector: @selector (_updateMouseoverWithFakeEvent)];
if ([webView.get() respondsToSelector: @selector (_updateMouseoverWithFakeEvent)])
[webView.get() performSelector: @selector (_updateMouseoverWithFakeEvent)];
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}
private:
#if JUCE_MAC
WebView* webView = nil;
id clickListener;
#else
UIWebView* webView = nil;
id webViewDelegate;
#endif
ObjCObjectHandle<WebView*> webView;
ObjCObjectHandle<id> clickListener;
};
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
#endif
class API_AVAILABLE (macos (10.11)) WKWebViewImpl : public WebViewBase
{
public:
WKWebViewImpl (WebBrowserComponent* owner)
{
#if JUCE_MAC
static WebViewKeyEquivalentResponder<WKWebView> webviewClass;
webView.reset ([webviewClass.createInstance() initWithFrame: NSMakeRect (0, 0, 100.0f, 100.0f)]);
#else
webView.reset ([[WKWebView alloc] initWithFrame: CGRectMake (0, 0, 100.0f, 100.0f)]);
#endif
static WebViewDelegateClass cls;
webViewDelegate.reset ([cls.createInstance() init]);
WebViewDelegateClass::setOwner (webViewDelegate.get(), owner);
[webView.get() setNavigationDelegate: webViewDelegate.get()];
[webView.get() setUIDelegate: webViewDelegate.get()];
}
~WKWebViewImpl() override
{
[webView.get() setNavigationDelegate: nil];
[webView.get() setUIDelegate: nil];
}
void goToURL (const String& url,
const StringArray* headers,
const MemoryBlock* postData) override
{
auto trimmed = url.trimStart();
if (trimmed.startsWithIgnoreCase ("javascript:"))
{
[webView.get() evaluateJavaScript: juceStringToNS (url.fromFirstOccurrenceOf (":", false, false))
completionHandler: nil];
return;
}
stop();
if (trimmed.startsWithIgnoreCase ("file:"))
{
auto file = URL (url).getLocalFile();
if (NSURL* nsUrl = [NSURL fileURLWithPath: juceStringToNS (file.getFullPathName())])
[webView.get() loadFileURL: appendParametersToFileURL (url, nsUrl) allowingReadAccessToURL: nsUrl];
}
else if (NSMutableURLRequest* request = getRequestForURL (url, headers, postData))
{
[webView.get() loadRequest: request];
}
}
void goBack() override { [webView.get() goBack]; }
void goForward() override { [webView.get() goForward]; }
void stop() override { [webView.get() stopLoading]; }
void refresh() override { [webView.get() reload]; }
id getWebView() override { return webView.get(); }
private:
ObjCObjectHandle<WKWebView*> webView;
ObjCObjectHandle<id> webViewDelegate;
};
#endif
//==============================================================================
class WebBrowserComponent::Pimpl
#if JUCE_MAC
: public NSViewComponent
#else
: public UIViewComponent
#endif
{
public:
Pimpl (WebBrowserComponent* owner)
{
if (@available (macOS 10.11, *))
webView = std::make_unique<WKWebViewImpl> (owner);
#if JUCE_MAC
else
webView = std::make_unique<WebViewImpl> (owner);
#endif
setView (webView->getWebView());
}
~Pimpl()
{
webView = nullptr;
setView (nil);
}
void goToURL (const String& url,
const StringArray* headers,
const MemoryBlock* postData)
{
webView->goToURL (url, headers, postData);
}
void goBack() { webView->goBack(); }
void goForward() { webView->goForward(); }
void stop() { webView->stop(); }
void refresh() { webView->refresh(); }
private:
std::unique_ptr<WebViewBase> webView;
};
//==============================================================================
WebBrowserComponent::WebBrowserComponent (bool unloadWhenHidden)
@ -678,9 +616,7 @@ WebBrowserComponent::WebBrowserComponent (bool unloadWhenHidden)
addAndMakeVisible (browser.get());
}
WebBrowserComponent::~WebBrowserComponent()
{
}
WebBrowserComponent::~WebBrowserComponent() = default;
//==============================================================================
void WebBrowserComponent::goToURL (const String& url,

View File

@ -1,496 +1,497 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-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
{
extern int64 getMouseEventTime();
JUCE_DECLARE_UUID_GETTER (IOleObject, "00000112-0000-0000-C000-000000000046")
JUCE_DECLARE_UUID_GETTER (IOleWindow, "00000114-0000-0000-C000-000000000046")
JUCE_DECLARE_UUID_GETTER (IOleInPlaceSite, "00000119-0000-0000-C000-000000000046")
namespace ActiveXHelpers
{
//==============================================================================
struct JuceIStorage : public ComBaseClassHelper<IStorage>
{
JuceIStorage() {}
JUCE_COMRESULT CreateStream (const WCHAR*, DWORD, DWORD, DWORD, IStream**) { return E_NOTIMPL; }
JUCE_COMRESULT OpenStream (const WCHAR*, void*, DWORD, DWORD, IStream**) { return E_NOTIMPL; }
JUCE_COMRESULT CreateStorage (const WCHAR*, DWORD, DWORD, DWORD, IStorage**) { return E_NOTIMPL; }
JUCE_COMRESULT OpenStorage (const WCHAR*, IStorage*, DWORD, SNB, DWORD, IStorage**) { return E_NOTIMPL; }
JUCE_COMRESULT CopyTo (DWORD, IID const*, SNB, IStorage*) { return E_NOTIMPL; }
JUCE_COMRESULT MoveElementTo (const OLECHAR*,IStorage*, const OLECHAR*, DWORD) { return E_NOTIMPL; }
JUCE_COMRESULT Commit (DWORD) { return E_NOTIMPL; }
JUCE_COMRESULT Revert() { return E_NOTIMPL; }
JUCE_COMRESULT EnumElements (DWORD, void*, DWORD, IEnumSTATSTG**) { return E_NOTIMPL; }
JUCE_COMRESULT DestroyElement (const OLECHAR*) { return E_NOTIMPL; }
JUCE_COMRESULT RenameElement (const WCHAR*, const WCHAR*) { return E_NOTIMPL; }
JUCE_COMRESULT SetElementTimes (const WCHAR*, FILETIME const*, FILETIME const*, FILETIME const*) { return E_NOTIMPL; }
JUCE_COMRESULT SetClass (REFCLSID) { return S_OK; }
JUCE_COMRESULT SetStateBits (DWORD, DWORD) { return E_NOTIMPL; }
JUCE_COMRESULT Stat (STATSTG*, DWORD) { return E_NOTIMPL; }
};
//==============================================================================
struct JuceOleInPlaceFrame : public ComBaseClassHelper<IOleInPlaceFrame>
{
JuceOleInPlaceFrame (HWND hwnd) : window (hwnd) {}
JUCE_COMRESULT GetWindow (HWND* lphwnd) { *lphwnd = window; return S_OK; }
JUCE_COMRESULT ContextSensitiveHelp (BOOL) { return E_NOTIMPL; }
JUCE_COMRESULT GetBorder (LPRECT) { return E_NOTIMPL; }
JUCE_COMRESULT RequestBorderSpace (LPCBORDERWIDTHS) { return E_NOTIMPL; }
JUCE_COMRESULT SetBorderSpace (LPCBORDERWIDTHS) { return E_NOTIMPL; }
JUCE_COMRESULT SetActiveObject (IOleInPlaceActiveObject* a, LPCOLESTR) { activeObject = a; return S_OK; }
JUCE_COMRESULT InsertMenus (HMENU, LPOLEMENUGROUPWIDTHS) { return E_NOTIMPL; }
JUCE_COMRESULT SetMenu (HMENU, HOLEMENU, HWND) { return S_OK; }
JUCE_COMRESULT RemoveMenus (HMENU) { return E_NOTIMPL; }
JUCE_COMRESULT SetStatusText (LPCOLESTR) { return S_OK; }
JUCE_COMRESULT EnableModeless (BOOL) { return S_OK; }
JUCE_COMRESULT TranslateAccelerator (LPMSG, WORD) { return E_NOTIMPL; }
HRESULT OfferKeyTranslation (LPMSG lpmsg)
{
if (activeObject != nullptr)
return activeObject->TranslateAcceleratorW (lpmsg);
return S_FALSE;
}
HWND window;
ComSmartPtr<IOleInPlaceActiveObject> activeObject;
};
//==============================================================================
struct JuceIOleInPlaceSite : public ComBaseClassHelper<IOleInPlaceSite>
{
JuceIOleInPlaceSite (HWND hwnd)
: window (hwnd),
frame (new JuceOleInPlaceFrame (window))
{}
~JuceIOleInPlaceSite()
{
frame->Release();
}
JUCE_COMRESULT GetWindow (HWND* lphwnd) { *lphwnd = window; return S_OK; }
JUCE_COMRESULT ContextSensitiveHelp (BOOL) { return E_NOTIMPL; }
JUCE_COMRESULT CanInPlaceActivate() { return S_OK; }
JUCE_COMRESULT OnInPlaceActivate() { return S_OK; }
JUCE_COMRESULT OnUIActivate() { return S_OK; }
JUCE_COMRESULT GetWindowContext (LPOLEINPLACEFRAME* lplpFrame, LPOLEINPLACEUIWINDOW* lplpDoc, LPRECT, LPRECT, LPOLEINPLACEFRAMEINFO lpFrameInfo)
{
/* Note: If you call AddRef on the frame here, then some types of object (e.g. web browser control) cause leaks..
If you don't call AddRef then others crash (e.g. QuickTime).. Bit of a catch-22, so letting it leak is probably preferable.
*/
if (lplpFrame != nullptr) { frame->AddRef(); *lplpFrame = frame; }
if (lplpDoc != nullptr) *lplpDoc = nullptr;
lpFrameInfo->fMDIApp = FALSE;
lpFrameInfo->hwndFrame = window;
lpFrameInfo->haccel = nullptr;
lpFrameInfo->cAccelEntries = 0;
return S_OK;
}
JUCE_COMRESULT Scroll (SIZE) { return E_NOTIMPL; }
JUCE_COMRESULT OnUIDeactivate (BOOL) { return S_OK; }
JUCE_COMRESULT OnInPlaceDeactivate() { return S_OK; }
JUCE_COMRESULT DiscardUndoState() { return E_NOTIMPL; }
JUCE_COMRESULT DeactivateAndUndo() { return E_NOTIMPL; }
JUCE_COMRESULT OnPosRectChange (LPCRECT) { return S_OK; }
LRESULT offerEventToActiveXControl (::MSG& msg)
{
if (frame != nullptr)
return frame->OfferKeyTranslation (&msg);
return S_FALSE;
}
HWND window;
JuceOleInPlaceFrame* frame;
};
//==============================================================================
struct JuceIOleClientSite : public ComBaseClassHelper<IOleClientSite>
{
JuceIOleClientSite (HWND window) : inplaceSite (new JuceIOleInPlaceSite (window))
{}
~JuceIOleClientSite()
{
inplaceSite->Release();
}
JUCE_COMRESULT QueryInterface (REFIID type, void** result)
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
if (type == __uuidof (IOleInPlaceSite))
{
inplaceSite->AddRef();
*result = static_cast<IOleInPlaceSite*> (inplaceSite);
return S_OK;
}
return ComBaseClassHelper <IOleClientSite>::QueryInterface (type, result);
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}
JUCE_COMRESULT SaveObject() { return E_NOTIMPL; }
JUCE_COMRESULT GetMoniker (DWORD, DWORD, IMoniker**) { return E_NOTIMPL; }
JUCE_COMRESULT GetContainer (LPOLECONTAINER* ppContainer) { *ppContainer = nullptr; return E_NOINTERFACE; }
JUCE_COMRESULT ShowObject() { return S_OK; }
JUCE_COMRESULT OnShowWindow (BOOL) { return E_NOTIMPL; }
JUCE_COMRESULT RequestNewObjectLayout() { return E_NOTIMPL; }
LRESULT offerEventToActiveXControl (::MSG& msg)
{
if (inplaceSite != nullptr)
return inplaceSite->offerEventToActiveXControl (msg);
return S_FALSE;
}
JuceIOleInPlaceSite* inplaceSite;
};
//==============================================================================
static Array<ActiveXControlComponent*> activeXComps;
static HWND getHWND (const ActiveXControlComponent* const component)
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
HWND hwnd = {};
const IID iid = __uuidof (IOleWindow);
if (auto* window = (IOleWindow*) component->queryInterface (&iid))
{
window->GetWindow (&hwnd);
window->Release();
}
return hwnd;
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}
static void offerActiveXMouseEventToPeer (ComponentPeer* peer, HWND hwnd, UINT message, LPARAM lParam)
{
switch (message)
{
case WM_MOUSEMOVE:
case WM_LBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_LBUTTONUP:
case WM_MBUTTONUP:
case WM_RBUTTONUP:
{
RECT activeXRect, peerRect;
GetWindowRect (hwnd, &activeXRect);
GetWindowRect ((HWND) peer->getNativeHandle(), &peerRect);
peer->handleMouseEvent (MouseInputSource::InputSourceType::mouse,
{ (float) (GET_X_LPARAM (lParam) + activeXRect.left - peerRect.left),
(float) (GET_Y_LPARAM (lParam) + activeXRect.top - peerRect.top) },
ComponentPeer::getCurrentModifiersRealtime(),
MouseInputSource::invalidPressure,
MouseInputSource::invalidOrientation,
getMouseEventTime());
break;
}
default:
break;
}
}
}
//==============================================================================
class ActiveXControlComponent::Pimpl : public ComponentMovementWatcher,
public ComponentPeer::ScaleFactorListener
{
public:
Pimpl (HWND hwnd, ActiveXControlComponent& activeXComp)
: ComponentMovementWatcher (&activeXComp),
owner (activeXComp),
storage (new ActiveXHelpers::JuceIStorage()),
clientSite (new ActiveXHelpers::JuceIOleClientSite (hwnd))
{
}
~Pimpl() override
{
if (control != nullptr)
{
control->Close (OLECLOSE_NOSAVE);
control->Release();
}
clientSite->Release();
storage->Release();
if (currentPeer != nullptr)
currentPeer->removeScaleFactorListener (this);
}
void setControlBounds (Rectangle<int> newBounds) const
{
if (controlHWND != nullptr)
{
if (auto* peer = owner.getTopLevelComponent()->getPeer())
newBounds = (newBounds.toDouble() * peer->getPlatformScaleFactor()).toNearestInt();
MoveWindow (controlHWND, newBounds.getX(), newBounds.getY(), newBounds.getWidth(), newBounds.getHeight(), TRUE);
}
}
void setControlVisible (bool shouldBeVisible) const
{
if (controlHWND != nullptr)
ShowWindow (controlHWND, shouldBeVisible ? SW_SHOWNA : SW_HIDE);
}
//==============================================================================
using ComponentMovementWatcher::componentMovedOrResized;
void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override
{
if (auto* peer = owner.getTopLevelComponent()->getPeer())
setControlBounds (peer->getAreaCoveredBy (owner));
}
void componentPeerChanged() override
{
if (currentPeer != nullptr)
currentPeer->removeScaleFactorListener (this);
componentMovedOrResized (true, true);
currentPeer = owner.getTopLevelComponent()->getPeer();
if (currentPeer != nullptr)
currentPeer->addScaleFactorListener (this);
}
using ComponentMovementWatcher::componentVisibilityChanged;
void componentVisibilityChanged() override
{
setControlVisible (owner.isShowing());
componentPeerChanged();
}
void nativeScaleFactorChanged (double /*newScaleFactor*/) override
{
componentMovedOrResized (true, true);
}
// intercepts events going to an activeX control, so we can sneakily use the mouse events
static LRESULT CALLBACK activeXHookWndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
for (auto* ax : ActiveXHelpers::activeXComps)
{
if (ax->control != nullptr && ax->control->controlHWND == hwnd)
{
switch (message)
{
case WM_MOUSEMOVE:
case WM_LBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_LBUTTONUP:
case WM_MBUTTONUP:
case WM_RBUTTONUP:
case WM_LBUTTONDBLCLK:
case WM_MBUTTONDBLCLK:
case WM_RBUTTONDBLCLK:
if (ax->isShowing())
{
if (auto* peer = ax->getPeer())
{
ActiveXHelpers::offerActiveXMouseEventToPeer (peer, hwnd, message, lParam);
if (! ax->areMouseEventsAllowed())
return 0;
}
}
break;
default:
break;
}
return CallWindowProc (ax->control->originalWndProc, hwnd, message, wParam, lParam);
}
}
return DefWindowProc (hwnd, message, wParam, lParam);
}
ActiveXControlComponent& owner;
ComponentPeer* currentPeer = nullptr;
HWND controlHWND = {};
IStorage* storage = nullptr;
ActiveXHelpers::JuceIOleClientSite* clientSite = nullptr;
IOleObject* control = nullptr;
WNDPROC originalWndProc = nullptr;
};
//==============================================================================
ActiveXControlComponent::ActiveXControlComponent()
{
ActiveXHelpers::activeXComps.add (this);
}
ActiveXControlComponent::~ActiveXControlComponent()
{
deleteControl();
ActiveXHelpers::activeXComps.removeFirstMatchingValue (this);
}
void ActiveXControlComponent::paint (Graphics& g)
{
if (control == nullptr)
g.fillAll (Colours::lightgrey);
}
bool ActiveXControlComponent::createControl (const void* controlIID)
{
deleteControl();
if (auto* peer = getPeer())
{
auto controlBounds = peer->getAreaCoveredBy (*this);
auto hwnd = (HWND) peer->getNativeHandle();
std::unique_ptr<Pimpl> newControl (new Pimpl (hwnd, *this));
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
HRESULT hr = OleCreate (*(const IID*) controlIID, __uuidof (IOleObject), 1 /*OLERENDER_DRAW*/, nullptr,
newControl->clientSite, newControl->storage,
(void**) &(newControl->control));
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
if (hr == S_OK)
{
newControl->control->SetHostNames (L"JUCE", nullptr);
if (OleSetContainedObject (newControl->control, TRUE) == S_OK)
{
RECT rect;
rect.left = controlBounds.getX();
rect.top = controlBounds.getY();
rect.right = controlBounds.getRight();
rect.bottom = controlBounds.getBottom();
if (newControl->control->DoVerb (OLEIVERB_SHOW, nullptr, newControl->clientSite, 0, hwnd, &rect) == S_OK)
{
control.reset (newControl.release());
control->controlHWND = ActiveXHelpers::getHWND (this);
if (control->controlHWND != nullptr)
{
control->setControlBounds (controlBounds);
control->originalWndProc = (WNDPROC) GetWindowLongPtr ((HWND) control->controlHWND, GWLP_WNDPROC);
SetWindowLongPtr ((HWND) control->controlHWND, GWLP_WNDPROC, (LONG_PTR) Pimpl::activeXHookWndProc);
}
return true;
}
}
}
}
else
{
// the component must have already been added to a real window when you call this!
jassertfalse;
}
return false;
}
void ActiveXControlComponent::deleteControl()
{
control = nullptr;
}
void* ActiveXControlComponent::queryInterface (const void* iid) const
{
void* result = nullptr;
if (control != nullptr && control->control != nullptr
&& SUCCEEDED (control->control->QueryInterface (*(const IID*) iid, &result)))
return result;
return nullptr;
}
void ActiveXControlComponent::setMouseEventsAllowed (const bool eventsCanReachControl)
{
mouseEventsAllowed = eventsCanReachControl;
}
intptr_t ActiveXControlComponent::offerEventToActiveXControl (void* ptr)
{
if (control != nullptr && control->clientSite != nullptr)
return (intptr_t) control->clientSite->offerEventToActiveXControl (*reinterpret_cast<::MSG*> (ptr));
return S_FALSE;
}
intptr_t ActiveXControlComponent::offerEventToActiveXControlStatic (void* ptr)
{
for (auto* ax : ActiveXHelpers::activeXComps)
{
auto result = ax->offerEventToActiveXControl (ptr);
if (result != S_FALSE)
return result;
}
return S_FALSE;
}
LRESULT juce_offerEventToActiveXControl (::MSG& msg)
{
if (msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST)
return ActiveXControlComponent::offerEventToActiveXControlStatic (&msg);
return S_FALSE;
}
} // namespace juce
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-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
{
extern int64 getMouseEventTime();
JUCE_DECLARE_UUID_GETTER (IOleObject, "00000112-0000-0000-C000-000000000046")
JUCE_DECLARE_UUID_GETTER (IOleWindow, "00000114-0000-0000-C000-000000000046")
JUCE_DECLARE_UUID_GETTER (IOleInPlaceSite, "00000119-0000-0000-C000-000000000046")
namespace ActiveXHelpers
{
//==============================================================================
struct JuceIStorage : public ComBaseClassHelper<IStorage>
{
JuceIStorage() {}
JUCE_COMRESULT CreateStream (const WCHAR*, DWORD, DWORD, DWORD, IStream**) { return E_NOTIMPL; }
JUCE_COMRESULT OpenStream (const WCHAR*, void*, DWORD, DWORD, IStream**) { return E_NOTIMPL; }
JUCE_COMRESULT CreateStorage (const WCHAR*, DWORD, DWORD, DWORD, IStorage**) { return E_NOTIMPL; }
JUCE_COMRESULT OpenStorage (const WCHAR*, IStorage*, DWORD, SNB, DWORD, IStorage**) { return E_NOTIMPL; }
JUCE_COMRESULT CopyTo (DWORD, IID const*, SNB, IStorage*) { return E_NOTIMPL; }
JUCE_COMRESULT MoveElementTo (const OLECHAR*,IStorage*, const OLECHAR*, DWORD) { return E_NOTIMPL; }
JUCE_COMRESULT Commit (DWORD) { return E_NOTIMPL; }
JUCE_COMRESULT Revert() { return E_NOTIMPL; }
JUCE_COMRESULT EnumElements (DWORD, void*, DWORD, IEnumSTATSTG**) { return E_NOTIMPL; }
JUCE_COMRESULT DestroyElement (const OLECHAR*) { return E_NOTIMPL; }
JUCE_COMRESULT RenameElement (const WCHAR*, const WCHAR*) { return E_NOTIMPL; }
JUCE_COMRESULT SetElementTimes (const WCHAR*, FILETIME const*, FILETIME const*, FILETIME const*) { return E_NOTIMPL; }
JUCE_COMRESULT SetClass (REFCLSID) { return S_OK; }
JUCE_COMRESULT SetStateBits (DWORD, DWORD) { return E_NOTIMPL; }
JUCE_COMRESULT Stat (STATSTG*, DWORD) { return E_NOTIMPL; }
};
//==============================================================================
struct JuceOleInPlaceFrame : public ComBaseClassHelper<IOleInPlaceFrame>
{
JuceOleInPlaceFrame (HWND hwnd) : window (hwnd) {}
JUCE_COMRESULT GetWindow (HWND* lphwnd) { *lphwnd = window; return S_OK; }
JUCE_COMRESULT ContextSensitiveHelp (BOOL) { return E_NOTIMPL; }
JUCE_COMRESULT GetBorder (LPRECT) { return E_NOTIMPL; }
JUCE_COMRESULT RequestBorderSpace (LPCBORDERWIDTHS) { return E_NOTIMPL; }
JUCE_COMRESULT SetBorderSpace (LPCBORDERWIDTHS) { return E_NOTIMPL; }
JUCE_COMRESULT SetActiveObject (IOleInPlaceActiveObject* a, LPCOLESTR) { activeObject = a; return S_OK; }
JUCE_COMRESULT InsertMenus (HMENU, LPOLEMENUGROUPWIDTHS) { return E_NOTIMPL; }
JUCE_COMRESULT SetMenu (HMENU, HOLEMENU, HWND) { return S_OK; }
JUCE_COMRESULT RemoveMenus (HMENU) { return E_NOTIMPL; }
JUCE_COMRESULT SetStatusText (LPCOLESTR) { return S_OK; }
JUCE_COMRESULT EnableModeless (BOOL) { return S_OK; }
JUCE_COMRESULT TranslateAccelerator (LPMSG, WORD) { return E_NOTIMPL; }
HRESULT OfferKeyTranslation (LPMSG lpmsg)
{
if (activeObject != nullptr)
return activeObject->TranslateAcceleratorW (lpmsg);
return S_FALSE;
}
HWND window;
ComSmartPtr<IOleInPlaceActiveObject> activeObject;
};
//==============================================================================
struct JuceIOleInPlaceSite : public ComBaseClassHelper<IOleInPlaceSite>
{
JuceIOleInPlaceSite (HWND hwnd)
: window (hwnd),
frame (new JuceOleInPlaceFrame (window))
{}
~JuceIOleInPlaceSite()
{
frame->Release();
}
JUCE_COMRESULT GetWindow (HWND* lphwnd) { *lphwnd = window; return S_OK; }
JUCE_COMRESULT ContextSensitiveHelp (BOOL) { return E_NOTIMPL; }
JUCE_COMRESULT CanInPlaceActivate() { return S_OK; }
JUCE_COMRESULT OnInPlaceActivate() { return S_OK; }
JUCE_COMRESULT OnUIActivate() { return S_OK; }
JUCE_COMRESULT GetWindowContext (LPOLEINPLACEFRAME* lplpFrame, LPOLEINPLACEUIWINDOW* lplpDoc, LPRECT, LPRECT, LPOLEINPLACEFRAMEINFO lpFrameInfo)
{
/* Note: If you call AddRef on the frame here, then some types of object (e.g. web browser control) cause leaks..
If you don't call AddRef then others crash (e.g. QuickTime).. Bit of a catch-22, so letting it leak is probably preferable.
*/
if (lplpFrame != nullptr) { frame->AddRef(); *lplpFrame = frame; }
if (lplpDoc != nullptr) *lplpDoc = nullptr;
lpFrameInfo->fMDIApp = FALSE;
lpFrameInfo->hwndFrame = window;
lpFrameInfo->haccel = nullptr;
lpFrameInfo->cAccelEntries = 0;
return S_OK;
}
JUCE_COMRESULT Scroll (SIZE) { return E_NOTIMPL; }
JUCE_COMRESULT OnUIDeactivate (BOOL) { return S_OK; }
JUCE_COMRESULT OnInPlaceDeactivate() { return S_OK; }
JUCE_COMRESULT DiscardUndoState() { return E_NOTIMPL; }
JUCE_COMRESULT DeactivateAndUndo() { return E_NOTIMPL; }
JUCE_COMRESULT OnPosRectChange (LPCRECT) { return S_OK; }
LRESULT offerEventToActiveXControl (::MSG& msg)
{
if (frame != nullptr)
return frame->OfferKeyTranslation (&msg);
return S_FALSE;
}
HWND window;
JuceOleInPlaceFrame* frame;
};
//==============================================================================
struct JuceIOleClientSite : public ComBaseClassHelper<IOleClientSite>
{
JuceIOleClientSite (HWND window) : inplaceSite (new JuceIOleInPlaceSite (window))
{}
~JuceIOleClientSite()
{
inplaceSite->Release();
}
JUCE_COMRESULT QueryInterface (REFIID type, void** result)
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
if (type == __uuidof (IOleInPlaceSite))
{
inplaceSite->AddRef();
*result = static_cast<IOleInPlaceSite*> (inplaceSite);
return S_OK;
}
return ComBaseClassHelper <IOleClientSite>::QueryInterface (type, result);
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}
JUCE_COMRESULT SaveObject() { return E_NOTIMPL; }
JUCE_COMRESULT GetMoniker (DWORD, DWORD, IMoniker**) { return E_NOTIMPL; }
JUCE_COMRESULT GetContainer (LPOLECONTAINER* ppContainer) { *ppContainer = nullptr; return E_NOINTERFACE; }
JUCE_COMRESULT ShowObject() { return S_OK; }
JUCE_COMRESULT OnShowWindow (BOOL) { return E_NOTIMPL; }
JUCE_COMRESULT RequestNewObjectLayout() { return E_NOTIMPL; }
LRESULT offerEventToActiveXControl (::MSG& msg)
{
if (inplaceSite != nullptr)
return inplaceSite->offerEventToActiveXControl (msg);
return S_FALSE;
}
JuceIOleInPlaceSite* inplaceSite;
};
//==============================================================================
static Array<ActiveXControlComponent*> activeXComps;
static HWND getHWND (const ActiveXControlComponent* const component)
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
HWND hwnd = {};
const IID iid = __uuidof (IOleWindow);
if (auto* window = (IOleWindow*) component->queryInterface (&iid))
{
window->GetWindow (&hwnd);
window->Release();
}
return hwnd;
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}
static void offerActiveXMouseEventToPeer (ComponentPeer* peer, HWND hwnd, UINT message, LPARAM lParam)
{
switch (message)
{
case WM_MOUSEMOVE:
case WM_LBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_LBUTTONUP:
case WM_MBUTTONUP:
case WM_RBUTTONUP:
{
RECT activeXRect, peerRect;
GetWindowRect (hwnd, &activeXRect);
GetWindowRect ((HWND) peer->getNativeHandle(), &peerRect);
peer->handleMouseEvent (MouseInputSource::InputSourceType::mouse,
{ (float) (GET_X_LPARAM (lParam) + activeXRect.left - peerRect.left),
(float) (GET_Y_LPARAM (lParam) + activeXRect.top - peerRect.top) },
ComponentPeer::getCurrentModifiersRealtime(),
MouseInputSource::defaultPressure,
MouseInputSource::defaultOrientation,
getMouseEventTime());
break;
}
default:
break;
}
}
}
//==============================================================================
class ActiveXControlComponent::Pimpl : public ComponentMovementWatcher,
public ComponentPeer::ScaleFactorListener
{
public:
Pimpl (HWND hwnd, ActiveXControlComponent& activeXComp)
: ComponentMovementWatcher (&activeXComp),
owner (activeXComp),
storage (new ActiveXHelpers::JuceIStorage()),
clientSite (new ActiveXHelpers::JuceIOleClientSite (hwnd))
{
}
~Pimpl() override
{
if (control != nullptr)
{
control->Close (OLECLOSE_NOSAVE);
control->Release();
}
clientSite->Release();
storage->Release();
if (currentPeer != nullptr)
currentPeer->removeScaleFactorListener (this);
}
void setControlBounds (Rectangle<int> newBounds) const
{
if (controlHWND != nullptr)
{
if (auto* peer = owner.getTopLevelComponent()->getPeer())
newBounds = (newBounds.toDouble() * peer->getPlatformScaleFactor()).toNearestInt();
MoveWindow (controlHWND, newBounds.getX(), newBounds.getY(), newBounds.getWidth(), newBounds.getHeight(), TRUE);
}
}
void setControlVisible (bool shouldBeVisible) const
{
if (controlHWND != nullptr)
ShowWindow (controlHWND, shouldBeVisible ? SW_SHOWNA : SW_HIDE);
}
//==============================================================================
using ComponentMovementWatcher::componentMovedOrResized;
void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override
{
if (auto* peer = owner.getTopLevelComponent()->getPeer())
setControlBounds (peer->getAreaCoveredBy (owner));
}
void componentPeerChanged() override
{
if (currentPeer != nullptr)
currentPeer->removeScaleFactorListener (this);
componentMovedOrResized (true, true);
currentPeer = owner.getTopLevelComponent()->getPeer();
if (currentPeer != nullptr)
currentPeer->addScaleFactorListener (this);
}
using ComponentMovementWatcher::componentVisibilityChanged;
void componentVisibilityChanged() override
{
setControlVisible (owner.isShowing());
componentPeerChanged();
}
void nativeScaleFactorChanged (double /*newScaleFactor*/) override
{
componentMovedOrResized (true, true);
}
// intercepts events going to an activeX control, so we can sneakily use the mouse events
static LRESULT CALLBACK activeXHookWndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
for (auto* ax : ActiveXHelpers::activeXComps)
{
if (ax->control != nullptr && ax->control->controlHWND == hwnd)
{
switch (message)
{
case WM_MOUSEMOVE:
case WM_LBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_LBUTTONUP:
case WM_MBUTTONUP:
case WM_RBUTTONUP:
case WM_LBUTTONDBLCLK:
case WM_MBUTTONDBLCLK:
case WM_RBUTTONDBLCLK:
if (ax->isShowing())
{
if (auto* peer = ax->getPeer())
{
ActiveXHelpers::offerActiveXMouseEventToPeer (peer, hwnd, message, lParam);
if (! ax->areMouseEventsAllowed())
return 0;
}
}
break;
default:
break;
}
return CallWindowProc (ax->control->originalWndProc, hwnd, message, wParam, lParam);
}
}
return DefWindowProc (hwnd, message, wParam, lParam);
}
ActiveXControlComponent& owner;
ComponentPeer* currentPeer = nullptr;
HWND controlHWND = {};
IStorage* storage = nullptr;
ActiveXHelpers::JuceIOleClientSite* clientSite = nullptr;
IOleObject* control = nullptr;
WNDPROC originalWndProc = nullptr;
};
//==============================================================================
ActiveXControlComponent::ActiveXControlComponent()
{
ActiveXHelpers::activeXComps.add (this);
}
ActiveXControlComponent::~ActiveXControlComponent()
{
deleteControl();
ActiveXHelpers::activeXComps.removeFirstMatchingValue (this);
}
void ActiveXControlComponent::paint (Graphics& g)
{
if (control == nullptr)
g.fillAll (Colours::lightgrey);
}
bool ActiveXControlComponent::createControl (const void* controlIID)
{
deleteControl();
if (auto* peer = getPeer())
{
auto controlBounds = peer->getAreaCoveredBy (*this);
auto hwnd = (HWND) peer->getNativeHandle();
std::unique_ptr<Pimpl> newControl (new Pimpl (hwnd, *this));
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
HRESULT hr = OleCreate (*(const IID*) controlIID, __uuidof (IOleObject), 1 /*OLERENDER_DRAW*/, nullptr,
newControl->clientSite, newControl->storage,
(void**) &(newControl->control));
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
if (hr == S_OK)
{
newControl->control->SetHostNames (L"JUCE", nullptr);
if (OleSetContainedObject (newControl->control, TRUE) == S_OK)
{
RECT rect;
rect.left = controlBounds.getX();
rect.top = controlBounds.getY();
rect.right = controlBounds.getRight();
rect.bottom = controlBounds.getBottom();
if (newControl->control->DoVerb (OLEIVERB_SHOW, nullptr, newControl->clientSite, 0, hwnd, &rect) == S_OK)
{
control.reset (newControl.release());
control->controlHWND = ActiveXHelpers::getHWND (this);
if (control->controlHWND != nullptr)
{
control->setControlBounds (controlBounds);
control->originalWndProc = (WNDPROC) GetWindowLongPtr ((HWND) control->controlHWND, GWLP_WNDPROC);
SetWindowLongPtr ((HWND) control->controlHWND, GWLP_WNDPROC, (LONG_PTR) Pimpl::activeXHookWndProc);
}
return true;
}
}
}
}
else
{
// the component must have already been added to a real window when you call this!
jassertfalse;
}
return false;
}
void ActiveXControlComponent::deleteControl()
{
control = nullptr;
}
void* ActiveXControlComponent::queryInterface (const void* iid) const
{
void* result = nullptr;
if (control != nullptr && control->control != nullptr
&& SUCCEEDED (control->control->QueryInterface (*(const IID*) iid, &result)))
return result;
return nullptr;
}
void ActiveXControlComponent::setMouseEventsAllowed (const bool eventsCanReachControl)
{
mouseEventsAllowed = eventsCanReachControl;
}
intptr_t ActiveXControlComponent::offerEventToActiveXControl (void* ptr)
{
if (control != nullptr && control->clientSite != nullptr)
return (intptr_t) control->clientSite->offerEventToActiveXControl (*reinterpret_cast<::MSG*> (ptr));
return S_FALSE;
}
intptr_t ActiveXControlComponent::offerEventToActiveXControlStatic (void* ptr)
{
for (auto* ax : ActiveXHelpers::activeXComps)
{
auto result = ax->offerEventToActiveXControl (ptr);
if (result != S_FALSE)
return result;
}
return S_FALSE;
}
LRESULT juce_offerEventToActiveXControl (::MSG& msg);
LRESULT juce_offerEventToActiveXControl (::MSG& msg)
{
if (msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST)
return ActiveXControlComponent::offerEventToActiveXControlStatic (&msg);
return S_FALSE;
}
} // namespace juce

View File

@ -1,175 +1,181 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-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
{
class HWNDComponent::Pimpl : public ComponentMovementWatcher
{
public:
Pimpl (HWND h, Component& comp)
: ComponentMovementWatcher (&comp),
hwnd (h),
owner (comp)
{
if (owner.isShowing())
componentPeerChanged();
}
~Pimpl() override
{
removeFromParent();
DestroyWindow (hwnd);
}
void componentMovedOrResized (bool wasMoved, bool wasResized) override
{
if (auto* peer = owner.getTopLevelComponent()->getPeer())
{
auto area = (peer->getAreaCoveredBy (owner).toFloat() * peer->getPlatformScaleFactor()).getSmallestIntegerContainer();
UINT flagsToSend = SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER;
if (! wasMoved) flagsToSend |= SWP_NOMOVE;
if (! wasResized) flagsToSend |= SWP_NOSIZE;
ScopedThreadDPIAwarenessSetter threadDpiAwarenessSetter { hwnd };
SetWindowPos (hwnd, nullptr, area.getX(), area.getY(), area.getWidth(), area.getHeight(), flagsToSend);
}
}
using ComponentMovementWatcher::componentMovedOrResized;
void componentPeerChanged() override
{
auto* peer = owner.getPeer();
if (currentPeer != peer)
{
removeFromParent();
currentPeer = peer;
addToParent();
}
auto isShowing = owner.isShowing();
ShowWindow (hwnd, isShowing ? SW_SHOWNA : SW_HIDE);
if (isShowing)
InvalidateRect (hwnd, nullptr, 0);
}
void componentVisibilityChanged() override
{
componentPeerChanged();
}
using ComponentMovementWatcher::componentVisibilityChanged;
void componentBroughtToFront (Component& comp) override
{
ComponentMovementWatcher::componentBroughtToFront (comp);
}
Rectangle<int> getHWNDBounds() const
{
if (auto* peer = owner.getPeer())
{
ScopedThreadDPIAwarenessSetter threadDpiAwarenessSetter { hwnd };
RECT r;
GetWindowRect (hwnd, &r);
Rectangle<int> windowRectangle (r.right - r.left, r.bottom - r.top);
return (windowRectangle.toFloat() / peer->getPlatformScaleFactor()).toNearestInt();
}
return {};
}
HWND hwnd;
private:
void addToParent()
{
if (currentPeer != nullptr)
{
auto windowFlags = GetWindowLongPtr (hwnd, -16);
using FlagType = decltype (windowFlags);
windowFlags &= ~(FlagType) WS_POPUP;
windowFlags |= (FlagType) WS_CHILD;
SetWindowLongPtr (hwnd, -16, windowFlags);
SetParent (hwnd, (HWND) currentPeer->getNativeHandle());
componentMovedOrResized (true, true);
}
}
void removeFromParent()
{
ShowWindow (hwnd, SW_HIDE);
SetParent (hwnd, nullptr);
}
Component& owner;
ComponentPeer* currentPeer = nullptr;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
};
//==============================================================================
HWNDComponent::HWNDComponent() {}
HWNDComponent::~HWNDComponent() {}
void HWNDComponent::paint (Graphics&) {}
void HWNDComponent::setHWND (void* hwnd)
{
if (hwnd != getHWND())
{
pimpl.reset();
if (hwnd != nullptr)
pimpl.reset (new Pimpl ((HWND) hwnd, *this));
}
}
void* HWNDComponent::getHWND() const
{
return pimpl == nullptr ? nullptr : (void*) pimpl->hwnd;
}
void HWNDComponent::resizeToFit()
{
if (pimpl != nullptr)
setBounds (pimpl->getHWNDBounds());
}
} // namespace juce
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-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
{
class HWNDComponent::Pimpl : public ComponentMovementWatcher
{
public:
Pimpl (HWND h, Component& comp)
: ComponentMovementWatcher (&comp),
hwnd (h),
owner (comp)
{
if (owner.isShowing())
componentPeerChanged();
}
~Pimpl() override
{
removeFromParent();
DestroyWindow (hwnd);
}
void componentMovedOrResized (bool wasMoved, bool wasResized) override
{
if (auto* peer = owner.getTopLevelComponent()->getPeer())
{
auto area = (peer->getAreaCoveredBy (owner).toFloat() * peer->getPlatformScaleFactor()).getSmallestIntegerContainer();
UINT flagsToSend = SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER;
if (! wasMoved) flagsToSend |= SWP_NOMOVE;
if (! wasResized) flagsToSend |= SWP_NOSIZE;
ScopedThreadDPIAwarenessSetter threadDpiAwarenessSetter { hwnd };
SetWindowPos (hwnd, nullptr, area.getX(), area.getY(), area.getWidth(), area.getHeight(), flagsToSend);
}
}
using ComponentMovementWatcher::componentMovedOrResized;
void componentPeerChanged() override
{
auto* peer = owner.getPeer();
if (currentPeer != peer)
{
removeFromParent();
currentPeer = peer;
addToParent();
}
auto isShowing = owner.isShowing();
ShowWindow (hwnd, isShowing ? SW_SHOWNA : SW_HIDE);
if (isShowing)
InvalidateRect (hwnd, nullptr, 0);
}
void componentVisibilityChanged() override
{
componentPeerChanged();
}
using ComponentMovementWatcher::componentVisibilityChanged;
void componentBroughtToFront (Component& comp) override
{
ComponentMovementWatcher::componentBroughtToFront (comp);
}
Rectangle<int> getHWNDBounds() const
{
if (auto* peer = owner.getPeer())
{
ScopedThreadDPIAwarenessSetter threadDpiAwarenessSetter { hwnd };
RECT r;
GetWindowRect (hwnd, &r);
Rectangle<int> windowRectangle (r.right - r.left, r.bottom - r.top);
return (windowRectangle.toFloat() / peer->getPlatformScaleFactor()).toNearestInt();
}
return {};
}
HWND hwnd;
private:
void addToParent()
{
if (currentPeer != nullptr)
{
auto windowFlags = GetWindowLongPtr (hwnd, -16);
using FlagType = decltype (windowFlags);
windowFlags &= ~(FlagType) WS_POPUP;
windowFlags |= (FlagType) WS_CHILD;
SetWindowLongPtr (hwnd, -16, windowFlags);
SetParent (hwnd, (HWND) currentPeer->getNativeHandle());
componentMovedOrResized (true, true);
}
}
void removeFromParent()
{
ShowWindow (hwnd, SW_HIDE);
SetParent (hwnd, nullptr);
}
Component& owner;
ComponentPeer* currentPeer = nullptr;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
};
//==============================================================================
HWNDComponent::HWNDComponent() {}
HWNDComponent::~HWNDComponent() {}
void HWNDComponent::paint (Graphics&) {}
void HWNDComponent::setHWND (void* hwnd)
{
if (hwnd != getHWND())
{
pimpl.reset();
if (hwnd != nullptr)
pimpl.reset (new Pimpl ((HWND) hwnd, *this));
}
}
void* HWNDComponent::getHWND() const
{
return pimpl == nullptr ? nullptr : (void*) pimpl->hwnd;
}
void HWNDComponent::resizeToFit()
{
if (pimpl != nullptr)
setBounds (pimpl->getHWNDBounds());
}
void HWNDComponent::updateHWNDBounds()
{
if (pimpl != nullptr)
pimpl->componentMovedOrResized (true, true);
}
} // namespace juce

View File

@ -1,242 +1,242 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-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
{
extern void* getUser32Function (const char*);
namespace IconConverters
{
extern HICON createHICONFromImage (const Image&, BOOL isIcon, int hotspotX, int hotspotY);
}
//==============================================================================
class SystemTrayIconComponent::Pimpl
{
public:
Pimpl (SystemTrayIconComponent& owner_, HICON hicon, HWND hwnd)
: owner (owner_),
originalWndProc ((WNDPROC) GetWindowLongPtr (hwnd, GWLP_WNDPROC)),
taskbarCreatedMessage (RegisterWindowMessage (TEXT ("TaskbarCreated")))
{
SetWindowLongPtr (hwnd, GWLP_WNDPROC, (LONG_PTR) hookedWndProc);
zerostruct (iconData);
iconData.cbSize = sizeof (iconData);
iconData.hWnd = hwnd;
iconData.uID = (UINT) (pointer_sized_int) hwnd;
iconData.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
iconData.uCallbackMessage = WM_TRAYNOTIFY;
iconData.hIcon = hicon;
notify (NIM_ADD);
// In order to receive the "TaskbarCreated" message, we need to request that it's not filtered out.
// (Need to load dynamically, as ChangeWindowMessageFilter is only available in Vista and later)
typedef BOOL (WINAPI* ChangeWindowMessageFilterType) (UINT, DWORD);
if (ChangeWindowMessageFilterType changeWindowMessageFilter
= (ChangeWindowMessageFilterType) getUser32Function ("ChangeWindowMessageFilter"))
changeWindowMessageFilter (taskbarCreatedMessage, 1 /* MSGFLT_ADD */);
}
~Pimpl()
{
SetWindowLongPtr (iconData.hWnd, GWLP_WNDPROC, (LONG_PTR) originalWndProc);
iconData.uFlags = 0;
notify (NIM_DELETE);
DestroyIcon (iconData.hIcon);
}
void updateIcon (HICON hicon)
{
HICON oldIcon = iconData.hIcon;
iconData.hIcon = hicon;
iconData.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
notify (NIM_MODIFY);
DestroyIcon (oldIcon);
}
void setToolTip (const String& toolTip)
{
iconData.uFlags = NIF_TIP;
toolTip.copyToUTF16 (iconData.szTip, sizeof (iconData.szTip) - 1);
notify (NIM_MODIFY);
}
void handleTaskBarEvent (const LPARAM lParam)
{
if (owner.isCurrentlyBlockedByAnotherModalComponent())
{
if (lParam == WM_LBUTTONDOWN || lParam == WM_RBUTTONDOWN
|| lParam == WM_LBUTTONDBLCLK || lParam == WM_RBUTTONDBLCLK)
{
if (auto* current = Component::getCurrentlyModalComponent())
current->inputAttemptWhenModal();
}
}
else
{
ModifierKeys eventMods (ComponentPeer::getCurrentModifiersRealtime());
if (lParam == WM_LBUTTONDOWN || lParam == WM_LBUTTONDBLCLK)
eventMods = eventMods.withFlags (ModifierKeys::leftButtonModifier);
else if (lParam == WM_RBUTTONDOWN || lParam == WM_RBUTTONDBLCLK)
eventMods = eventMods.withFlags (ModifierKeys::rightButtonModifier);
else if (lParam == WM_LBUTTONUP || lParam == WM_RBUTTONUP)
eventMods = eventMods.withoutMouseButtons();
const Time eventTime (getMouseEventTime());
const MouseEvent e (Desktop::getInstance().getMainMouseSource(), {}, eventMods,
MouseInputSource::invalidPressure, MouseInputSource::invalidOrientation,
MouseInputSource::invalidRotation, MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY,
&owner, &owner, eventTime, {}, eventTime, 1, false);
if (lParam == WM_LBUTTONDOWN || lParam == WM_RBUTTONDOWN)
{
SetFocus (iconData.hWnd);
SetForegroundWindow (iconData.hWnd);
owner.mouseDown (e);
}
else if (lParam == WM_LBUTTONUP || lParam == WM_RBUTTONUP)
{
owner.mouseUp (e);
}
else if (lParam == WM_LBUTTONDBLCLK || lParam == WM_RBUTTONDBLCLK)
{
owner.mouseDoubleClick (e);
}
else if (lParam == WM_MOUSEMOVE)
{
owner.mouseMove (e);
}
}
}
static Pimpl* getPimpl (HWND hwnd)
{
if (JuceWindowIdentifier::isJUCEWindow (hwnd))
if (ComponentPeer* peer = (ComponentPeer*) GetWindowLongPtr (hwnd, 8))
if (SystemTrayIconComponent* const iconComp = dynamic_cast<SystemTrayIconComponent*> (&(peer->getComponent())))
return iconComp->pimpl.get();
return nullptr;
}
static LRESULT CALLBACK hookedWndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
if (Pimpl* const p = getPimpl (hwnd))
return p->windowProc (hwnd, message, wParam, lParam);
return DefWindowProcW (hwnd, message, wParam, lParam);
}
LRESULT windowProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
if (message == WM_TRAYNOTIFY)
{
handleTaskBarEvent (lParam);
}
else if (message == taskbarCreatedMessage)
{
iconData.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
notify (NIM_ADD);
}
return CallWindowProc (originalWndProc, hwnd, message, wParam, lParam);
}
void showBubble (const String& title, const String& content)
{
iconData.uFlags = 0x10 /*NIF_INFO*/;
title.copyToUTF16 (iconData.szInfoTitle, sizeof (iconData.szInfoTitle) - 1);
content.copyToUTF16 (iconData.szInfo, sizeof (iconData.szInfo) - 1);
notify (NIM_MODIFY);
}
SystemTrayIconComponent& owner;
NOTIFYICONDATA iconData;
private:
WNDPROC originalWndProc;
const DWORD taskbarCreatedMessage;
enum { WM_TRAYNOTIFY = WM_USER + 100 };
void notify (DWORD message) noexcept { Shell_NotifyIcon (message, &iconData); }
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
};
//==============================================================================
void SystemTrayIconComponent::setIconImage (const Image& colourImage, const Image&)
{
if (colourImage.isValid())
{
HICON hicon = IconConverters::createHICONFromImage (colourImage, TRUE, 0, 0);
if (pimpl == nullptr)
pimpl.reset (new Pimpl (*this, hicon, (HWND) getWindowHandle()));
else
pimpl->updateIcon (hicon);
}
else
{
pimpl.reset();
}
}
void SystemTrayIconComponent::setIconTooltip (const String& tooltip)
{
if (pimpl != nullptr)
pimpl->setToolTip (tooltip);
}
void SystemTrayIconComponent::setHighlighted (bool)
{
// N/A on Windows.
}
void SystemTrayIconComponent::showInfoBubble (const String& title, const String& content)
{
if (pimpl != nullptr)
pimpl->showBubble (title, content);
}
void SystemTrayIconComponent::hideInfoBubble()
{
showInfoBubble (String(), String());
}
void* SystemTrayIconComponent::getNativeHandle() const
{
return pimpl != nullptr ? &(pimpl->iconData) : nullptr;
}
} // namespace juce
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-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
{
extern void* getUser32Function (const char*);
namespace IconConverters
{
extern HICON createHICONFromImage (const Image&, BOOL isIcon, int hotspotX, int hotspotY);
}
//==============================================================================
class SystemTrayIconComponent::Pimpl
{
public:
Pimpl (SystemTrayIconComponent& owner_, HICON hicon, HWND hwnd)
: owner (owner_),
originalWndProc ((WNDPROC) GetWindowLongPtr (hwnd, GWLP_WNDPROC)),
taskbarCreatedMessage (RegisterWindowMessage (TEXT ("TaskbarCreated")))
{
SetWindowLongPtr (hwnd, GWLP_WNDPROC, (LONG_PTR) hookedWndProc);
zerostruct (iconData);
iconData.cbSize = sizeof (iconData);
iconData.hWnd = hwnd;
iconData.uID = (UINT) (pointer_sized_int) hwnd;
iconData.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
iconData.uCallbackMessage = WM_TRAYNOTIFY;
iconData.hIcon = hicon;
notify (NIM_ADD);
// In order to receive the "TaskbarCreated" message, we need to request that it's not filtered out.
// (Need to load dynamically, as ChangeWindowMessageFilter is only available in Vista and later)
typedef BOOL (WINAPI* ChangeWindowMessageFilterType) (UINT, DWORD);
if (ChangeWindowMessageFilterType changeWindowMessageFilter
= (ChangeWindowMessageFilterType) getUser32Function ("ChangeWindowMessageFilter"))
changeWindowMessageFilter (taskbarCreatedMessage, 1 /* MSGFLT_ADD */);
}
~Pimpl()
{
SetWindowLongPtr (iconData.hWnd, GWLP_WNDPROC, (LONG_PTR) originalWndProc);
iconData.uFlags = 0;
notify (NIM_DELETE);
DestroyIcon (iconData.hIcon);
}
void updateIcon (HICON hicon)
{
HICON oldIcon = iconData.hIcon;
iconData.hIcon = hicon;
iconData.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
notify (NIM_MODIFY);
DestroyIcon (oldIcon);
}
void setToolTip (const String& toolTip)
{
iconData.uFlags = NIF_TIP;
toolTip.copyToUTF16 (iconData.szTip, sizeof (iconData.szTip) - 1);
notify (NIM_MODIFY);
}
void handleTaskBarEvent (const LPARAM lParam)
{
if (owner.isCurrentlyBlockedByAnotherModalComponent())
{
if (lParam == WM_LBUTTONDOWN || lParam == WM_RBUTTONDOWN
|| lParam == WM_LBUTTONDBLCLK || lParam == WM_RBUTTONDBLCLK)
{
if (auto* current = Component::getCurrentlyModalComponent())
current->inputAttemptWhenModal();
}
}
else
{
ModifierKeys eventMods (ComponentPeer::getCurrentModifiersRealtime());
if (lParam == WM_LBUTTONDOWN || lParam == WM_LBUTTONDBLCLK)
eventMods = eventMods.withFlags (ModifierKeys::leftButtonModifier);
else if (lParam == WM_RBUTTONDOWN || lParam == WM_RBUTTONDBLCLK)
eventMods = eventMods.withFlags (ModifierKeys::rightButtonModifier);
else if (lParam == WM_LBUTTONUP || lParam == WM_RBUTTONUP)
eventMods = eventMods.withoutMouseButtons();
const Time eventTime (getMouseEventTime());
const MouseEvent e (Desktop::getInstance().getMainMouseSource(), {}, eventMods,
MouseInputSource::defaultPressure, MouseInputSource::defaultOrientation,
MouseInputSource::defaultRotation, MouseInputSource::defaultTiltX, MouseInputSource::defaultTiltY,
&owner, &owner, eventTime, {}, eventTime, 1, false);
if (lParam == WM_LBUTTONDOWN || lParam == WM_RBUTTONDOWN)
{
SetFocus (iconData.hWnd);
SetForegroundWindow (iconData.hWnd);
owner.mouseDown (e);
}
else if (lParam == WM_LBUTTONUP || lParam == WM_RBUTTONUP)
{
owner.mouseUp (e);
}
else if (lParam == WM_LBUTTONDBLCLK || lParam == WM_RBUTTONDBLCLK)
{
owner.mouseDoubleClick (e);
}
else if (lParam == WM_MOUSEMOVE)
{
owner.mouseMove (e);
}
}
}
static Pimpl* getPimpl (HWND hwnd)
{
if (JuceWindowIdentifier::isJUCEWindow (hwnd))
if (ComponentPeer* peer = (ComponentPeer*) GetWindowLongPtr (hwnd, 8))
if (SystemTrayIconComponent* const iconComp = dynamic_cast<SystemTrayIconComponent*> (&(peer->getComponent())))
return iconComp->pimpl.get();
return nullptr;
}
static LRESULT CALLBACK hookedWndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
if (Pimpl* const p = getPimpl (hwnd))
return p->windowProc (hwnd, message, wParam, lParam);
return DefWindowProcW (hwnd, message, wParam, lParam);
}
LRESULT windowProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
if (message == WM_TRAYNOTIFY)
{
handleTaskBarEvent (lParam);
}
else if (message == taskbarCreatedMessage)
{
iconData.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
notify (NIM_ADD);
}
return CallWindowProc (originalWndProc, hwnd, message, wParam, lParam);
}
void showBubble (const String& title, const String& content)
{
iconData.uFlags = 0x10 /*NIF_INFO*/;
title.copyToUTF16 (iconData.szInfoTitle, sizeof (iconData.szInfoTitle) - 1);
content.copyToUTF16 (iconData.szInfo, sizeof (iconData.szInfo) - 1);
notify (NIM_MODIFY);
}
SystemTrayIconComponent& owner;
NOTIFYICONDATA iconData;
private:
WNDPROC originalWndProc;
const DWORD taskbarCreatedMessage;
enum { WM_TRAYNOTIFY = WM_USER + 100 };
void notify (DWORD message) noexcept { Shell_NotifyIcon (message, &iconData); }
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
};
//==============================================================================
void SystemTrayIconComponent::setIconImage (const Image& colourImage, const Image&)
{
if (colourImage.isValid())
{
HICON hicon = IconConverters::createHICONFromImage (colourImage, TRUE, 0, 0);
if (pimpl == nullptr)
pimpl.reset (new Pimpl (*this, hicon, (HWND) getWindowHandle()));
else
pimpl->updateIcon (hicon);
}
else
{
pimpl.reset();
}
}
void SystemTrayIconComponent::setIconTooltip (const String& tooltip)
{
if (pimpl != nullptr)
pimpl->setToolTip (tooltip);
}
void SystemTrayIconComponent::setHighlighted (bool)
{
// N/A on Windows.
}
void SystemTrayIconComponent::showInfoBubble (const String& title, const String& content)
{
if (pimpl != nullptr)
pimpl->showBubble (title, content);
}
void SystemTrayIconComponent::hideInfoBubble()
{
showInfoBubble (String(), String());
}
void* SystemTrayIconComponent::getNativeHandle() const
{
return pimpl != nullptr ? &(pimpl->iconData) : nullptr;
}
} // namespace juce

File diff suppressed because it is too large Load Diff