migrating to the latest JUCE version

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,274 +1,271 @@
/*
==============================================================================
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
{
CallOutBox::CallOutBox (Component& c, Rectangle<int> area, Component* const parent)
: content (c)
{
addAndMakeVisible (content);
if (parent != nullptr)
{
parent->addChildComponent (this);
updatePosition (area, parent->getLocalBounds());
setVisible (true);
}
else
{
setAlwaysOnTop (juce_areThereAnyAlwaysOnTopWindows());
updatePosition (area, Desktop::getInstance().getDisplays().getDisplayForRect (area)->userArea);
addToDesktop (ComponentPeer::windowIsTemporary);
startTimer (100);
}
creationTime = Time::getCurrentTime();
}
//==============================================================================
class CallOutBoxCallback : public ModalComponentManager::Callback,
private Timer
{
public:
CallOutBoxCallback (std::unique_ptr<Component> c, const Rectangle<int>& area, Component* parent, bool dismissIfBg)
: content (std::move (c)),
callout (*content, area, parent), dismissIfBackgrounded(dismissIfBg)
{
callout.setVisible (true);
callout.enterModalState (true, this);
if (dismissIfBackgrounded) {
startTimer (200);
}
}
void modalStateFinished (int) override {}
void timerCallback() override
{
if (! isForegroundOrEmbeddedProcess (&callout))
callout.dismiss();
}
std::unique_ptr<Component> content;
CallOutBox callout;
bool dismissIfBackgrounded = true;
JUCE_DECLARE_NON_COPYABLE (CallOutBoxCallback)
};
CallOutBox& CallOutBox::launchAsynchronously (std::unique_ptr<Component> content, Rectangle<int> area, Component* parent, bool dismissIfBackgrounded)
{
jassert (content != nullptr); // must be a valid content component!
return (new CallOutBoxCallback (std::move (content), area, parent, dismissIfBackgrounded))->callout;
}
//==============================================================================
void CallOutBox::setArrowSize (const float newSize)
{
arrowSize = newSize;
refreshPath();
}
int CallOutBox::getBorderSize() const noexcept
{
return jmax (getLookAndFeel().getCallOutBoxBorderSize (*this), (int) arrowSize);
}
void CallOutBox::lookAndFeelChanged()
{
resized();
repaint();
}
void CallOutBox::paint (Graphics& g)
{
getLookAndFeel().drawCallOutBoxBackground (*this, g, outline, background);
}
void CallOutBox::resized()
{
auto borderSpace = getBorderSize();
content.setTopLeftPosition (borderSpace, borderSpace);
refreshPath();
}
void CallOutBox::moved()
{
refreshPath();
}
void CallOutBox::childBoundsChanged (Component*)
{
updatePosition (targetArea, availableArea);
}
bool CallOutBox::hitTest (int x, int y)
{
return outline.contains ((float) x, (float) y);
}
void CallOutBox::inputAttemptWhenModal()
{
if (dismissalMouseClicksAreAlwaysConsumed
|| targetArea.contains (getMouseXYRelative() + getBounds().getPosition()))
{
// if you click on the area that originally popped-up the callout, you expect it
// to get rid of the box, but deleting the box here allows the click to pass through and
// probably re-trigger it, so we need to dismiss the box asynchronously to consume the click..
// For touchscreens, we make sure not to dismiss the CallOutBox immediately,
// as Windows still sends touch events before the CallOutBox had a chance
// to really open.
auto elapsed = Time::getCurrentTime() - creationTime;
if (elapsed.inMilliseconds() > 200)
dismiss();
}
else
{
exitModalState (0);
setVisible (false);
}
}
void CallOutBox::setDismissalMouseClicksAreAlwaysConsumed (bool b) noexcept
{
dismissalMouseClicksAreAlwaysConsumed = b;
}
static constexpr int callOutBoxDismissCommandId = 0x4f83a04b;
void CallOutBox::handleCommandMessage (int commandId)
{
Component::handleCommandMessage (commandId);
if (commandId == callOutBoxDismissCommandId)
{
exitModalState (0);
setVisible (false);
}
}
void CallOutBox::dismiss()
{
postCommandMessage (callOutBoxDismissCommandId);
}
bool CallOutBox::keyPressed (const KeyPress& key)
{
if (key.isKeyCode (KeyPress::escapeKey))
{
inputAttemptWhenModal();
return true;
}
return false;
}
void CallOutBox::updatePosition (const Rectangle<int>& newAreaToPointTo, const Rectangle<int>& newAreaToFitIn)
{
targetArea = newAreaToPointTo;
availableArea = newAreaToFitIn;
auto borderSpace = getBorderSize();
auto newBounds = getLocalArea (&content, Rectangle<int> (content.getWidth() + borderSpace * 2,
content.getHeight() + borderSpace * 2));
auto hw = newBounds.getWidth() / 2;
auto hh = newBounds.getHeight() / 2;
auto hwReduced = (float) (hw - borderSpace * 2);
auto hhReduced = (float) (hh - borderSpace * 2);
auto arrowIndent = (float) borderSpace - arrowSize;
Point<float> targets[4] = { { (float) targetArea.getCentreX(), (float) targetArea.getBottom() },
{ (float) targetArea.getRight(), (float) targetArea.getCentreY() },
{ (float) targetArea.getX(), (float) targetArea.getCentreY() },
{ (float) targetArea.getCentreX(), (float) targetArea.getY() } };
Line<float> lines[4] = { { targets[0].translated (-hwReduced, hh - arrowIndent), targets[0].translated (hwReduced, hh - arrowIndent) },
{ targets[1].translated (hw - arrowIndent, -hhReduced), targets[1].translated (hw - arrowIndent, hhReduced) },
{ targets[2].translated (-(hw - arrowIndent), -hhReduced), targets[2].translated (-(hw - arrowIndent), hhReduced) },
{ targets[3].translated (-hwReduced, -(hh - arrowIndent)), targets[3].translated (hwReduced, -(hh - arrowIndent)) } };
auto centrePointArea = newAreaToFitIn.reduced (hw, hh).toFloat();
auto targetCentre = targetArea.getCentre().toFloat();
float nearest = 1.0e9f;
for (int i = 0; i < 4; ++i)
{
Line<float> constrainedLine (centrePointArea.getConstrainedPoint (lines[i].getStart()),
centrePointArea.getConstrainedPoint (lines[i].getEnd()));
auto centre = constrainedLine.findNearestPointTo (targetCentre);
auto distanceFromCentre = centre.getDistanceFrom (targets[i]);
if (! centrePointArea.intersects (lines[i]))
distanceFromCentre += 1000.0f;
if (distanceFromCentre < nearest)
{
nearest = distanceFromCentre;
targetPoint = targets[i];
newBounds.setPosition ((int) (centre.x - (float) hw),
(int) (centre.y - (float) hh));
}
}
setBounds (newBounds);
}
void CallOutBox::refreshPath()
{
repaint();
background = {};
outline.clear();
const float gap = 4.5f;
outline.addBubble (getLocalArea (&content, content.getLocalBounds().toFloat()).expanded (gap, gap),
getLocalBounds().toFloat(),
targetPoint - getPosition().toFloat(),
getLookAndFeel().getCallOutBoxCornerSize (*this), arrowSize * 0.7f);
}
void CallOutBox::timerCallback()
{
toFront (true);
stopTimer();
}
//==============================================================================
std::unique_ptr<AccessibilityHandler> CallOutBox::createAccessibilityHandler()
{
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::window);
}
} // 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
{
CallOutBox::CallOutBox (Component& c, Rectangle<int> area, Component* const parent)
: content (c)
{
addAndMakeVisible (content);
if (parent != nullptr)
{
parent->addChildComponent (this);
updatePosition (area, parent->getLocalBounds());
setVisible (true);
}
else
{
setAlwaysOnTop (juce_areThereAnyAlwaysOnTopWindows());
updatePosition (area, Desktop::getInstance().getDisplays().getDisplayForRect (area)->userArea);
addToDesktop (ComponentPeer::windowIsTemporary);
startTimer (100);
}
creationTime = Time::getCurrentTime();
}
//==============================================================================
class CallOutBoxCallback : public ModalComponentManager::Callback,
private Timer
{
public:
CallOutBoxCallback (std::unique_ptr<Component> c, const Rectangle<int>& area, Component* parent)
: content (std::move (c)),
callout (*content, area, parent)
{
callout.setVisible (true);
callout.enterModalState (true, this);
startTimer (200);
}
void modalStateFinished (int) override {}
void timerCallback() override
{
if (! isForegroundOrEmbeddedProcess (&callout))
callout.dismiss();
}
std::unique_ptr<Component> content;
CallOutBox callout;
JUCE_DECLARE_NON_COPYABLE (CallOutBoxCallback)
};
CallOutBox& CallOutBox::launchAsynchronously (std::unique_ptr<Component> content, Rectangle<int> area, Component* parent)
{
jassert (content != nullptr); // must be a valid content component!
return (new CallOutBoxCallback (std::move (content), area, parent))->callout;
}
//==============================================================================
void CallOutBox::setArrowSize (const float newSize)
{
arrowSize = newSize;
refreshPath();
}
int CallOutBox::getBorderSize() const noexcept
{
return jmax (getLookAndFeel().getCallOutBoxBorderSize (*this), (int) arrowSize);
}
void CallOutBox::lookAndFeelChanged()
{
resized();
repaint();
}
void CallOutBox::paint (Graphics& g)
{
getLookAndFeel().drawCallOutBoxBackground (*this, g, outline, background);
}
void CallOutBox::resized()
{
auto borderSpace = getBorderSize();
content.setTopLeftPosition (borderSpace, borderSpace);
refreshPath();
}
void CallOutBox::moved()
{
refreshPath();
}
void CallOutBox::childBoundsChanged (Component*)
{
updatePosition (targetArea, availableArea);
}
bool CallOutBox::hitTest (int x, int y)
{
return outline.contains ((float) x, (float) y);
}
void CallOutBox::inputAttemptWhenModal()
{
if (dismissalMouseClicksAreAlwaysConsumed
|| targetArea.contains (getMouseXYRelative() + getBounds().getPosition()))
{
// if you click on the area that originally popped-up the callout, you expect it
// to get rid of the box, but deleting the box here allows the click to pass through and
// probably re-trigger it, so we need to dismiss the box asynchronously to consume the click..
// For touchscreens, we make sure not to dismiss the CallOutBox immediately,
// as Windows still sends touch events before the CallOutBox had a chance
// to really open.
auto elapsed = Time::getCurrentTime() - creationTime;
if (elapsed.inMilliseconds() > 200)
dismiss();
}
else
{
exitModalState (0);
setVisible (false);
}
}
void CallOutBox::setDismissalMouseClicksAreAlwaysConsumed (bool b) noexcept
{
dismissalMouseClicksAreAlwaysConsumed = b;
}
static constexpr int callOutBoxDismissCommandId = 0x4f83a04b;
void CallOutBox::handleCommandMessage (int commandId)
{
Component::handleCommandMessage (commandId);
if (commandId == callOutBoxDismissCommandId)
{
exitModalState (0);
setVisible (false);
}
}
void CallOutBox::dismiss()
{
postCommandMessage (callOutBoxDismissCommandId);
}
bool CallOutBox::keyPressed (const KeyPress& key)
{
if (key.isKeyCode (KeyPress::escapeKey))
{
inputAttemptWhenModal();
return true;
}
return false;
}
void CallOutBox::updatePosition (const Rectangle<int>& newAreaToPointTo, const Rectangle<int>& newAreaToFitIn)
{
targetArea = newAreaToPointTo;
availableArea = newAreaToFitIn;
auto borderSpace = getBorderSize();
auto newBounds = getLocalArea (&content, Rectangle<int> (content.getWidth() + borderSpace * 2,
content.getHeight() + borderSpace * 2));
auto hw = newBounds.getWidth() / 2;
auto hh = newBounds.getHeight() / 2;
auto hwReduced = (float) (hw - borderSpace * 2);
auto hhReduced = (float) (hh - borderSpace * 2);
auto arrowIndent = (float) borderSpace - arrowSize;
Point<float> targets[4] = { { (float) targetArea.getCentreX(), (float) targetArea.getBottom() },
{ (float) targetArea.getRight(), (float) targetArea.getCentreY() },
{ (float) targetArea.getX(), (float) targetArea.getCentreY() },
{ (float) targetArea.getCentreX(), (float) targetArea.getY() } };
Line<float> lines[4] = { { targets[0].translated (-hwReduced, hh - arrowIndent), targets[0].translated (hwReduced, hh - arrowIndent) },
{ targets[1].translated (hw - arrowIndent, -hhReduced), targets[1].translated (hw - arrowIndent, hhReduced) },
{ targets[2].translated (-(hw - arrowIndent), -hhReduced), targets[2].translated (-(hw - arrowIndent), hhReduced) },
{ targets[3].translated (-hwReduced, -(hh - arrowIndent)), targets[3].translated (hwReduced, -(hh - arrowIndent)) } };
auto centrePointArea = newAreaToFitIn.reduced (hw, hh).toFloat();
auto targetCentre = targetArea.getCentre().toFloat();
float nearest = 1.0e9f;
for (int i = 0; i < 4; ++i)
{
Line<float> constrainedLine (centrePointArea.getConstrainedPoint (lines[i].getStart()),
centrePointArea.getConstrainedPoint (lines[i].getEnd()));
auto centre = constrainedLine.findNearestPointTo (targetCentre);
auto distanceFromCentre = centre.getDistanceFrom (targets[i]);
if (! centrePointArea.intersects (lines[i]))
distanceFromCentre += 1000.0f;
if (distanceFromCentre < nearest)
{
nearest = distanceFromCentre;
targetPoint = targets[i];
newBounds.setPosition ((int) (centre.x - (float) hw),
(int) (centre.y - (float) hh));
}
}
setBounds (newBounds);
}
void CallOutBox::refreshPath()
{
repaint();
background = {};
outline.clear();
const float gap = 4.5f;
outline.addBubble (getLocalArea (&content, content.getLocalBounds().toFloat()).expanded (gap, gap),
getLocalBounds().toFloat(),
targetPoint - getPosition().toFloat(),
getLookAndFeel().getCallOutBoxCornerSize (*this), arrowSize * 0.7f);
}
void CallOutBox::timerCallback()
{
toFront (true);
stopTimer();
}
//==============================================================================
std::unique_ptr<AccessibilityHandler> CallOutBox::createAccessibilityHandler()
{
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::dialogWindow);
}
} // namespace juce

View File

@ -1,190 +1,187 @@
/*
==============================================================================
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
{
//==============================================================================
/**
A box with a small arrow that can be used as a temporary pop-up window to show
extra controls when a button or other component is clicked.
Using one of these is similar to having a popup menu attached to a button or
other component - but it looks fancier, and has an arrow that can indicate the
object that it applies to.
The class works best when shown modally, but obviously running modal loops is
evil and must never be done, so the launchAsynchronously method is provided as
a handy way of launching an instance of a CallOutBox and automatically managing
its lifetime, e.g.
@code
void mouseUp (const MouseEvent&)
{
auto content = std::make_unique<FoobarContentComp>();
content->setSize (300, 300);
auto& myBox = CallOutBox::launchAsynchronously (std::move (content),
getScreenBounds(),
nullptr);
}
@endcode
The call-out will resize and position itself when the content changes size.
@tags{GUI}
*/
class JUCE_API CallOutBox : public Component,
private Timer
{
public:
//==============================================================================
/** Creates a CallOutBox.
@param contentComponent the component to display inside the call-out. This should
already have a size set (although the call-out will also
update itself when the component's size is changed later).
Obviously this component must not be deleted until the
call-out box has been deleted.
@param areaToPointTo the area that the call-out's arrow should point towards. If
a parentComponent is supplied, then this is relative to that
parent; otherwise, it's a global screen coord.
@param parentComponent if not a nullptr, this is the component to add the call-out to.
If this is a nullptr, the call-out will be added to the desktop.
*/
CallOutBox (Component& contentComponent,
Rectangle<int> areaToPointTo,
Component* parentComponent);
//==============================================================================
/** Changes the base width of the arrow. */
void setArrowSize (float newSize);
/** Updates the position and size of the box.
You shouldn't normally need to call this, unless you need more precise control over the
layout.
@param newAreaToPointTo the rectangle to make the box's arrow point to
@param newAreaToFitIn the area within which the box's position should be constrained
*/
void updatePosition (const Rectangle<int>& newAreaToPointTo,
const Rectangle<int>& newAreaToFitIn);
/** This will launch a callout box containing the given content, pointing to the
specified target component.
This method will create and display a callout, returning immediately, after which
the box will continue to run modally until the user clicks on some other component, at
which point it will be dismissed and deleted automatically.
It returns a reference to the newly-created box so that you can customise it, but don't
keep a pointer to it, as it'll be deleted at some point when it gets closed.
@param contentComponent the component to display inside the call-out. This should
already have a size set (although the call-out will also
update itself when the component's size is changed later).
@param areaToPointTo the area that the call-out's arrow should point towards. If
a parentComponent is supplied, then this is relative to that
parent; otherwise, it's a global screen coord.
@param parentComponent if not a nullptr, this is the component to add the call-out to.
If this is a nullptr, the call-out will be added to the desktop.
@param dismissIfBackgrounded If this is true, the call-out will be dismissed if we are no
longer the foreground app.
*/
static CallOutBox& launchAsynchronously (std::unique_ptr<Component> contentComponent,
Rectangle<int> areaToPointTo,
Component* parentComponent,
bool dismissIfBackgrounded=true);
/** Posts a message which will dismiss the callout box asynchronously.
NB: it's safe to call this method from any thread.
*/
void dismiss();
/** Determines whether the mouse events for clicks outside the calloutbox are
consumed, or allowed to arrive at the other component that they were aimed at.
By default this is false, so that when you click on something outside the calloutbox,
that event will also be sent to the component that was clicked on. If you set it to
true, then the first click will always just dismiss the box and not be sent to
anything else.
*/
void setDismissalMouseClicksAreAlwaysConsumed (bool shouldAlwaysBeConsumed) noexcept;
//==============================================================================
/** This abstract base class is implemented by LookAndFeel classes. */
struct JUCE_API LookAndFeelMethods
{
virtual ~LookAndFeelMethods() = default;
virtual void drawCallOutBoxBackground (CallOutBox&, Graphics&, const Path&, Image&) = 0;
virtual int getCallOutBoxBorderSize (const CallOutBox&) = 0;
virtual float getCallOutBoxCornerSize (const CallOutBox&) = 0;
};
//==============================================================================
/** @internal */
void paint (Graphics&) override;
/** @internal */
void resized() override;
/** @internal */
void moved() override;
/** @internal */
void childBoundsChanged (Component*) override;
/** @internal */
bool hitTest (int x, int y) override;
/** @internal */
void inputAttemptWhenModal() override;
/** @internal */
bool keyPressed (const KeyPress&) override;
/** @internal */
void handleCommandMessage (int) override;
/** @internal */
int getBorderSize() const noexcept;
/** @internal */
void lookAndFeelChanged() override;
private:
//==============================================================================
Component& content;
Path outline;
Point<float> targetPoint;
Rectangle<int> availableArea, targetArea;
Image background;
float arrowSize = 16.0f;
bool dismissalMouseClicksAreAlwaysConsumed = false;
Time creationTime;
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
void refreshPath();
void timerCallback() override;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CallOutBox)
};
} // 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
{
//==============================================================================
/**
A box with a small arrow that can be used as a temporary pop-up window to show
extra controls when a button or other component is clicked.
Using one of these is similar to having a popup menu attached to a button or
other component - but it looks fancier, and has an arrow that can indicate the
object that it applies to.
The class works best when shown modally, but obviously running modal loops is
evil and must never be done, so the launchAsynchronously method is provided as
a handy way of launching an instance of a CallOutBox and automatically managing
its lifetime, e.g.
@code
void mouseUp (const MouseEvent&)
{
auto content = std::make_unique<FoobarContentComp>();
content->setSize (300, 300);
auto& myBox = CallOutBox::launchAsynchronously (std::move (content),
getScreenBounds(),
nullptr);
}
@endcode
The call-out will resize and position itself when the content changes size.
@tags{GUI}
*/
class JUCE_API CallOutBox : public Component,
private Timer
{
public:
//==============================================================================
/** Creates a CallOutBox.
@param contentComponent the component to display inside the call-out. This should
already have a size set (although the call-out will also
update itself when the component's size is changed later).
Obviously this component must not be deleted until the
call-out box has been deleted.
@param areaToPointTo the area that the call-out's arrow should point towards. If
a parentComponent is supplied, then this is relative to that
parent; otherwise, it's a global screen coord.
@param parentComponent if not a nullptr, this is the component to add the call-out to.
If this is a nullptr, the call-out will be added to the desktop.
*/
CallOutBox (Component& contentComponent,
Rectangle<int> areaToPointTo,
Component* parentComponent);
//==============================================================================
/** Changes the base width of the arrow. */
void setArrowSize (float newSize);
/** Updates the position and size of the box.
You shouldn't normally need to call this, unless you need more precise control over the
layout.
@param newAreaToPointTo the rectangle to make the box's arrow point to
@param newAreaToFitIn the area within which the box's position should be constrained
*/
void updatePosition (const Rectangle<int>& newAreaToPointTo,
const Rectangle<int>& newAreaToFitIn);
/** This will launch a callout box containing the given content, pointing to the
specified target component.
This method will create and display a callout, returning immediately, after which
the box will continue to run modally until the user clicks on some other component, at
which point it will be dismissed and deleted automatically.
It returns a reference to the newly-created box so that you can customise it, but don't
keep a pointer to it, as it'll be deleted at some point when it gets closed.
@param contentComponent the component to display inside the call-out. This should
already have a size set (although the call-out will also
update itself when the component's size is changed later).
@param areaToPointTo the area that the call-out's arrow should point towards. If
a parentComponent is supplied, then this is relative to that
parent; otherwise, it's a global screen coord.
@param parentComponent if not a nullptr, this is the component to add the call-out to.
If this is a nullptr, the call-out will be added to the desktop.
*/
static CallOutBox& launchAsynchronously (std::unique_ptr<Component> contentComponent,
Rectangle<int> areaToPointTo,
Component* parentComponent);
/** Posts a message which will dismiss the callout box asynchronously.
NB: it's safe to call this method from any thread.
*/
void dismiss();
/** Determines whether the mouse events for clicks outside the calloutbox are
consumed, or allowed to arrive at the other component that they were aimed at.
By default this is false, so that when you click on something outside the calloutbox,
that event will also be sent to the component that was clicked on. If you set it to
true, then the first click will always just dismiss the box and not be sent to
anything else.
*/
void setDismissalMouseClicksAreAlwaysConsumed (bool shouldAlwaysBeConsumed) noexcept;
//==============================================================================
/** This abstract base class is implemented by LookAndFeel classes. */
struct JUCE_API LookAndFeelMethods
{
virtual ~LookAndFeelMethods() = default;
virtual void drawCallOutBoxBackground (CallOutBox&, Graphics&, const Path&, Image&) = 0;
virtual int getCallOutBoxBorderSize (const CallOutBox&) = 0;
virtual float getCallOutBoxCornerSize (const CallOutBox&) = 0;
};
//==============================================================================
/** @internal */
void paint (Graphics&) override;
/** @internal */
void resized() override;
/** @internal */
void moved() override;
/** @internal */
void childBoundsChanged (Component*) override;
/** @internal */
bool hitTest (int x, int y) override;
/** @internal */
void inputAttemptWhenModal() override;
/** @internal */
bool keyPressed (const KeyPress&) override;
/** @internal */
void handleCommandMessage (int) override;
/** @internal */
int getBorderSize() const noexcept;
/** @internal */
void lookAndFeelChanged() override;
private:
//==============================================================================
Component& content;
Path outline;
Point<float> targetPoint;
Rectangle<int> availableArea, targetArea;
Image background;
float arrowSize = 16.0f;
bool dismissalMouseClicksAreAlwaysConsumed = false;
Time creationTime;
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
void refreshPath();
void timerCallback() override;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CallOutBox)
};
} // namespace juce

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,181 +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
{
DialogWindow::DialogWindow (const String& name, Colour colour,
const bool escapeCloses, const bool onDesktop,
const float scale)
: DocumentWindow (name, colour, DocumentWindow::closeButton, onDesktop),
desktopScale (scale),
escapeKeyTriggersCloseButton (escapeCloses)
{
}
DialogWindow::~DialogWindow() = default;
bool DialogWindow::escapeKeyPressed()
{
if (escapeKeyTriggersCloseButton)
{
setVisible (false);
return true;
}
return false;
}
bool DialogWindow::keyPressed (const KeyPress& key)
{
if (key == KeyPress::escapeKey && escapeKeyPressed())
return true;
return DocumentWindow::keyPressed (key);
}
void DialogWindow::resized()
{
DocumentWindow::resized();
if (escapeKeyTriggersCloseButton)
{
if (auto* close = getCloseButton())
{
const KeyPress esc (KeyPress::escapeKey, 0, 0);
if (! close->isRegisteredForShortcut (esc))
close->addShortcut (esc);
}
}
}
//==============================================================================
class DefaultDialogWindow : public DialogWindow
{
public:
DefaultDialogWindow (LaunchOptions& options)
: DialogWindow (options.dialogTitle, options.dialogBackgroundColour,
options.escapeKeyTriggersCloseButton, true,
options.componentToCentreAround != nullptr
? Component::getApproximateScaleFactorForComponent (options.componentToCentreAround)
: 1.0f)
{
setUsingNativeTitleBar (options.useNativeTitleBar);
setAlwaysOnTop (juce_areThereAnyAlwaysOnTopWindows());
if (options.content.willDeleteObject())
setContentOwned (options.content.release(), true);
else
setContentNonOwned (options.content.release(), true);
centreAroundComponent (options.componentToCentreAround, getWidth(), getHeight());
setResizable (options.resizable, options.useBottomRightCornerResizer);
}
void closeButtonPressed() override
{
setVisible (false);
}
private:
JUCE_DECLARE_NON_COPYABLE (DefaultDialogWindow)
};
DialogWindow::LaunchOptions::LaunchOptions() noexcept {}
DialogWindow* DialogWindow::LaunchOptions::create()
{
jassert (content != nullptr); // You need to provide some kind of content for the dialog!
return new DefaultDialogWindow (*this);
}
DialogWindow* DialogWindow::LaunchOptions::launchAsync()
{
auto* d = create();
d->enterModalState (true, nullptr, true);
return d;
}
#if JUCE_MODAL_LOOPS_PERMITTED
int DialogWindow::LaunchOptions::runModal()
{
return launchAsync()->runModalLoop();
}
#endif
//==============================================================================
void DialogWindow::showDialog (const String& dialogTitle,
Component* const contentComponent,
Component* const componentToCentreAround,
Colour backgroundColour,
const bool escapeKeyTriggersCloseButton,
const bool resizable,
const bool useBottomRightCornerResizer)
{
LaunchOptions o;
o.dialogTitle = dialogTitle;
o.content.setNonOwned (contentComponent);
o.componentToCentreAround = componentToCentreAround;
o.dialogBackgroundColour = backgroundColour;
o.escapeKeyTriggersCloseButton = escapeKeyTriggersCloseButton;
o.useNativeTitleBar = false;
o.resizable = resizable;
o.useBottomRightCornerResizer = useBottomRightCornerResizer;
o.launchAsync();
}
#if JUCE_MODAL_LOOPS_PERMITTED
int DialogWindow::showModalDialog (const String& dialogTitle,
Component* const contentComponent,
Component* const componentToCentreAround,
Colour backgroundColour,
const bool escapeKeyTriggersCloseButton,
const bool resizable,
const bool useBottomRightCornerResizer)
{
LaunchOptions o;
o.dialogTitle = dialogTitle;
o.content.setNonOwned (contentComponent);
o.componentToCentreAround = componentToCentreAround;
o.dialogBackgroundColour = backgroundColour;
o.escapeKeyTriggersCloseButton = escapeKeyTriggersCloseButton;
o.useNativeTitleBar = false;
o.resizable = resizable;
o.useBottomRightCornerResizer = useBottomRightCornerResizer;
return o.runModal();
}
#endif
//==============================================================================
std::unique_ptr<AccessibilityHandler> DialogWindow::createAccessibilityHandler()
{
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::dialogWindow);
}
} // 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
{
DialogWindow::DialogWindow (const String& name, Colour colour,
const bool escapeCloses, const bool onDesktop,
const float scale)
: DocumentWindow (name, colour, DocumentWindow::closeButton, onDesktop),
desktopScale (scale),
escapeKeyTriggersCloseButton (escapeCloses)
{
}
DialogWindow::~DialogWindow() = default;
bool DialogWindow::escapeKeyPressed()
{
if (escapeKeyTriggersCloseButton)
{
setVisible (false);
return true;
}
return false;
}
bool DialogWindow::keyPressed (const KeyPress& key)
{
if (key == KeyPress::escapeKey && escapeKeyPressed())
return true;
return DocumentWindow::keyPressed (key);
}
void DialogWindow::resized()
{
DocumentWindow::resized();
if (escapeKeyTriggersCloseButton)
{
if (auto* close = getCloseButton())
{
const KeyPress esc (KeyPress::escapeKey, 0, 0);
if (! close->isRegisteredForShortcut (esc))
close->addShortcut (esc);
}
}
}
//==============================================================================
class DefaultDialogWindow : public DialogWindow
{
public:
DefaultDialogWindow (LaunchOptions& options)
: DialogWindow (options.dialogTitle, options.dialogBackgroundColour,
options.escapeKeyTriggersCloseButton, true,
options.componentToCentreAround != nullptr
? Component::getApproximateScaleFactorForComponent (options.componentToCentreAround)
: 1.0f)
{
if (options.content.willDeleteObject())
setContentOwned (options.content.release(), true);
else
setContentNonOwned (options.content.release(), true);
centreAroundComponent (options.componentToCentreAround, getWidth(), getHeight());
setResizable (options.resizable, options.useBottomRightCornerResizer);
setUsingNativeTitleBar (options.useNativeTitleBar);
setAlwaysOnTop (juce_areThereAnyAlwaysOnTopWindows());
}
void closeButtonPressed() override
{
setVisible (false);
}
private:
JUCE_DECLARE_NON_COPYABLE (DefaultDialogWindow)
};
DialogWindow::LaunchOptions::LaunchOptions() noexcept {}
DialogWindow* DialogWindow::LaunchOptions::create()
{
jassert (content != nullptr); // You need to provide some kind of content for the dialog!
return new DefaultDialogWindow (*this);
}
DialogWindow* DialogWindow::LaunchOptions::launchAsync()
{
auto* d = create();
d->enterModalState (true, nullptr, true);
return d;
}
#if JUCE_MODAL_LOOPS_PERMITTED
int DialogWindow::LaunchOptions::runModal()
{
return launchAsync()->runModalLoop();
}
#endif
//==============================================================================
void DialogWindow::showDialog (const String& dialogTitle,
Component* const contentComponent,
Component* const componentToCentreAround,
Colour backgroundColour,
const bool escapeKeyTriggersCloseButton,
const bool resizable,
const bool useBottomRightCornerResizer)
{
LaunchOptions o;
o.dialogTitle = dialogTitle;
o.content.setNonOwned (contentComponent);
o.componentToCentreAround = componentToCentreAround;
o.dialogBackgroundColour = backgroundColour;
o.escapeKeyTriggersCloseButton = escapeKeyTriggersCloseButton;
o.useNativeTitleBar = false;
o.resizable = resizable;
o.useBottomRightCornerResizer = useBottomRightCornerResizer;
o.launchAsync();
}
#if JUCE_MODAL_LOOPS_PERMITTED
int DialogWindow::showModalDialog (const String& dialogTitle,
Component* const contentComponent,
Component* const componentToCentreAround,
Colour backgroundColour,
const bool escapeKeyTriggersCloseButton,
const bool resizable,
const bool useBottomRightCornerResizer)
{
LaunchOptions o;
o.dialogTitle = dialogTitle;
o.content.setNonOwned (contentComponent);
o.componentToCentreAround = componentToCentreAround;
o.dialogBackgroundColour = backgroundColour;
o.escapeKeyTriggersCloseButton = escapeKeyTriggersCloseButton;
o.useNativeTitleBar = false;
o.resizable = resizable;
o.useBottomRightCornerResizer = useBottomRightCornerResizer;
return o.runModal();
}
#endif
//==============================================================================
std::unique_ptr<AccessibilityHandler> DialogWindow::createAccessibilityHandler()
{
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::dialogWindow);
}
} // namespace juce

