migrating to the latest JUCE version
This commit is contained in:
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
@ -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
|
||||
|
@ -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
File diff suppressed because it is too large
Load Diff
@ -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)
|
||||
|
@ -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
|
@ -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
|
||||
{
|
||||
|
111
deps/juce/modules/juce_gui_extra/native/juce_mac_NSViewFrameWatcher.h
vendored
Normal file
111
deps/juce/modules/juce_gui_extra/native/juce_mac_NSViewFrameWatcher.h
vendored
Normal 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
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
Reference in New Issue
Block a user