View File

@ -1,276 +1,276 @@
/*
==============================================================================
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
{
//==============================================================================
/**
A dialog-box style window.
This class is a convenient way of creating a DocumentWindow with a close button
that can be triggered by pressing the escape key.
Any of the methods available to a DocumentWindow or ResizableWindow are also
available to this, so it can be made resizable, have a menu bar, etc.
You can either override or use an instance of the DialogWindow class directly,
or you can use a DialogWindow::LaunchOptions structure to quickly set up and
launch a box containing a content component.
If you use the class directly, you'll need to override the
DocumentWindow::closeButtonPressed() method to handle the user clicking the close
button - for more info, see the DocumentWindow help.
@see DocumentWindow, ResizableWindow
@tags{GUI}
*/
class JUCE_API DialogWindow : public DocumentWindow
{
public:
//==============================================================================
/** Creates a DialogWindow.
@param name the name to give the component - this is also
the title shown at the top of the window. To change
this later, use setName()
@param backgroundColour the colour to use for filling the window's background.
@param escapeKeyTriggersCloseButton if true, then pressing the escape key will cause the
close button to be triggered
@param addToDesktop if true, the window will be automatically added to the
desktop; if false, you can use it as a child component
@param desktopScale specifies the scale to use when drawing the window. In a plugin,
the host controls the scale used to render the plugin editor.
You should query the editor scale with
Component::getApproximateScaleFactorForComponent() and pass the
result here. You can ignore this parameter in a standalone app
*/
DialogWindow (const String& name,
Colour backgroundColour,
bool escapeKeyTriggersCloseButton,
bool addToDesktop = true,
float desktopScale = 1.0f);
/** Destructor.
If a content component has been set with setContentOwned(), it will be deleted.
*/
~DialogWindow() override;
//==============================================================================
/** This class defines a collection of settings to be used to open a DialogWindow.
The easiest way to open a DialogWindow is to create yourself a LaunchOptions structure,
initialise its fields with the appropriate details, and then call its launchAsync()
method to launch the dialog.
*/
struct JUCE_API LaunchOptions
{
LaunchOptions() noexcept;
/** The title to give the window. */
String dialogTitle;
/** The background colour for the window. */
Colour dialogBackgroundColour = Colours::lightgrey;
/** The content component to show in the window. This must not be null!
Using an OptionalScopedPointer to hold this pointer lets you indicate whether
you'd like the dialog to automatically delete the component when the dialog
has terminated.
*/
OptionalScopedPointer<Component> content;
/** If this is not a nullptr, it indicates a component that you'd like to position this
dialog box in front of. See the DocumentWindow::centreAroundComponent() method for
more info about this parameter.
*/
Component* componentToCentreAround = nullptr;
/** If true, then the escape key will trigger the dialog's close button. */
bool escapeKeyTriggersCloseButton = true;
/** If true, the dialog will use a native title bar. See TopLevelWindow::setUsingNativeTitleBar() */
bool useNativeTitleBar = true;
/** If true, the window will be resizable. See ResizableWindow::setResizable() */
bool resizable = true;
/** Indicates whether to use a border or corner resizer component. See ResizableWindow::setResizable() */
bool useBottomRightCornerResizer = false;
/** Launches a new modal dialog window.
This will create a dialog based on the settings in this structure,
launch it modally, and return immediately. The window that is returned
will be automatically deleted when the modal state is terminated.
When the dialog's close button is clicked, it'll automatically terminate its
modal state, but you can also do this programmatically by calling
exitModalState (returnValue) on the DialogWindow.
If your content component needs to find the dialog window that it is
contained in, a quick trick is to do this:
@code
if (DialogWindow* dw = contentComponent->findParentComponentOfClass<DialogWindow>())
dw->exitModalState (1234);
@endcode
*/
DialogWindow* launchAsync();
/** Creates a new DialogWindow instance with these settings.
This method simply creates the window, it doesn't run it modally. In most cases
you'll want to use launchAsync() or runModal() instead.
*/
DialogWindow* create();
#if JUCE_MODAL_LOOPS_PERMITTED
/** Launches and runs the dialog modally, returning the status code that was
used to terminate the modal loop.
Note that running modal loops inline is a BAD technique. If possible, always
use launchAsync() instead of this method.
*/
int runModal();
#endif
JUCE_DECLARE_NON_COPYABLE (LaunchOptions)
};
//==============================================================================
/** Easy way of quickly showing a dialog box containing a given component.
Note: This method has been superseded by the DialogWindow::LaunchOptions structure,
which does the same job with some extra flexibility. The showDialog method is here
for backwards compatibility, but please use DialogWindow::LaunchOptions in new code.
This will open and display a DialogWindow containing a given component, making it
modal, but returning immediately to allow the dialog to finish in its own time. If
you want to block and run a modal loop until the dialog is dismissed, use showModalDialog()
instead.
To close the dialog programmatically, you should call exitModalState (returnValue) on
the DialogWindow that is created. To find a pointer to this window from your
contentComponent, you can do something like this:
@code
if (DialogWindow* dw = contentComponent->findParentComponentOfClass<DialogWindow>())
dw->exitModalState (1234);
@endcode
@param dialogTitle the dialog box's title
@param contentComponent the content component for the dialog box. Make sure
that this has been set to the size you want it to
be before calling this method. The component won't
be deleted by this call, so you can re-use it or delete
it afterwards
@param componentToCentreAround if this is not a nullptr, it indicates a component that
you'd like to show this dialog box in front of. See the
DocumentWindow::centreAroundComponent() method for more
info on this parameter
@param backgroundColour a colour to use for the dialog box's background colour
@param escapeKeyTriggersCloseButton if true, then pressing the escape key will cause the
close button to be triggered
@param shouldBeResizable if true, the dialog window has either a resizable border, or
a corner resizer
@param useBottomRightCornerResizer if shouldBeResizable is true, this indicates whether
to use a border or corner resizer component. See ResizableWindow::setResizable()
*/
static void showDialog (const String& dialogTitle,
Component* contentComponent,
Component* componentToCentreAround,
Colour backgroundColour,
bool escapeKeyTriggersCloseButton,
bool shouldBeResizable = false,
bool useBottomRightCornerResizer = false);
#if JUCE_MODAL_LOOPS_PERMITTED
/** Easy way of quickly showing a dialog box containing a given component.
Note: This method has been superseded by the DialogWindow::LaunchOptions structure,
which does the same job with some extra flexibility. The showDialog method is here
for backwards compatibility, but please use DialogWindow::LaunchOptions in new code.
This will open and display a DialogWindow containing a given component, returning
when the user clicks its close button.
It returns the value that was returned by the dialog box's runModalLoop() call.
To close the dialog programmatically, you should call exitModalState (returnValue) on
the DialogWindow that is created. To find a pointer to this window from your
contentComponent, you can do something like this:
@code
if (DialogWindow* dw = contentComponent->findParentComponentOfClass<DialogWindow>())
dw->exitModalState (1234);
@endcode
@param dialogTitle the dialog box's title
@param contentComponent the content component for the dialog box. Make sure
that this has been set to the size you want it to
be before calling this method. The component won't
be deleted by this call, so you can re-use it or delete
it afterwards
@param componentToCentreAround if this is not a nullptr, it indicates a component that
you'd like to show this dialog box in front of. See the
DocumentWindow::centreAroundComponent() method for more
info on this parameter
@param backgroundColour a colour to use for the dialog box's background colour
@param escapeKeyTriggersCloseButton if true, then pressing the escape key will cause the
close button to be triggered
@param shouldBeResizable if true, the dialog window has either a resizable border, or
a corner resizer
@param useBottomRightCornerResizer if shouldBeResizable is true, this indicates whether
to use a border or corner resizer component. See ResizableWindow::setResizable()
*/
static int showModalDialog (const String& dialogTitle,
Component* contentComponent,
Component* componentToCentreAround,
Colour backgroundColour,
bool escapeKeyTriggersCloseButton,
bool shouldBeResizable = false,
bool useBottomRightCornerResizer = false);
#endif
/** Called when the escape key is pressed.
This can be overridden to do things other than the default behaviour, which is to hide
the window. Return true if the key has been used, or false if it was ignored.
*/
virtual bool escapeKeyPressed();
protected:
//==============================================================================
/** @internal */
void resized() override;
/** @internal */
bool keyPressed (const KeyPress&) override;
/** @internal */
float getDesktopScaleFactor() const override { return desktopScale * Desktop::getInstance().getGlobalScaleFactor(); }
private:
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
float desktopScale = 1.0f;
bool escapeKeyTriggersCloseButton;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DialogWindow)
};
} // 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
{
//==============================================================================
/**
A dialog-box style window.
This class is a convenient way of creating a DocumentWindow with a close button
that can be triggered by pressing the escape key.
Any of the methods available to a DocumentWindow or ResizableWindow are also
available to this, so it can be made resizable, have a menu bar, etc.
You can either override or use an instance of the DialogWindow class directly,
or you can use a DialogWindow::LaunchOptions structure to quickly set up and
launch a box containing a content component.
If you use the class directly, you'll need to override the
DocumentWindow::closeButtonPressed() method to handle the user clicking the close
button - for more info, see the DocumentWindow help.
@see DocumentWindow, ResizableWindow
@tags{GUI}
*/
class JUCE_API DialogWindow : public DocumentWindow
{
public:
//==============================================================================
/** Creates a DialogWindow.
@param name the name to give the component - this is also
the title shown at the top of the window. To change
this later, use setName()
@param backgroundColour the colour to use for filling the window's background.
@param escapeKeyTriggersCloseButton if true, then pressing the escape key will cause the
close button to be triggered
@param addToDesktop if true, the window will be automatically added to the
desktop; if false, you can use it as a child component
@param desktopScale specifies the scale to use when drawing the window. In a plugin,
the host controls the scale used to render the plugin editor.
You should query the editor scale with
Component::getApproximateScaleFactorForComponent() and pass the
result here. You can ignore this parameter in a standalone app
*/
DialogWindow (const String& name,
Colour backgroundColour,
bool escapeKeyTriggersCloseButton,
bool addToDesktop = true,
float desktopScale = 1.0f);
/** Destructor.
If a content component has been set with setContentOwned(), it will be deleted.
*/
~DialogWindow() override;
//==============================================================================
/** This class defines a collection of settings to be used to open a DialogWindow.
The easiest way to open a DialogWindow is to create yourself a LaunchOptions structure,
initialise its fields with the appropriate details, and then call its launchAsync()
method to launch the dialog.
*/
struct JUCE_API LaunchOptions
{
LaunchOptions() noexcept;
/** The title to give the window. */
String dialogTitle;
/** The background colour for the window. */
Colour dialogBackgroundColour = Colours::lightgrey;
/** The content component to show in the window. This must not be null!
Using an OptionalScopedPointer to hold this pointer lets you indicate whether
you'd like the dialog to automatically delete the component when the dialog
has terminated.
*/
OptionalScopedPointer<Component> content;
/** If this is not a nullptr, it indicates a component that you'd like to position this
dialog box in front of. See the DocumentWindow::centreAroundComponent() method for
more info about this parameter.
*/
Component* componentToCentreAround = nullptr;
/** If true, then the escape key will trigger the dialog's close button. */
bool escapeKeyTriggersCloseButton = true;
/** If true, the dialog will use a native title bar. See TopLevelWindow::setUsingNativeTitleBar() */
bool useNativeTitleBar = true;
/** If true, the window will be resizable. See ResizableWindow::setResizable() */
bool resizable = true;
/** Indicates whether to use a border or corner resizer component. See ResizableWindow::setResizable() */
bool useBottomRightCornerResizer = false;
/** Launches a new modal dialog window.
This will create a dialog based on the settings in this structure,
launch it modally, and return immediately. The window that is returned
will be automatically deleted when the modal state is terminated.
When the dialog's close button is clicked, it'll automatically terminate its
modal state, but you can also do this programmatically by calling
exitModalState (returnValue) on the DialogWindow.
If your content component needs to find the dialog window that it is
contained in, a quick trick is to do this:
@code
if (DialogWindow* dw = contentComponent->findParentComponentOfClass<DialogWindow>())
dw->exitModalState (1234);
@endcode
*/
DialogWindow* launchAsync();
/** Creates a new DialogWindow instance with these settings.
This method simply creates the window, it doesn't run it modally. In most cases
you'll want to use launchAsync() or runModal() instead.
*/
DialogWindow* create();
#if JUCE_MODAL_LOOPS_PERMITTED
/** Launches and runs the dialog modally, returning the status code that was
used to terminate the modal loop.
Note that running modal loops inline is a BAD technique. If possible, always
use launchAsync() instead of this method.
*/
int runModal();
#endif
JUCE_DECLARE_NON_COPYABLE (LaunchOptions)
};
//==============================================================================
/** Easy way of quickly showing a dialog box containing a given component.
Note: This method has been superseded by the DialogWindow::LaunchOptions structure,
which does the same job with some extra flexibility. The showDialog method is here
for backwards compatibility, but please use DialogWindow::LaunchOptions in new code.
This will open and display a DialogWindow containing a given component, making it
modal, but returning immediately to allow the dialog to finish in its own time. If
you want to block and run a modal loop until the dialog is dismissed, use showModalDialog()
instead.
To close the dialog programmatically, you should call exitModalState (returnValue) on
the DialogWindow that is created. To find a pointer to this window from your
contentComponent, you can do something like this:
@code
if (DialogWindow* dw = contentComponent->findParentComponentOfClass<DialogWindow>())
dw->exitModalState (1234);
@endcode
@param dialogTitle the dialog box's title
@param contentComponent the content component for the dialog box. Make sure
that this has been set to the size you want it to
be before calling this method. The component won't
be deleted by this call, so you can re-use it or delete
it afterwards
@param componentToCentreAround if this is not a nullptr, it indicates a component that
you'd like to show this dialog box in front of. See the
DocumentWindow::centreAroundComponent() method for more
info on this parameter
@param backgroundColour a colour to use for the dialog box's background colour
@param escapeKeyTriggersCloseButton if true, then pressing the escape key will cause the
close button to be triggered
@param shouldBeResizable if true, the dialog window has either a resizable border, or
a corner resizer
@param useBottomRightCornerResizer if shouldBeResizable is true, this indicates whether
to use a border or corner resizer component. See ResizableWindow::setResizable()
*/
static void showDialog (const String& dialogTitle,
Component* contentComponent,
Component* componentToCentreAround,
Colour backgroundColour,
bool escapeKeyTriggersCloseButton,
bool shouldBeResizable = false,
bool useBottomRightCornerResizer = false);
#if JUCE_MODAL_LOOPS_PERMITTED
/** Easy way of quickly showing a dialog box containing a given component.
Note: This method has been superseded by the DialogWindow::LaunchOptions structure,
which does the same job with some extra flexibility. The showDialog method is here
for backwards compatibility, but please use DialogWindow::LaunchOptions in new code.
This will open and display a DialogWindow containing a given component, returning
when the user clicks its close button.
It returns the value that was returned by the dialog box's runModalLoop() call.
To close the dialog programmatically, you should call exitModalState (returnValue) on
the DialogWindow that is created. To find a pointer to this window from your
contentComponent, you can do something like this:
@code
if (DialogWindow* dw = contentComponent->findParentComponentOfClass<DialogWindow>())
dw->exitModalState (1234);
@endcode
@param dialogTitle the dialog box's title
@param contentComponent the content component for the dialog box. Make sure
that this has been set to the size you want it to
be before calling this method. The component won't
be deleted by this call, so you can re-use it or delete
it afterwards
@param componentToCentreAround if this is not a nullptr, it indicates a component that
you'd like to show this dialog box in front of. See the
DocumentWindow::centreAroundComponent() method for more
info on this parameter
@param backgroundColour a colour to use for the dialog box's background colour
@param escapeKeyTriggersCloseButton if true, then pressing the escape key will cause the
close button to be triggered
@param shouldBeResizable if true, the dialog window has either a resizable border, or
a corner resizer
@param useBottomRightCornerResizer if shouldBeResizable is true, this indicates whether
to use a border or corner resizer component. See ResizableWindow::setResizable()
*/
static int showModalDialog (const String& dialogTitle,
Component* contentComponent,
Component* componentToCentreAround,
Colour backgroundColour,
bool escapeKeyTriggersCloseButton,
bool shouldBeResizable = false,
bool useBottomRightCornerResizer = false);
#endif
/** Called when the escape key is pressed.
This can be overridden to do things other than the default behaviour, which is to hide
the window. Return true if the key has been used, or false if it was ignored.
*/
virtual bool escapeKeyPressed();
protected:
//==============================================================================
/** @internal */
void resized() override;
/** @internal */
bool keyPressed (const KeyPress&) override;
/** @internal */
float getDesktopScaleFactor() const override { return desktopScale * Desktop::getInstance().getGlobalScaleFactor(); }
private:
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
float desktopScale = 1.0f;
bool escapeKeyTriggersCloseButton;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DialogWindow)
};
} // namespace juce

View File

@ -1,360 +1,360 @@
/*
==============================================================================
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 DocumentWindow::ButtonListenerProxy : public Button::Listener
{
public:
ButtonListenerProxy (DocumentWindow& w) : owner (w) {}
void buttonClicked (Button* button) override
{
if (button == owner.getMinimiseButton()) owner.minimiseButtonPressed();
else if (button == owner.getMaximiseButton()) owner.maximiseButtonPressed();
else if (button == owner.getCloseButton()) owner.closeButtonPressed();
}
private:
DocumentWindow& owner;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ButtonListenerProxy)
};
//==============================================================================
DocumentWindow::DocumentWindow (const String& title,
Colour backgroundColour,
int requiredButtons_,
bool addToDesktop_)
: ResizableWindow (title, backgroundColour, addToDesktop_),
requiredButtons (requiredButtons_),
#if JUCE_MAC
positionTitleBarButtonsOnLeft (true)
#else
positionTitleBarButtonsOnLeft (false)
#endif
{
setResizeLimits (128, 128, 32768, 32768);
DocumentWindow::lookAndFeelChanged();
}
DocumentWindow::~DocumentWindow()
{
// Don't delete or remove the resizer components yourself! They're managed by the
// DocumentWindow, and you should leave them alone! You may have deleted them
// accidentally by careless use of deleteAllChildren()..?
jassert (menuBar == nullptr || getIndexOfChildComponent (menuBar.get()) >= 0);
jassert (titleBarButtons[0] == nullptr || getIndexOfChildComponent (titleBarButtons[0].get()) >= 0);
jassert (titleBarButtons[1] == nullptr || getIndexOfChildComponent (titleBarButtons[1].get()) >= 0);
jassert (titleBarButtons[2] == nullptr || getIndexOfChildComponent (titleBarButtons[2].get()) >= 0);
for (auto& b : titleBarButtons)
b.reset();
menuBar.reset();
}
//==============================================================================
void DocumentWindow::repaintTitleBar()
{
repaint (getTitleBarArea());
}
void DocumentWindow::setName (const String& newName)
{
if (newName != getName())
{
Component::setName (newName);
repaintTitleBar();
}
}
void DocumentWindow::setIcon (const Image& imageToUse)
{
titleBarIcon = imageToUse;
repaintTitleBar();
}
void DocumentWindow::setTitleBarHeight (const int newHeight)
{
titleBarHeight = newHeight;
resized();
repaintTitleBar();
}
void DocumentWindow::setTitleBarButtonsRequired (const int buttons, const bool onLeft)
{
requiredButtons = buttons;
positionTitleBarButtonsOnLeft = onLeft;
lookAndFeelChanged();
}
void DocumentWindow::setTitleBarTextCentred (const bool textShouldBeCentred)
{
drawTitleTextCentred = textShouldBeCentred;
repaintTitleBar();
}
//==============================================================================
void DocumentWindow::setMenuBar (MenuBarModel* newMenuBarModel, const int newMenuBarHeight)
{
if (menuBarModel != newMenuBarModel)
{
menuBar.reset();
menuBarModel = newMenuBarModel;
menuBarHeight = newMenuBarHeight > 0 ? newMenuBarHeight
: getLookAndFeel().getDefaultMenuBarHeight();
if (menuBarModel != nullptr)
setMenuBarComponent (new MenuBarComponent (menuBarModel));
resized();
}
}
Component* DocumentWindow::getMenuBarComponent() const noexcept
{
return menuBar.get();
}
void DocumentWindow::setMenuBarComponent (Component* newMenuBarComponent)
{
menuBar.reset (newMenuBarComponent);
Component::addAndMakeVisible (menuBar.get()); // (call the superclass method directly to avoid the assertion in ResizableWindow)
if (menuBar != nullptr)
menuBar->setEnabled (isActiveWindow());
resized();
}
//==============================================================================
void DocumentWindow::closeButtonPressed()
{
/* If you've got a close button, you have to override this method to get
rid of your window!
If the window is just a pop-up, you should override this method and make
it delete the window in whatever way is appropriate for your app. E.g. you
might just want to call "delete this".
If your app is centred around this window such that the whole app should quit when
the window is closed, then you will probably want to use this method as an opportunity
to call JUCEApplicationBase::quit(), and leave the window to be deleted later by your
JUCEApplicationBase::shutdown() method. (Doing it this way means that your window will
still get cleaned-up if the app is quit by some other means (e.g. a cmd-Q on the mac
or closing it via the taskbar icon on Windows).
*/
jassertfalse;
}
void DocumentWindow::minimiseButtonPressed()
{
setMinimised (true);
}
void DocumentWindow::maximiseButtonPressed()
{
setFullScreen (! isFullScreen());
}
//==============================================================================
void DocumentWindow::paint (Graphics& g)
{
ResizableWindow::paint (g);
auto titleBarArea = getTitleBarArea();
g.reduceClipRegion (titleBarArea);
g.setOrigin (titleBarArea.getPosition());
int titleSpaceX1 = 6;
int titleSpaceX2 = titleBarArea.getWidth() - 6;
for (auto& b : titleBarButtons)
{
if (b != nullptr)
{
if (positionTitleBarButtonsOnLeft)
titleSpaceX1 = jmax (titleSpaceX1, b->getRight() + (getWidth() - b->getRight()) / 8);
else
titleSpaceX2 = jmin (titleSpaceX2, b->getX() - (b->getX() / 8));
}
}
getLookAndFeel().drawDocumentWindowTitleBar (*this, g,
titleBarArea.getWidth(),
titleBarArea.getHeight(),
titleSpaceX1,
jmax (1, titleSpaceX2 - titleSpaceX1),
titleBarIcon.isValid() ? &titleBarIcon : nullptr,
! drawTitleTextCentred);
}
void DocumentWindow::resized()
{
ResizableWindow::resized();
if (auto* b = getMaximiseButton())
b->setToggleState (isFullScreen(), dontSendNotification);
auto titleBarArea = getTitleBarArea();
getLookAndFeel()
.positionDocumentWindowButtons (*this,
titleBarArea.getX(), titleBarArea.getY(),
titleBarArea.getWidth(), titleBarArea.getHeight(),
titleBarButtons[0].get(),
titleBarButtons[1].get(),
titleBarButtons[2].get(),
positionTitleBarButtonsOnLeft);
if (menuBar != nullptr)
menuBar->setBounds (titleBarArea.getX(), titleBarArea.getBottom(),
titleBarArea.getWidth(), menuBarHeight);
}
BorderSize<int> DocumentWindow::getBorderThickness()
{
return ResizableWindow::getBorderThickness();
}
BorderSize<int> DocumentWindow::getContentComponentBorder()
{
auto border = getBorderThickness();
if (! isKioskMode())
border.setTop (border.getTop()
+ (isUsingNativeTitleBar() ? 0 : titleBarHeight)
+ (menuBar != nullptr ? menuBarHeight : 0));
return border;
}
int DocumentWindow::getTitleBarHeight() const
{
return isUsingNativeTitleBar() ? 0 : jmin (titleBarHeight, getHeight() - 4);
}
Rectangle<int> DocumentWindow::getTitleBarArea()
{
if (isKioskMode())
return {};
auto border = getBorderThickness();
return { border.getLeft(), border.getTop(), getWidth() - border.getLeftAndRight(), getTitleBarHeight() };
}
Button* DocumentWindow::getCloseButton() const noexcept { return titleBarButtons[2].get(); }
Button* DocumentWindow::getMinimiseButton() const noexcept { return titleBarButtons[0].get(); }
Button* DocumentWindow::getMaximiseButton() const noexcept { return titleBarButtons[1].get(); }
int DocumentWindow::getDesktopWindowStyleFlags() const
{
auto styleFlags = ResizableWindow::getDesktopWindowStyleFlags();
if ((requiredButtons & minimiseButton) != 0) styleFlags |= ComponentPeer::windowHasMinimiseButton;
if ((requiredButtons & maximiseButton) != 0) styleFlags |= ComponentPeer::windowHasMaximiseButton;
if ((requiredButtons & closeButton) != 0) styleFlags |= ComponentPeer::windowHasCloseButton;
return styleFlags;
}
void DocumentWindow::lookAndFeelChanged()
{
for (auto& b : titleBarButtons)
b.reset();
if (! isUsingNativeTitleBar())
{
auto& lf = getLookAndFeel();
if ((requiredButtons & minimiseButton) != 0) titleBarButtons[0].reset (lf.createDocumentWindowButton (minimiseButton));
if ((requiredButtons & maximiseButton) != 0) titleBarButtons[1].reset (lf.createDocumentWindowButton (maximiseButton));
if ((requiredButtons & closeButton) != 0) titleBarButtons[2].reset (lf.createDocumentWindowButton (closeButton));
for (auto& b : titleBarButtons)
{
if (b != nullptr)
{
if (buttonListener == nullptr)
buttonListener.reset (new ButtonListenerProxy (*this));
b->addListener (buttonListener.get());
b->setWantsKeyboardFocus (false);
// (call the Component method directly to avoid the assertion in ResizableWindow)
Component::addAndMakeVisible (b.get());
}
}
if (auto* b = getCloseButton())
{
#if JUCE_MAC
b->addShortcut (KeyPress ('w', ModifierKeys::commandModifier, 0));
#else
b->addShortcut (KeyPress (KeyPress::F4Key, ModifierKeys::altModifier, 0));
#endif
}
}
activeWindowStatusChanged();
ResizableWindow::lookAndFeelChanged();
}
void DocumentWindow::parentHierarchyChanged()
{
lookAndFeelChanged();
}
void DocumentWindow::activeWindowStatusChanged()
{
ResizableWindow::activeWindowStatusChanged();
bool isActive = isActiveWindow();
for (auto& b : titleBarButtons)
if (b != nullptr)
b->setEnabled (isActive);
if (menuBar != nullptr)
menuBar->setEnabled (isActive);
}
void DocumentWindow::mouseDoubleClick (const MouseEvent& e)
{
if (getTitleBarArea().contains (e.x, e.y))
if (auto* maximise = getMaximiseButton())
maximise->triggerClick();
}
void DocumentWindow::userTriedToCloseWindow()
{
closeButtonPressed();
}
} // 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 DocumentWindow::ButtonListenerProxy : public Button::Listener
{
public:
ButtonListenerProxy (DocumentWindow& w) : owner (w) {}
void buttonClicked (Button* button) override
{
if (button == owner.getMinimiseButton()) owner.minimiseButtonPressed();
else if (button == owner.getMaximiseButton()) owner.maximiseButtonPressed();
else if (button == owner.getCloseButton()) owner.closeButtonPressed();
}
private:
DocumentWindow& owner;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ButtonListenerProxy)
};
//==============================================================================
DocumentWindow::DocumentWindow (const String& title,
Colour backgroundColour,
int requiredButtons_,
bool addToDesktop_)
: ResizableWindow (title, backgroundColour, addToDesktop_),
requiredButtons (requiredButtons_),
#if JUCE_MAC
positionTitleBarButtonsOnLeft (true)
#else
positionTitleBarButtonsOnLeft (false)
#endif
{
setResizeLimits (128, 128, 32768, 32768);
DocumentWindow::lookAndFeelChanged();
}
DocumentWindow::~DocumentWindow()
{
// Don't delete or remove the resizer components yourself! They're managed by the
// DocumentWindow, and you should leave them alone! You may have deleted them
// accidentally by careless use of deleteAllChildren()..?
jassert (menuBar == nullptr || getIndexOfChildComponent (menuBar.get()) >= 0);
jassert (titleBarButtons[0] == nullptr || getIndexOfChildComponent (titleBarButtons[0].get()) >= 0);
jassert (titleBarButtons[1] == nullptr || getIndexOfChildComponent (titleBarButtons[1].get()) >= 0);
jassert (titleBarButtons[2] == nullptr || getIndexOfChildComponent (titleBarButtons[2].get()) >= 0);
for (auto& b : titleBarButtons)
b.reset();
menuBar.reset();
}
//==============================================================================
void DocumentWindow::repaintTitleBar()
{
repaint (getTitleBarArea());
}
void DocumentWindow::setName (const String& newName)
{
if (newName != getName())
{
Component::setName (newName);
repaintTitleBar();
}
}
void DocumentWindow::setIcon (const Image& imageToUse)
{
titleBarIcon = imageToUse;
repaintTitleBar();
}
void DocumentWindow::setTitleBarHeight (const int newHeight)
{
titleBarHeight = newHeight;
resized();
repaintTitleBar();
}
void DocumentWindow::setTitleBarButtonsRequired (const int buttons, const bool onLeft)
{
requiredButtons = buttons;
positionTitleBarButtonsOnLeft = onLeft;
lookAndFeelChanged();
}
void DocumentWindow::setTitleBarTextCentred (const bool textShouldBeCentred)
{
drawTitleTextCentred = textShouldBeCentred;
repaintTitleBar();
}
//==============================================================================
void DocumentWindow::setMenuBar (MenuBarModel* newMenuBarModel, const int newMenuBarHeight)
{
if (menuBarModel != newMenuBarModel)
{
menuBar.reset();
menuBarModel = newMenuBarModel;
menuBarHeight = newMenuBarHeight > 0 ? newMenuBarHeight
: getLookAndFeel().getDefaultMenuBarHeight();
if (menuBarModel != nullptr)
setMenuBarComponent (new MenuBarComponent (menuBarModel));
resized();
}
}
Component* DocumentWindow::getMenuBarComponent() const noexcept
{
return menuBar.get();
}
void DocumentWindow::setMenuBarComponent (Component* newMenuBarComponent)
{
menuBar.reset (newMenuBarComponent);
Component::addAndMakeVisible (menuBar.get()); // (call the superclass method directly to avoid the assertion in ResizableWindow)
if (menuBar != nullptr)
menuBar->setEnabled (isActiveWindow());
resized();
}
//==============================================================================
void DocumentWindow::closeButtonPressed()
{
/* If you've got a close button, you have to override this method to get
rid of your window!
If the window is just a pop-up, you should override this method and make
it delete the window in whatever way is appropriate for your app. E.g. you
might just want to call "delete this".
If your app is centred around this window such that the whole app should quit when
the window is closed, then you will probably want to use this method as an opportunity
to call JUCEApplicationBase::quit(), and leave the window to be deleted later by your
JUCEApplicationBase::shutdown() method. (Doing it this way means that your window will
still get cleaned-up if the app is quit by some other means (e.g. a cmd-Q on the mac
or closing it via the taskbar icon on Windows).
*/
jassertfalse;
}
void DocumentWindow::minimiseButtonPressed()
{
setMinimised (true);
}
void DocumentWindow::maximiseButtonPressed()
{
setFullScreen (! isFullScreen());
}
//==============================================================================
void DocumentWindow::paint (Graphics& g)
{
ResizableWindow::paint (g);
auto titleBarArea = getTitleBarArea();
g.reduceClipRegion (titleBarArea);
g.setOrigin (titleBarArea.getPosition());
int titleSpaceX1 = 6;
int titleSpaceX2 = titleBarArea.getWidth() - 6;
for (auto& b : titleBarButtons)
{
if (b != nullptr)
{
if (positionTitleBarButtonsOnLeft)
titleSpaceX1 = jmax (titleSpaceX1, b->getRight() + (getWidth() - b->getRight()) / 8);
else
titleSpaceX2 = jmin (titleSpaceX2, b->getX() - (b->getX() / 8));
}
}
getLookAndFeel().drawDocumentWindowTitleBar (*this, g,
titleBarArea.getWidth(),
titleBarArea.getHeight(),
titleSpaceX1,
jmax (1, titleSpaceX2 - titleSpaceX1),
titleBarIcon.isValid() ? &titleBarIcon : nullptr,
! drawTitleTextCentred);
}
void DocumentWindow::resized()
{
ResizableWindow::resized();
if (auto* b = getMaximiseButton())
b->setToggleState (isFullScreen(), dontSendNotification);
auto titleBarArea = getTitleBarArea();
getLookAndFeel()
.positionDocumentWindowButtons (*this,
titleBarArea.getX(), titleBarArea.getY(),
titleBarArea.getWidth(), titleBarArea.getHeight(),
titleBarButtons[0].get(),
titleBarButtons[1].get(),
titleBarButtons[2].get(),
positionTitleBarButtonsOnLeft);
if (menuBar != nullptr)
menuBar->setBounds (titleBarArea.getX(), titleBarArea.getBottom(),
titleBarArea.getWidth(), menuBarHeight);
}
BorderSize<int> DocumentWindow::getBorderThickness()
{
return ResizableWindow::getBorderThickness();
}
BorderSize<int> DocumentWindow::getContentComponentBorder()
{
auto border = getBorderThickness();
if (! isKioskMode())
border.setTop (border.getTop()
+ (isUsingNativeTitleBar() ? 0 : titleBarHeight)
+ (menuBar != nullptr ? menuBarHeight : 0));
return border;
}
int DocumentWindow::getTitleBarHeight() const
{
return isUsingNativeTitleBar() ? 0 : jmin (titleBarHeight, getHeight() - 4);
}
Rectangle<int> DocumentWindow::getTitleBarArea()
{
if (isKioskMode())
return {};
auto border = getBorderThickness();
return { border.getLeft(), border.getTop(), getWidth() - border.getLeftAndRight(), getTitleBarHeight() };
}
Button* DocumentWindow::getCloseButton() const noexcept { return titleBarButtons[2].get(); }
Button* DocumentWindow::getMinimiseButton() const noexcept { return titleBarButtons[0].get(); }
Button* DocumentWindow::getMaximiseButton() const noexcept { return titleBarButtons[1].get(); }
int DocumentWindow::getDesktopWindowStyleFlags() const
{
auto styleFlags = ResizableWindow::getDesktopWindowStyleFlags();
if ((requiredButtons & minimiseButton) != 0) styleFlags |= ComponentPeer::windowHasMinimiseButton;
if ((requiredButtons & maximiseButton) != 0) styleFlags |= ComponentPeer::windowHasMaximiseButton;
if ((requiredButtons & closeButton) != 0) styleFlags |= ComponentPeer::windowHasCloseButton;
return styleFlags;
}
void DocumentWindow::lookAndFeelChanged()
{
for (auto& b : titleBarButtons)
b.reset();
if (! isUsingNativeTitleBar())
{
auto& lf = getLookAndFeel();
if ((requiredButtons & minimiseButton) != 0) titleBarButtons[0].reset (lf.createDocumentWindowButton (minimiseButton));
if ((requiredButtons & maximiseButton) != 0) titleBarButtons[1].reset (lf.createDocumentWindowButton (maximiseButton));
if ((requiredButtons & closeButton) != 0) titleBarButtons[2].reset (lf.createDocumentWindowButton (closeButton));
for (auto& b : titleBarButtons)
{
if (b != nullptr)
{
if (buttonListener == nullptr)
buttonListener.reset (new ButtonListenerProxy (*this));
b->addListener (buttonListener.get());
b->setWantsKeyboardFocus (false);
// (call the Component method directly to avoid the assertion in ResizableWindow)
Component::addAndMakeVisible (b.get());
}
}
if (auto* b = getCloseButton())
{
#if JUCE_MAC
b->addShortcut (KeyPress ('w', ModifierKeys::commandModifier, 0));
#else
b->addShortcut (KeyPress (KeyPress::F4Key, ModifierKeys::altModifier, 0));
#endif
}
}
activeWindowStatusChanged();
ResizableWindow::lookAndFeelChanged();
}
void DocumentWindow::parentHierarchyChanged()
{
lookAndFeelChanged();
}
void DocumentWindow::activeWindowStatusChanged()
{
ResizableWindow::activeWindowStatusChanged();
bool isActive = isActiveWindow();
for (auto& b : titleBarButtons)
if (b != nullptr)
b->setEnabled (isActive);
if (menuBar != nullptr)
menuBar->setEnabled (isActive);
}
void DocumentWindow::mouseDoubleClick (const MouseEvent& e)
{
if (getTitleBarArea().contains (e.x, e.y))
if (auto* maximise = getMaximiseButton())
maximise->triggerClick();
}
void DocumentWindow::userTriedToCloseWindow()
{
closeButtonPressed();
}
} // namespace juce

View File

@ -1,295 +1,307 @@
/*
==============================================================================
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
{
//==============================================================================
/**
A resizable window with a title bar and maximise, minimise and close buttons.
This subclass of ResizableWindow creates a fairly standard type of window with
a title bar and various buttons. The name of the component is shown in the
title bar, and an icon can optionally be specified with setIcon().
All the methods available to a ResizableWindow are also available to this,
so it can easily be made resizable, minimised, maximised, etc.
It's not advisable to add child components directly to a DocumentWindow: put them
inside your content component instead. And overriding methods like resized(), moved(), etc
is also not recommended - instead override these methods for your content component.
(If for some obscure reason you do need to override these methods, always remember to
call the super-class's resized() method too, otherwise it'll fail to lay out the window
decorations correctly).
You can also automatically add a menu bar to the window, using the setMenuBar()
method.
@see ResizableWindow, DialogWindow
@tags{GUI}
*/
class JUCE_API DocumentWindow : public ResizableWindow
{
public:
//==============================================================================
/** The set of available button-types that can be put on the title bar.
@see setTitleBarButtonsRequired
*/
enum TitleBarButtons
{
minimiseButton = 1,
maximiseButton = 2,
closeButton = 4,
/** A combination of all the buttons above. */
allButtons = 7
};
//==============================================================================
/** Creates a DocumentWindow.
@param name the name to give the component - this is also
the title shown at the top of the window. To change
this later, use setName()
@param backgroundColour the colour to use for filling the window's background.
@param requiredButtons specifies which of the buttons (close, minimise, maximise)
should be shown on the title bar. This value is a bitwise
combination of values from the TitleBarButtons enum. Note
that it can be "allButtons" to get them all. You
can change this later with the setTitleBarButtonsRequired()
method, which can also specify where they are positioned.
@param addToDesktop if true, the window will be automatically added to the
desktop; if false, you can use it as a child component
@see TitleBarButtons
*/
DocumentWindow (const String& name,
Colour backgroundColour,
int requiredButtons,
bool addToDesktop = true);
/** Destructor.
If a content component has been set with setContentOwned(), it will be deleted.
*/
~DocumentWindow() override;
//==============================================================================
/** Changes the component's name.
(This is overridden from Component::setName() to cause a repaint, as
the name is what gets drawn across the window's title bar).
*/
void setName (const String& newName) override;
/** Sets an icon to show in the title bar, next to the title.
A copy is made internally of the image, so the caller can delete the
image after calling this. If an empty Image is passed-in, any existing icon
will be removed.
*/
void setIcon (const Image& imageToUse);
/** Changes the height of the title-bar. */
void setTitleBarHeight (int newHeight);
/** Returns the current title bar height. */
int getTitleBarHeight() const;
/** Changes the set of title-bar buttons being shown.
@param requiredButtons specifies which of the buttons (close, minimise, maximise)
should be shown on the title bar. This value is a bitwise
combination of values from the TitleBarButtons enum. Note
that it can be "allButtons" to get them all.
@param positionTitleBarButtonsOnLeft if true, the buttons should go at the
left side of the bar; if false, they'll be placed at the right
*/
void setTitleBarButtonsRequired (int requiredButtons,
bool positionTitleBarButtonsOnLeft);
/** Sets whether the title should be centred within the window.
If true, the title text is shown in the middle of the title-bar; if false,
it'll be shown at the left of the bar.
*/
void setTitleBarTextCentred (bool textShouldBeCentred);
//==============================================================================
/** Creates a menu inside this window.
@param menuBarModel this specifies a MenuBarModel that should be used to
generate the contents of a menu bar that will be placed
just below the title bar, and just above any content
component. If this value is a nullptr, any existing menu bar
will be removed from the component; if it is not a nullptr,
one will be added if it's required.
@param menuBarHeight the height of the menu bar component, if one is needed. Pass a value of zero
or less to use the look-and-feel's default size.
*/
void setMenuBar (MenuBarModel* menuBarModel,
int menuBarHeight = 0);
/** Returns the current menu bar component, or null if there isn't one.
This is probably a MenuBarComponent, unless a custom one has been set using
setMenuBarComponent().
*/
Component* getMenuBarComponent() const noexcept;
/** Replaces the current menu bar with a custom component.
The component will be owned and deleted by the document window.
*/
void setMenuBarComponent (Component* newMenuBarComponent);
//==============================================================================
/** This method is called when the user tries to close the window.
This is triggered by the user clicking the close button, or using some other
OS-specific key shortcut or OS menu for getting rid of a window.
If the window is just a pop-up, you should override this closeButtonPressed()
method and make it delete the window in whatever way is appropriate for your
app. E.g. you might just want to call "delete this".
If your app is centred around this window such that the whole app should quit when
the window is closed, then you will probably want to use this method as an opportunity
to call JUCEApplicationBase::quit(), and leave the window to be deleted later by your
JUCEApplicationBase::shutdown() method. (Doing it this way means that your window will
still get cleaned-up if the app is quit by some other means (e.g. a cmd-Q on the mac
or closing it via the taskbar icon on Windows).
(Note that the DocumentWindow class overrides Component::userTriedToCloseWindow() and
redirects it to call this method, so any methods of closing the window that are
caught by userTriedToCloseWindow() will also end up here).
*/
virtual void closeButtonPressed();
/** Callback that is triggered when the minimise button is pressed.
The default implementation of this calls ResizableWindow::setMinimised(), but
you can override it to do more customised behaviour.
*/
virtual void minimiseButtonPressed();
/** Callback that is triggered when the maximise button is pressed, or when the
title-bar is double-clicked.
The default implementation of this calls ResizableWindow::setFullScreen(), but
you can override it to do more customised behaviour.
*/
virtual void maximiseButtonPressed();
//==============================================================================
/** Returns the close button, (or nullptr if there isn't one). */
Button* getCloseButton() const noexcept;
/** Returns the minimise button, (or nullptr if there isn't one). */
Button* getMinimiseButton() const noexcept;
/** Returns the maximise button, (or nullptr if there isn't one). */
Button* getMaximiseButton() const noexcept;
//==============================================================================
/** A set of colour IDs to use to change the colour of various aspects of the window.
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
methods.
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
*/
enum ColourIds
{
textColourId = 0x1005701, /**< The colour to draw any text with. It's up to the look
and feel class how this is used. */
};
//==============================================================================
/** This abstract base class is implemented by LookAndFeel classes to provide
window drawing functionality.
*/
struct JUCE_API LookAndFeelMethods
{
virtual ~LookAndFeelMethods() = default;
virtual void drawDocumentWindowTitleBar (DocumentWindow&,
Graphics&, int w, int h,
int titleSpaceX, int titleSpaceW,
const Image* icon,
bool drawTitleTextOnLeft) = 0;
virtual Button* createDocumentWindowButton (int buttonType) = 0;
virtual void positionDocumentWindowButtons (DocumentWindow&,
int titleBarX, int titleBarY, int titleBarW, int titleBarH,
Button* minimiseButton,
Button* maximiseButton,
Button* closeButton,
bool positionTitleBarButtonsOnLeft) = 0;
};
//==============================================================================
#ifndef DOXYGEN
/** @internal */
void paint (Graphics&) override;
/** @internal */
void resized() override;
/** @internal */
void lookAndFeelChanged() override;
/** @internal */
BorderSize<int> getBorderThickness() override;
/** @internal */
BorderSize<int> getContentComponentBorder() override;
/** @internal */
void mouseDoubleClick (const MouseEvent&) override;
/** @internal */
void userTriedToCloseWindow() override;
/** @internal */
void activeWindowStatusChanged() override;
/** @internal */
int getDesktopWindowStyleFlags() const override;
/** @internal */
void parentHierarchyChanged() override;
/** @internal */
Rectangle<int> getTitleBarArea();
#endif
private:
//==============================================================================
int titleBarHeight = 26, menuBarHeight = 24, requiredButtons;
bool positionTitleBarButtonsOnLeft, drawTitleTextCentred = true;
std::unique_ptr<Button> titleBarButtons [3];
Image titleBarIcon;
std::unique_ptr<Component> menuBar;
MenuBarModel* menuBarModel = nullptr;
class ButtonListenerProxy;
std::unique_ptr<ButtonListenerProxy> buttonListener;
void repaintTitleBar();
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DocumentWindow)
};
} // 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
{
//==============================================================================
/**
A resizable window with a title bar and maximise, minimise and close buttons.
This subclass of ResizableWindow creates a fairly standard type of window with
a title bar and various buttons. The name of the component is shown in the
title bar, and an icon can optionally be specified with setIcon().
All the methods available to a ResizableWindow are also available to this,
so it can easily be made resizable, minimised, maximised, etc.
It's not advisable to add child components directly to a DocumentWindow: put them
inside your content component instead. And overriding methods like resized(), moved(), etc
is also not recommended - instead override these methods for your content component.
(If for some obscure reason you do need to override these methods, always remember to
call the super-class's resized() method too, otherwise it'll fail to lay out the window
decorations correctly).
You can also automatically add a menu bar to the window, using the setMenuBar()
method.
@see ResizableWindow, DialogWindow
@tags{GUI}
*/
class JUCE_API DocumentWindow : public ResizableWindow
{
public:
//==============================================================================
/** The set of available button-types that can be put on the title bar.
@see setTitleBarButtonsRequired
*/
enum TitleBarButtons
{
minimiseButton = 1,
maximiseButton = 2,
closeButton = 4,
/** A combination of all the buttons above. */
allButtons = 7
};
//==============================================================================
/** Creates a DocumentWindow.
@param name the name to give the component - this is also
the title shown at the top of the window. To change
this later, use setName()
@param backgroundColour the colour to use for filling the window's background.
@param requiredButtons specifies which of the buttons (close, minimise, maximise)
should be shown on the title bar. This value is a bitwise
combination of values from the TitleBarButtons enum. Note
that it can be "allButtons" to get them all. You
can change this later with the setTitleBarButtonsRequired()
method, which can also specify where they are positioned.
The behaviour of native titlebars on macOS is slightly different:
the maximiseButton flag controls whether or not the window can enter
native fullscreen mode, and the zoom button can be disabled by
making the window non-resizable.
@param addToDesktop if true, the window will be automatically added to the
desktop; if false, you can use it as a child component
@see TitleBarButtons
*/
DocumentWindow (const String& name,
Colour backgroundColour,
int requiredButtons,
bool addToDesktop = true);
/** Destructor.
If a content component has been set with setContentOwned(), it will be deleted.
*/
~DocumentWindow() override;
//==============================================================================
/** Changes the component's name.
(This is overridden from Component::setName() to cause a repaint, as
the name is what gets drawn across the window's title bar).
*/
void setName (const String& newName) override;
/** Sets an icon to show in the title bar, next to the title.
A copy is made internally of the image, so the caller can delete the
image after calling this. If an empty Image is passed-in, any existing icon
will be removed.
*/
void setIcon (const Image& imageToUse);
/** Changes the height of the title-bar. */
void setTitleBarHeight (int newHeight);
/** Returns the current title bar height. */
int getTitleBarHeight() const;
/** Changes the set of title-bar buttons being shown.
@param requiredButtons specifies which of the buttons (close, minimise, maximise)
should be shown on the title bar. This value is a bitwise
combination of values from the TitleBarButtons enum. Note
that it can be "allButtons" to get them all.
The behaviour of native titlebars on macOS is slightly different:
the maximiseButton flag controls whether or not the window can enter
native fullscreen mode, and the zoom button can be disabled by
making the window non-resizable.
@param positionTitleBarButtonsOnLeft if true, the buttons should go at the
left side of the bar; if false, they'll be placed at the right
*/
void setTitleBarButtonsRequired (int requiredButtons,
bool positionTitleBarButtonsOnLeft);
/** Sets whether the title should be centred within the window.
If true, the title text is shown in the middle of the title-bar; if false,
it'll be shown at the left of the bar.
*/
void setTitleBarTextCentred (bool textShouldBeCentred);
//==============================================================================
/** Creates a menu inside this window.
@param menuBarModel this specifies a MenuBarModel that should be used to
generate the contents of a menu bar that will be placed
just below the title bar, and just above any content
component. If this value is a nullptr, any existing menu bar
will be removed from the component; if it is not a nullptr,
one will be added if it's required.
@param menuBarHeight the height of the menu bar component, if one is needed. Pass a value of zero
or less to use the look-and-feel's default size.
*/
void setMenuBar (MenuBarModel* menuBarModel,
int menuBarHeight = 0);
/** Returns the current menu bar component, or null if there isn't one.
This is probably a MenuBarComponent, unless a custom one has been set using
setMenuBarComponent().
*/
Component* getMenuBarComponent() const noexcept;
/** Replaces the current menu bar with a custom component.
The component will be owned and deleted by the document window.
*/
void setMenuBarComponent (Component* newMenuBarComponent);
//==============================================================================
/** This method is called when the user tries to close the window.
This is triggered by the user clicking the close button, or using some other
OS-specific key shortcut or OS menu for getting rid of a window.
If the window is just a pop-up, you should override this closeButtonPressed()
method and make it delete the window in whatever way is appropriate for your
app. E.g. you might just want to call "delete this".
If your app is centred around this window such that the whole app should quit when
the window is closed, then you will probably want to use this method as an opportunity
to call JUCEApplicationBase::quit(), and leave the window to be deleted later by your
JUCEApplicationBase::shutdown() method. (Doing it this way means that your window will
still get cleaned-up if the app is quit by some other means (e.g. a cmd-Q on the mac
or closing it via the taskbar icon on Windows).
(Note that the DocumentWindow class overrides Component::userTriedToCloseWindow() and
redirects it to call this method, so any methods of closing the window that are
caught by userTriedToCloseWindow() will also end up here).
*/
virtual void closeButtonPressed();
/** Callback that is triggered when the minimise button is pressed.
This function is only called when using a non-native titlebar.
The default implementation of this calls ResizableWindow::setMinimised(), but
you can override it to do more customised behaviour.
*/
virtual void minimiseButtonPressed();
/** Callback that is triggered when the maximise button is pressed, or when the
title-bar is double-clicked.
This function is only called when using a non-native titlebar.
The default implementation of this calls ResizableWindow::setFullScreen(), but
you can override it to do more customised behaviour.
*/
virtual void maximiseButtonPressed();
//==============================================================================
/** Returns the close button, (or nullptr if there isn't one). */
Button* getCloseButton() const noexcept;
/** Returns the minimise button, (or nullptr if there isn't one). */
Button* getMinimiseButton() const noexcept;
/** Returns the maximise button, (or nullptr if there isn't one). */
Button* getMaximiseButton() const noexcept;
//==============================================================================
/** A set of colour IDs to use to change the colour of various aspects of the window.
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
methods.
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
*/
enum ColourIds
{
textColourId = 0x1005701, /**< The colour to draw any text with. It's up to the look
and feel class how this is used. */
};
//==============================================================================
/** This abstract base class is implemented by LookAndFeel classes to provide
window drawing functionality.
*/
struct JUCE_API LookAndFeelMethods
{
virtual ~LookAndFeelMethods() = default;
virtual void drawDocumentWindowTitleBar (DocumentWindow&,
Graphics&, int w, int h,
int titleSpaceX, int titleSpaceW,
const Image* icon,
bool drawTitleTextOnLeft) = 0;
virtual Button* createDocumentWindowButton (int buttonType) = 0;
virtual void positionDocumentWindowButtons (DocumentWindow&,
int titleBarX, int titleBarY, int titleBarW, int titleBarH,
Button* minimiseButton,
Button* maximiseButton,
Button* closeButton,
bool positionTitleBarButtonsOnLeft) = 0;
};
//==============================================================================
#ifndef DOXYGEN
/** @internal */
void paint (Graphics&) override;
/** @internal */
void resized() override;
/** @internal */
void lookAndFeelChanged() override;
/** @internal */
BorderSize<int> getBorderThickness() override;
/** @internal */
BorderSize<int> getContentComponentBorder() override;
/** @internal */
void mouseDoubleClick (const MouseEvent&) override;
/** @internal */
void userTriedToCloseWindow() override;
/** @internal */
void activeWindowStatusChanged() override;
/** @internal */
int getDesktopWindowStyleFlags() const override;
/** @internal */
void parentHierarchyChanged() override;
/** @internal */
Rectangle<int> getTitleBarArea();
#endif
private:
//==============================================================================
int titleBarHeight = 26, menuBarHeight = 24, requiredButtons;
bool positionTitleBarButtonsOnLeft, drawTitleTextCentred = true;
std::unique_ptr<Button> titleBarButtons [3];
Image titleBarIcon;
std::unique_ptr<Component> menuBar;
MenuBarModel* menuBarModel = nullptr;
class ButtonListenerProxy;
std::unique_ptr<ButtonListenerProxy> buttonListener;
void repaintTitleBar();
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DocumentWindow)
};
} // namespace juce

View File

@ -1,143 +1,143 @@
/*
==============================================================================
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
{
/** The type of icon to show in the dialog box. */
enum class MessageBoxIconType
{
NoIcon, /**< No icon will be shown on the dialog box. */
QuestionIcon, /**< A question-mark icon, for dialog boxes that need the
user to answer a question. */
WarningIcon, /**< An exclamation mark to indicate that the dialog is a
warning about something and shouldn't be ignored. */
InfoIcon /**< An icon that indicates that the dialog box is just
giving the user some information, which doesn't require
a response from them. */
};
//==============================================================================
/** Class used to create a set of options to pass to the AlertWindow and NativeMessageBox
methods for showing dialog boxes.
You can chain together a series of calls to this class's methods to create
a set of whatever options you want to specify.
E.g. @code
AlertWindow::showAsync (MessageBoxOptions()
.withIconType (MessageBoxIconType::InfoIcon)
.withTitle ("A Title")
.withMessage ("A message.")
.withButton ("OK")
.withButton ("Cancel")
.withAssociatedComponent (myComp),
myCallback);
@endcode
@tags{GUI}
*/
class JUCE_API MessageBoxOptions
{
public:
MessageBoxOptions() = default;
MessageBoxOptions (const MessageBoxOptions&) = default;
MessageBoxOptions& operator= (const MessageBoxOptions&) = default;
//==============================================================================
/** Sets the type of icon that should be used for the dialog box. */
MessageBoxOptions withIconType (MessageBoxIconType type) const { return with (*this, &MessageBoxOptions::iconType, type); }
/** Sets the title of the dialog box. */
MessageBoxOptions withTitle (const String& boxTitle) const { return with (*this, &MessageBoxOptions::title, boxTitle); }
/** Sets the message that should be displayed in the dialog box. */
MessageBoxOptions withMessage (const String& boxMessage) const { return with (*this, &MessageBoxOptions::message, boxMessage); }
/** If the string passed in is not empty, this will add a button to the
dialog box with the specified text.
Generally up to 3 buttons are supported for dialog boxes, so adding any more
than this may have no effect.
*/
MessageBoxOptions withButton (const String& text) const { auto copy = *this; copy.buttons.add (text); return copy; }
/** The component that the dialog box should be associated with. */
MessageBoxOptions withAssociatedComponent (Component* component) const { return with (*this, &MessageBoxOptions::associatedComponent, component); }
//==============================================================================
/** Returns the icon type of the dialog box.
@see withIconType
*/
MessageBoxIconType getIconType() const noexcept { return iconType; }
/** Returns the title of the dialog box.
@see withTitle
*/
String getTitle() const { return title; }
/** Returns the message of the dialog box.
@see withMessage
*/
String getMessage() const { return message; }
/** Returns the number of buttons that have been added to the dialog box.
@see withButtonText
*/
int getNumButtons() const noexcept { return buttons.size(); }
/** Returns the text that has been set for one of the buttons of the dialog box.
@see withButtonText, getNumButtons
*/
String getButtonText (int buttonIndex) const { return buttons[buttonIndex]; }
/** Returns the component that the dialog box is associated with.
@see withAssociatedComponent
*/
Component* getAssociatedComponent() const noexcept { return associatedComponent; }
private:
//==============================================================================
template <typename Member, typename Item>
static MessageBoxOptions with (MessageBoxOptions options, Member&& member, Item&& item)
{
options.*member = std::forward<Item> (item);
return options;
}
//==============================================================================
MessageBoxIconType iconType = MessageBoxIconType::InfoIcon;
String title, message;
StringArray buttons;
WeakReference<Component> associatedComponent;
};
} // 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
{
/** The type of icon to show in the dialog box. */
enum class MessageBoxIconType
{
NoIcon, /**< No icon will be shown on the dialog box. */
QuestionIcon, /**< A question-mark icon, for dialog boxes that need the
user to answer a question. */
WarningIcon, /**< An exclamation mark to indicate that the dialog is a
warning about something and shouldn't be ignored. */
InfoIcon /**< An icon that indicates that the dialog box is just
giving the user some information, which doesn't require
a response from them. */
};
//==============================================================================
/** Class used to create a set of options to pass to the AlertWindow and NativeMessageBox
methods for showing dialog boxes.
You can chain together a series of calls to this class's methods to create
a set of whatever options you want to specify.
E.g. @code
AlertWindow::showAsync (MessageBoxOptions()
.withIconType (MessageBoxIconType::InfoIcon)
.withTitle ("A Title")
.withMessage ("A message.")
.withButton ("OK")
.withButton ("Cancel")
.withAssociatedComponent (myComp),
myCallback);
@endcode
@tags{GUI}
*/
class JUCE_API MessageBoxOptions
{
public:
MessageBoxOptions() = default;
MessageBoxOptions (const MessageBoxOptions&) = default;
MessageBoxOptions& operator= (const MessageBoxOptions&) = default;
//==============================================================================
/** Sets the type of icon that should be used for the dialog box. */
JUCE_NODISCARD MessageBoxOptions withIconType (MessageBoxIconType type) const { return with (*this, &MessageBoxOptions::iconType, type); }
/** Sets the title of the dialog box. */
JUCE_NODISCARD MessageBoxOptions withTitle (const String& boxTitle) const { return with (*this, &MessageBoxOptions::title, boxTitle); }
/** Sets the message that should be displayed in the dialog box. */
JUCE_NODISCARD MessageBoxOptions withMessage (const String& boxMessage) const { return with (*this, &MessageBoxOptions::message, boxMessage); }
/** If the string passed in is not empty, this will add a button to the
dialog box with the specified text.
Generally up to 3 buttons are supported for dialog boxes, so adding any more
than this may have no effect.
*/
JUCE_NODISCARD MessageBoxOptions withButton (const String& text) const { auto copy = *this; copy.buttons.add (text); return copy; }
/** The component that the dialog box should be associated with. */
JUCE_NODISCARD MessageBoxOptions withAssociatedComponent (Component* component) const { return with (*this, &MessageBoxOptions::associatedComponent, component); }
//==============================================================================
/** Returns the icon type of the dialog box.
@see withIconType
*/
MessageBoxIconType getIconType() const noexcept { return iconType; }
/** Returns the title of the dialog box.
@see withTitle
*/
String getTitle() const { return title; }
/** Returns the message of the dialog box.
@see withMessage
*/
String getMessage() const { return message; }
/** Returns the number of buttons that have been added to the dialog box.
@see withButtonText
*/
int getNumButtons() const noexcept { return buttons.size(); }
/** Returns the text that has been set for one of the buttons of the dialog box.
@see withButtonText, getNumButtons
*/
String getButtonText (int buttonIndex) const { return buttons[buttonIndex]; }
/** Returns the component that the dialog box is associated with.
@see withAssociatedComponent
*/
Component* getAssociatedComponent() const noexcept { return associatedComponent; }
private:
//==============================================================================
template <typename Member, typename Item>
static MessageBoxOptions with (MessageBoxOptions options, Member&& member, Item&& item)
{
options.*member = std::forward<Item> (item);
return options;
}
//==============================================================================
MessageBoxIconType iconType = MessageBoxIconType::InfoIcon;
String title, message;
StringArray buttons;
WeakReference<Component> associatedComponent;
};
} // namespace juce

View File

@ -1,265 +1,265 @@
/*
==============================================================================
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
{
//==============================================================================
/**
This class contains some static methods for showing native alert windows.
@tags{GUI}
*/
class NativeMessageBox
{
public:
#if JUCE_MODAL_LOOPS_PERMITTED
/** Shows a dialog box that just has a message and a single 'ok' button to close it.
The box is shown modally, and the method will block until the user has clicked its
button (or pressed the escape or return keys).
@param iconType the type of icon to show.
@param title the headline to show at the top of the box.
@param message a longer, more descriptive message to show underneath the title.
@param associatedComponent if this is non-null, it specifies the component that the
alert window should be associated with. Depending on the look
and feel, this might be used for positioning of the alert window.
*/
static void JUCE_CALLTYPE showMessageBox (MessageBoxIconType iconType,
const String& title,
const String& message,
Component* associatedComponent = nullptr);
/** Shows a dialog box using the specified options.
The box is shown modally, and the method will block until the user dismisses it.
@param options the options to use when creating the dialog.
@returns the index of the button that was clicked.
@see MessageBoxOptions
*/
static int JUCE_CALLTYPE show (const MessageBoxOptions& options);
#endif
/** Shows a dialog box using the specified options.
The box will be displayed and placed into a modal state, but this method will return
immediately, and the callback will be invoked later when the user dismisses the box.
@param options the options to use when creating the dialog.
@param callback if this is non-null, the callback will receive a call to its
modalStateFinished() when the box is dismissed with the index of the
button that was clicked as its argument.
The callback object will be owned and deleted by the system, so make sure
that it works safely and doesn't keep any references to objects that might
be deleted before it gets called.
@see MessageBoxOptions
*/
static void JUCE_CALLTYPE showAsync (const MessageBoxOptions& options,
ModalComponentManager::Callback* callback);
/** Shows a dialog box using the specified options.
The box will be displayed and placed into a modal state, but this method will return
immediately, and the callback will be invoked later when the user dismisses the box.
@param options the options to use when creating the dialog.
@param callback if this is non-null, the callback will be called when the box is
dismissed with the index of the button that was clicked as its argument.
@see MessageBoxOptions
*/
static void JUCE_CALLTYPE showAsync (const MessageBoxOptions& options,
std::function<void (int)> callback);
/** Shows a dialog box that just has a message and a single 'ok' button to close it.
The box will be displayed and placed into a modal state, but this method will return
immediately, and the callback will be invoked later when the user dismisses the box.
@param iconType the type of icon to show.
@param title the headline to show at the top of the box.
@param message a longer, more descriptive message to show underneath the title.
@param associatedComponent if this is non-null, it specifies the component that the
alert window should be associated with. Depending on the look
and feel, this might be used for positioning of the alert window.
@param callback if this is non-null, the callback will receive a call to its
modalStateFinished() when the box is dismissed. The callback object
will be owned and deleted by the system, so make sure that it works
safely and doesn't keep any references to objects that might be deleted
before it gets called. You can use the ModalCallbackFunction to easily
pass in a lambda for this parameter.
@see ModalCallbackFunction
*/
static void JUCE_CALLTYPE showMessageBoxAsync (MessageBoxIconType iconType,
const String& title,
const String& message,
Component* associatedComponent = nullptr,
ModalComponentManager::Callback* callback = nullptr);
/** Shows a dialog box with two buttons.
Ideal for ok/cancel or yes/no choices. The return key can also be used
to trigger the first button, and the escape key for the second button.
If the callback parameter is null and modal loops are enabled, the box is shown modally,
and the method will block until the user has clicked the button (or pressed the escape or
return keys). If the callback parameter is non-null, the box will be displayed and placed
into a modal state, but this method will return immediately, and the callback will be invoked
later when the user dismisses the box.
@param iconType the type of icon to show.
@param title the headline to show at the top of the box.
@param message a longer, more descriptive message to show underneath the title.
@param associatedComponent if this is non-null, it specifies the component that the
alert window should be associated with. Depending on the look
and feel, this might be used for positioning of the alert window.
@param callback if this is non-null, the box will be launched asynchronously,
returning immediately, and the callback will receive a call to its
modalStateFinished() when the box is dismissed, with its parameter
being 1 if the ok button was pressed, or 0 for cancel, The callback object
will be owned and deleted by the system, so make sure that it works
safely and doesn't keep any references to objects that might be deleted
before it gets called. You can use the ModalCallbackFunction to easily
pass in a lambda for this parameter.
@returns true if button 1 was clicked, false if it was button 2. If the callback parameter
is not null, the method always returns false, and the user's choice is delivered
later by the callback.
@see ModalCallbackFunction
*/
static bool JUCE_CALLTYPE showOkCancelBox (MessageBoxIconType iconType,
const String& title,
const String& message,
#if JUCE_MODAL_LOOPS_PERMITTED
Component* associatedComponent = nullptr,
ModalComponentManager::Callback* callback = nullptr);
#else
Component* associatedComponent,
ModalComponentManager::Callback* callback);
#endif
/** Shows a dialog box with three buttons.
Ideal for yes/no/cancel boxes.
The escape key can be used to trigger the third button.
If the callback parameter is null and modal loops are enabled, the box is shown modally,
and the method will block until the user has clicked the button (or pressed the escape or
return keys). If the callback parameter is non-null, the box will be displayed and placed
into a modal state, but this method will return immediately, and the callback will be invoked
later when the user dismisses the box.
@param iconType the type of icon to show.
@param title the headline to show at the top of the box.
@param message a longer, more descriptive message to show underneath the title.
@param associatedComponent if this is non-null, it specifies the component that the
alert window should be associated with. Depending on the look
and feel, this might be used for positioning of the alert window.
@param callback if this is non-null, the box will be launched asynchronously,
returning immediately, and the callback will receive a call to its
modalStateFinished() when the box is dismissed, with its parameter
being 1 if the "yes" button was pressed, 2 for the "no" button, or 0
if it was cancelled, The callback object will be owned and deleted by the
system, so make sure that it works safely and doesn't keep any references
to objects that might be deleted before it gets called. You can use the
ModalCallbackFunction to easily pass in a lambda for this parameter.
@returns If the callback parameter has been set, this returns 0. Otherwise, it returns one
of the following values:
- 0 if 'cancel' was pressed
- 1 if 'yes' was pressed
- 2 if 'no' was pressed
@see ModalCallbackFunction
*/
static int JUCE_CALLTYPE showYesNoCancelBox (MessageBoxIconType iconType,
const String& title,
const String& message,
#if JUCE_MODAL_LOOPS_PERMITTED
Component* associatedComponent = nullptr,
ModalComponentManager::Callback* callback = nullptr);
#else
Component* associatedComponent,
ModalComponentManager::Callback* callback);
#endif
/** Shows a dialog box with two buttons.
Ideal for yes/no boxes.
The escape key can be used to trigger the no button.
If the callback parameter is null and modal loops are enabled, the box is shown modally,
and the method will block until the user has clicked the button (or pressed the escape or
return keys). If the callback parameter is non-null, the box will be displayed and placed
into a modal state, but this method will return immediately, and the callback will be invoked
later when the user dismisses the box.
@param iconType the type of icon to show.
@param title the headline to show at the top of the box.
@param message a longer, more descriptive message to show underneath the title.
@param associatedComponent if this is non-null, it specifies the component that the
alert window should be associated with. Depending on the look
and feel, this might be used for positioning of the alert window.
@param callback if this is non-null, the box will be launched asynchronously,
returning immediately, and the callback will receive a call to its
modalStateFinished() when the box is dismissed, with its parameter
being 1 if the "yes" button was pressed or 0 for the "no" button was
pressed. The callback object will be owned and deleted by the
system, so make sure that it works safely and doesn't keep any references
to objects that might be deleted before it gets called. You can use the
ModalCallbackFunction to easily pass in a lambda for this parameter.
@returns If the callback parameter has been set, this returns 0. Otherwise, it returns one
of the following values:
- 0 if 'no' was pressed
- 1 if 'yes' was pressed
@see ModalCallbackFunction
*/
static int JUCE_CALLTYPE showYesNoBox (MessageBoxIconType iconType,
const String& title,
const String& message,
#if JUCE_MODAL_LOOPS_PERMITTED
Component* associatedComponent = nullptr,
ModalComponentManager::Callback* callback = nullptr);
#else
Component* associatedComponent,
ModalComponentManager::Callback* callback);
#endif
private:
NativeMessageBox() = delete;
JUCE_DECLARE_NON_COPYABLE (NativeMessageBox)
};
} // 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
{
//==============================================================================
/**
This class contains some static methods for showing native alert windows.
@tags{GUI}
*/
class NativeMessageBox
{
public:
#if JUCE_MODAL_LOOPS_PERMITTED
/** Shows a dialog box that just has a message and a single 'ok' button to close it.
The box is shown modally, and the method will block until the user has clicked its
button (or pressed the escape or return keys).
@param iconType the type of icon to show.
@param title the headline to show at the top of the box.
@param message a longer, more descriptive message to show underneath the title.
@param associatedComponent if this is non-null, it specifies the component that the
alert window should be associated with. Depending on the look
and feel, this might be used for positioning of the alert window.
*/
static void JUCE_CALLTYPE showMessageBox (MessageBoxIconType iconType,
const String& title,
const String& message,
Component* associatedComponent = nullptr);
/** Shows a dialog box using the specified options.
The box is shown modally, and the method will block until the user dismisses it.
@param options the options to use when creating the dialog.
@returns the index of the button that was clicked.
@see MessageBoxOptions
*/
static int JUCE_CALLTYPE show (const MessageBoxOptions& options);
#endif
/** Shows a dialog box using the specified options.
The box will be displayed and placed into a modal state, but this method will return
immediately, and the callback will be invoked later when the user dismisses the box.
@param options the options to use when creating the dialog.
@param callback if this is non-null, the callback will receive a call to its
modalStateFinished() when the box is dismissed with the index of the
button that was clicked as its argument.
The callback object will be owned and deleted by the system, so make sure
that it works safely and doesn't keep any references to objects that might
be deleted before it gets called.
@see MessageBoxOptions
*/
static void JUCE_CALLTYPE showAsync (const MessageBoxOptions& options,
ModalComponentManager::Callback* callback);
/** Shows a dialog box using the specified options.
The box will be displayed and placed into a modal state, but this method will return
immediately, and the callback will be invoked later when the user dismisses the box.
@param options the options to use when creating the dialog.
@param callback if this is non-null, the callback will be called when the box is
dismissed with the index of the button that was clicked as its argument.
@see MessageBoxOptions
*/
static void JUCE_CALLTYPE showAsync (const MessageBoxOptions& options,
std::function<void (int)> callback);
/** Shows a dialog box that just has a message and a single 'ok' button to close it.
The box will be displayed and placed into a modal state, but this method will return
immediately, and the callback will be invoked later when the user dismisses the box.
@param iconType the type of icon to show.
@param title the headline to show at the top of the box.
@param message a longer, more descriptive message to show underneath the title.
@param associatedComponent if this is non-null, it specifies the component that the
alert window should be associated with. Depending on the look
and feel, this might be used for positioning of the alert window.
@param callback if this is non-null, the callback will receive a call to its
modalStateFinished() when the box is dismissed. The callback object
will be owned and deleted by the system, so make sure that it works
safely and doesn't keep any references to objects that might be deleted
before it gets called. You can use the ModalCallbackFunction to easily
pass in a lambda for this parameter.
@see ModalCallbackFunction
*/
static void JUCE_CALLTYPE showMessageBoxAsync (MessageBoxIconType iconType,
const String& title,
const String& message,
Component* associatedComponent = nullptr,
ModalComponentManager::Callback* callback = nullptr);
/** Shows a dialog box with two buttons.
Ideal for ok/cancel or yes/no choices. The return key can also be used
to trigger the first button, and the escape key for the second button.
If the callback parameter is null and modal loops are enabled, the box is shown modally,
and the method will block until the user has clicked the button (or pressed the escape or
return keys). If the callback parameter is non-null, the box will be displayed and placed
into a modal state, but this method will return immediately, and the callback will be invoked
later when the user dismisses the box.
@param iconType the type of icon to show.
@param title the headline to show at the top of the box.
@param message a longer, more descriptive message to show underneath the title.
@param associatedComponent if this is non-null, it specifies the component that the
alert window should be associated with. Depending on the look
and feel, this might be used for positioning of the alert window.
@param callback if this is non-null, the box will be launched asynchronously,
returning immediately, and the callback will receive a call to its
modalStateFinished() when the box is dismissed, with its parameter
being 1 if the ok button was pressed, or 0 for cancel, The callback object
will be owned and deleted by the system, so make sure that it works
safely and doesn't keep any references to objects that might be deleted
before it gets called. You can use the ModalCallbackFunction to easily
pass in a lambda for this parameter.
@returns true if button 1 was clicked, false if it was button 2. If the callback parameter
is not null, the method always returns false, and the user's choice is delivered
later by the callback.
@see ModalCallbackFunction
*/
static bool JUCE_CALLTYPE showOkCancelBox (MessageBoxIconType iconType,
const String& title,
const String& message,
#if JUCE_MODAL_LOOPS_PERMITTED
Component* associatedComponent = nullptr,
ModalComponentManager::Callback* callback = nullptr);
#else
Component* associatedComponent,
ModalComponentManager::Callback* callback);
#endif
/** Shows a dialog box with three buttons.
Ideal for yes/no/cancel boxes.
The escape key can be used to trigger the third button.
If the callback parameter is null and modal loops are enabled, the box is shown modally,
and the method will block until the user has clicked the button (or pressed the escape or
return keys). If the callback parameter is non-null, the box will be displayed and placed
into a modal state, but this method will return immediately, and the callback will be invoked
later when the user dismisses the box.
@param iconType the type of icon to show.
@param title the headline to show at the top of the box.
@param message a longer, more descriptive message to show underneath the title.
@param associatedComponent if this is non-null, it specifies the component that the
alert window should be associated with. Depending on the look
and feel, this might be used for positioning of the alert window.
@param callback if this is non-null, the box will be launched asynchronously,
returning immediately, and the callback will receive a call to its
modalStateFinished() when the box is dismissed, with its parameter
being 1 if the "yes" button was pressed, 2 for the "no" button, or 0
if it was cancelled, The callback object will be owned and deleted by the
system, so make sure that it works safely and doesn't keep any references
to objects that might be deleted before it gets called. You can use the
ModalCallbackFunction to easily pass in a lambda for this parameter.
@returns If the callback parameter has been set, this returns 0. Otherwise, it returns one
of the following values:
- 0 if 'cancel' was pressed
- 1 if 'yes' was pressed
- 2 if 'no' was pressed
@see ModalCallbackFunction
*/
static int JUCE_CALLTYPE showYesNoCancelBox (MessageBoxIconType iconType,
const String& title,
const String& message,
#if JUCE_MODAL_LOOPS_PERMITTED
Component* associatedComponent = nullptr,
ModalComponentManager::Callback* callback = nullptr);
#else
Component* associatedComponent,
ModalComponentManager::Callback* callback);
#endif
/** Shows a dialog box with two buttons.
Ideal for yes/no boxes.
The escape key can be used to trigger the no button.
If the callback parameter is null and modal loops are enabled, the box is shown modally,
and the method will block until the user has clicked the button (or pressed the escape or
return keys). If the callback parameter is non-null, the box will be displayed and placed
into a modal state, but this method will return immediately, and the callback will be invoked
later when the user dismisses the box.
@param iconType the type of icon to show.
@param title the headline to show at the top of the box.
@param message a longer, more descriptive message to show underneath the title.
@param associatedComponent if this is non-null, it specifies the component that the
alert window should be associated with. Depending on the look
and feel, this might be used for positioning of the alert window.
@param callback if this is non-null, the box will be launched asynchronously,
returning immediately, and the callback will receive a call to its
modalStateFinished() when the box is dismissed, with its parameter
being 1 if the "yes" button was pressed or 0 for the "no" button was
pressed. The callback object will be owned and deleted by the
system, so make sure that it works safely and doesn't keep any references
to objects that might be deleted before it gets called. You can use the
ModalCallbackFunction to easily pass in a lambda for this parameter.
@returns If the callback parameter has been set, this returns 0. Otherwise, it returns one
of the following values:
- 0 if 'no' was pressed
- 1 if 'yes' was pressed
@see ModalCallbackFunction
*/
static int JUCE_CALLTYPE showYesNoBox (MessageBoxIconType iconType,
const String& title,
const String& message,
#if JUCE_MODAL_LOOPS_PERMITTED
Component* associatedComponent = nullptr,
ModalComponentManager::Callback* callback = nullptr);
#else
Component* associatedComponent,
ModalComponentManager::Callback* callback);
#endif
private:
NativeMessageBox() = delete;
JUCE_DECLARE_NON_COPYABLE (NativeMessageBox)
};
} // namespace juce

File diff suppressed because it is too large Load Diff

View File

@ -1,413 +1,413 @@
/*
==============================================================================
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
{
//==============================================================================
/**
A base class for top-level windows that can be dragged around and resized.
To add content to the window, use its setContentOwned() or setContentNonOwned() methods
to give it a component that will remain positioned inside it (leaving a gap around
the edges for a border).
It's not advisable to add child components directly to a ResizableWindow: put them
inside your content component instead. And overriding methods like resized(), moved(), etc
is also not recommended - instead override these methods for your content component.
(If for some obscure reason you do need to override these methods, always remember to
call the super-class's resized() method too, otherwise it'll fail to lay out the window
decorations correctly).
By default resizing isn't enabled - use the setResizable() method to enable it and
to choose the style of resizing to use.
@see TopLevelWindow
@tags{GUI}
*/
class JUCE_API ResizableWindow : public TopLevelWindow
{
public:
//==============================================================================
/** Creates a ResizableWindow.
This constructor doesn't specify a background colour, so the LookAndFeel's default
background colour will be used.
@param name the name to give the component
@param addToDesktop if true, the window will be automatically added to the
desktop; if false, you can use it as a child component
*/
ResizableWindow (const String& name,
bool addToDesktop);
/** Creates a ResizableWindow.
@param name the name to give the component
@param backgroundColour the colour to use for filling the window's background.
@param addToDesktop if true, the window will be automatically added to the
desktop; if false, you can use it as a child component
*/
ResizableWindow (const String& name,
Colour backgroundColour,
bool addToDesktop);
/** Destructor.
If a content component has been set with setContentOwned(), it will be deleted.
*/
~ResizableWindow() override;
//==============================================================================
/** Returns the colour currently being used for the window's background.
As a convenience the window will fill itself with this colour, but you
can override the paint() method if you need more customised behaviour.
This method is the same as retrieving the colour for ResizableWindow::backgroundColourId.
@see setBackgroundColour
*/
Colour getBackgroundColour() const noexcept;
/** Changes the colour currently being used for the window's background.
As a convenience the window will fill itself with this colour, but you
can override the paint() method if you need more customised behaviour.
Note that the opaque state of this window is altered by this call to reflect
the opacity of the colour passed-in. On window systems which can't support
semi-transparent windows this might cause problems, (though it's unlikely you'll
be using this class as a base for a semi-transparent component anyway).
You can also use the ResizableWindow::backgroundColourId colour id to set
this colour.
@see getBackgroundColour
*/
void setBackgroundColour (Colour newColour);
//==============================================================================
/** Make the window resizable or fixed.
@param shouldBeResizable whether it's resizable at all
@param useBottomRightCornerResizer if true, it'll add a ResizableCornerComponent at the
bottom-right; if false, it'll use a ResizableBorderComponent
around the edge
@see setResizeLimits, isResizable
*/
void setResizable (bool shouldBeResizable,
bool useBottomRightCornerResizer);
/** Returns true if resizing is enabled.
@see setResizable
*/
bool isResizable() const noexcept;
/** This sets the maximum and minimum sizes for the window.
If the window's current size is outside these limits, it will be resized to
make sure it's within them.
A direct call to setBounds() will bypass any constraint checks, but when the
window is dragged by the user or resized by other indirect means, the constrainer
will limit the numbers involved.
@see setResizable, setFixedAspectRatio
*/
void setResizeLimits (int newMinimumWidth,
int newMinimumHeight,
int newMaximumWidth,
int newMaximumHeight) noexcept;
/** Can be used to enable or disable user-dragging of the window. */
void setDraggable (bool shouldBeDraggable) noexcept;
/** Returns true if the window can be dragged around by the user. */
bool isDraggable() const noexcept { return canDrag; }
/** Returns the bounds constrainer object that this window is using.
You can access this to change its properties.
*/
ComponentBoundsConstrainer* getConstrainer() noexcept { return constrainer; }
/** Sets the bounds-constrainer object to use for resizing and dragging this window.
A pointer to the object you pass in will be kept, but it won't be deleted
by this object, so it's the caller's responsibility to manage it.
If you pass a nullptr, then no constraints will be placed on the positioning of the window.
*/
void setConstrainer (ComponentBoundsConstrainer* newConstrainer);
/** Calls the window's setBounds method, after first checking these bounds
with the current constrainer.
@see setConstrainer
*/
void setBoundsConstrained (const Rectangle<int>& newBounds);
//==============================================================================
/** Returns true if the window is currently in full-screen mode.
@see setFullScreen
*/
bool isFullScreen() const;
/** Puts the window into full-screen mode, or restores it to its normal size.
If true, the window will become full-screen; if false, it will return to the
last size it was before being made full-screen.
@see isFullScreen
*/
void setFullScreen (bool shouldBeFullScreen);
/** Returns true if the window is currently minimised.
@see setMinimised
*/
bool isMinimised() const;
/** Minimises the window, or restores it to its previous position and size.
When being un-minimised, it'll return to the last position and size it
was in before being minimised.
@see isMinimised
*/
void setMinimised (bool shouldMinimise);
/** Returns true if the window has been placed in kiosk-mode.
@see Desktop::setKioskComponent
*/
bool isKioskMode() const;
//==============================================================================
/** Returns a string which encodes the window's current size and position.
This string will encapsulate the window's size, position, and whether it's
in full-screen mode. It's intended for letting your application save and
restore a window's position.
Use the restoreWindowStateFromString() to restore from a saved state.
@see restoreWindowStateFromString
*/
String getWindowStateAsString();
/** Restores the window to a previously-saved size and position.
This restores the window's size, position and full-screen status from an
string that was previously created with the getWindowStateAsString()
method.
@returns false if the string wasn't a valid window state
@see getWindowStateAsString
*/
bool restoreWindowStateFromString (const String& previousState);
//==============================================================================
/** Returns the current content component.
This will be the component set by setContentOwned() or setContentNonOwned, or
nullptr if none has yet been specified.
@see setContentOwned, setContentNonOwned
*/
Component* getContentComponent() const noexcept { return contentComponent; }
/** Changes the current content component.
This sets a component that will be placed in the centre of the ResizableWindow,
(leaving a space around the edge for the border).
You should never add components directly to a ResizableWindow (or any of its subclasses)
with addChildComponent(). Instead, add them to the content component.
@param newContentComponent the new component to use - this component will be deleted when it's
no longer needed (i.e. when the window is deleted or a new content
component is set for it). To set a component that this window will not
delete, call setContentNonOwned() instead.
@param resizeToFitWhenContentChangesSize if true, then the ResizableWindow will maintain its size
such that it always fits around the size of the content component. If false,
the new content will be resized to fit the current space available.
*/
void setContentOwned (Component* newContentComponent,
bool resizeToFitWhenContentChangesSize);
/** Changes the current content component.
This sets a component that will be placed in the centre of the ResizableWindow,
(leaving a space around the edge for the border).
You should never add components directly to a ResizableWindow (or any of its subclasses)
with addChildComponent(). Instead, add them to the content component.
@param newContentComponent the new component to use - this component will NOT be deleted by this
component, so it's the caller's responsibility to manage its lifetime (it's
ok to delete it while this window is still using it). To set a content
component that the window will delete, call setContentOwned() instead.
@param resizeToFitWhenContentChangesSize if true, then the ResizableWindow will maintain its size
such that it always fits around the size of the content component. If false,
the new content will be resized to fit the current space available.
*/
void setContentNonOwned (Component* newContentComponent,
bool resizeToFitWhenContentChangesSize);
/** Removes the current content component.
If the previous content component was added with setContentOwned(), it will also be deleted. If
it was added with setContentNonOwned(), it will simply be removed from this component.
*/
void clearContentComponent();
/** Changes the window so that the content component ends up with the specified size.
This is basically a setSize call on the window, but which adds on the borders,
so you can specify the content component's target size.
*/
void setContentComponentSize (int width, int height);
/** Returns the width of the frame to use around the window.
@see getContentComponentBorder
*/
virtual BorderSize<int> getBorderThickness();
/** Returns the insets to use when positioning the content component.
@see getBorderThickness
*/
virtual BorderSize<int> getContentComponentBorder();
//==============================================================================
/** A set of colour IDs to use to change the colour of various aspects of the window.
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
methods.
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
*/
enum ColourIds
{
backgroundColourId = 0x1005700, /**< A colour to use to fill the window's background. */
};
//==============================================================================
#ifndef DOXYGEN
[[deprecated ("use setContentOwned and setContentNonOwned instead.")]]
void setContentComponent (Component* newContentComponent,
bool deleteOldOne = true,
bool resizeToFit = false);
#endif
using TopLevelWindow::addToDesktop;
//==============================================================================
/** This abstract base class is implemented by LookAndFeel classes to provide
window drawing functionality.
*/
struct JUCE_API LookAndFeelMethods
{
virtual ~LookAndFeelMethods() = default;
//==============================================================================
virtual void drawCornerResizer (Graphics&, int w, int h, bool isMouseOver, bool isMouseDragging) = 0;
virtual void drawResizableFrame (Graphics&, int w, int h, const BorderSize<int>&) = 0;
virtual void fillResizableWindowBackground (Graphics&, int w, int h, const BorderSize<int>&, ResizableWindow&) = 0;
virtual void drawResizableWindowBorder (Graphics&, int w, int h, const BorderSize<int>& border, ResizableWindow&) = 0;
};
protected:
/** @internal */
void paint (Graphics&) override;
/** (if overriding this, make sure you call ResizableWindow::moved() in your subclass) */
void moved() override;
/** (if overriding this, make sure you call ResizableWindow::resized() in your subclass) */
void resized() override;
/** @internal */
void mouseDown (const MouseEvent&) override;
/** @internal */
void mouseDrag (const MouseEvent&) override;
/** @internal */
void mouseUp (const MouseEvent&) override;
/** @internal */
void lookAndFeelChanged() override;
/** @internal */
void childBoundsChanged (Component*) override;
/** @internal */
void parentSizeChanged() override;
/** @internal */
void visibilityChanged() override;
/** @internal */
void activeWindowStatusChanged() override;
/** @internal */
int getDesktopWindowStyleFlags() const override;
#if JUCE_DEBUG
/** Overridden to warn people about adding components directly to this component
instead of using setContentOwned().
If you know what you're doing and are sure you really want to add a component, specify
a base-class method call to Component::addAndMakeVisible(), to side-step this warning.
*/
void addChildComponent (Component*, int zOrder = -1);
/** Overridden to warn people about adding components directly to this component
instead of using setContentOwned().
If you know what you're doing and are sure you really want to add a component, specify
a base-class method call to Component::addAndMakeVisible(), to side-step this warning.
*/
void addAndMakeVisible (Component*, int zOrder = -1);
#endif
std::unique_ptr<ResizableCornerComponent> resizableCorner;
std::unique_ptr<ResizableBorderComponent> resizableBorder;
//==============================================================================
// The parameters for these methods have changed - please update your code!
void getBorderThickness (int& left, int& top, int& right, int& bottom);
void getContentComponentBorder (int& left, int& top, int& right, int& bottom);
private:
//==============================================================================
Component::SafePointer<Component> contentComponent, splashScreen;
bool ownsContentComponent = false, resizeToFitContent = false, fullscreen = false, canDrag = true, dragStarted = false;
ComponentDragger dragger;
Rectangle<int> lastNonFullScreenPos;
ComponentBoundsConstrainer defaultConstrainer;
ComponentBoundsConstrainer* constrainer = nullptr;
#if JUCE_DEBUG
bool hasBeenResized = false;
#endif
void initialise (bool addToDesktop);
void updateLastPosIfNotFullScreen();
void updateLastPosIfShowing();
void setContent (Component*, bool takeOwnership, bool resizeToFit);
void updatePeerConstrainer();
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ResizableWindow)
};
} // 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
{
//==============================================================================
/**
A base class for top-level windows that can be dragged around and resized.
To add content to the window, use its setContentOwned() or setContentNonOwned() methods
to give it a component that will remain positioned inside it (leaving a gap around
the edges for a border).
It's not advisable to add child components directly to a ResizableWindow: put them
inside your content component instead. And overriding methods like resized(), moved(), etc
is also not recommended - instead override these methods for your content component.
(If for some obscure reason you do need to override these methods, always remember to
call the super-class's resized() method too, otherwise it'll fail to lay out the window
decorations correctly).
By default resizing isn't enabled - use the setResizable() method to enable it and
to choose the style of resizing to use.
@see TopLevelWindow
@tags{GUI}
*/
class JUCE_API ResizableWindow : public TopLevelWindow
{
public:
//==============================================================================
/** Creates a ResizableWindow.
This constructor doesn't specify a background colour, so the LookAndFeel's default
background colour will be used.
@param name the name to give the component
@param addToDesktop if true, the window will be automatically added to the
desktop; if false, you can use it as a child component
*/
ResizableWindow (const String& name,
bool addToDesktop);
/** Creates a ResizableWindow.
@param name the name to give the component
@param backgroundColour the colour to use for filling the window's background.
@param addToDesktop if true, the window will be automatically added to the
desktop; if false, you can use it as a child component
*/
ResizableWindow (const String& name,
Colour backgroundColour,
bool addToDesktop);
/** Destructor.
If a content component has been set with setContentOwned(), it will be deleted.
*/
~ResizableWindow() override;
//==============================================================================
/** Returns the colour currently being used for the window's background.
As a convenience the window will fill itself with this colour, but you
can override the paint() method if you need more customised behaviour.
This method is the same as retrieving the colour for ResizableWindow::backgroundColourId.
@see setBackgroundColour
*/
Colour getBackgroundColour() const noexcept;
/** Changes the colour currently being used for the window's background.
As a convenience the window will fill itself with this colour, but you
can override the paint() method if you need more customised behaviour.
Note that the opaque state of this window is altered by this call to reflect
the opacity of the colour passed-in. On window systems which can't support
semi-transparent windows this might cause problems, (though it's unlikely you'll
be using this class as a base for a semi-transparent component anyway).
You can also use the ResizableWindow::backgroundColourId colour id to set
this colour.
@see getBackgroundColour
*/
void setBackgroundColour (Colour newColour);
//==============================================================================
/** Make the window resizable or fixed.
@param shouldBeResizable whether it's resizable at all
@param useBottomRightCornerResizer if true, it'll add a ResizableCornerComponent at the
bottom-right; if false, it'll use a ResizableBorderComponent
around the edge
@see setResizeLimits, isResizable
*/
void setResizable (bool shouldBeResizable,
bool useBottomRightCornerResizer);
/** Returns true if resizing is enabled.
@see setResizable
*/
bool isResizable() const noexcept;
/** This sets the maximum and minimum sizes for the window.
If the window's current size is outside these limits, it will be resized to
make sure it's within them.
A direct call to setBounds() will bypass any constraint checks, but when the
window is dragged by the user or resized by other indirect means, the constrainer
will limit the numbers involved.
@see setResizable, setFixedAspectRatio
*/
void setResizeLimits (int newMinimumWidth,
int newMinimumHeight,
int newMaximumWidth,
int newMaximumHeight) noexcept;
/** Can be used to enable or disable user-dragging of the window. */
void setDraggable (bool shouldBeDraggable) noexcept;
/** Returns true if the window can be dragged around by the user. */
bool isDraggable() const noexcept { return canDrag; }
/** Returns the bounds constrainer object that this window is using.
You can access this to change its properties.
*/
ComponentBoundsConstrainer* getConstrainer() noexcept { return constrainer; }
/** Sets the bounds-constrainer object to use for resizing and dragging this window.
A pointer to the object you pass in will be kept, but it won't be deleted
by this object, so it's the caller's responsibility to manage it.
If you pass a nullptr, then no constraints will be placed on the positioning of the window.
*/
void setConstrainer (ComponentBoundsConstrainer* newConstrainer);
/** Calls the window's setBounds method, after first checking these bounds
with the current constrainer.
@see setConstrainer
*/
void setBoundsConstrained (const Rectangle<int>& newBounds);
//==============================================================================
/** Returns true if the window is currently in full-screen mode.
@see setFullScreen
*/
bool isFullScreen() const;
/** Puts the window into full-screen mode, or restores it to its normal size.
If true, the window will become full-screen; if false, it will return to the
last size it was before being made full-screen.
@see isFullScreen
*/
void setFullScreen (bool shouldBeFullScreen);
/** Returns true if the window is currently minimised.
@see setMinimised
*/
bool isMinimised() const;
/** Minimises the window, or restores it to its previous position and size.
When being un-minimised, it'll return to the last position and size it
was in before being minimised.
@see isMinimised
*/
void setMinimised (bool shouldMinimise);
/** Returns true if the window has been placed in kiosk-mode.
@see Desktop::setKioskComponent
*/
bool isKioskMode() const;
//==============================================================================
/** Returns a string which encodes the window's current size and position.
This string will encapsulate the window's size, position, and whether it's
in full-screen mode. It's intended for letting your application save and
restore a window's position.
Use the restoreWindowStateFromString() to restore from a saved state.
@see restoreWindowStateFromString
*/
String getWindowStateAsString();
/** Restores the window to a previously-saved size and position.
This restores the window's size, position and full-screen status from an
string that was previously created with the getWindowStateAsString()
method.
@returns false if the string wasn't a valid window state
@see getWindowStateAsString
*/
bool restoreWindowStateFromString (const String& previousState);
//==============================================================================
/** Returns the current content component.
This will be the component set by setContentOwned() or setContentNonOwned, or
nullptr if none has yet been specified.
@see setContentOwned, setContentNonOwned
*/
Component* getContentComponent() const noexcept { return contentComponent; }
/** Changes the current content component.
This sets a component that will be placed in the centre of the ResizableWindow,
(leaving a space around the edge for the border).
You should never add components directly to a ResizableWindow (or any of its subclasses)
with addChildComponent(). Instead, add them to the content component.
@param newContentComponent the new component to use - this component will be deleted when it's
no longer needed (i.e. when the window is deleted or a new content
component is set for it). To set a component that this window will not
delete, call setContentNonOwned() instead.
@param resizeToFitWhenContentChangesSize if true, then the ResizableWindow will maintain its size
such that it always fits around the size of the content component. If false,
the new content will be resized to fit the current space available.
*/
void setContentOwned (Component* newContentComponent,
bool resizeToFitWhenContentChangesSize);
/** Changes the current content component.
This sets a component that will be placed in the centre of the ResizableWindow,
(leaving a space around the edge for the border).
You should never add components directly to a ResizableWindow (or any of its subclasses)
with addChildComponent(). Instead, add them to the content component.
@param newContentComponent the new component to use - this component will NOT be deleted by this
component, so it's the caller's responsibility to manage its lifetime (it's
ok to delete it while this window is still using it). To set a content
component that the window will delete, call setContentOwned() instead.
@param resizeToFitWhenContentChangesSize if true, then the ResizableWindow will maintain its size
such that it always fits around the size of the content component. If false,
the new content will be resized to fit the current space available.
*/
void setContentNonOwned (Component* newContentComponent,
bool resizeToFitWhenContentChangesSize);
/** Removes the current content component.
If the previous content component was added with setContentOwned(), it will also be deleted. If
it was added with setContentNonOwned(), it will simply be removed from this component.
*/
void clearContentComponent();
/** Changes the window so that the content component ends up with the specified size.
This is basically a setSize call on the window, but which adds on the borders,
so you can specify the content component's target size.
*/
void setContentComponentSize (int width, int height);
/** Returns the width of the frame to use around the window.
@see getContentComponentBorder
*/
virtual BorderSize<int> getBorderThickness();
/** Returns the insets to use when positioning the content component.
@see getBorderThickness
*/
virtual BorderSize<int> getContentComponentBorder();
//==============================================================================
/** A set of colour IDs to use to change the colour of various aspects of the window.
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
methods.
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
*/
enum ColourIds
{
backgroundColourId = 0x1005700, /**< A colour to use to fill the window's background. */
};
//==============================================================================
#ifndef DOXYGEN
[[deprecated ("use setContentOwned and setContentNonOwned instead.")]]
void setContentComponent (Component* newContentComponent,
bool deleteOldOne = true,
bool resizeToFit = false);
#endif
using TopLevelWindow::addToDesktop;
//==============================================================================
/** This abstract base class is implemented by LookAndFeel classes to provide
window drawing functionality.
*/
struct JUCE_API LookAndFeelMethods
{
virtual ~LookAndFeelMethods() = default;
//==============================================================================
virtual void drawCornerResizer (Graphics&, int w, int h, bool isMouseOver, bool isMouseDragging) = 0;
virtual void drawResizableFrame (Graphics&, int w, int h, const BorderSize<int>&) = 0;
virtual void fillResizableWindowBackground (Graphics&, int w, int h, const BorderSize<int>&, ResizableWindow&) = 0;
virtual void drawResizableWindowBorder (Graphics&, int w, int h, const BorderSize<int>& border, ResizableWindow&) = 0;
};
protected:
/** @internal */
void paint (Graphics&) override;
/** (if overriding this, make sure you call ResizableWindow::moved() in your subclass) */
void moved() override;
/** (if overriding this, make sure you call ResizableWindow::resized() in your subclass) */
void resized() override;
/** @internal */
void mouseDown (const MouseEvent&) override;
/** @internal */
void mouseDrag (const MouseEvent&) override;
/** @internal */
void mouseUp (const MouseEvent&) override;
/** @internal */
void lookAndFeelChanged() override;
/** @internal */
void childBoundsChanged (Component*) override;
/** @internal */
void parentSizeChanged() override;
/** @internal */
void visibilityChanged() override;
/** @internal */
void activeWindowStatusChanged() override;
/** @internal */
int getDesktopWindowStyleFlags() const override;
#if JUCE_DEBUG
/** Overridden to warn people about adding components directly to this component
instead of using setContentOwned().
If you know what you're doing and are sure you really want to add a component, specify
a base-class method call to Component::addAndMakeVisible(), to side-step this warning.
*/
void addChildComponent (Component*, int zOrder = -1);
/** Overridden to warn people about adding components directly to this component
instead of using setContentOwned().
If you know what you're doing and are sure you really want to add a component, specify
a base-class method call to Component::addAndMakeVisible(), to side-step this warning.
*/
void addAndMakeVisible (Component*, int zOrder = -1);
#endif
std::unique_ptr<ResizableCornerComponent> resizableCorner;
std::unique_ptr<ResizableBorderComponent> resizableBorder;
//==============================================================================
// The parameters for these methods have changed - please update your code!
void getBorderThickness (int& left, int& top, int& right, int& bottom);
void getContentComponentBorder (int& left, int& top, int& right, int& bottom);
private:
//==============================================================================
Component::SafePointer<Component> contentComponent, splashScreen;
bool ownsContentComponent = false, resizeToFitContent = false, fullscreen = false, canDrag = true, dragStarted = false;
ComponentDragger dragger;
Rectangle<int> lastNonFullScreenPos;
ComponentBoundsConstrainer defaultConstrainer;
ComponentBoundsConstrainer* constrainer = nullptr;
#if JUCE_DEBUG
bool hasBeenResized = false;
#endif
void initialise (bool addToDesktop);
void updateLastPosIfNotFullScreen();
void updateLastPosIfShowing();
void setContent (Component*, bool takeOwnership, bool resizeToFit);
void updatePeerConstrainer();
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ResizableWindow)
};
} // namespace juce

View File

@ -1,119 +1,119 @@
/*
==============================================================================
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
{
ThreadWithProgressWindow::ThreadWithProgressWindow (const String& title,
const bool hasProgressBar,
const bool hasCancelButton,
const int cancellingTimeOutMs,
const String& cancelButtonText,
Component* componentToCentreAround)
: Thread ("ThreadWithProgressWindow"),
progress (0.0),
timeOutMsWhenCancelling (cancellingTimeOutMs),
wasCancelledByUser (false)
{
alertWindow.reset (LookAndFeel::getDefaultLookAndFeel()
.createAlertWindow (title, {},
cancelButtonText.isEmpty() ? TRANS("Cancel")
: cancelButtonText,
{}, {}, MessageBoxIconType::NoIcon, hasCancelButton ? 1 : 0,
componentToCentreAround));
// if there are no buttons, we won't allow the user to interrupt the thread.
alertWindow->setEscapeKeyCancels (false);
if (hasProgressBar)
alertWindow->addProgressBarComponent (progress);
}
ThreadWithProgressWindow::~ThreadWithProgressWindow()
{
stopThread (timeOutMsWhenCancelling);
}
void ThreadWithProgressWindow::launchThread (int priority)
{
JUCE_ASSERT_MESSAGE_THREAD
startThread (priority);
startTimer (100);
{
const ScopedLock sl (messageLock);
alertWindow->setMessage (message);
}
alertWindow->enterModalState();
}
void ThreadWithProgressWindow::setProgress (const double newProgress)
{
progress = newProgress;
}
void ThreadWithProgressWindow::setStatusMessage (const String& newStatusMessage)
{
const ScopedLock sl (messageLock);
message = newStatusMessage;
}
void ThreadWithProgressWindow::timerCallback()
{
bool threadStillRunning = isThreadRunning();
if (! (threadStillRunning && alertWindow->isCurrentlyModal (false)))
{
stopTimer();
stopThread (timeOutMsWhenCancelling);
alertWindow->exitModalState (1);
alertWindow->setVisible (false);
wasCancelledByUser = threadStillRunning;
threadComplete (threadStillRunning);
return; // (this may be deleted now)
}
const ScopedLock sl (messageLock);
alertWindow->setMessage (message);
}
void ThreadWithProgressWindow::threadComplete (bool) {}
#if JUCE_MODAL_LOOPS_PERMITTED
bool ThreadWithProgressWindow::runThread (const int priority)
{
launchThread (priority);
while (isTimerRunning())
MessageManager::getInstance()->runDispatchLoopUntil (5);
return ! wasCancelledByUser;
}
#endif
} // 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
{
ThreadWithProgressWindow::ThreadWithProgressWindow (const String& title,
const bool hasProgressBar,
const bool hasCancelButton,
const int cancellingTimeOutMs,
const String& cancelButtonText,
Component* componentToCentreAround)
: Thread ("ThreadWithProgressWindow"),
progress (0.0),
timeOutMsWhenCancelling (cancellingTimeOutMs),
wasCancelledByUser (false)
{
alertWindow.reset (LookAndFeel::getDefaultLookAndFeel()
.createAlertWindow (title, {},
cancelButtonText.isEmpty() ? TRANS("Cancel")
: cancelButtonText,
{}, {}, MessageBoxIconType::NoIcon, hasCancelButton ? 1 : 0,
componentToCentreAround));
// if there are no buttons, we won't allow the user to interrupt the thread.
alertWindow->setEscapeKeyCancels (false);
if (hasProgressBar)
alertWindow->addProgressBarComponent (progress);
}
ThreadWithProgressWindow::~ThreadWithProgressWindow()
{
stopThread (timeOutMsWhenCancelling);
}
void ThreadWithProgressWindow::launchThread (int priority)
{
JUCE_ASSERT_MESSAGE_THREAD
startThread (priority);
startTimer (100);
{
const ScopedLock sl (messageLock);
alertWindow->setMessage (message);
}
alertWindow->enterModalState();
}
void ThreadWithProgressWindow::setProgress (const double newProgress)
{
progress = newProgress;
}
void ThreadWithProgressWindow::setStatusMessage (const String& newStatusMessage)
{
const ScopedLock sl (messageLock);
message = newStatusMessage;
}
void ThreadWithProgressWindow::timerCallback()
{
bool threadStillRunning = isThreadRunning();
if (! (threadStillRunning && alertWindow->isCurrentlyModal (false)))
{
stopTimer();
stopThread (timeOutMsWhenCancelling);
alertWindow->exitModalState (1);
alertWindow->setVisible (false);
wasCancelledByUser = threadStillRunning;
threadComplete (threadStillRunning);
return; // (this may be deleted now)
}
const ScopedLock sl (messageLock);
alertWindow->setMessage (message);
}
void ThreadWithProgressWindow::threadComplete (bool) {}
#if JUCE_MODAL_LOOPS_PERMITTED
bool ThreadWithProgressWindow::runThread (const int priority)
{
launchThread (priority);
while (isTimerRunning())
MessageManager::getInstance()->runDispatchLoopUntil (5);
return ! wasCancelledByUser;
}
#endif
} // namespace juce

View File

@ -1,175 +1,175 @@
/*
==============================================================================
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
{
//==============================================================================
/**
A thread that automatically pops up a modal dialog box with a progress bar
and cancel button while it's busy running.
These are handy for performing some sort of task while giving the user feedback
about how long there is to go, etc.
E.g. @code
class MyTask : public ThreadWithProgressWindow
{
public:
MyTask() : ThreadWithProgressWindow ("busy...", true, true)
{
}
void run()
{
for (int i = 0; i < thingsToDo; ++i)
{
// must check this as often as possible, because this is
// how we know if the user's pressed 'cancel'
if (threadShouldExit())
break;
// this will update the progress bar on the dialog box
setProgress (i / (double) thingsToDo);
// ... do the business here...
}
}
};
void doTheTask()
{
MyTask m;
if (m.runThread())
{
// thread finished normally..
}
else
{
// user pressed the cancel button..
}
}
@endcode
@see Thread, AlertWindow
@tags{GUI}
*/
class JUCE_API ThreadWithProgressWindow : public Thread,
private Timer
{
public:
//==============================================================================
/** Creates the thread.
Initially, the dialog box won't be visible, it'll only appear when the
runThread() method is called.
@param windowTitle the title to go at the top of the dialog box
@param hasProgressBar whether the dialog box should have a progress bar (see
setProgress() )
@param hasCancelButton whether the dialog box should have a cancel button
@param timeOutMsWhenCancelling when 'cancel' is pressed, this is how long to wait for
the thread to stop before killing it forcibly (see
Thread::stopThread() )
@param cancelButtonText the text that should be shown in the cancel button
(if it has one). Leave this empty for the default "Cancel"
@param componentToCentreAround if this is non-null, the window will be positioned
so that it's centred around this component.
*/
ThreadWithProgressWindow (const String& windowTitle,
bool hasProgressBar,
bool hasCancelButton,
int timeOutMsWhenCancelling = 10000,
const String& cancelButtonText = String(),
Component* componentToCentreAround = nullptr);
/** Destructor. */
~ThreadWithProgressWindow() override;
//==============================================================================
#if JUCE_MODAL_LOOPS_PERMITTED
/** Starts the thread and waits for it to finish.
This will start the thread, make the dialog box appear, and wait until either
the thread finishes normally, or until the cancel button is pressed.
Before returning, the dialog box will be hidden.
@param priority the priority to use when starting the thread - see
Thread::startThread() for values
@returns true if the thread finished normally; false if the user pressed cancel
*/
bool runThread (int priority = 5);
#endif
/** Starts the thread and returns.
This will start the thread and make the dialog box appear in a modal state. When
the thread finishes normally, or the cancel button is pressed, the window will be
hidden and the threadComplete() method will be called.
@param priority the priority to use when starting the thread - see
Thread::startThread() for values
*/
void launchThread (int priority = 5);
/** The thread should call this periodically to update the position of the progress bar.
@param newProgress the progress, from 0.0 to 1.0
@see setStatusMessage
*/
void setProgress (double newProgress);
/** The thread can call this to change the message that's displayed in the dialog box. */
void setStatusMessage (const String& newStatusMessage);
/** Returns the AlertWindow that is being used. */
AlertWindow* getAlertWindow() const noexcept { return alertWindow.get(); }
//==============================================================================
/** This method is called (on the message thread) when the operation has finished.
You may choose to use this callback to delete the ThreadWithProgressWindow object.
*/
virtual void threadComplete (bool userPressedCancel);
private:
//==============================================================================
void timerCallback() override;
double progress;
std::unique_ptr<AlertWindow> alertWindow;
String message;
CriticalSection messageLock;
const int timeOutMsWhenCancelling;
bool wasCancelledByUser;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ThreadWithProgressWindow)
};
} // 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
{
//==============================================================================
/**
A thread that automatically pops up a modal dialog box with a progress bar
and cancel button while it's busy running.
These are handy for performing some sort of task while giving the user feedback
about how long there is to go, etc.
E.g. @code
class MyTask : public ThreadWithProgressWindow
{
public:
MyTask() : ThreadWithProgressWindow ("busy...", true, true)
{
}
void run()
{
for (int i = 0; i < thingsToDo; ++i)
{
// must check this as often as possible, because this is
// how we know if the user's pressed 'cancel'
if (threadShouldExit())
break;
// this will update the progress bar on the dialog box
setProgress (i / (double) thingsToDo);
// ... do the business here...
}
}
};
void doTheTask()
{
MyTask m;
if (m.runThread())
{
// thread finished normally..
}
else
{
// user pressed the cancel button..
}
}
@endcode
@see Thread, AlertWindow
@tags{GUI}
*/
class JUCE_API ThreadWithProgressWindow : public Thread,
private Timer
{
public:
//==============================================================================
/** Creates the thread.
Initially, the dialog box won't be visible, it'll only appear when the
runThread() method is called.
@param windowTitle the title to go at the top of the dialog box
@param hasProgressBar whether the dialog box should have a progress bar (see
setProgress() )
@param hasCancelButton whether the dialog box should have a cancel button
@param timeOutMsWhenCancelling when 'cancel' is pressed, this is how long to wait for
the thread to stop before killing it forcibly (see
Thread::stopThread() )
@param cancelButtonText the text that should be shown in the cancel button
(if it has one). Leave this empty for the default "Cancel"
@param componentToCentreAround if this is non-null, the window will be positioned
so that it's centred around this component.
*/
ThreadWithProgressWindow (const String& windowTitle,
bool hasProgressBar,
bool hasCancelButton,
int timeOutMsWhenCancelling = 10000,
const String& cancelButtonText = String(),
Component* componentToCentreAround = nullptr);
/** Destructor. */
~ThreadWithProgressWindow() override;
//==============================================================================
#if JUCE_MODAL_LOOPS_PERMITTED
/** Starts the thread and waits for it to finish.
This will start the thread, make the dialog box appear, and wait until either
the thread finishes normally, or until the cancel button is pressed.
Before returning, the dialog box will be hidden.
@param priority the priority to use when starting the thread - see
Thread::startThread() for values
@returns true if the thread finished normally; false if the user pressed cancel
*/
bool runThread (int priority = 5);
#endif
/** Starts the thread and returns.
This will start the thread and make the dialog box appear in a modal state. When
the thread finishes normally, or the cancel button is pressed, the window will be
hidden and the threadComplete() method will be called.
@param priority the priority to use when starting the thread - see
Thread::startThread() for values
*/
void launchThread (int priority = 5);
/** The thread should call this periodically to update the position of the progress bar.
@param newProgress the progress, from 0.0 to 1.0
@see setStatusMessage
*/
void setProgress (double newProgress);
/** The thread can call this to change the message that's displayed in the dialog box. */
void setStatusMessage (const String& newStatusMessage);
/** Returns the AlertWindow that is being used. */
AlertWindow* getAlertWindow() const noexcept { return alertWindow.get(); }
//==============================================================================
/** This method is called (on the message thread) when the operation has finished.
You may choose to use this callback to delete the ThreadWithProgressWindow object.
*/
virtual void threadComplete (bool userPressedCancel);
private:
//==============================================================================
void timerCallback() override;
double progress;
std::unique_ptr<AlertWindow> alertWindow;
String message;
CriticalSection messageLock;
const int timeOutMsWhenCancelling;
bool wasCancelledByUser;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ThreadWithProgressWindow)
};
} // namespace juce

View File

@ -1,232 +1,255 @@
/*
==============================================================================
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
{
TooltipWindow::TooltipWindow (Component* parentComp, int delayMs)
: Component ("tooltip"),
millisecondsBeforeTipAppears (delayMs)
{
setAlwaysOnTop (true);
setOpaque (true);
setAccessible (false);
if (parentComp != nullptr)
parentComp->addChildComponent (this);
if (Desktop::getInstance().getMainMouseSource().canHover())
startTimer (123);
}
TooltipWindow::~TooltipWindow()
{
hideTip();
}
void TooltipWindow::setMillisecondsBeforeTipAppears (const int newTimeMs) noexcept
{
millisecondsBeforeTipAppears = newTimeMs;
}
void TooltipWindow::paint (Graphics& g)
{
getLookAndFeel().drawTooltip (g, tipShowing, getWidth(), getHeight());
}
void TooltipWindow::mouseEnter (const MouseEvent&)
{
hideTip();
}
void TooltipWindow::updatePosition (const String& tip, Point<int> pos, Rectangle<int> parentArea)
{
setBounds (getLookAndFeel().getTooltipBounds (tip, pos, parentArea));
setVisible (true);
}
#if JUCE_DEBUG
static Array<TooltipWindow*> activeTooltipWindows;
#endif
void TooltipWindow::displayTip (Point<int> screenPos, const String& tip)
{
jassert (tip.isNotEmpty());
if (! reentrant)
{
ScopedValueSetter<bool> setter (reentrant, true, false);
if (tipShowing != tip)
{
tipShowing = tip;
repaint();
}
if (auto* parent = getParentComponent())
{
updatePosition (tip, parent->getLocalPoint (nullptr, screenPos),
parent->getLocalBounds());
}
else
{
const auto physicalPos = ScalingHelpers::scaledScreenPosToUnscaled (screenPos);
const auto scaledPos = ScalingHelpers::unscaledScreenPosToScaled (*this, physicalPos);
updatePosition (tip, scaledPos, Desktop::getInstance().getDisplays().getDisplayForPoint (screenPos)->userArea);
addToDesktop (ComponentPeer::windowHasDropShadow
| ComponentPeer::windowIsTemporary
| ComponentPeer::windowIgnoresKeyPresses
| ComponentPeer::windowIgnoresMouseClicks);
}
#if JUCE_DEBUG
activeTooltipWindows.addIfNotAlreadyThere (this);
auto* parent = getParentComponent();
for (auto* w : activeTooltipWindows)
{
if (w != nullptr && w != this && w->tipShowing == tipShowing && w->getParentComponent() == parent)
{
// Looks like you have more than one TooltipWindow showing the same tip..
// Be careful not to create more than one instance of this class with the
// same parent component!
jassertfalse;
}
}
#endif
toFront (false);
}
}
String TooltipWindow::getTipFor (Component& c)
{
if (isForegroundOrEmbeddedProcess (&c)
&& ! ModifierKeys::currentModifiers.isAnyMouseButtonDown())
{
if (auto* ttc = dynamic_cast<TooltipClient*> (&c))
if (! c.isCurrentlyBlockedByAnotherModalComponent())
return ttc->getTooltip();
}
return {};
}
void TooltipWindow::hideTip()
{
if (! reentrant)
{
tipShowing.clear();
removeFromDesktop();
setVisible (false);
#if JUCE_DEBUG
activeTooltipWindows.removeAllInstancesOf (this);
#endif
}
}
float TooltipWindow::getDesktopScaleFactor() const
{
if (lastComponentUnderMouse != nullptr)
return Component::getApproximateScaleFactorForComponent (lastComponentUnderMouse);
return Component::getDesktopScaleFactor();
}
std::unique_ptr<AccessibilityHandler> TooltipWindow::createAccessibilityHandler()
{
return createIgnoredAccessibilityHandler (*this);
}
void TooltipWindow::timerCallback()
{
auto& desktop = Desktop::getInstance();
auto mouseSource = desktop.getMainMouseSource();
auto now = Time::getApproximateMillisecondCounter();
auto* newComp = mouseSource.isTouch() ? nullptr : mouseSource.getComponentUnderMouse();
if (newComp == nullptr || getParentComponent() == nullptr || newComp->getPeer() == getPeer())
{
auto newTip = newComp != nullptr ? getTipFor (*newComp) : String();
bool tipChanged = (newTip != lastTipUnderMouse || newComp != lastComponentUnderMouse);
lastComponentUnderMouse = newComp;
lastTipUnderMouse = newTip;
auto clickCount = desktop.getMouseButtonClickCounter();
auto wheelCount = desktop.getMouseWheelMoveCounter();
bool mouseWasClicked = (clickCount > mouseClicks || wheelCount > mouseWheelMoves);
mouseClicks = clickCount;
mouseWheelMoves = wheelCount;
auto mousePos = mouseSource.getScreenPosition();
bool mouseMovedQuickly = mousePos.getDistanceFrom (lastMousePos) > 12;
lastMousePos = mousePos;
if (tipChanged || mouseWasClicked || mouseMovedQuickly)
lastCompChangeTime = now;
auto showTip = [this, &mouseSource, &mousePos, &newTip]
{
bool mouseHasMovedSinceClick = mouseSource.getLastMouseDownPosition() != lastMousePos;
if (mouseHasMovedSinceClick)
displayTip (mousePos.roundToInt(), newTip);
};
if (isVisible() || now < lastHideTime + 500)
{
// if a tip is currently visible (or has just disappeared), update to a new one
// immediately if needed..
if (newComp == nullptr || mouseWasClicked || newTip.isEmpty())
{
if (isVisible())
{
lastHideTime = now;
hideTip();
}
}
else if (tipChanged)
{
showTip();
}
}
else
{
// if there isn't currently a tip, but one is needed, only let it appear after a timeout
if (newTip.isNotEmpty()
&& newTip != tipShowing
&& now > lastCompChangeTime + (uint32) millisecondsBeforeTipAppears)
{
showTip();
}
}
}
}
} // 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
{
TooltipWindow::TooltipWindow (Component* parentComp, int delayMs)
: Component ("tooltip"),
millisecondsBeforeTipAppears (delayMs)
{
setAlwaysOnTop (true);
setOpaque (true);
setAccessible (false);
if (parentComp != nullptr)
parentComp->addChildComponent (this);
auto& desktop = Desktop::getInstance();
if (desktop.getMainMouseSource().canHover())
{
desktop.addGlobalMouseListener (this);
startTimer (123);
}
}
TooltipWindow::~TooltipWindow()
{
hideTip();
Desktop::getInstance().removeGlobalMouseListener (this);
}
void TooltipWindow::setMillisecondsBeforeTipAppears (const int newTimeMs) noexcept
{
millisecondsBeforeTipAppears = newTimeMs;
}
void TooltipWindow::paint (Graphics& g)
{
getLookAndFeel().drawTooltip (g, tipShowing, getWidth(), getHeight());
}
void TooltipWindow::mouseEnter (const MouseEvent& e)
{
if (e.eventComponent == this)
hideTip();
}
void TooltipWindow::mouseDown (const MouseEvent&)
{
if (isVisible())
dismissalMouseEventOccurred = true;
}
void TooltipWindow::mouseWheelMove (const MouseEvent&, const MouseWheelDetails&)
{
if (isVisible())
dismissalMouseEventOccurred = true;
}
void TooltipWindow::updatePosition (const String& tip, Point<int> pos, Rectangle<int> parentArea)
{
setBounds (getLookAndFeel().getTooltipBounds (tip, pos, parentArea));
setVisible (true);
}
#if JUCE_DEBUG
static Array<TooltipWindow*> activeTooltipWindows;
#endif
void TooltipWindow::displayTip (Point<int> screenPos, const String& tip)
{
jassert (tip.isNotEmpty());
displayTipInternal (screenPos, tip, ShownManually::yes);
}
void TooltipWindow::displayTipInternal (Point<int> screenPos, const String& tip, ShownManually shownManually)
{
if (! reentrant)
{
ScopedValueSetter<bool> setter (reentrant, true, false);
if (tipShowing != tip)
{
tipShowing = tip;
repaint();
}
if (auto* parent = getParentComponent())
{
updatePosition (tip, parent->getLocalPoint (nullptr, screenPos),
parent->getLocalBounds());
}
else
{
const auto physicalPos = ScalingHelpers::scaledScreenPosToUnscaled (screenPos);
const auto scaledPos = ScalingHelpers::unscaledScreenPosToScaled (*this, physicalPos);
updatePosition (tip, scaledPos, Desktop::getInstance().getDisplays().getDisplayForPoint (screenPos)->userArea);
addToDesktop (ComponentPeer::windowHasDropShadow
| ComponentPeer::windowIsTemporary
| ComponentPeer::windowIgnoresKeyPresses
| ComponentPeer::windowIgnoresMouseClicks);
}
#if JUCE_DEBUG
activeTooltipWindows.addIfNotAlreadyThere (this);
auto* parent = getParentComponent();
for (auto* w : activeTooltipWindows)
{
if (w != nullptr && w != this && w->tipShowing == tipShowing && w->getParentComponent() == parent)
{
// Looks like you have more than one TooltipWindow showing the same tip..
// Be careful not to create more than one instance of this class with the
// same parent component!
jassertfalse;
}
}
#endif
toFront (false);
manuallyShownTip = shownManually == ShownManually::yes ? tip : String();
dismissalMouseEventOccurred = false;
}
}
String TooltipWindow::getTipFor (Component& c)
{
if (isForegroundOrEmbeddedProcess (&c)
&& ! ModifierKeys::currentModifiers.isAnyMouseButtonDown())
{
if (auto* ttc = dynamic_cast<TooltipClient*> (&c))
if (! c.isCurrentlyBlockedByAnotherModalComponent())
return ttc->getTooltip();
}
return {};
}
void TooltipWindow::hideTip()
{
if (isVisible() && ! reentrant)
{
tipShowing = {};
manuallyShownTip = {};
dismissalMouseEventOccurred = false;
removeFromDesktop();
setVisible (false);
lastHideTime = Time::getApproximateMillisecondCounter();
#if JUCE_DEBUG
activeTooltipWindows.removeAllInstancesOf (this);
#endif
}
}
float TooltipWindow::getDesktopScaleFactor() const
{
if (lastComponentUnderMouse != nullptr)
return Component::getApproximateScaleFactorForComponent (lastComponentUnderMouse);
return Component::getDesktopScaleFactor();
}
std::unique_ptr<AccessibilityHandler> TooltipWindow::createAccessibilityHandler()
{
return createIgnoredAccessibilityHandler (*this);
}
void TooltipWindow::timerCallback()
{
const auto mouseSource = Desktop::getInstance().getMainMouseSource();
auto* newComp = mouseSource.isTouch() ? nullptr : mouseSource.getComponentUnderMouse();
if (manuallyShownTip.isNotEmpty())
{
if (dismissalMouseEventOccurred || newComp == nullptr)
hideTip();
return;
}
if (newComp == nullptr || getParentComponent() == nullptr || newComp->getPeer() == getPeer())
{
const auto newTip = newComp != nullptr ? getTipFor (*newComp) : String();
const auto mousePos = mouseSource.getScreenPosition();
const auto mouseMovedQuickly = (mousePos.getDistanceFrom (lastMousePos) > 12);
lastMousePos = mousePos;
const auto tipChanged = (newTip != lastTipUnderMouse || newComp != lastComponentUnderMouse);
const auto now = Time::getApproximateMillisecondCounter();
lastComponentUnderMouse = newComp;
lastTipUnderMouse = newTip;
if (tipChanged || dismissalMouseEventOccurred || mouseMovedQuickly)
lastCompChangeTime = now;
const auto showTip = [this, &mouseSource, &mousePos, &newTip]
{
if (mouseSource.getLastMouseDownPosition() != lastMousePos)
displayTipInternal (mousePos.roundToInt(), newTip, ShownManually::no);
};
if (isVisible() || now < lastHideTime + 500)
{
// if a tip is currently visible (or has just disappeared), update to a new one
// immediately if needed..
if (newComp == nullptr || dismissalMouseEventOccurred || newTip.isEmpty())
hideTip();
else if (tipChanged)
showTip();
}
else
{
// if there isn't currently a tip, but one is needed, only let it appear after a timeout
if (newTip.isNotEmpty()
&& newTip != tipShowing
&& now > lastCompChangeTime + (uint32) millisecondsBeforeTipAppears)
{
showTip();
}
}
}
}
} // namespace juce

View File

@ -1,149 +1,159 @@
/*
==============================================================================
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
{
//==============================================================================
/**
A window that displays a pop-up tooltip when the mouse hovers over another component.
To enable tooltips in your app, just create a single instance of a TooltipWindow
object. Note that if you instantiate more than one instance of this class with the
same parentComponent (even if both TooltipWindow's parentComponent is nil), you'll
end up with multiple tooltips being shown! To avoid this use a SharedResourcePointer
to instantiate the TooltipWindow only once.
For audio plug-ins (which should not be opening native windows) it is better
to add a TooltipWindow as a member variable to the editor and ensure that the
editor is the parentComponent of your TooltipWindow. This will ensure that your
TooltipWindow is scaled according to your editor and the DAWs scaling setting.
The TooltipWindow object will then stay invisible, waiting until the mouse
hovers for the specified length of time - it will then see if it's currently
over a component which implements the TooltipClient interface, and if so,
it will make itself visible to show the tooltip in the appropriate place.
@see TooltipClient, SettableTooltipClient, SharedResourcePointer
@tags{GUI}
*/
class JUCE_API TooltipWindow : public Component,
private Timer
{
public:
//==============================================================================
/** Creates a tooltip window.
Make sure your app only creates one instance of this class, otherwise you'll
get multiple overlaid tooltips appearing. The window will initially be invisible
and will make itself visible when it needs to display a tip.
To change the style of tooltips, see the LookAndFeel class for its tooltip
methods.
@param parentComponent if set to nullptr, the TooltipWindow will appear on the desktop,
otherwise the tooltip will be added to the given parent
component.
@param millisecondsBeforeTipAppears the time for which the mouse has to stay still
before a tooltip will be shown
@see TooltipClient, LookAndFeel::drawTooltip, LookAndFeel::getTooltipBounds
*/
explicit TooltipWindow (Component* parentComponent = nullptr,
int millisecondsBeforeTipAppears = 700);
/** Destructor. */
~TooltipWindow() override;
//==============================================================================
/** Changes the time before the tip appears.
This lets you change the value that was set in the constructor.
*/
void setMillisecondsBeforeTipAppears (int newTimeMs = 700) noexcept;
/** Can be called to manually force a tip to be shown at a particular location. */
void displayTip (Point<int> screenPosition, const String& text);
/** Can be called to manually hide the tip if it's showing. */
void hideTip();
/** Asks a component for its tooltip.
This can be overridden if you need custom lookup behaviour or to modify the strings.
*/
virtual String getTipFor (Component&);
//==============================================================================
/** A set of colour IDs to use to change the colour of various aspects of the tooltip.
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
methods.
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
*/
enum ColourIds
{
backgroundColourId = 0x1001b00, /**< The colour to fill the background with. */
textColourId = 0x1001c00, /**< The colour to use for the text. */
outlineColourId = 0x1001c10 /**< The colour to use to draw an outline around the tooltip. */
};
//==============================================================================
/** This abstract base class is implemented by LookAndFeel classes to provide
window drawing functionality.
*/
struct JUCE_API LookAndFeelMethods
{
virtual ~LookAndFeelMethods() = default;
/** returns the bounds for a tooltip at the given screen coordinate, constrained within the given desktop area. */
virtual Rectangle<int> getTooltipBounds (const String& tipText, Point<int> screenPos, Rectangle<int> parentArea) = 0;
virtual void drawTooltip (Graphics&, const String& text, int width, int height) = 0;
};
//==============================================================================
/** @internal */
float getDesktopScaleFactor() const override;
private:
//==============================================================================
Point<float> lastMousePos;
Component* lastComponentUnderMouse = nullptr;
String tipShowing, lastTipUnderMouse;
int millisecondsBeforeTipAppears;
int mouseClicks = 0, mouseWheelMoves = 0;
unsigned int lastCompChangeTime = 0, lastHideTime = 0;
bool reentrant = false;
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
void paint (Graphics&) override;
void mouseEnter (const MouseEvent&) override;
void timerCallback() override;
void updatePosition (const String&, Point<int>, Rectangle<int>);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TooltipWindow)
};
} // 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
{
//==============================================================================
/**
A window that displays a pop-up tooltip when the mouse hovers over another component.
To enable tooltips in your app, just create a single instance of a TooltipWindow
object. Note that if you instantiate more than one instance of this class with the
same parentComponent (even if both TooltipWindow's parentComponent is nil), you'll
end up with multiple tooltips being shown! To avoid this use a SharedResourcePointer
to instantiate the TooltipWindow only once.
For audio plug-ins (which should not be opening native windows) it is better
to add a TooltipWindow as a member variable to the editor and ensure that the
editor is the parentComponent of your TooltipWindow. This will ensure that your
TooltipWindow is scaled according to your editor and the DAWs scaling setting.
The TooltipWindow object will then stay invisible, waiting until the mouse
hovers for the specified length of time - it will then see if it's currently
over a component which implements the TooltipClient interface, and if so,
it will make itself visible to show the tooltip in the appropriate place.
@see TooltipClient, SettableTooltipClient, SharedResourcePointer
@tags{GUI}
*/
class JUCE_API TooltipWindow : public Component,
private Timer
{
public:
//==============================================================================
/** Creates a tooltip window.
Make sure your app only creates one instance of this class, otherwise you'll
get multiple overlaid tooltips appearing. The window will initially be invisible
and will make itself visible when it needs to display a tip.
To change the style of tooltips, see the LookAndFeel class for its tooltip
methods.
@param parentComponent if set to nullptr, the TooltipWindow will appear on the desktop,
otherwise the tooltip will be added to the given parent
component.
@param millisecondsBeforeTipAppears the time for which the mouse has to stay still
before a tooltip will be shown
@see TooltipClient, LookAndFeel::drawTooltip, LookAndFeel::getTooltipBounds
*/
explicit TooltipWindow (Component* parentComponent = nullptr,
int millisecondsBeforeTipAppears = 700);
/** Destructor. */
~TooltipWindow() override;
//==============================================================================
/** Changes the time before the tip appears.
This lets you change the value that was set in the constructor.
*/
void setMillisecondsBeforeTipAppears (int newTimeMs = 700) noexcept;
/** Can be called to manually force a tip to be shown at a particular location.
The tip will be shown until hideTip() is called, or a dismissal mouse event
occurs.
@see hideTip
*/
void displayTip (Point<int> screenPosition, const String& text);
/** Can be called to manually hide the tip if it's showing. */
void hideTip();
/** Asks a component for its tooltip.
This can be overridden if you need custom lookup behaviour or to modify the strings.
*/
virtual String getTipFor (Component&);
//==============================================================================
/** A set of colour IDs to use to change the colour of various aspects of the tooltip.
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
methods.
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
*/
enum ColourIds
{
backgroundColourId = 0x1001b00, /**< The colour to fill the background with. */
textColourId = 0x1001c00, /**< The colour to use for the text. */
outlineColourId = 0x1001c10 /**< The colour to use to draw an outline around the tooltip. */
};
//==============================================================================
/** This abstract base class is implemented by LookAndFeel classes to provide
window drawing functionality.
*/
struct JUCE_API LookAndFeelMethods
{
virtual ~LookAndFeelMethods() = default;
/** returns the bounds for a tooltip at the given screen coordinate, constrained within the given desktop area. */
virtual Rectangle<int> getTooltipBounds (const String& tipText, Point<int> screenPos, Rectangle<int> parentArea) = 0;
virtual void drawTooltip (Graphics&, const String& text, int width, int height) = 0;
};
//==============================================================================
/** @internal */
float getDesktopScaleFactor() const override;
private:
//==============================================================================
Point<float> lastMousePos;
SafePointer<Component> lastComponentUnderMouse;
String tipShowing, lastTipUnderMouse, manuallyShownTip;
int millisecondsBeforeTipAppears;
unsigned int lastCompChangeTime = 0, lastHideTime = 0;
bool reentrant = false, dismissalMouseEventOccurred = false;
enum ShownManually { yes, no };
void displayTipInternal (Point<int>, const String&, ShownManually);
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
void paint (Graphics&) override;
void mouseEnter (const MouseEvent&) override;
void mouseDown (const MouseEvent&) override;
void mouseWheelMove (const MouseEvent&, const MouseWheelDetails&) override;
void timerCallback() override;
void updatePosition (const String&, Point<int>, Rectangle<int>);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TooltipWindow)
};
} // namespace juce

View File

@ -1,358 +1,358 @@
/*
==============================================================================
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
{
/** Keeps track of the active top level window. */
class TopLevelWindowManager : private Timer,
private DeletedAtShutdown
{
public:
TopLevelWindowManager() {}
~TopLevelWindowManager() override { clearSingletonInstance(); }
JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (TopLevelWindowManager)
void checkFocusAsync()
{
startTimer (10);
}
void checkFocus()
{
startTimer (jmin (1731, getTimerInterval() * 2));
auto* newActive = findCurrentlyActiveWindow();
if (newActive != currentActive)
{
currentActive = newActive;
for (int i = windows.size(); --i >= 0;)
if (auto* tlw = windows[i])
tlw->setWindowActive (isWindowActive (tlw));
Desktop::getInstance().triggerFocusCallback();
}
}
bool addWindow (TopLevelWindow* const w)
{
windows.add (w);
checkFocusAsync();
return isWindowActive (w);
}
void removeWindow (TopLevelWindow* const w)
{
checkFocusAsync();
if (currentActive == w)
currentActive = nullptr;
windows.removeFirstMatchingValue (w);
if (windows.isEmpty())
deleteInstance();
}
Array<TopLevelWindow*> windows;
private:
TopLevelWindow* currentActive = nullptr;
void timerCallback() override
{
checkFocus();
}
bool isWindowActive (TopLevelWindow* const tlw) const
{
return (tlw == currentActive
|| tlw->isParentOf (currentActive)
|| tlw->hasKeyboardFocus (true))
&& tlw->isShowing();
}
TopLevelWindow* findCurrentlyActiveWindow() const
{
if (Process::isForegroundProcess())
{
auto* focusedComp = Component::getCurrentlyFocusedComponent();
auto* w = dynamic_cast<TopLevelWindow*> (focusedComp);
if (w == nullptr && focusedComp != nullptr)
w = focusedComp->findParentComponentOfClass<TopLevelWindow>();
if (w == nullptr)
w = currentActive;
if (w != nullptr && w->isShowing())
return w;
}
return nullptr;
}
JUCE_DECLARE_NON_COPYABLE (TopLevelWindowManager)
};
JUCE_IMPLEMENT_SINGLETON (TopLevelWindowManager)
void juce_checkCurrentlyFocusedTopLevelWindow();
void juce_checkCurrentlyFocusedTopLevelWindow()
{
if (auto* wm = TopLevelWindowManager::getInstanceWithoutCreating())
wm->checkFocusAsync();
}
//==============================================================================
TopLevelWindow::TopLevelWindow (const String& name, const bool shouldAddToDesktop)
: Component (name)
{
setTitle (name);
setOpaque (true);
if (shouldAddToDesktop)
Component::addToDesktop (TopLevelWindow::getDesktopWindowStyleFlags());
else
setDropShadowEnabled (true);
setWantsKeyboardFocus (true);
setBroughtToFrontOnMouseClick (true);
isCurrentlyActive = TopLevelWindowManager::getInstance()->addWindow (this);
}
TopLevelWindow::~TopLevelWindow()
{
shadower.reset();
TopLevelWindowManager::getInstance()->removeWindow (this);
}
//==============================================================================
void TopLevelWindow::focusOfChildComponentChanged (FocusChangeType)
{
auto* wm = TopLevelWindowManager::getInstance();
if (hasKeyboardFocus (true))
wm->checkFocus();
else
wm->checkFocusAsync();
}
void TopLevelWindow::setWindowActive (const bool isNowActive)
{
if (isCurrentlyActive != isNowActive)
{
isCurrentlyActive = isNowActive;
activeWindowStatusChanged();
}
}
void TopLevelWindow::activeWindowStatusChanged()
{
}
bool TopLevelWindow::isUsingNativeTitleBar() const noexcept
{
return useNativeTitleBar && (isOnDesktop() || ! isShowing());
}
void TopLevelWindow::visibilityChanged()
{
if (isShowing())
if (auto* p = getPeer())
if ((p->getStyleFlags() & (ComponentPeer::windowIsTemporary
| ComponentPeer::windowIgnoresKeyPresses)) == 0)
toFront (true);
}
void TopLevelWindow::parentHierarchyChanged()
{
setDropShadowEnabled (useDropShadow);
}
int TopLevelWindow::getDesktopWindowStyleFlags() const
{
int styleFlags = ComponentPeer::windowAppearsOnTaskbar;
if (useDropShadow) styleFlags |= ComponentPeer::windowHasDropShadow;
if (useNativeTitleBar) styleFlags |= ComponentPeer::windowHasTitleBar;
return styleFlags;
}
void TopLevelWindow::setDropShadowEnabled (const bool useShadow)
{
useDropShadow = useShadow;
if (isOnDesktop())
{
shadower.reset();
Component::addToDesktop (getDesktopWindowStyleFlags());
}
else
{
if (useShadow && isOpaque())
{
if (shadower == nullptr)
{
shadower.reset (getLookAndFeel().createDropShadowerForComponent (this));
if (shadower != nullptr)
shadower->setOwner (this);
}
}
else
{
shadower.reset();
}
}
}
void TopLevelWindow::setUsingNativeTitleBar (const bool shouldUseNativeTitleBar)
{
if (useNativeTitleBar != shouldUseNativeTitleBar)
{
FocusRestorer focusRestorer;
useNativeTitleBar = shouldUseNativeTitleBar;
recreateDesktopWindow();
sendLookAndFeelChange();
}
}
void TopLevelWindow::recreateDesktopWindow()
{
if (isOnDesktop())
{
Component::addToDesktop (getDesktopWindowStyleFlags());
toFront (true);
}
}
void TopLevelWindow::addToDesktop()
{
shadower.reset();
Component::addToDesktop (getDesktopWindowStyleFlags());
setDropShadowEnabled (isDropShadowEnabled()); // force an update to clear away any fake shadows if necessary.
}
void TopLevelWindow::addToDesktop (int windowStyleFlags, void* nativeWindowToAttachTo)
{
/* It's not recommended to change the desktop window flags directly for a TopLevelWindow,
because this class needs to make sure its layout corresponds with settings like whether
it's got a native title bar or not.
If you need custom flags for your window, you can override the getDesktopWindowStyleFlags()
method. If you do this, it's best to call the base class's getDesktopWindowStyleFlags()
method, then add or remove whatever flags are necessary from this value before returning it.
*/
jassert ((windowStyleFlags & ~ComponentPeer::windowIsSemiTransparent)
== (getDesktopWindowStyleFlags() & ~ComponentPeer::windowIsSemiTransparent));
Component::addToDesktop (windowStyleFlags, nativeWindowToAttachTo);
if (windowStyleFlags != getDesktopWindowStyleFlags())
sendLookAndFeelChange();
}
std::unique_ptr<AccessibilityHandler> TopLevelWindow::createAccessibilityHandler()
{
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::window);
}
//==============================================================================
void TopLevelWindow::centreAroundComponent (Component* c, const int width, const int height)
{
if (c == nullptr)
c = TopLevelWindow::getActiveTopLevelWindow();
if (c == nullptr || c->getBounds().isEmpty())
{
centreWithSize (width, height);
}
else
{
const auto scale = getDesktopScaleFactor() / Desktop::getInstance().getGlobalScaleFactor();
auto targetCentre = c->localPointToGlobal (c->getLocalBounds().getCentre()) / scale;
auto parentArea = c->getParentMonitorArea();
if (auto* parent = getParentComponent())
{
targetCentre = parent->getLocalPoint (nullptr, targetCentre);
parentArea = parent->getLocalBounds();
}
setBounds (Rectangle<int> (targetCentre.x - width / 2,
targetCentre.y - height / 2,
width, height)
.constrainedWithin (parentArea.reduced (12, 12)));
}
}
//==============================================================================
int TopLevelWindow::getNumTopLevelWindows() noexcept
{
return TopLevelWindowManager::getInstance()->windows.size();
}
TopLevelWindow* TopLevelWindow::getTopLevelWindow (const int index) noexcept
{
return TopLevelWindowManager::getInstance()->windows [index];
}
TopLevelWindow* TopLevelWindow::getActiveTopLevelWindow() noexcept
{
TopLevelWindow* best = nullptr;
int bestNumTWLParents = -1;
for (int i = TopLevelWindow::getNumTopLevelWindows(); --i >= 0;)
{
auto* tlw = TopLevelWindow::getTopLevelWindow (i);
if (tlw->isActiveWindow())
{
int numTWLParents = 0;
for (auto* c = tlw->getParentComponent(); c != nullptr; c = c->getParentComponent())
if (dynamic_cast<const TopLevelWindow*> (c) != nullptr)
++numTWLParents;
if (bestNumTWLParents < numTWLParents)
{
best = tlw;
bestNumTWLParents = numTWLParents;
}
}
}
return best;
}
} // 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
{
/** Keeps track of the active top level window. */
class TopLevelWindowManager : private Timer,
private DeletedAtShutdown
{
public:
TopLevelWindowManager() {}
~TopLevelWindowManager() override { clearSingletonInstance(); }
JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (TopLevelWindowManager)
void checkFocusAsync()
{
startTimer (10);
}
void checkFocus()
{
startTimer (jmin (1731, getTimerInterval() * 2));
auto* newActive = findCurrentlyActiveWindow();
if (newActive != currentActive)
{
currentActive = newActive;
for (int i = windows.size(); --i >= 0;)
if (auto* tlw = windows[i])
tlw->setWindowActive (isWindowActive (tlw));
Desktop::getInstance().triggerFocusCallback();
}
}
bool addWindow (TopLevelWindow* const w)
{
windows.add (w);
checkFocusAsync();
return isWindowActive (w);
}
void removeWindow (TopLevelWindow* const w)
{
checkFocusAsync();
if (currentActive == w)
currentActive = nullptr;
windows.removeFirstMatchingValue (w);
if (windows.isEmpty())
deleteInstance();
}
Array<TopLevelWindow*> windows;
private:
TopLevelWindow* currentActive = nullptr;
void timerCallback() override
{
checkFocus();
}
bool isWindowActive (TopLevelWindow* const tlw) const
{
return (tlw == currentActive
|| tlw->isParentOf (currentActive)
|| tlw->hasKeyboardFocus (true))
&& tlw->isShowing();
}
TopLevelWindow* findCurrentlyActiveWindow() const
{
if (Process::isForegroundProcess())
{
auto* focusedComp = Component::getCurrentlyFocusedComponent();
auto* w = dynamic_cast<TopLevelWindow*> (focusedComp);
if (w == nullptr && focusedComp != nullptr)
w = focusedComp->findParentComponentOfClass<TopLevelWindow>();
if (w == nullptr)
w = currentActive;
if (w != nullptr && w->isShowing())
return w;
}
return nullptr;
}
JUCE_DECLARE_NON_COPYABLE (TopLevelWindowManager)
};
JUCE_IMPLEMENT_SINGLETON (TopLevelWindowManager)
void juce_checkCurrentlyFocusedTopLevelWindow();
void juce_checkCurrentlyFocusedTopLevelWindow()
{
if (auto* wm = TopLevelWindowManager::getInstanceWithoutCreating())
wm->checkFocusAsync();
}
//==============================================================================
TopLevelWindow::TopLevelWindow (const String& name, const bool shouldAddToDesktop)
: Component (name)
{
setTitle (name);
setOpaque (true);
if (shouldAddToDesktop)
Component::addToDesktop (TopLevelWindow::getDesktopWindowStyleFlags());
else
setDropShadowEnabled (true);
setWantsKeyboardFocus (true);
setBroughtToFrontOnMouseClick (true);
isCurrentlyActive = TopLevelWindowManager::getInstance()->addWindow (this);
}
TopLevelWindow::~TopLevelWindow()
{
shadower = nullptr;
TopLevelWindowManager::getInstance()->removeWindow (this);
}
//==============================================================================
void TopLevelWindow::focusOfChildComponentChanged (FocusChangeType)
{
auto* wm = TopLevelWindowManager::getInstance();
if (hasKeyboardFocus (true))
wm->checkFocus();
else
wm->checkFocusAsync();
}
void TopLevelWindow::setWindowActive (const bool isNowActive)
{
if (isCurrentlyActive != isNowActive)
{
isCurrentlyActive = isNowActive;
activeWindowStatusChanged();
}
}
void TopLevelWindow::activeWindowStatusChanged()
{
}
bool TopLevelWindow::isUsingNativeTitleBar() const noexcept
{
return useNativeTitleBar && (isOnDesktop() || ! isShowing());
}
void TopLevelWindow::visibilityChanged()
{
if (isShowing())
if (auto* p = getPeer())
if ((p->getStyleFlags() & (ComponentPeer::windowIsTemporary
| ComponentPeer::windowIgnoresKeyPresses)) == 0)
toFront (true);
}
void TopLevelWindow::parentHierarchyChanged()
{
setDropShadowEnabled (useDropShadow);
}
int TopLevelWindow::getDesktopWindowStyleFlags() const
{
int styleFlags = ComponentPeer::windowAppearsOnTaskbar;
if (useDropShadow) styleFlags |= ComponentPeer::windowHasDropShadow;
if (useNativeTitleBar) styleFlags |= ComponentPeer::windowHasTitleBar;
return styleFlags;
}
void TopLevelWindow::setDropShadowEnabled (const bool useShadow)
{
useDropShadow = useShadow;
if (isOnDesktop())
{
shadower = nullptr;
Component::addToDesktop (getDesktopWindowStyleFlags());
}
else
{
if (useShadow && isOpaque())
{
if (shadower == nullptr)
{
shadower = getLookAndFeel().createDropShadowerForComponent (*this);
if (shadower != nullptr)
shadower->setOwner (this);
}
}
else
{
shadower = nullptr;
}
}
}
void TopLevelWindow::setUsingNativeTitleBar (const bool shouldUseNativeTitleBar)
{
if (useNativeTitleBar != shouldUseNativeTitleBar)
{
FocusRestorer focusRestorer;
useNativeTitleBar = shouldUseNativeTitleBar;
recreateDesktopWindow();
sendLookAndFeelChange();
}
}
void TopLevelWindow::recreateDesktopWindow()
{
if (isOnDesktop())
{
Component::addToDesktop (getDesktopWindowStyleFlags());
toFront (true);
}
}
void TopLevelWindow::addToDesktop()
{
shadower = nullptr;
Component::addToDesktop (getDesktopWindowStyleFlags());
setDropShadowEnabled (isDropShadowEnabled()); // force an update to clear away any fake shadows if necessary.
}
void TopLevelWindow::addToDesktop (int windowStyleFlags, void* nativeWindowToAttachTo)
{
/* It's not recommended to change the desktop window flags directly for a TopLevelWindow,
because this class needs to make sure its layout corresponds with settings like whether
it's got a native title bar or not.
If you need custom flags for your window, you can override the getDesktopWindowStyleFlags()
method. If you do this, it's best to call the base class's getDesktopWindowStyleFlags()
method, then add or remove whatever flags are necessary from this value before returning it.
*/
jassert ((windowStyleFlags & ~ComponentPeer::windowIsSemiTransparent)
== (getDesktopWindowStyleFlags() & ~ComponentPeer::windowIsSemiTransparent));
Component::addToDesktop (windowStyleFlags, nativeWindowToAttachTo);
if (windowStyleFlags != getDesktopWindowStyleFlags())
sendLookAndFeelChange();
}
std::unique_ptr<AccessibilityHandler> TopLevelWindow::createAccessibilityHandler()
{
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::window);
}
//==============================================================================
void TopLevelWindow::centreAroundComponent (Component* c, const int width, const int height)
{
if (c == nullptr)
c = TopLevelWindow::getActiveTopLevelWindow();
if (c == nullptr || c->getBounds().isEmpty())
{
centreWithSize (width, height);
}
else
{
const auto scale = getDesktopScaleFactor() / Desktop::getInstance().getGlobalScaleFactor();
auto targetCentre = c->localPointToGlobal (c->getLocalBounds().getCentre()) / scale;
auto parentArea = getLocalArea (nullptr, c->getParentMonitorArea());
if (auto* parent = getParentComponent())
{
targetCentre = parent->getLocalPoint (nullptr, targetCentre);
parentArea = parent->getLocalBounds();
}
setBounds (Rectangle<int> (targetCentre.x - width / 2,
targetCentre.y - height / 2,
width, height)
.constrainedWithin (parentArea.reduced (12, 12)));
}
}
//==============================================================================
int TopLevelWindow::getNumTopLevelWindows() noexcept
{
return TopLevelWindowManager::getInstance()->windows.size();
}
TopLevelWindow* TopLevelWindow::getTopLevelWindow (const int index) noexcept
{
return TopLevelWindowManager::getInstance()->windows [index];
}
TopLevelWindow* TopLevelWindow::getActiveTopLevelWindow() noexcept
{
TopLevelWindow* best = nullptr;
int bestNumTWLParents = -1;
for (int i = TopLevelWindow::getNumTopLevelWindows(); --i >= 0;)
{
auto* tlw = TopLevelWindow::getTopLevelWindow (i);
if (tlw->isActiveWindow())
{
int numTWLParents = 0;
for (auto* c = tlw->getParentComponent(); c != nullptr; c = c->getParentComponent())
if (dynamic_cast<const TopLevelWindow*> (c) != nullptr)
++numTWLParents;
if (bestNumTWLParents < numTWLParents)
{
best = tlw;
bestNumTWLParents = numTWLParents;
}
}
}
return best;
}
} // namespace juce

View File

@ -1,166 +1,166 @@
/*
==============================================================================
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
{
//==============================================================================
/**
A base class for top-level windows.
This class is used for components that are considered a major part of your
application - e.g. ResizableWindow, DocumentWindow, DialogWindow, AlertWindow,
etc. Things like menus that pop up briefly aren't derived from it.
A TopLevelWindow is probably on the desktop, but this isn't mandatory - it
could itself be the child of another component.
The class manages a list of all instances of top-level windows that are in use,
and each one is also given the concept of being "active". The active window is
one that is actively being used by the user. This isn't quite the same as the
component with the keyboard focus, because there may be a popup menu or other
temporary window which gets keyboard focus while the active top level window is
unchanged.
A top-level window also has an optional drop-shadow.
@see ResizableWindow, DocumentWindow, DialogWindow
@tags{GUI}
*/
class JUCE_API TopLevelWindow : public Component
{
public:
//==============================================================================
/** Creates a TopLevelWindow.
@param name the name to give the component
@param addToDesktop if true, the window will be automatically added to the
desktop; if false, you can use it as a child component
*/
TopLevelWindow (const String& name, bool addToDesktop);
/** Destructor. */
~TopLevelWindow() override;
//==============================================================================
/** True if this is currently the TopLevelWindow that is actively being used.
This isn't quite the same as having keyboard focus, because the focus may be
on a child component or a temporary pop-up menu, etc, while this window is
still considered to be active.
@see activeWindowStatusChanged
*/
bool isActiveWindow() const noexcept { return isCurrentlyActive; }
//==============================================================================
/** This will set the bounds of the window so that it's centred in front of another
window.
If your app has a few windows open and want to pop up a dialog box for one of
them, you can use this to show it in front of the relevant parent window, which
is a bit neater than just having it appear in the middle of the screen.
If componentToCentreAround is nullptr, then the currently active TopLevelWindow will
be used instead. If no window is focused, it'll just default to the middle of the
screen.
*/
void centreAroundComponent (Component* componentToCentreAround,
int width, int height);
//==============================================================================
/** Turns the drop-shadow on and off. */
void setDropShadowEnabled (bool useShadow);
/** True if drop-shadowing is enabled. */
bool isDropShadowEnabled() const noexcept { return useDropShadow; }
/** Sets whether an OS-native title bar will be used, or a JUCE one.
@see isUsingNativeTitleBar
*/
void setUsingNativeTitleBar (bool useNativeTitleBar);
/** Returns true if the window is currently using an OS-native title bar.
@see setUsingNativeTitleBar
*/
bool isUsingNativeTitleBar() const noexcept;
//==============================================================================
/** Returns the number of TopLevelWindow objects currently in use.
@see getTopLevelWindow
*/
static int getNumTopLevelWindows() noexcept;
/** Returns one of the TopLevelWindow objects currently in use.
The index is 0 to (getNumTopLevelWindows() - 1).
*/
static TopLevelWindow* getTopLevelWindow (int index) noexcept;
/** Returns the currently-active top level window.
There might not be one, of course, so this can return nullptr.
*/
static TopLevelWindow* getActiveTopLevelWindow() noexcept;
/** Adds the window to the desktop using the default flags. */
void addToDesktop();
//==============================================================================
/** @internal */
void addToDesktop (int windowStyleFlags, void* nativeWindowToAttachTo = nullptr) override;
protected:
//==============================================================================
/** This callback happens when this window becomes active or inactive.
@see isActiveWindow
*/
virtual void activeWindowStatusChanged();
//==============================================================================
/** @internal */
void focusOfChildComponentChanged (FocusChangeType) override;
/** @internal */
void parentHierarchyChanged() override;
/** @internal */
virtual int getDesktopWindowStyleFlags() const;
/** @internal */
void recreateDesktopWindow();
/** @internal */
void visibilityChanged() override;
private:
friend class TopLevelWindowManager;
friend class ResizableWindow;
bool useDropShadow = true, useNativeTitleBar = false, isCurrentlyActive = false;
std::unique_ptr<DropShadower> shadower;
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
void setWindowActive (bool);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TopLevelWindow)
};
} // 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
{
//==============================================================================
/**
A base class for top-level windows.
This class is used for components that are considered a major part of your
application - e.g. ResizableWindow, DocumentWindow, DialogWindow, AlertWindow,
etc. Things like menus that pop up briefly aren't derived from it.
A TopLevelWindow is probably on the desktop, but this isn't mandatory - it
could itself be the child of another component.
The class manages a list of all instances of top-level windows that are in use,
and each one is also given the concept of being "active". The active window is
one that is actively being used by the user. This isn't quite the same as the
component with the keyboard focus, because there may be a popup menu or other
temporary window which gets keyboard focus while the active top level window is
unchanged.
A top-level window also has an optional drop-shadow.
@see ResizableWindow, DocumentWindow, DialogWindow
@tags{GUI}
*/
class JUCE_API TopLevelWindow : public Component
{
public:
//==============================================================================
/** Creates a TopLevelWindow.
@param name the name to give the component
@param addToDesktop if true, the window will be automatically added to the
desktop; if false, you can use it as a child component
*/
TopLevelWindow (const String& name, bool addToDesktop);
/** Destructor. */
~TopLevelWindow() override;
//==============================================================================
/** True if this is currently the TopLevelWindow that is actively being used.
This isn't quite the same as having keyboard focus, because the focus may be
on a child component or a temporary pop-up menu, etc, while this window is
still considered to be active.
@see activeWindowStatusChanged
*/
bool isActiveWindow() const noexcept { return isCurrentlyActive; }
//==============================================================================
/** This will set the bounds of the window so that it's centred in front of another
window.
If your app has a few windows open and want to pop up a dialog box for one of
them, you can use this to show it in front of the relevant parent window, which
is a bit neater than just having it appear in the middle of the screen.
If componentToCentreAround is nullptr, then the currently active TopLevelWindow will
be used instead. If no window is focused, it'll just default to the middle of the
screen.
*/
void centreAroundComponent (Component* componentToCentreAround,
int width, int height);
//==============================================================================
/** Turns the drop-shadow on and off. */
void setDropShadowEnabled (bool useShadow);
/** True if drop-shadowing is enabled. */
bool isDropShadowEnabled() const noexcept { return useDropShadow; }
/** Sets whether an OS-native title bar will be used, or a JUCE one.
@see isUsingNativeTitleBar
*/
void setUsingNativeTitleBar (bool useNativeTitleBar);
/** Returns true if the window is currently using an OS-native title bar.
@see setUsingNativeTitleBar
*/
bool isUsingNativeTitleBar() const noexcept;
//==============================================================================
/** Returns the number of TopLevelWindow objects currently in use.
@see getTopLevelWindow
*/
static int getNumTopLevelWindows() noexcept;
/** Returns one of the TopLevelWindow objects currently in use.
The index is 0 to (getNumTopLevelWindows() - 1).
*/
static TopLevelWindow* getTopLevelWindow (int index) noexcept;
/** Returns the currently-active top level window.
There might not be one, of course, so this can return nullptr.
*/
static TopLevelWindow* getActiveTopLevelWindow() noexcept;
/** Adds the window to the desktop using the default flags. */
void addToDesktop();
//==============================================================================
/** @internal */
void addToDesktop (int windowStyleFlags, void* nativeWindowToAttachTo = nullptr) override;
protected:
//==============================================================================
/** This callback happens when this window becomes active or inactive.
@see isActiveWindow
*/
virtual void activeWindowStatusChanged();
//==============================================================================
/** @internal */
void focusOfChildComponentChanged (FocusChangeType) override;
/** @internal */
void parentHierarchyChanged() override;
/** @internal */
virtual int getDesktopWindowStyleFlags() const;
/** @internal */
void recreateDesktopWindow();
/** @internal */
void visibilityChanged() override;
private:
friend class TopLevelWindowManager;
friend class ResizableWindow;
bool useDropShadow = true, useNativeTitleBar = false, isCurrentlyActive = false;
std::unique_ptr<DropShadower> shadower;
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
void setWindowActive (bool);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TopLevelWindow)
};
} // namespace juce