build(deps): vendor JUCE 7.0.12

This commit is contained in:
2026-09-08 14:55:21 +02:00
parent 5b99f51bd1
commit d6705ab29f
3591 changed files with 1218267 additions and 0 deletions
@@ -0,0 +1,703 @@
/*
==============================================================================
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
{
static juce_wchar getDefaultPasswordChar() noexcept
{
#if JUCE_LINUX || JUCE_BSD
return 0x2022;
#else
return 0x25cf;
#endif
}
static int showAlertWindowUnmanaged (const MessageBoxOptions& opts, ModalComponentManager::Callback* cb)
{
return detail::ConcreteScopedMessageBoxImpl::showUnmanaged (detail::AlertWindowHelpers::create (opts), cb);
}
//==============================================================================
AlertWindow::AlertWindow (const String& title,
const String& message,
MessageBoxIconType iconType,
Component* comp)
: TopLevelWindow (title, true),
alertIconType (iconType),
associatedComponent (comp),
desktopScale (comp != nullptr ? Component::getApproximateScaleFactorForComponent (comp) : 1.0f)
{
setAlwaysOnTop (WindowUtils::areThereAnyAlwaysOnTopWindows());
accessibleMessageLabel.setColour (Label::textColourId, Colours::transparentBlack);
accessibleMessageLabel.setColour (Label::backgroundColourId, Colours::transparentBlack);
accessibleMessageLabel.setColour (Label::outlineColourId, Colours::transparentBlack);
accessibleMessageLabel.setInterceptsMouseClicks (false, false);
addAndMakeVisible (accessibleMessageLabel);
if (message.isEmpty())
text = " "; // to force an update if the message is empty
setMessage (message);
AlertWindow::lookAndFeelChanged();
constrainer.setMinimumOnscreenAmounts (0x10000, 0x10000, 0x10000, 0x10000);
}
AlertWindow::~AlertWindow()
{
// Ensure that the focus does not jump to another TextEditor while we
// remove children.
for (auto* t : textBoxes)
t->setWantsKeyboardFocus (false);
// Give away focus before removing the editors, so that any TextEditor
// with focus has a chance to dismiss native keyboard if shown.
giveAwayKeyboardFocus();
removeAllChildren();
}
void AlertWindow::userTriedToCloseWindow()
{
if (escapeKeyCancels || buttons.size() > 0)
exitModalState (0);
}
//==============================================================================
void AlertWindow::setMessage (const String& message)
{
auto newMessage = message.substring (0, 2048);
if (text != newMessage)
{
text = newMessage;
auto accessibleText = getName() + ". " + text;
accessibleMessageLabel.setText (accessibleText, NotificationType::dontSendNotification);
setDescription (accessibleText);
updateLayout (true);
repaint();
}
}
//==============================================================================
void AlertWindow::exitAlert (Button* button)
{
if (auto* parent = button->getParentComponent())
parent->exitModalState (button->getCommandID());
}
//==============================================================================
void AlertWindow::addButton (const String& name,
const int returnValue,
const KeyPress& shortcutKey1,
const KeyPress& shortcutKey2)
{
auto* b = new TextButton (name, {});
buttons.add (b);
b->setWantsKeyboardFocus (true);
b->setExplicitFocusOrder (1);
b->setMouseClickGrabsKeyboardFocus (false);
b->setCommandToTrigger (nullptr, returnValue, false);
b->addShortcut (shortcutKey1);
b->addShortcut (shortcutKey2);
b->onClick = [this, b] { exitAlert (b); };
Array<TextButton*> buttonsArray (buttons.begin(), buttons.size());
auto& lf = getLookAndFeel();
auto buttonHeight = lf.getAlertWindowButtonHeight();
auto buttonWidths = lf.getWidthsForTextButtons (*this, buttonsArray);
jassert (buttonWidths.size() == buttons.size());
int i = 0;
for (auto* button : buttons)
button->setSize (buttonWidths[i++], buttonHeight);
addAndMakeVisible (b, 0);
updateLayout (false);
}
int AlertWindow::getNumButtons() const
{
return buttons.size();
}
Button* AlertWindow::getButton (int index) const
{
return buttons[index];
}
Button* AlertWindow::getButton (const String& buttonName) const
{
for (auto* button : buttons)
if (buttonName == button->getName())
return button;
return nullptr;
}
void AlertWindow::triggerButtonClick (const String& buttonName)
{
if (auto* button = getButton (buttonName))
button->triggerClick();
}
void AlertWindow::setEscapeKeyCancels (bool shouldEscapeKeyCancel)
{
escapeKeyCancels = shouldEscapeKeyCancel;
}
//==============================================================================
void AlertWindow::addTextEditor (const String& name,
const String& initialContents,
const String& onScreenLabel,
const bool isPasswordBox)
{
auto* ed = new TextEditor (name, isPasswordBox ? getDefaultPasswordChar() : 0);
ed->setSelectAllWhenFocused (true);
ed->setEscapeAndReturnKeysConsumed (false);
textBoxes.add (ed);
allComps.add (ed);
ed->setColour (TextEditor::outlineColourId, findColour (ComboBox::outlineColourId));
ed->setFont (getLookAndFeel().getAlertWindowMessageFont());
addAndMakeVisible (ed);
ed->setText (initialContents);
ed->setCaretPosition (initialContents.length());
textboxNames.add (onScreenLabel);
updateLayout (false);
}
TextEditor* AlertWindow::getTextEditor (const String& nameOfTextEditor) const
{
for (auto* tb : textBoxes)
if (tb->getName() == nameOfTextEditor)
return tb;
return nullptr;
}
String AlertWindow::getTextEditorContents (const String& nameOfTextEditor) const
{
if (auto* t = getTextEditor (nameOfTextEditor))
return t->getText();
return {};
}
//==============================================================================
void AlertWindow::addComboBox (const String& name,
const StringArray& items,
const String& onScreenLabel)
{
auto* cb = new ComboBox (name);
comboBoxes.add (cb);
allComps.add (cb);
cb->addItemList (items, 1);
addAndMakeVisible (cb);
cb->setSelectedItemIndex (0);
comboBoxNames.add (onScreenLabel);
updateLayout (false);
}
ComboBox* AlertWindow::getComboBoxComponent (const String& nameOfList) const
{
for (auto* cb : comboBoxes)
if (cb->getName() == nameOfList)
return cb;
return nullptr;
}
//==============================================================================
class AlertTextComp final : public TextEditor
{
public:
AlertTextComp (AlertWindow& owner, const String& message, const Font& font)
{
if (owner.isColourSpecified (AlertWindow::textColourId))
setColour (TextEditor::textColourId, owner.findColour (AlertWindow::textColourId));
setColour (TextEditor::backgroundColourId, Colours::transparentBlack);
setColour (TextEditor::outlineColourId, Colours::transparentBlack);
setColour (TextEditor::shadowColourId, Colours::transparentBlack);
setReadOnly (true);
setMultiLine (true, true);
setCaretVisible (false);
setScrollbarsShown (true);
lookAndFeelChanged();
setWantsKeyboardFocus (false);
setFont (font);
setText (message, false);
bestWidth = 2 * (int) std::sqrt (font.getHeight() * (float) font.getStringWidth (message));
}
void updateLayout (const int width)
{
AttributedString s;
s.setJustification (Justification::topLeft);
s.append (getText(), getFont());
TextLayout text;
text.createLayoutWithBalancedLineLengths (s, (float) width - 8.0f);
setSize (width, jmin (width, (int) (text.getHeight() + getFont().getHeight())));
}
int bestWidth;
JUCE_DECLARE_NON_COPYABLE (AlertTextComp)
};
void AlertWindow::addTextBlock (const String& textBlock)
{
auto* c = new AlertTextComp (*this, textBlock, getLookAndFeel().getAlertWindowMessageFont());
textBlocks.add (c);
allComps.add (c);
addAndMakeVisible (c);
updateLayout (false);
}
//==============================================================================
void AlertWindow::addProgressBarComponent (double& progressValue, std::optional<ProgressBar::Style> style)
{
auto* pb = new ProgressBar (progressValue, style);
progressBars.add (pb);
allComps.add (pb);
addAndMakeVisible (pb);
updateLayout (false);
}
//==============================================================================
void AlertWindow::addCustomComponent (Component* const component)
{
customComps.add (component);
allComps.add (component);
addAndMakeVisible (component);
updateLayout (false);
}
int AlertWindow::getNumCustomComponents() const { return customComps.size(); }
Component* AlertWindow::getCustomComponent (int index) const { return customComps [index]; }
Component* AlertWindow::removeCustomComponent (const int index)
{
auto* c = getCustomComponent (index);
if (c != nullptr)
{
customComps.removeFirstMatchingValue (c);
allComps.removeFirstMatchingValue (c);
removeChildComponent (c);
updateLayout (false);
}
return c;
}
//==============================================================================
void AlertWindow::paint (Graphics& g)
{
auto& lf = getLookAndFeel();
lf.drawAlertBox (g, *this, textArea, textLayout);
g.setColour (findColour (textColourId));
g.setFont (lf.getAlertWindowFont());
for (int i = textBoxes.size(); --i >= 0;)
{
auto* te = textBoxes.getUnchecked (i);
g.drawFittedText (textboxNames[i],
te->getX(), te->getY() - 14,
te->getWidth(), 14,
Justification::centredLeft, 1);
}
for (int i = comboBoxNames.size(); --i >= 0;)
{
auto* cb = comboBoxes.getUnchecked (i);
g.drawFittedText (comboBoxNames[i],
cb->getX(), cb->getY() - 14,
cb->getWidth(), 14,
Justification::centredLeft, 1);
}
for (auto* c : customComps)
g.drawFittedText (c->getName(),
c->getX(), c->getY() - 14,
c->getWidth(), 14,
Justification::centredLeft, 1);
}
void AlertWindow::updateLayout (const bool onlyIncreaseSize)
{
const int titleH = 24;
const int iconWidth = 80;
auto& lf = getLookAndFeel();
auto messageFont (lf.getAlertWindowMessageFont());
auto wid = jmax (messageFont.getStringWidth (text),
messageFont.getStringWidth (getName()));
auto sw = (int) std::sqrt (messageFont.getHeight() * (float) wid);
auto w = jmin (300 + sw * 2, (int) ((float) getParentWidth() * 0.7f));
const int edgeGap = 10;
const int labelHeight = 18;
int iconSpace = 0;
AttributedString attributedText;
attributedText.append (getName(), lf.getAlertWindowTitleFont());
if (text.isNotEmpty())
attributedText.append ("\n\n" + text, messageFont);
attributedText.setColour (findColour (textColourId));
if (alertIconType == NoIcon)
{
attributedText.setJustification (Justification::centredTop);
textLayout.createLayoutWithBalancedLineLengths (attributedText, (float) w);
}
else
{
attributedText.setJustification (Justification::topLeft);
textLayout.createLayoutWithBalancedLineLengths (attributedText, (float) w);
iconSpace = iconWidth;
}
w = jmax (350, (int) textLayout.getWidth() + iconSpace + edgeGap * 4);
w = jmin (w, (int) ((float) getParentWidth() * 0.7f));
auto textLayoutH = (int) textLayout.getHeight();
auto textBottom = 16 + titleH + textLayoutH;
int h = textBottom;
int buttonW = 40;
for (auto* b : buttons)
buttonW += 16 + b->getWidth();
w = jmax (buttonW, w);
h += (textBoxes.size() + comboBoxes.size() + progressBars.size()) * 50;
if (auto* b = buttons[0])
h += 20 + b->getHeight();
for (auto* c : customComps)
{
w = jmax (w, (c->getWidth() * 100) / 80);
h += 10 + c->getHeight();
if (c->getName().isNotEmpty())
h += labelHeight;
}
for (auto* tb : textBlocks)
w = jmax (w, static_cast<const AlertTextComp*> (tb)->bestWidth);
w = jmin (w, (int) ((float) getParentWidth() * 0.7f));
for (auto* tb : textBlocks)
{
auto* ac = static_cast<AlertTextComp*> (tb);
ac->updateLayout ((int) ((float) w * 0.8f));
h += ac->getHeight() + 10;
}
h = jmin (getParentHeight() - 50, h);
if (onlyIncreaseSize)
{
w = jmax (w, getWidth());
h = jmax (h, getHeight());
}
if (! isVisible())
centreAroundComponent (associatedComponent, w, h);
else
setBounds (getBounds().withSizeKeepingCentre (w, h));
textArea.setBounds (edgeGap, edgeGap, w - (edgeGap * 2), h - edgeGap);
accessibleMessageLabel.setBounds (textArea);
const int spacer = 16;
int totalWidth = -spacer;
for (auto* b : buttons)
totalWidth += b->getWidth() + spacer;
auto x = (w - totalWidth) / 2;
auto y = (int) ((float) getHeight() * 0.95f);
for (auto* c : buttons)
{
int ny = proportionOfHeight (0.95f) - c->getHeight();
c->setTopLeftPosition (x, ny);
if (ny < y)
y = ny;
x += c->getWidth() + spacer;
c->toFront (false);
}
y = textBottom;
for (auto* c : allComps)
{
h = 22;
const int comboIndex = comboBoxes.indexOf (dynamic_cast<ComboBox*> (c));
if (comboIndex >= 0 && comboBoxNames [comboIndex].isNotEmpty())
y += labelHeight;
const int tbIndex = textBoxes.indexOf (dynamic_cast<TextEditor*> (c));
if (tbIndex >= 0 && textboxNames[tbIndex].isNotEmpty())
y += labelHeight;
if (customComps.contains (c))
{
if (c->getName().isNotEmpty())
y += labelHeight;
c->setTopLeftPosition (proportionOfWidth (0.1f), y);
h = c->getHeight();
}
else if (textBlocks.contains (c))
{
c->setTopLeftPosition ((getWidth() - c->getWidth()) / 2, y);
h = c->getHeight();
}
else
{
c->setBounds (proportionOfWidth (0.1f), y, proportionOfWidth (0.8f), h);
}
y += h + 10;
}
setWantsKeyboardFocus (getNumChildComponents() == 0);
}
bool AlertWindow::containsAnyExtraComponents() const
{
return allComps.size() > 0;
}
//==============================================================================
void AlertWindow::mouseDown (const MouseEvent& e)
{
dragger.startDraggingComponent (this, e);
}
void AlertWindow::mouseDrag (const MouseEvent& e)
{
dragger.dragComponent (this, e, &constrainer);
}
bool AlertWindow::keyPressed (const KeyPress& key)
{
for (auto* b : buttons)
{
if (b->isRegisteredForShortcut (key))
{
b->triggerClick();
return true;
}
}
if (key.isKeyCode (KeyPress::escapeKey) && escapeKeyCancels)
{
exitModalState (0);
return true;
}
if (key.isKeyCode (KeyPress::returnKey) && buttons.size() == 1)
{
buttons.getUnchecked (0)->triggerClick();
return true;
}
return false;
}
void AlertWindow::lookAndFeelChanged()
{
const int newFlags = getLookAndFeel().getAlertBoxWindowFlags();
setUsingNativeTitleBar ((newFlags & ComponentPeer::windowHasTitleBar) != 0);
setDropShadowEnabled (isOpaque() && (newFlags & ComponentPeer::windowHasDropShadow) != 0);
updateLayout (false);
}
int AlertWindow::getDesktopWindowStyleFlags() const
{
return getLookAndFeel().getAlertBoxWindowFlags();
}
//==============================================================================
#if JUCE_MODAL_LOOPS_PERMITTED
void AlertWindow::showMessageBox (MessageBoxIconType iconType,
const String& title,
const String& message,
const String& buttonText,
Component* associatedComponent)
{
show (MessageBoxOptions()
.withIconType (iconType)
.withTitle (title)
.withMessage (message)
.withButton (buttonText.isEmpty() ? TRANS ("OK") : buttonText)
.withAssociatedComponent (associatedComponent));
}
int AlertWindow::show (const MessageBoxOptions& options)
{
if (LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows())
return NativeMessageBox::show (options);
return showAlertWindowUnmanaged (options, nullptr);
}
bool AlertWindow::showNativeDialogBox (const String& title,
const String& bodyText,
bool isOkCancel)
{
if (isOkCancel)
return NativeMessageBox::showOkCancelBox (AlertWindow::NoIcon, title, bodyText);
NativeMessageBox::showMessageBox (AlertWindow::NoIcon, title, bodyText);
return true;
}
#endif
void AlertWindow::showAsync (const MessageBoxOptions& options, ModalComponentManager::Callback* callback)
{
if (LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows())
NativeMessageBox::showAsync (options, callback);
else
showAlertWindowUnmanaged (options, callback);
}
void AlertWindow::showAsync (const MessageBoxOptions& options, std::function<void (int)> callback)
{
showAsync (options, ModalCallbackFunction::create (callback));
}
void AlertWindow::showMessageBoxAsync (MessageBoxIconType iconType,
const String& title,
const String& message,
const String& buttonText,
Component* associatedComponent,
ModalComponentManager::Callback* callback)
{
auto options = MessageBoxOptions::makeOptionsOk (iconType,
title,
message,
buttonText,
associatedComponent);
showAsync (options, callback);
}
static int showMaybeAsync (const MessageBoxOptions& options,
ModalComponentManager::Callback* callbackIn)
{
if (LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows())
return showNativeBoxUnmanaged (options, callbackIn, ResultCodeMappingMode::alertWindow);
return showAlertWindowUnmanaged (options, callbackIn);
}
bool AlertWindow::showOkCancelBox (MessageBoxIconType iconType,
const String& title,
const String& message,
const String& button1Text,
const String& button2Text,
Component* associatedComponent,
ModalComponentManager::Callback* callback)
{
auto options = MessageBoxOptions::makeOptionsOkCancel (iconType,
title,
message,
button1Text,
button2Text,
associatedComponent);
return showMaybeAsync (options, callback) == 1;
}
int AlertWindow::showYesNoCancelBox (MessageBoxIconType iconType,
const String& title,
const String& message,
const String& button1Text,
const String& button2Text,
const String& button3Text,
Component* associatedComponent,
ModalComponentManager::Callback* callback)
{
auto options = MessageBoxOptions::makeOptionsYesNoCancel (iconType,
title,
message,
button1Text,
button2Text,
button3Text,
associatedComponent);
return showMaybeAsync (options, callback);
}
ScopedMessageBox AlertWindow::showScopedAsync (const MessageBoxOptions& options, std::function<void (int)> callback)
{
if (LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows())
return NativeMessageBox::showScopedAsync (options, std::move (callback));
return detail::ConcreteScopedMessageBoxImpl::show (detail::AlertWindowHelpers::create (options), std::move (callback));
}
//==============================================================================
std::unique_ptr<AccessibilityHandler> AlertWindow::createAccessibilityHandler()
{
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::dialogWindow);
}
} // namespace juce
@@ -0,0 +1,591 @@
/*
==============================================================================
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 message and has buttons for the user to react to it.
For simple dialog boxes with just a couple of buttons on them, there are
some static methods for running these.
For more complex dialogs, an AlertWindow can be created, then it can have some
buttons and components added to it, and its enterModalState() method is used to
show it. The value returned to the ModalComponentManager::Callback shows
which button the user pressed to dismiss the box.
@see ThreadWithProgressWindow, Component::enterModalState
@tags{GUI}
*/
class JUCE_API AlertWindow : public TopLevelWindow
{
public:
//==============================================================================
/** Creates an AlertWindow.
@param title the headline to show at the top of the dialog box
@param message a longer, more descriptive message to show underneath the
headline
@param iconType the type of icon to display
@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.
*/
AlertWindow (const String& title,
const String& message,
MessageBoxIconType iconType,
Component* associatedComponent = nullptr);
/** Destroys the AlertWindow */
~AlertWindow() override;
//==============================================================================
/** Returns the type of alert icon that was specified when the window
was created. */
MessageBoxIconType getAlertType() const noexcept { return alertIconType; }
//==============================================================================
/** Changes the dialog box's message.
This will also resize the window to fit the new message if required.
*/
void setMessage (const String& message);
//==============================================================================
/** Adds a button to the window.
@param name the text to show on the button
@param returnValue the value that should be returned from runModalLoop()
if this is the button that the user presses.
@param shortcutKey1 an optional key that can be pressed to trigger this button
@param shortcutKey2 a second optional key that can be pressed to trigger this button
*/
void addButton (const String& name,
int returnValue,
const KeyPress& shortcutKey1 = KeyPress(),
const KeyPress& shortcutKey2 = KeyPress());
/** Returns the number of buttons that the window currently has. */
int getNumButtons() const;
/** Returns a Button that was added to the AlertWindow.
@param index the index of the button in order that it was added with the addButton() method.
@returns the Button component, or nullptr if the index is out of bounds.
@see getNumButtons
*/
Button* getButton (int index) const;
/** Returns a Button that was added to the AlertWindow.
@param buttonName the name that was passed into the addButton() method
@returns the Button component, or nullptr if none was found for the given name.
*/
Button* getButton (const String& buttonName) const;
/** Invokes a click of one of the buttons. */
void triggerButtonClick (const String& buttonName);
/** If set to true and the window contains no buttons, then pressing the escape key will make
the alert cancel its modal state.
By default this setting is true - turn it off if you don't want the box to respond to
the escape key. Note that it is ignored if you have any buttons, and in that case you
should give the buttons appropriate keypresses to trigger cancelling if you want to.
*/
void setEscapeKeyCancels (bool shouldEscapeKeyCancel);
//==============================================================================
/** Adds a textbox to the window for entering strings.
@param name an internal name for the text-box. This is the name to pass to
the getTextEditorContents() method to find out what the
user typed-in.
@param initialContents a string to show in the text box when it's first shown
@param onScreenLabel if this is non-empty, it will be displayed next to the
text-box to label it.
@param isPasswordBox if true, the text editor will display asterisks instead of
the actual text
@see getTextEditorContents
*/
void addTextEditor (const String& name,
const String& initialContents,
const String& onScreenLabel = String(),
bool isPasswordBox = false);
/** Returns the contents of a named textbox.
After showing an AlertWindow that contains a text editor, this can be
used to find out what the user has typed into it.
@param nameOfTextEditor the name of the text box that you're interested in
@see addTextEditor
*/
String getTextEditorContents (const String& nameOfTextEditor) const;
/** Returns a pointer to a textbox that was added with addTextEditor(). */
TextEditor* getTextEditor (const String& nameOfTextEditor) const;
//==============================================================================
/** Adds a drop-down list of choices to the box.
After the box has been shown, the getComboBoxComponent() method can
be used to find out which item the user picked.
@param name the label to use for the drop-down list
@param items the list of items to show in it
@param onScreenLabel if this is non-empty, it will be displayed next to the
combo-box to label it.
@see getComboBoxComponent
*/
void addComboBox (const String& name,
const StringArray& items,
const String& onScreenLabel = String());
/** Returns a drop-down list that was added to the AlertWindow.
@param nameOfList the name that was passed into the addComboBox() method
when creating the drop-down
@returns the ComboBox component, or nullptr if none was found for the given name.
*/
ComboBox* getComboBoxComponent (const String& nameOfList) const;
//==============================================================================
/** Adds a block of text.
This is handy for adding a multi-line note next to a textbox or combo-box,
to provide more details about what's going on.
*/
void addTextBlock (const String& text);
//==============================================================================
/** Adds a progress-bar to the window.
@param progressValue a variable that will be repeatedly checked while the
dialog box is visible, to see how far the process has
got. The value should be in the range 0 to 1.0
@param style determines the style the ProgressBar should adopt.
By default this use a style automatically chosen by
the LookAndFeel, but you can force a particular style
by passing a non-optional value.
@see ProgressBar::setStyle
*/
void addProgressBarComponent (double& progressValue, std::optional<ProgressBar::Style> style = std::nullopt);
//==============================================================================
/** Adds a user-defined component to the dialog box.
@param component the component to add - its size should be set up correctly
before it is passed in. The caller is responsible for deleting
the component later on - the AlertWindow won't delete it.
*/
void addCustomComponent (Component* component);
/** Returns the number of custom components in the dialog box.
@see getCustomComponent, addCustomComponent
*/
int getNumCustomComponents() const;
/** Returns one of the custom components in the dialog box.
@param index a value 0 to (getNumCustomComponents() - 1).
Out-of-range indexes will return nullptr
@see getNumCustomComponents, addCustomComponent
*/
Component* getCustomComponent (int index) const;
/** Removes one of the custom components in the dialog box.
Note that this won't delete it, it just removes the component from the window
@param index a value 0 to (getNumCustomComponents() - 1).
Out-of-range indexes will return nullptr
@returns the component that was removed (or null)
@see getNumCustomComponents, addCustomComponent
*/
Component* removeCustomComponent (int index);
//==============================================================================
/** Returns true if the window contains any components other than just buttons.*/
bool containsAnyExtraComponents() const;
//==============================================================================
#if JUCE_MODAL_LOOPS_PERMITTED
/** Shows a dialog box that just has a message and a single button to get rid of it.
The box is shown modally, and the method will block until the user has clicked the
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
headline
@param buttonText the text to show in the button - if this string is empty, the
default string "OK" (or a localised version) will be used.
@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,
const String& buttonText = String(),
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 button to get rid of it.
The box will be displayed and placed into a modal state, but this method will
return immediately, and if a callback was supplied, it 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
headline
@param buttonText the text to show in the button - if this string is empty, the
default string "OK" (or a localised version) will be used.
@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.
*/
static void JUCE_CALLTYPE showMessageBoxAsync (MessageBoxIconType iconType,
const String& title,
const String& message,
const String& buttonText = String(),
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 JUCE_MODAL_LOOPS_PERMITTED is not defined or the callback parameter is non-null,
this function will return immediately. The object passed as the callback argument will
receive the result of the alert window asynchronously.
Otherwise, if JUCE_MODAL_LOOPS_PERMITTED is defined and the callback parameter is null,
the box is shown modally, and the method will block until the user has clicked the button
(or pressed the escape or return keys). This mode of operation can cause problems,
especially in plugins, so it is not recommended.
@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
headline
@param button1Text the text to show in the first button - if this string is
empty, the default string "OK" (or a localised version of it)
will be used.
@param button2Text the text to show in the second button - if this string is
empty, the default string "cancel" (or a localised version of it)
will be used.
@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 menu 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.
@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.
*/
static bool JUCE_CALLTYPE showOkCancelBox (MessageBoxIconType iconType,
const String& title,
const String& message,
#if JUCE_MODAL_LOOPS_PERMITTED
const String& button1Text = String(),
const String& button2Text = String(),
Component* associatedComponent = nullptr,
ModalComponentManager::Callback* callback = nullptr);
#else
const String& button1Text,
const String& button2Text,
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 JUCE_MODAL_LOOPS_PERMITTED is not defined or the callback parameter is non-null,
this function will return immediately. The object passed as the callback argument will
receive the result of the alert window asynchronously.
Otherwise, if JUCE_MODAL_LOOPS_PERMITTED is defined and the callback parameter is null,
the box is shown modally, and the method will block until the user has clicked the button
(or pressed the escape or return keys). This mode of operation can cause problems,
especially in plugins, so it is not recommended.
@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
headline
@param button1Text the text to show in the first button - if an empty string, then
"yes" will be used (or a localised version of it)
@param button2Text the text to show in the first button - if an empty string, then
"no" will be used (or a localised version of it)
@param button3Text the text to show in the first button - if an empty string, then
"cancel" will be used (or a localised version of it)
@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 menu 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.
@returns If the callback parameter has been set, this returns 0. Otherwise, it
returns one of the following values:
- 0 if the third button was pressed (normally used for 'cancel')
- 1 if the first button was pressed (normally used for 'yes')
- 2 if the middle button was pressed (normally used for 'no')
*/
static int JUCE_CALLTYPE showYesNoCancelBox (MessageBoxIconType iconType,
const String& title,
const String& message,
#if JUCE_MODAL_LOOPS_PERMITTED
const String& button1Text = String(),
const String& button2Text = String(),
const String& button3Text = String(),
Component* associatedComponent = nullptr,
ModalComponentManager::Callback* callback = nullptr);
#else
const String& button1Text,
const String& button2Text,
const String& button3Text,
Component* associatedComponent,
ModalComponentManager::Callback* callback);
#endif
/** Shows an alert window 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.
This function is always asynchronous, even if the callback is null.
The result codes returned by the alert window are as follows.
- One button:
- button[0] returns 0
- Two buttons:
- button[0] returns 1
- button[1] returns 0
- Three buttons:
- button[0] returns 1
- button[1] returns 2
- button[2] returns 0
@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.
@returns a ScopedMessageBox instance. The message box will remain visible for no
longer than the ScopedMessageBox remains alive.
@see MessageBoxOptions
*/
[[nodiscard]] static ScopedMessageBox showScopedAsync (const MessageBoxOptions& options,
std::function<void (int)> callback);
//==============================================================================
#if JUCE_MODAL_LOOPS_PERMITTED && ! defined (DOXYGEN)
/** Shows an operating-system native dialog box.
@param title the title to use at the top
@param bodyText the longer message to show
@param isOkCancel if true, this will show an ok/cancel box, if false,
it'll show a box with just an ok button
@returns true if the ok button was pressed, false if they pressed cancel.
*/
[[deprecated ("Use the NativeMessageBox methods instead for more options")]]
static bool JUCE_CALLTYPE showNativeDialogBox (const String& title,
const String& bodyText,
bool isOkCancel);
#endif
//==============================================================================
/** A set of colour IDs to use to change the colour of various aspects of the alert box.
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 = 0x1001800, /**< The background colour for the window. */
textColourId = 0x1001810, /**< The colour for the text. */
outlineColourId = 0x1001820 /**< An optional colour to use to draw a border around the window. */
};
//==============================================================================
/** This abstract base class is implemented by LookAndFeel classes to provide
alert-window drawing functionality.
*/
struct JUCE_API LookAndFeelMethods
{
virtual ~LookAndFeelMethods() = default;
virtual AlertWindow* createAlertWindow (const String& title, const String& message,
const String& button1,
const String& button2,
const String& button3,
MessageBoxIconType iconType,
int numButtons,
Component* associatedComponent) = 0;
virtual void drawAlertBox (Graphics&, AlertWindow&, const Rectangle<int>& textArea, TextLayout&) = 0;
virtual int getAlertBoxWindowFlags() = 0;
virtual Array<int> getWidthsForTextButtons (AlertWindow&, const Array<TextButton*>&) = 0;
virtual int getAlertWindowButtonHeight() = 0;
virtual Font getAlertWindowTitleFont() = 0;
virtual Font getAlertWindowMessageFont() = 0;
virtual Font getAlertWindowFont() = 0;
};
//==============================================================================
using AlertIconType = MessageBoxIconType;
static constexpr auto NoIcon = MessageBoxIconType::NoIcon;
static constexpr auto QuestionIcon = MessageBoxIconType::QuestionIcon;
static constexpr auto WarningIcon = MessageBoxIconType::WarningIcon;
static constexpr auto InfoIcon = MessageBoxIconType::InfoIcon;
/** @internal */
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
protected:
//==============================================================================
/** @internal */
void paint (Graphics&) override;
/** @internal */
void mouseDown (const MouseEvent&) override;
/** @internal */
void mouseDrag (const MouseEvent&) override;
/** @internal */
bool keyPressed (const KeyPress&) override;
/** @internal */
void lookAndFeelChanged() override;
/** @internal */
void userTriedToCloseWindow() override;
/** @internal */
int getDesktopWindowStyleFlags() const override;
/** @internal */
float getDesktopScaleFactor() const override { return desktopScale * Desktop::getInstance().getGlobalScaleFactor(); }
private:
//==============================================================================
String text;
TextLayout textLayout;
Label accessibleMessageLabel;
MessageBoxIconType alertIconType;
ComponentBoundsConstrainer constrainer;
ComponentDragger dragger;
Rectangle<int> textArea;
OwnedArray<TextButton> buttons;
OwnedArray<TextEditor> textBoxes;
OwnedArray<ComboBox> comboBoxes;
OwnedArray<ProgressBar> progressBars;
Array<Component*> customComps;
OwnedArray<Component> textBlocks;
Array<Component*> allComps;
StringArray textboxNames, comboBoxNames;
Component* const associatedComponent;
bool escapeKeyCancels = true;
float desktopScale = 1.0f;
void exitAlert (Button* button);
void updateLayout (bool onlyIncreaseSize);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AlertWindow)
};
} // namespace juce
@@ -0,0 +1,270 @@
/*
==============================================================================
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 (WindowUtils::areThereAnyAlwaysOnTopWindows());
updatePosition (area, Desktop::getInstance().getDisplays().getDisplayForRect (area)->userArea);
addToDesktop (ComponentPeer::windowIsTemporary);
startTimer (100);
}
creationTime = Time::getCurrentTime();
}
//==============================================================================
class CallOutBoxCallback final : 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 (! detail::WindowingHelpers::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();
}
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
@@ -0,0 +1,188 @@
/*
==============================================================================
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;
/** @internal */
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
private:
//==============================================================================
Component& content;
Path outline;
Point<float> targetPoint;
Rectangle<int> availableArea, targetArea;
Image background;
float arrowSize = 16.0f;
bool dismissalMouseClicksAreAlwaysConsumed = false;
Time creationTime;
void refreshPath();
void timerCallback() override;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CallOutBox)
};
} // namespace juce
@@ -0,0 +1,614 @@
/*
==============================================================================
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
{
static uint32 lastUniquePeerID = 1;
//==============================================================================
ComponentPeer::ComponentPeer (Component& comp, int flags)
: component (comp),
styleFlags (flags),
uniqueID (lastUniquePeerID += 2) // increment by 2 so that this can never hit 0
{
auto& desktop = Desktop::getInstance();
desktop.peers.add (this);
desktop.addFocusChangeListener (this);
}
ComponentPeer::~ComponentPeer()
{
auto& desktop = Desktop::getInstance();
desktop.removeFocusChangeListener (this);
desktop.peers.removeFirstMatchingValue (this);
desktop.triggerFocusCallback();
}
//==============================================================================
int ComponentPeer::getNumPeers() noexcept
{
return Desktop::getInstance().peers.size();
}
ComponentPeer* ComponentPeer::getPeer (const int index) noexcept
{
return Desktop::getInstance().peers [index];
}
ComponentPeer* ComponentPeer::getPeerFor (const Component* const component) noexcept
{
for (auto* peer : Desktop::getInstance().peers)
if (&(peer->getComponent()) == component)
return peer;
return nullptr;
}
bool ComponentPeer::isValidPeer (const ComponentPeer* const peer) noexcept
{
return Desktop::getInstance().peers.contains (const_cast<ComponentPeer*> (peer));
}
void ComponentPeer::updateBounds()
{
setBounds (detail::ScalingHelpers::scaledScreenPosToUnscaled (component, component.getBoundsInParent()), false);
}
bool ComponentPeer::isKioskMode() const
{
return Desktop::getInstance().getKioskModeComponent() == &component;
}
//==============================================================================
void ComponentPeer::handleMouseEvent (MouseInputSource::InputSourceType type, Point<float> pos, ModifierKeys newMods,
float newPressure, float newOrientation, int64 time, PenDetails pen, int touchIndex)
{
if (auto* mouse = Desktop::getInstance().mouseSources->getOrCreateMouseInputSource (type, touchIndex))
MouseInputSource (*mouse).handleEvent (*this, pos, time, newMods, newPressure, newOrientation, pen);
}
void ComponentPeer::handleMouseWheel (MouseInputSource::InputSourceType type, Point<float> pos, int64 time, const MouseWheelDetails& wheel, int touchIndex)
{
if (auto* mouse = Desktop::getInstance().mouseSources->getOrCreateMouseInputSource (type, touchIndex))
MouseInputSource (*mouse).handleWheel (*this, pos, time, wheel);
}
void ComponentPeer::handleMagnifyGesture (MouseInputSource::InputSourceType type, Point<float> pos, int64 time, float scaleFactor, int touchIndex)
{
if (auto* mouse = Desktop::getInstance().mouseSources->getOrCreateMouseInputSource (type, touchIndex))
MouseInputSource (*mouse).handleMagnifyGesture (*this, pos, time, scaleFactor);
}
//==============================================================================
void ComponentPeer::handlePaint (LowLevelGraphicsContext& contextToPaintTo)
{
Graphics g (contextToPaintTo);
if (component.isTransformed())
g.addTransform (component.getTransform());
auto peerBounds = getBounds();
auto componentBounds = component.getLocalBounds();
if (component.isTransformed())
componentBounds = componentBounds.transformedBy (component.getTransform());
if (peerBounds.getWidth() != componentBounds.getWidth() || peerBounds.getHeight() != componentBounds.getHeight())
// Tweak the scaling so that the component's integer size exactly aligns with the peer's scaled size
g.addTransform (AffineTransform::scale ((float) peerBounds.getWidth() / (float) componentBounds.getWidth(),
(float) peerBounds.getHeight() / (float) componentBounds.getHeight()));
#if JUCE_ENABLE_REPAINT_DEBUGGING
#ifdef JUCE_IS_REPAINT_DEBUGGING_ACTIVE
if (JUCE_IS_REPAINT_DEBUGGING_ACTIVE)
#endif
{
g.saveState();
}
#endif
JUCE_TRY
{
component.paintEntireComponent (g, true);
}
JUCE_CATCH_EXCEPTION
#if JUCE_ENABLE_REPAINT_DEBUGGING
#ifdef JUCE_IS_REPAINT_DEBUGGING_ACTIVE
if (JUCE_IS_REPAINT_DEBUGGING_ACTIVE)
#endif
{
// enabling this code will fill all areas that get repainted with a colour overlay, to show
// clearly when things are being repainted.
g.restoreState();
static Random rng;
g.fillAll (Colour ((uint8) rng.nextInt (255),
(uint8) rng.nextInt (255),
(uint8) rng.nextInt (255),
(uint8) 0x50));
}
#endif
/** If this fails, it's probably be because your CPU floating-point precision mode has
been set to low.. This setting is sometimes changed by things like Direct3D, and can
mess up a lot of the calculations that the library needs to do.
*/
jassert (roundToInt (10.1f) == 10);
}
Component* ComponentPeer::getTargetForKeyPress()
{
auto* c = Component::getCurrentlyFocusedComponent();
if (c == nullptr)
c = &component;
if (c->isCurrentlyBlockedByAnotherModalComponent())
if (auto* currentModalComp = Component::getCurrentlyModalComponent())
c = currentModalComp;
return c;
}
bool ComponentPeer::handleKeyPress (const int keyCode, const juce_wchar textCharacter)
{
return handleKeyPress (KeyPress (keyCode,
ModifierKeys::currentModifiers.withoutMouseButtons(),
textCharacter));
}
bool ComponentPeer::handleKeyPress (const KeyPress& keyInfo)
{
bool keyWasUsed = false;
for (auto* target = getTargetForKeyPress(); target != nullptr; target = target->getParentComponent())
{
const WeakReference<Component> deletionChecker (target);
if (auto* keyListeners = target->keyListeners.get())
{
for (int i = keyListeners->size(); --i >= 0;)
{
keyWasUsed = keyListeners->getUnchecked (i)->keyPressed (keyInfo, target);
if (keyWasUsed || deletionChecker == nullptr)
return keyWasUsed;
i = jmin (i, keyListeners->size());
}
}
keyWasUsed = target->keyPressed (keyInfo);
if (keyWasUsed || deletionChecker == nullptr)
break;
}
if (! keyWasUsed && keyInfo.isKeyCode (KeyPress::tabKey))
{
if (auto* currentlyFocused = Component::getCurrentlyFocusedComponent())
{
currentlyFocused->moveKeyboardFocusToSibling (! keyInfo.getModifiers().isShiftDown());
return true;
}
}
return keyWasUsed;
}
bool ComponentPeer::handleKeyUpOrDown (const bool isKeyDown)
{
bool keyWasUsed = false;
for (auto* target = getTargetForKeyPress(); target != nullptr; target = target->getParentComponent())
{
const WeakReference<Component> deletionChecker (target);
keyWasUsed = target->keyStateChanged (isKeyDown);
if (keyWasUsed || deletionChecker == nullptr)
break;
if (auto* keyListeners = target->keyListeners.get())
{
for (int i = keyListeners->size(); --i >= 0;)
{
keyWasUsed = keyListeners->getUnchecked (i)->keyStateChanged (isKeyDown, target);
if (keyWasUsed || deletionChecker == nullptr)
return keyWasUsed;
i = jmin (i, keyListeners->size());
}
}
}
return keyWasUsed;
}
void ComponentPeer::handleModifierKeysChange()
{
auto* target = Desktop::getInstance().getMainMouseSource().getComponentUnderMouse();
if (target == nullptr)
target = Component::getCurrentlyFocusedComponent();
if (target == nullptr)
target = &component;
target->internalModifierKeysChanged();
}
void ComponentPeer::refreshTextInputTarget()
{
const auto* lastTarget = std::exchange (textInputTarget, findCurrentTextInputTarget());
if (lastTarget == textInputTarget)
return;
if (textInputTarget == nullptr)
dismissPendingTextInput();
else if (auto* c = Component::getCurrentlyFocusedComponent())
textInputRequired (globalToLocal (c->getScreenPosition()), *textInputTarget);
}
TextInputTarget* ComponentPeer::findCurrentTextInputTarget()
{
auto* c = Component::getCurrentlyFocusedComponent();
if (c == &component || component.isParentOf (c))
if (auto* ti = dynamic_cast<TextInputTarget*> (c))
if (ti->isTextInputActive())
return ti;
return nullptr;
}
void ComponentPeer::closeInputMethodContext() {}
void ComponentPeer::dismissPendingTextInput()
{
closeInputMethodContext();
}
//==============================================================================
void ComponentPeer::handleBroughtToFront()
{
component.internalBroughtToFront();
}
void ComponentPeer::setConstrainer (ComponentBoundsConstrainer* const newConstrainer) noexcept
{
constrainer = newConstrainer;
}
void ComponentPeer::handleMovedOrResized()
{
const bool nowMinimised = isMinimised();
if (component.flags.hasHeavyweightPeerFlag && ! nowMinimised)
{
const WeakReference<Component> deletionChecker (&component);
auto newBounds = detail::ComponentHelpers::rawPeerPositionToLocal (component, getBounds());
auto oldBounds = component.getBounds();
const bool wasMoved = (oldBounds.getPosition() != newBounds.getPosition());
const bool wasResized = (oldBounds.getWidth() != newBounds.getWidth() || oldBounds.getHeight() != newBounds.getHeight());
if (wasMoved || wasResized)
{
component.boundsRelativeToParent = newBounds;
if (wasResized)
component.repaint();
component.sendMovedResizedMessages (wasMoved, wasResized);
if (deletionChecker == nullptr)
return;
}
}
if (isWindowMinimised != nowMinimised)
{
isWindowMinimised = nowMinimised;
component.minimisationStateChanged (nowMinimised);
component.sendVisibilityChangeMessage();
}
const auto windowInSpecialState = isFullScreen() || isKioskMode() || nowMinimised;
if (! windowInSpecialState)
lastNonFullscreenBounds = component.getBounds();
}
void ComponentPeer::handleFocusGain()
{
if (component.isParentOf (lastFocusedComponent)
&& lastFocusedComponent->isShowing()
&& lastFocusedComponent->getWantsKeyboardFocus())
{
Component::currentlyFocusedComponent = lastFocusedComponent;
Desktop::getInstance().triggerFocusCallback();
lastFocusedComponent->internalKeyboardFocusGain (Component::focusChangedDirectly);
}
else
{
if (! component.isCurrentlyBlockedByAnotherModalComponent())
component.grabKeyboardFocus();
else
ModalComponentManager::getInstance()->bringModalComponentsToFront();
}
}
void ComponentPeer::handleFocusLoss()
{
if (component.hasKeyboardFocus (true))
{
lastFocusedComponent = Component::currentlyFocusedComponent;
if (lastFocusedComponent != nullptr)
{
Component::currentlyFocusedComponent = nullptr;
Desktop::getInstance().triggerFocusCallback();
lastFocusedComponent->internalKeyboardFocusLoss (Component::focusChangedByMouseClick);
}
}
}
Component* ComponentPeer::getLastFocusedSubcomponent() const noexcept
{
return (component.isParentOf (lastFocusedComponent) && lastFocusedComponent->isShowing())
? static_cast<Component*> (lastFocusedComponent)
: &component;
}
void ComponentPeer::handleScreenSizeChange()
{
component.parentSizeChanged();
handleMovedOrResized();
}
void ComponentPeer::setNonFullScreenBounds (const Rectangle<int>& newBounds) noexcept
{
lastNonFullscreenBounds = newBounds;
}
const Rectangle<int>& ComponentPeer::getNonFullScreenBounds() const noexcept
{
return lastNonFullscreenBounds;
}
Point<int> ComponentPeer::localToGlobal (Point<int> p) { return localToGlobal (p.toFloat()).roundToInt(); }
Point<int> ComponentPeer::globalToLocal (Point<int> p) { return globalToLocal (p.toFloat()).roundToInt(); }
Rectangle<int> ComponentPeer::localToGlobal (const Rectangle<int>& relativePosition)
{
return relativePosition.withPosition (localToGlobal (relativePosition.getPosition()));
}
Rectangle<int> ComponentPeer::globalToLocal (const Rectangle<int>& screenPosition)
{
return screenPosition.withPosition (globalToLocal (screenPosition.getPosition()));
}
Rectangle<float> ComponentPeer::localToGlobal (const Rectangle<float>& relativePosition)
{
return relativePosition.withPosition (localToGlobal (relativePosition.getPosition()));
}
Rectangle<float> ComponentPeer::globalToLocal (const Rectangle<float>& screenPosition)
{
return screenPosition.withPosition (globalToLocal (screenPosition.getPosition()));
}
Rectangle<int> ComponentPeer::getAreaCoveredBy (const Component& subComponent) const
{
return detail::ScalingHelpers::scaledScreenPosToUnscaled
(component, component.getLocalArea (&subComponent, subComponent.getLocalBounds()));
}
//==============================================================================
namespace DragHelpers
{
static bool isFileDrag (const ComponentPeer::DragInfo& info)
{
return ! info.files.isEmpty();
}
static bool isSuitableTarget (const ComponentPeer::DragInfo& info, Component* target)
{
return isFileDrag (info) ? dynamic_cast<FileDragAndDropTarget*> (target) != nullptr
: dynamic_cast<TextDragAndDropTarget*> (target) != nullptr;
}
static bool isInterested (const ComponentPeer::DragInfo& info, Component* target)
{
return isFileDrag (info) ? dynamic_cast<FileDragAndDropTarget*> (target)->isInterestedInFileDrag (info.files)
: dynamic_cast<TextDragAndDropTarget*> (target)->isInterestedInTextDrag (info.text);
}
static Component* findDragAndDropTarget (Component* c, const ComponentPeer::DragInfo& info, Component* lastOne)
{
for (; c != nullptr; c = c->getParentComponent())
if (isSuitableTarget (info, c) && (c == lastOne || isInterested (info, c)))
return c;
return nullptr;
}
}
bool ComponentPeer::handleDragMove (const ComponentPeer::DragInfo& info)
{
auto* compUnderMouse = component.getComponentAt (info.position);
auto* lastTarget = dragAndDropTargetComponent.get();
Component* newTarget = nullptr;
if (compUnderMouse != lastDragAndDropCompUnderMouse)
{
lastDragAndDropCompUnderMouse = compUnderMouse;
newTarget = DragHelpers::findDragAndDropTarget (compUnderMouse, info, lastTarget);
if (newTarget != lastTarget)
{
if (lastTarget != nullptr)
{
if (DragHelpers::isFileDrag (info))
dynamic_cast<FileDragAndDropTarget*> (lastTarget)->fileDragExit (info.files);
else
dynamic_cast<TextDragAndDropTarget*> (lastTarget)->textDragExit (info.text);
}
dragAndDropTargetComponent = nullptr;
if (DragHelpers::isSuitableTarget (info, newTarget))
{
dragAndDropTargetComponent = newTarget;
auto pos = newTarget->getLocalPoint (&component, info.position);
if (DragHelpers::isFileDrag (info))
dynamic_cast<FileDragAndDropTarget*> (newTarget)->fileDragEnter (info.files, pos.x, pos.y);
else
dynamic_cast<TextDragAndDropTarget*> (newTarget)->textDragEnter (info.text, pos.x, pos.y);
}
}
}
else
{
newTarget = lastTarget;
}
if (! DragHelpers::isSuitableTarget (info, newTarget))
return false;
auto pos = newTarget->getLocalPoint (&component, info.position);
if (DragHelpers::isFileDrag (info))
dynamic_cast<FileDragAndDropTarget*> (newTarget)->fileDragMove (info.files, pos.x, pos.y);
else
dynamic_cast<TextDragAndDropTarget*> (newTarget)->textDragMove (info.text, pos.x, pos.y);
return true;
}
bool ComponentPeer::handleDragExit (const ComponentPeer::DragInfo& info)
{
DragInfo info2 (info);
info2.position.setXY (-1, -1);
const bool used = handleDragMove (info2);
jassert (dragAndDropTargetComponent == nullptr);
lastDragAndDropCompUnderMouse = nullptr;
return used;
}
bool ComponentPeer::handleDragDrop (const ComponentPeer::DragInfo& info)
{
handleDragMove (info);
if (WeakReference<Component> targetComp = dragAndDropTargetComponent)
{
dragAndDropTargetComponent = nullptr;
lastDragAndDropCompUnderMouse = nullptr;
if (DragHelpers::isSuitableTarget (info, targetComp))
{
if (targetComp->isCurrentlyBlockedByAnotherModalComponent())
{
targetComp->internalModalInputAttempt();
if (targetComp->isCurrentlyBlockedByAnotherModalComponent())
return true;
}
ComponentPeer::DragInfo infoCopy (info);
infoCopy.position = targetComp->getLocalPoint (&component, info.position);
// We'll use an async message to deliver the drop, because if the target decides
// to run a modal loop, it can gum-up the operating system..
MessageManager::callAsync ([=]
{
if (auto* c = targetComp.get())
{
if (DragHelpers::isFileDrag (info))
dynamic_cast<FileDragAndDropTarget*> (c)->filesDropped (infoCopy.files, infoCopy.position.x, infoCopy.position.y);
else
dynamic_cast<TextDragAndDropTarget*> (c)->textDropped (infoCopy.text, infoCopy.position.x, infoCopy.position.y);
}
});
return true;
}
}
return false;
}
//==============================================================================
void ComponentPeer::handleUserClosingWindow()
{
component.userTriedToCloseWindow();
}
bool ComponentPeer::setDocumentEditedStatus (bool)
{
return false;
}
void ComponentPeer::setRepresentedFile (const File&)
{
}
//==============================================================================
int ComponentPeer::getCurrentRenderingEngine() const { return 0; }
void ComponentPeer::setCurrentRenderingEngine ([[maybe_unused]] int index) { jassert (index == 0); }
//==============================================================================
std::function<ModifierKeys()> ComponentPeer::getNativeRealtimeModifiers = nullptr;
ModifierKeys ComponentPeer::getCurrentModifiersRealtime() noexcept
{
if (getNativeRealtimeModifiers != nullptr)
return getNativeRealtimeModifiers();
return ModifierKeys::currentModifiers;
}
//==============================================================================
void ComponentPeer::forceDisplayUpdate()
{
Desktop::getInstance().displays->refresh();
}
void ComponentPeer::globalFocusChanged ([[maybe_unused]] Component* comp)
{
refreshTextInputTarget();
}
} // namespace juce
@@ -0,0 +1,603 @@
/*
==============================================================================
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 Component class uses a ComponentPeer internally to create and manage a real
operating-system window.
This is an abstract base class - the platform specific code contains implementations of
it for the various platforms.
User-code should very rarely need to have any involvement with this class.
@see Component::createNewPeer
@tags{GUI}
*/
class JUCE_API ComponentPeer : private FocusChangeListener
{
public:
//==============================================================================
/** A combination of these flags is passed to the ComponentPeer constructor. */
enum StyleFlags
{
windowAppearsOnTaskbar = (1 << 0), /**< Indicates that the window should have a corresponding
entry on the taskbar (ignored on MacOSX) */
windowIsTemporary = (1 << 1), /**< Indicates that the window is a temporary popup, like a menu,
tooltip, etc. */
windowIgnoresMouseClicks = (1 << 2), /**< Indicates that the window should let mouse clicks pass
through it (may not be possible on some platforms). */
windowHasTitleBar = (1 << 3), /**< Indicates that the window should have a normal OS-specific
title bar and frame. if not specified, the window will be
borderless. */
windowIsResizable = (1 << 4), /**< Indicates that the window should have a resizable border. */
windowHasMinimiseButton = (1 << 5), /**< Indicates that if the window has a title bar, it should have a
minimise button on it. */
windowHasMaximiseButton = (1 << 6), /**< Indicates that if the window has a title bar, it should have a
maximise button on it. */
windowHasCloseButton = (1 << 7), /**< Indicates that if the window has a title bar, it should have a
close button on it. */
windowHasDropShadow = (1 << 8), /**< Indicates that the window should have a drop-shadow (this may
not be possible on all platforms). */
windowRepaintedExplictly = (1 << 9), /**< Not intended for public use - this tells a window not to
do its own repainting, but only to repaint when the
performAnyPendingRepaintsNow() method is called. */
windowIgnoresKeyPresses = (1 << 10), /**< Tells the window not to catch any keypresses. This can
be used for things like plugin windows, to stop them interfering
with the host's shortcut keys. */
windowRequiresSynchronousCoreGraphicsRendering = (1 << 11), /**< Indicates that the window should not be rendered with
asynchronous Core Graphics drawing operations. Use this if there
are issues with regions not being redrawn at the expected time
(macOS and iOS only). */
windowIsSemiTransparent = (1 << 30) /**< Not intended for public use - makes a window transparent. */
};
/** Represents the window borders around a window component.
You must use operator bool() to evaluate the validity of the object before accessing
its value.
Returned by getFrameSizeIfPresent(). A missing value may be returned on Linux for a
short time after window creation.
*/
class JUCE_API OptionalBorderSize final
{
public:
/** Default constructor. Creates an invalid object. */
OptionalBorderSize() : valid (false) {}
/** Constructor. Creates a valid object containing the provided BorderSize<int>. */
explicit OptionalBorderSize (BorderSize<int> size) : valid (true), borderSize (std::move (size)) {}
/** Returns true if a valid value has been provided. */
explicit operator bool() const noexcept { return valid; }
/** Returns a reference to the value.
You must not call this function on an invalid object. Use operator bool() to
determine validity.
*/
const auto& operator*() const noexcept
{
jassert (valid);
return borderSize;
}
/** Returns a pointer to the value.
You must not call this function on an invalid object. Use operator bool() to
determine validity.
*/
const auto* operator->() const noexcept
{
jassert (valid);
return &borderSize;
}
private:
bool valid;
BorderSize<int> borderSize;
};
//==============================================================================
/** Creates a peer.
The component is the one that we intend to represent, and the style flags are
a combination of the values in the StyleFlags enum
*/
ComponentPeer (Component& component, int styleFlags);
/** Destructor. */
~ComponentPeer() override;
//==============================================================================
/** Returns the component being represented by this peer. */
Component& getComponent() noexcept { return component; }
/** Returns the set of style flags that were set when the window was created.
@see Component::addToDesktop
*/
int getStyleFlags() const noexcept { return styleFlags; }
/** Returns a unique ID for this peer.
Each peer that is created is given a different ID.
*/
uint32 getUniqueID() const noexcept { return uniqueID; }
//==============================================================================
/** Returns the raw handle to whatever kind of window is being used.
On windows, this is probably a HWND, on the mac, it's likely to be a WindowRef,
but remember there's no guarantees what you'll get back.
*/
virtual void* getNativeHandle() const = 0;
/** Shows or hides the window. */
virtual void setVisible (bool shouldBeVisible) = 0;
/** Changes the title of the window. */
virtual void setTitle (const String& title) = 0;
/** If this type of window is capable of indicating that the document in it has been
edited, then this changes its status.
For example in OSX, this changes the appearance of the close button.
@returns true if the window has a mechanism for showing this, or false if not.
*/
virtual bool setDocumentEditedStatus (bool edited);
/** If this type of window is capable of indicating that it represents a file, then
this lets you set the file.
E.g. in OSX it'll show an icon for the file in the title bar.
*/
virtual void setRepresentedFile (const File&);
//==============================================================================
/** Moves and resizes the window.
If the native window is contained in another window, then the coordinates are
relative to the parent window's origin, not the screen origin.
This should result in a callback to handleMovedOrResized().
*/
virtual void setBounds (const Rectangle<int>& newBounds, bool isNowFullScreen) = 0;
/** Updates the peer's bounds to match its component. */
void updateBounds();
/** Returns the current position and size of the window.
If the native window is contained in another window, then the coordinates are
relative to the parent window's origin, not the screen origin.
*/
virtual Rectangle<int> getBounds() const = 0;
/** Converts a position relative to the top-left of this component to screen coordinates. */
virtual Point<float> localToGlobal (Point<float> relativePosition) = 0;
/** Converts a screen coordinate to a position relative to the top-left of this component. */
virtual Point<float> globalToLocal (Point<float> screenPosition) = 0;
/** Converts a position relative to the top-left of this component to screen coordinates. */
Point<int> localToGlobal (Point<int> relativePosition);
/** Converts a screen coordinate to a position relative to the top-left of this component. */
Point<int> globalToLocal (Point<int> screenPosition);
/** Converts a rectangle relative to the top-left of this component to screen coordinates. */
virtual Rectangle<int> localToGlobal (const Rectangle<int>& relativePosition);
/** Converts a screen area to a position relative to the top-left of this component. */
virtual Rectangle<int> globalToLocal (const Rectangle<int>& screenPosition);
/** Converts a rectangle relative to the top-left of this component to screen coordinates. */
Rectangle<float> localToGlobal (const Rectangle<float>& relativePosition);
/** Converts a screen area to a position relative to the top-left of this component. */
Rectangle<float> globalToLocal (const Rectangle<float>& screenPosition);
/** Returns the area in peer coordinates that is covered by the given sub-comp (which
may be at any depth)
*/
Rectangle<int> getAreaCoveredBy (const Component& subComponent) const;
/** Minimises the window. */
virtual void setMinimised (bool shouldBeMinimised) = 0;
/** True if the window is currently minimised. */
virtual bool isMinimised() const = 0;
/** Enable/disable fullscreen mode for the window. */
virtual void setFullScreen (bool shouldBeFullScreen) = 0;
/** True if the window is currently full-screen. */
virtual bool isFullScreen() const = 0;
/** True if the window is in kiosk-mode. */
virtual bool isKioskMode() const;
/** Sets the size to restore to if fullscreen mode is turned off. */
void setNonFullScreenBounds (const Rectangle<int>& newBounds) noexcept;
/** Returns the size to restore to if fullscreen mode is turned off. */
const Rectangle<int>& getNonFullScreenBounds() const noexcept;
/** Attempts to change the icon associated with this window. */
virtual void setIcon (const Image& newIcon) = 0;
/** Sets a constrainer to use if the peer can resize itself.
The constrainer won't be deleted by this object, so the caller must manage its lifetime.
*/
void setConstrainer (ComponentBoundsConstrainer* newConstrainer) noexcept;
/** Asks the window-manager to begin resizing this window, on platforms where this is useful
(currently just Linux/X11).
@param mouseDownPosition The position of the mouse event that started the resize in
unscaled peer coordinates
@param zone The edges of the window that may be moved during the resize
*/
virtual void startHostManagedResize ([[maybe_unused]] Point<int> mouseDownPosition,
[[maybe_unused]] ResizableBorderComponent::Zone zone) {}
/** Returns the current constrainer, if one has been set. */
ComponentBoundsConstrainer* getConstrainer() const noexcept { return constrainer; }
/** Checks if a point is in the window.
The position is relative to the top-left of this window, in unscaled peer coordinates.
If trueIfInAChildWindow is false, then this returns false if the point is actually
inside a child of this window.
*/
virtual bool contains (Point<int> localPos, bool trueIfInAChildWindow) const = 0;
/** Returns the size of the window frame that's around this window.
Depending on the platform the border size may be invalid for a short transient
after creating a new window. Hence the returned value must be checked using
operator bool() and the contained value can be accessed using operator*() only
if it is present.
Whether or not the window has a normal window frame depends on the flags
that were set when the window was created by Component::addToDesktop()
*/
virtual OptionalBorderSize getFrameSizeIfPresent() const = 0;
/** Returns the size of the window frame that's around this window.
Whether or not the window has a normal window frame depends on the flags
that were set when the window was created by Component::addToDesktop()
*/
#if JUCE_LINUX || JUCE_BSD
[[deprecated ("Use getFrameSizeIfPresent instead.")]]
#endif
virtual BorderSize<int> getFrameSize() const = 0;
/** This is called when the window's bounds change.
A peer implementation must call this when the window is moved and resized, so that
this method can pass the message on to the component.
*/
void handleMovedOrResized();
/** This is called if the screen resolution changes.
A peer implementation must call this if the monitor arrangement changes or the available
screen size changes.
*/
virtual void handleScreenSizeChange();
//==============================================================================
/** This is called to repaint the component into the given context. */
void handlePaint (LowLevelGraphicsContext& contextToPaintTo);
//==============================================================================
/** Sets this window to either be always-on-top or normal.
Some kinds of window might not be able to do this, so should return false.
*/
virtual bool setAlwaysOnTop (bool alwaysOnTop) = 0;
/** Brings the window to the top, optionally also giving it keyboard focus. */
virtual void toFront (bool takeKeyboardFocus) = 0;
/** Moves the window to be just behind another one. */
virtual void toBehind (ComponentPeer* other) = 0;
/** Called when the window is brought to the front, either by the OS or by a call
to toFront().
*/
void handleBroughtToFront();
//==============================================================================
/** True if the window has the keyboard focus. */
virtual bool isFocused() const = 0;
/** Tries to give the window keyboard focus. */
virtual void grabFocus() = 0;
/** Called when the window gains keyboard focus. */
void handleFocusGain();
/** Called when the window loses keyboard focus. */
void handleFocusLoss();
Component* getLastFocusedSubcomponent() const noexcept;
/** Called when a key is pressed.
For keycode info, see the KeyPress class.
Returns true if the keystroke was used.
*/
bool handleKeyPress (int keyCode, juce_wchar textCharacter);
/** Called when a key is pressed.
Returns true if the keystroke was used.
*/
bool handleKeyPress (const KeyPress& key);
/** Called whenever a key is pressed or released.
Returns true if the keystroke was used.
*/
bool handleKeyUpOrDown (bool isKeyDown);
/** Called whenever a modifier key is pressed or released. */
void handleModifierKeysChange();
/** If there's a currently active input-method context - i.e. characters are being
composed using multiple keystrokes - this should commit the current state of the
context to the text and clear the context. This should not hide the virtual keyboard.
*/
virtual void closeInputMethodContext();
/** Alerts the peer that the current text input target has changed somehow.
The peer may hide or show the virtual keyboard as a result of this call.
*/
void refreshTextInputTarget();
//==============================================================================
/** Returns the currently focused TextInputTarget, or null if none is found. */
TextInputTarget* findCurrentTextInputTarget();
//==============================================================================
/** Invalidates a region of the window to be repainted asynchronously. */
virtual void repaint (const Rectangle<int>& area) = 0;
/** This can be called (from the message thread) to cause the immediate redrawing
of any areas of this window that need repainting.
You shouldn't ever really need to use this, it's mainly for special purposes
like supporting audio plugins where the host's event loop is out of our control.
*/
virtual void performAnyPendingRepaintsNow() = 0;
/** Changes the window's transparency. */
virtual void setAlpha (float newAlpha) = 0;
//==============================================================================
void handleMouseEvent (MouseInputSource::InputSourceType type, Point<float> positionWithinPeer, ModifierKeys newMods, float pressure,
float orientation, int64 time, PenDetails pen = {}, int touchIndex = 0);
void handleMouseWheel (MouseInputSource::InputSourceType type, Point<float> positionWithinPeer,
int64 time, const MouseWheelDetails&, int touchIndex = 0);
void handleMagnifyGesture (MouseInputSource::InputSourceType type, Point<float> positionWithinPeer,
int64 time, float scaleFactor, int touchIndex = 0);
void handleUserClosingWindow();
/** Structure to describe drag and drop information */
struct DragInfo
{
StringArray files;
String text;
Point<int> position;
bool isEmpty() const noexcept { return files.size() == 0 && text.isEmpty(); }
void clear() noexcept { files.clear(); text.clear(); }
};
bool handleDragMove (const DragInfo&);
bool handleDragExit (const DragInfo&);
bool handleDragDrop (const DragInfo&);
//==============================================================================
/** Returns the number of currently-active peers.
@see getPeer
*/
static int getNumPeers() noexcept;
/** Returns one of the currently-active peers.
@see getNumPeers
*/
static ComponentPeer* getPeer (int index) noexcept;
/** Returns the peer that's attached to the given component, or nullptr if there isn't one. */
static ComponentPeer* getPeerFor (const Component*) noexcept;
/** Checks if this peer object is valid.
@see getNumPeers
*/
static bool isValidPeer (const ComponentPeer* peer) noexcept;
//==============================================================================
virtual StringArray getAvailableRenderingEngines() = 0;
virtual int getCurrentRenderingEngine() const;
virtual void setCurrentRenderingEngine (int index);
//==============================================================================
/** On desktop platforms this method will check all the mouse and key states and return
a ModifierKeys object representing them.
This isn't recommended and is only needed in special circumstances for up-to-date
modifier information at times when the app's event loop isn't running normally.
Another reason to avoid this method is that it's not stateless and calling it may
update the ModifierKeys::currentModifiers object, which could cause subtle changes
in the behaviour of some components.
*/
static ModifierKeys getCurrentModifiersRealtime() noexcept;
//==============================================================================
/** Used to receive callbacks when the OS scale factor of this ComponentPeer changes.
This is used internally by some native JUCE windows on Windows and Linux and you
shouldn't need to worry about it in your own code unless you are dealing directly
with native windows.
*/
struct JUCE_API ScaleFactorListener
{
/** Destructor. */
virtual ~ScaleFactorListener() = default;
/** Called when the scale factor changes. */
virtual void nativeScaleFactorChanged (double newScaleFactor) = 0;
};
/** Adds a scale factor listener. */
void addScaleFactorListener (ScaleFactorListener* listenerToAdd) { scaleFactorListeners.add (listenerToAdd); }
/** Removes a scale factor listener. */
void removeScaleFactorListener (ScaleFactorListener* listenerToRemove) { scaleFactorListeners.remove (listenerToRemove); }
//==============================================================================
/** Used to receive callbacks on every vertical blank event of the display that the peer
currently belongs to.
On Linux this is currently limited to receiving callbacks from a timer approximately at
display refresh rate.
This is a low-level facility used by the peer implementations. If you wish to synchronise
Component events with the display refresh, you should probably use the VBlankAttachment,
which automatically takes care of listening to the vblank events of the right peer.
@see VBlankAttachment
*/
struct JUCE_API VBlankListener
{
/** Destructor. */
virtual ~VBlankListener() = default;
/** Called on every vertical blank of the display to which the peer is associated. */
virtual void onVBlank() = 0;
};
/** Adds a VBlankListener. */
void addVBlankListener (VBlankListener* listenerToAdd) { vBlankListeners.add (listenerToAdd); }
/** Removes a VBlankListener. */
void removeVBlankListener (VBlankListener* listenerToRemove) { vBlankListeners.remove (listenerToRemove); }
//==============================================================================
/** On Windows and Linux this will return the OS scaling factor currently being applied
to the native window. This is used to convert between physical and logical pixels
at the OS API level and you shouldn't need to use it in your own code unless you
are dealing directly with the native window.
*/
virtual double getPlatformScaleFactor() const noexcept { return 1.0; }
/** On platforms that support it, this will update the window's titlebar in some
way to indicate that the window's document needs saving.
*/
virtual void setHasChangedSinceSaved (bool) {}
enum class Style
{
/** A style that matches the system-wide style. */
automatic,
/** A light style, which will probably use dark text on a light background. */
light,
/** A dark style, which will probably use light text on a dark background. */
dark
};
/** On operating systems that support it, this will update the style of this
peer as requested.
Note that this will not update the theme system-wide. This will only
update UI elements so that they display appropriately for this peer!
*/
void setAppStyle (Style s)
{
if (std::exchange (style, s) != style)
appStyleChanged();
}
/** Returns the style requested for this app. */
Style getAppStyle() const { return style; }
protected:
//==============================================================================
static void forceDisplayUpdate();
Component& component;
const int styleFlags;
Rectangle<int> lastNonFullscreenBounds;
ComponentBoundsConstrainer* constrainer = nullptr;
static std::function<ModifierKeys()> getNativeRealtimeModifiers;
ListenerList<ScaleFactorListener> scaleFactorListeners;
ListenerList<VBlankListener> vBlankListeners;
Style style = Style::automatic;
private:
//==============================================================================
virtual void appStyleChanged() {}
/** Tells the window that text input may be required at the given position.
This may cause things like a virtual on-screen keyboard to appear, depending
on the OS.
This function should not be called directly by Components - use refreshTextInputTarget
instead.
*/
virtual void textInputRequired (Point<int>, TextInputTarget&) = 0;
/** If there's some kind of OS input-method in progress, this should dismiss it.
Overrides of this function should call closeInputMethodContext().
This function should not be called directly by Components - use refreshTextInputTarget
instead.
*/
virtual void dismissPendingTextInput();
void globalFocusChanged (Component*) override;
Component* getTargetForKeyPress();
WeakReference<Component> lastFocusedComponent, dragAndDropTargetComponent;
Component* lastDragAndDropCompUnderMouse = nullptr;
TextInputTarget* textInputTarget = nullptr;
const uint32 uniqueID;
bool isWindowMinimised = false;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComponentPeer)
};
} // namespace juce
@@ -0,0 +1,181 @@
/*
==============================================================================
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 final : 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 (WindowUtils::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
@@ -0,0 +1,277 @@
/*
==============================================================================
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();
/** @internal */
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
protected:
//==============================================================================
/** @internal */
void resized() override;
/** @internal */
bool keyPressed (const KeyPress&) override;
/** @internal */
float getDesktopScaleFactor() const override { return desktopScale * Desktop::getInstance().getGlobalScaleFactor(); }
private:
float desktopScale = 1.0f;
bool escapeKeyTriggersCloseButton;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DialogWindow)
};
} // namespace juce
@@ -0,0 +1,360 @@
/*
==============================================================================
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 final : 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
@@ -0,0 +1,307 @@
/*
==============================================================================
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
@@ -0,0 +1,93 @@
/*
==============================================================================
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
{
MessageBoxOptions MessageBoxOptions::makeOptionsOk (MessageBoxIconType iconType,
const String& title,
const String& message,
const String& buttonText,
Component* associatedComponent)
{
return MessageBoxOptions()
.withIconType (iconType)
.withTitle (title)
.withMessage (message)
.withButton (buttonText.isEmpty() ? TRANS ("OK") : buttonText)
.withAssociatedComponent (associatedComponent);
}
MessageBoxOptions MessageBoxOptions::makeOptionsOkCancel (MessageBoxIconType iconType,
const String& title,
const String& message,
const String& button1Text,
const String& button2Text,
Component* associatedComponent)
{
return MessageBoxOptions()
.withIconType (iconType)
.withTitle (title)
.withMessage (message)
.withButton (button1Text.isEmpty() ? TRANS ("OK") : button1Text)
.withButton (button2Text.isEmpty() ? TRANS ("Cancel") : button2Text)
.withAssociatedComponent (associatedComponent);
}
MessageBoxOptions MessageBoxOptions::makeOptionsYesNo (MessageBoxIconType iconType,
const String& title,
const String& message,
const String& button1Text,
const String& button2Text,
Component* associatedComponent)
{
return MessageBoxOptions()
.withIconType (iconType)
.withTitle (title)
.withMessage (message)
.withButton (button1Text.isEmpty() ? TRANS ("Yes") : button1Text)
.withButton (button2Text.isEmpty() ? TRANS ("No") : button2Text)
.withAssociatedComponent (associatedComponent);
}
MessageBoxOptions MessageBoxOptions::makeOptionsYesNoCancel (MessageBoxIconType iconType,
const String& title,
const String& message,
const String& button1Text,
const String& button2Text,
const String& button3Text,
Component* associatedComponent)
{
return MessageBoxOptions()
.withIconType (iconType)
.withTitle (title)
.withMessage (message)
.withButton (button1Text.isEmpty() ? TRANS ("Yes") : button1Text)
.withButton (button2Text.isEmpty() ? TRANS ("No") : button2Text)
.withButton (button3Text.isEmpty() ? TRANS ("Cancel") : button3Text)
.withAssociatedComponent (associatedComponent);
}
} // namespace juce
@@ -0,0 +1,193 @@
/*
==============================================================================
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. */
[[nodiscard]] MessageBoxOptions withIconType (MessageBoxIconType type) const { return withMember (*this, &MessageBoxOptions::iconType, type); }
/** Sets the title of the dialog box. */
[[nodiscard]] MessageBoxOptions withTitle (const String& boxTitle) const { return withMember (*this, &MessageBoxOptions::title, boxTitle); }
/** Sets the message that should be displayed in the dialog box. */
[[nodiscard]] MessageBoxOptions withMessage (const String& boxMessage) const { return withMember (*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.
*/
[[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. */
[[nodiscard]] MessageBoxOptions withAssociatedComponent (Component* component) const { return withMember (*this, &MessageBoxOptions::associatedComponent, component); }
/** The component that will contain the message box (e.g. the AudioProcessorEditor in a plugin).
This will only affect JUCE AlertWindows. It won't affect the drawing of native message boxes.
This is mainly intended for use in AU plugins, where opening additional windows can be problematic.
*/
[[nodiscard]] MessageBoxOptions withParentComponent (Component* component) const { return withMember (*this, &MessageBoxOptions::parentComponent, 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; }
/** Returns the component that will be used as the parent of the dialog box.
@see withParentComponent
*/
Component* getParentComponent() const noexcept { return parentComponent; }
/** Creates options suitable for a message box with a single button.
If no button text is supplied, "OK" will be used.
*/
static MessageBoxOptions makeOptionsOk (MessageBoxIconType iconType,
const String& title,
const String& message,
const String& buttonText = String(),
Component* associatedComponent = nullptr);
/** Creates options suitable for a message box with two buttons.
If no button text is supplied, "OK" and "Cancel" will be used.
*/
static MessageBoxOptions makeOptionsOkCancel (MessageBoxIconType iconType,
const String& title,
const String& message,
const String& button1Text = String(),
const String& button2Text = String(),
Component* associatedComponent = nullptr);
/** Creates options suitable for a message box with two buttons.
If no button text is supplied, "Yes" and "No" will be used.
*/
static MessageBoxOptions makeOptionsYesNo (MessageBoxIconType iconType,
const String& title,
const String& message,
const String& button1Text = String(),
const String& button2Text = String(),
Component* associatedComponent = nullptr);
/** Creates options suitable for a message box with three buttons.
*
If no button text is supplied, "Yes", "No", and "Cancel" will be used.
*/
static MessageBoxOptions makeOptionsYesNoCancel (MessageBoxIconType iconType,
const String& title,
const String& message,
const String& button1Text = String(),
const String& button2Text = String(),
const String& button3Text = String(),
Component* associatedComponent = nullptr);
private:
//==============================================================================
MessageBoxIconType iconType = MessageBoxIconType::InfoIcon;
String title, message;
StringArray buttons;
WeakReference<Component> associatedComponent;
WeakReference<Component> parentComponent;
};
} // namespace juce
@@ -0,0 +1,158 @@
/*
==============================================================================
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
{
enum class ResultCodeMappingMode
{
plainIndex, // The result code is equal to the index of the selected button.
// This is used for NativeMessageBox::show, showAsync, and showMessageBox.
alertWindow, // The result code is mapped in the same way as AlertWindow, i.e. if there
// are N buttons then button X will return ((X + 1) % N).
};
static std::unique_ptr<detail::ScopedMessageBoxInterface> makeNativeMessageBoxWithMappedResult (const MessageBoxOptions& opts,
ResultCodeMappingMode mode)
{
class Adapter final : public detail::ScopedMessageBoxInterface
{
public:
explicit Adapter (const MessageBoxOptions& options)
: inner (detail::ScopedMessageBoxInterface::create (options)),
numButtons (options.getNumButtons()) {}
void runAsync (std::function<void (int)> fn) override
{
inner->runAsync ([fn, n = numButtons] (int result)
{
fn (map (result, n));
});
}
int runSync() override
{
return map (inner->runSync(), numButtons);
}
void close() override
{
inner->close();
}
private:
static int map (int button, int numButtons) { return (button + 1) % numButtons; }
std::unique_ptr<detail::ScopedMessageBoxInterface> inner;
int numButtons = 0;
};
return mode == ResultCodeMappingMode::plainIndex ? detail::ScopedMessageBoxInterface::create (opts)
: std::make_unique<Adapter> (opts);
}
static int showNativeBoxUnmanaged (const MessageBoxOptions& opts,
ModalComponentManager::Callback* cb,
ResultCodeMappingMode mode)
{
auto implementation = makeNativeMessageBoxWithMappedResult (opts, mode);
return detail::ConcreteScopedMessageBoxImpl::showUnmanaged (std::move (implementation), cb);
}
#if JUCE_MODAL_LOOPS_PERMITTED
void JUCE_CALLTYPE NativeMessageBox::showMessageBox (MessageBoxIconType iconType,
const String& title, const String& message,
Component* associatedComponent)
{
showNativeBoxUnmanaged (MessageBoxOptions().withIconType (iconType)
.withTitle (title)
.withMessage (message)
.withButton (TRANS ("OK"))
.withAssociatedComponent (associatedComponent),
nullptr,
ResultCodeMappingMode::plainIndex);
}
int JUCE_CALLTYPE NativeMessageBox::show (const MessageBoxOptions& options)
{
return showNativeBoxUnmanaged (options, nullptr, ResultCodeMappingMode::plainIndex);
}
#endif
void JUCE_CALLTYPE NativeMessageBox::showMessageBoxAsync (MessageBoxIconType iconType,
const String& title, const String& message,
Component* associatedComponent,
ModalComponentManager::Callback* callback)
{
auto options = MessageBoxOptions::makeOptionsOk (iconType, title, message, {}, associatedComponent);
showNativeBoxUnmanaged (options, callback, ResultCodeMappingMode::alertWindow);
}
bool JUCE_CALLTYPE NativeMessageBox::showOkCancelBox (MessageBoxIconType iconType,
const String& title, const String& message,
Component* associatedComponent,
ModalComponentManager::Callback* callback)
{
auto options = MessageBoxOptions::makeOptionsOkCancel (iconType, title, message, {}, {}, associatedComponent);
return showNativeBoxUnmanaged (options, callback, ResultCodeMappingMode::alertWindow) != 0;
}
int JUCE_CALLTYPE NativeMessageBox::showYesNoCancelBox (MessageBoxIconType iconType,
const String& title, const String& message,
Component* associatedComponent,
ModalComponentManager::Callback* callback)
{
auto options = MessageBoxOptions::makeOptionsYesNoCancel (iconType, title, message, {}, {}, {}, associatedComponent);
return showNativeBoxUnmanaged (options, callback, ResultCodeMappingMode::alertWindow);
}
int JUCE_CALLTYPE NativeMessageBox::showYesNoBox (MessageBoxIconType iconType,
const String& title, const String& message,
Component* associatedComponent,
ModalComponentManager::Callback* callback)
{
auto options = MessageBoxOptions::makeOptionsYesNo (iconType, title, message, {}, {}, associatedComponent);
return showNativeBoxUnmanaged (options, callback, ResultCodeMappingMode::alertWindow);
}
void JUCE_CALLTYPE NativeMessageBox::showAsync (const MessageBoxOptions& options,
ModalComponentManager::Callback* callback)
{
showNativeBoxUnmanaged (options, callback, ResultCodeMappingMode::plainIndex);
}
void JUCE_CALLTYPE NativeMessageBox::showAsync (const MessageBoxOptions& options,
std::function<void (int)> callback)
{
showAsync (options, ModalCallbackFunction::create (callback));
}
ScopedMessageBox NativeMessageBox::showScopedAsync (const MessageBoxOptions& options, std::function<void (int)> callback)
{
auto implementation = makeNativeMessageBoxWithMappedResult (options, ResultCodeMappingMode::alertWindow);
return detail::ConcreteScopedMessageBoxImpl::show (std::move (implementation), std::move (callback));
}
} // namespace juce
@@ -0,0 +1,302 @@
/*
==============================================================================
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
/** 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.
This function is always asynchronous, even if the callback is null.
For consistency with AlertWindow, the result codes returned by the alert window are as
follows.
- One button:
- button[0] returns 0
- Two buttons:
- button[0] returns 1
- button[1] returns 0
- Three buttons:
- button[0] returns 1
- button[1] returns 2
- button[2] returns 0
Another way of expressing this is that, when there are N buttons, then the result code for
button X is equal to ((X + 1) % N).
@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.
@returns a ScopedMessageBox instance. The message box will remain visible for no
longer than the ScopedMessageBox remains alive.
@see MessageBoxOptions
*/
[[nodiscard]] static ScopedMessageBox showScopedAsync (const MessageBoxOptions& options,
std::function<void (int)> callback);
private:
NativeMessageBox() = delete;
JUCE_DECLARE_NON_COPYABLE (NativeMessageBox)
};
} // namespace juce
@@ -0,0 +1,66 @@
/*
==============================================================================
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
{
static void removeScaleFactorListenerFromAllPeers (ComponentPeer::ScaleFactorListener& listener)
{
for (int i = 0; i < ComponentPeer::getNumPeers(); ++i)
ComponentPeer::getPeer (i)->removeScaleFactorListener (&listener);
}
NativeScaleFactorNotifier::NativeScaleFactorNotifier (Component* comp, std::function<void (float)> onScaleChanged)
: ComponentMovementWatcher (comp),
scaleChanged (std::move (onScaleChanged))
{
componentPeerChanged();
}
NativeScaleFactorNotifier::~NativeScaleFactorNotifier()
{
removeScaleFactorListenerFromAllPeers (*this);
}
void NativeScaleFactorNotifier::nativeScaleFactorChanged (double newScaleFactor)
{
NullCheckedInvocation::invoke (scaleChanged, (float) newScaleFactor);
}
void NativeScaleFactorNotifier::componentPeerChanged()
{
removeScaleFactorListenerFromAllPeers (*this);
if (auto* x = getComponent())
peer = x->getPeer();
if (auto* x = peer)
{
x->addScaleFactorListener (this);
nativeScaleFactorChanged (x->getPlatformScaleFactor());
}
}
} // namespace juce
@@ -0,0 +1,69 @@
/*
==============================================================================
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
{
/**
Calls a function every time the native scale factor of a component's peer changes.
This is used in the VST and VST3 wrappers to ensure that the editor's scale is kept in sync with
the scale of its containing component.
@tags{GUI}
*/
class NativeScaleFactorNotifier : private ComponentMovementWatcher,
private ComponentPeer::ScaleFactorListener
{
public:
/** Constructs an instance.
While the instance is alive, it will listen for changes to the scale factor of the
comp's peer, and will call onScaleChanged whenever this scale factor changes.
@param comp The component to observe
@param onScaleChanged A function that will be called when the backing scale factor changes
*/
NativeScaleFactorNotifier (Component* comp, std::function<void (float)> onScaleChanged);
~NativeScaleFactorNotifier() override;
private:
void nativeScaleFactorChanged (double newScaleFactor) override;
void componentPeerChanged() override;
using ComponentMovementWatcher::componentVisibilityChanged;
void componentVisibilityChanged() override {}
using ComponentMovementWatcher::componentMovedOrResized;
void componentMovedOrResized (bool, bool) override {}
ComponentPeer* peer = nullptr;
std::function<void (float)> scaleChanged;
JUCE_DECLARE_NON_COPYABLE (NativeScaleFactorNotifier)
JUCE_DECLARE_NON_MOVEABLE (NativeScaleFactorNotifier)
};
} // namespace juce
@@ -0,0 +1,693 @@
/*
==============================================================================
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
{
ResizableWindow::ResizableWindow (const String& name, bool shouldAddToDesktop)
: TopLevelWindow (name, shouldAddToDesktop)
{
initialise (shouldAddToDesktop);
}
ResizableWindow::ResizableWindow (const String& name, Colour bkgnd, bool shouldAddToDesktop)
: TopLevelWindow (name, shouldAddToDesktop)
{
setBackgroundColour (bkgnd);
initialise (shouldAddToDesktop);
}
ResizableWindow::~ResizableWindow()
{
splashScreen.deleteAndZero();
// Don't delete or remove the resizer components yourself! They're managed by the
// ResizableWindow, and you should leave them alone! You may have deleted them
// accidentally by careless use of deleteAllChildren()..?
jassert (resizableCorner == nullptr || getIndexOfChildComponent (resizableCorner.get()) >= 0);
jassert (resizableBorder == nullptr || getIndexOfChildComponent (resizableBorder.get()) >= 0);
resizableCorner.reset();
resizableBorder.reset();
clearContentComponent();
// have you been adding your own components directly to this window..? tut tut tut.
// Read the instructions for using a ResizableWindow!
jassert (getNumChildComponents() == 0);
}
void ResizableWindow::initialise (const bool shouldAddToDesktop)
{
/*
==========================================================================
In accordance with the terms of the JUCE 7 End-Use License Agreement, the
JUCE Code in SECTION A cannot be removed, changed or otherwise rendered
ineffective unless you have a JUCE Indie or Pro license, or are using
JUCE under the GPL v3 license.
End User License Agreement: www.juce.com/juce-7-licence
==========================================================================
*/
// BEGIN SECTION A
#if ! JucePlugin_Build_Standalone
splashScreen = new JUCESplashScreen (*this);
#endif
// END SECTION A
defaultConstrainer.setMinimumOnscreenAmounts (0x10000, 16, 24, 16);
lastNonFullScreenPos.setBounds (50, 50, 256, 256);
if (shouldAddToDesktop)
addToDesktop();
}
int ResizableWindow::getDesktopWindowStyleFlags() const
{
int styleFlags = TopLevelWindow::getDesktopWindowStyleFlags();
if (isResizable() && (styleFlags & ComponentPeer::windowHasTitleBar) != 0)
styleFlags |= ComponentPeer::windowIsResizable;
return styleFlags;
}
//==============================================================================
void ResizableWindow::clearContentComponent()
{
if (ownsContentComponent)
{
contentComponent.deleteAndZero();
}
else
{
removeChildComponent (contentComponent);
contentComponent = nullptr;
}
}
void ResizableWindow::setContent (Component* newContentComponent,
bool takeOwnership,
bool resizeToFitWhenContentChangesSize)
{
if (newContentComponent != contentComponent)
{
clearContentComponent();
contentComponent = newContentComponent;
Component::addAndMakeVisible (contentComponent);
}
ownsContentComponent = takeOwnership;
resizeToFitContent = resizeToFitWhenContentChangesSize;
if (resizeToFitWhenContentChangesSize)
childBoundsChanged (contentComponent);
resized(); // must always be called to position the new content comp
}
void ResizableWindow::setContentOwned (Component* newContentComponent, const bool resizeToFitWhenContentChangesSize)
{
setContent (newContentComponent, true, resizeToFitWhenContentChangesSize);
}
void ResizableWindow::setContentNonOwned (Component* newContentComponent, const bool resizeToFitWhenContentChangesSize)
{
setContent (newContentComponent, false, resizeToFitWhenContentChangesSize);
}
void ResizableWindow::setContentComponent (Component* const newContentComponent,
const bool deleteOldOne,
const bool resizeToFitWhenContentChangesSize)
{
if (newContentComponent != contentComponent)
{
if (deleteOldOne)
{
contentComponent.deleteAndZero();
}
else
{
removeChildComponent (contentComponent);
contentComponent = nullptr;
}
}
setContent (newContentComponent, true, resizeToFitWhenContentChangesSize);
}
void ResizableWindow::setContentComponentSize (int width, int height)
{
jassert (width > 0 && height > 0); // not a great idea to give it a zero size..
auto border = getContentComponentBorder();
setSize (width + border.getLeftAndRight(),
height + border.getTopAndBottom());
}
BorderSize<int> ResizableWindow::getBorderThickness()
{
if (isUsingNativeTitleBar() || isKioskMode())
return {};
return BorderSize<int> ((resizableBorder != nullptr && ! isFullScreen()) ? 4 : 1);
}
BorderSize<int> ResizableWindow::getContentComponentBorder()
{
return getBorderThickness();
}
void ResizableWindow::moved()
{
updateLastPosIfShowing();
}
void ResizableWindow::visibilityChanged()
{
TopLevelWindow::visibilityChanged();
updateLastPosIfShowing();
}
void ResizableWindow::resized()
{
const bool resizerHidden = isFullScreen() || isKioskMode() || isUsingNativeTitleBar();
if (resizableBorder != nullptr)
{
resizableBorder->setVisible (! resizerHidden);
resizableBorder->setBorderThickness (getBorderThickness());
resizableBorder->setSize (getWidth(), getHeight());
resizableBorder->toBack();
}
if (resizableCorner != nullptr)
{
resizableCorner->setVisible (! resizerHidden);
const int resizerSize = 18;
resizableCorner->setBounds (getWidth() - resizerSize,
getHeight() - resizerSize,
resizerSize, resizerSize);
}
if (contentComponent != nullptr)
{
// The window expects to be able to be able to manage the size and position
// of its content component, so you can't arbitrarily add a transform to it!
jassert (! contentComponent->isTransformed());
contentComponent->setBoundsInset (getContentComponentBorder());
}
updateLastPosIfShowing();
#if JUCE_DEBUG
hasBeenResized = true;
#endif
}
void ResizableWindow::childBoundsChanged (Component* child)
{
if ((child == contentComponent) && (child != nullptr) && resizeToFitContent)
{
// not going to look very good if this component has a zero size..
jassert (child->getWidth() > 0);
jassert (child->getHeight() > 0);
auto borders = getContentComponentBorder();
setSize (child->getWidth() + borders.getLeftAndRight(),
child->getHeight() + borders.getTopAndBottom());
}
}
//==============================================================================
void ResizableWindow::activeWindowStatusChanged()
{
auto border = getContentComponentBorder();
auto area = getLocalBounds();
repaint (area.removeFromTop (border.getTop()));
repaint (area.removeFromLeft (border.getLeft()));
repaint (area.removeFromRight (border.getRight()));
repaint (area.removeFromBottom (border.getBottom()));
}
//==============================================================================
void ResizableWindow::setResizable (const bool shouldBeResizable,
const bool useBottomRightCornerResizer)
{
if (shouldBeResizable)
{
if (useBottomRightCornerResizer)
{
resizableBorder.reset();
if (resizableCorner == nullptr)
{
resizableCorner.reset (new ResizableCornerComponent (this, constrainer));
Component::addChildComponent (resizableCorner.get());
resizableCorner->setAlwaysOnTop (true);
}
}
else
{
resizableCorner.reset();
if (resizableBorder == nullptr)
{
resizableBorder.reset (new ResizableBorderComponent (this, constrainer));
Component::addChildComponent (resizableBorder.get());
}
}
}
else
{
resizableCorner.reset();
resizableBorder.reset();
}
if (isUsingNativeTitleBar())
recreateDesktopWindow();
childBoundsChanged (contentComponent);
resized();
}
bool ResizableWindow::isResizable() const noexcept
{
return resizableCorner != nullptr
|| resizableBorder != nullptr;
}
void ResizableWindow::setResizeLimits (int newMinimumWidth,
int newMinimumHeight,
int newMaximumWidth,
int newMaximumHeight) noexcept
{
// if you've set up a custom constrainer then these settings won't have any effect..
jassert (constrainer == &defaultConstrainer || constrainer == nullptr);
if (constrainer == nullptr)
setConstrainer (&defaultConstrainer);
defaultConstrainer.setSizeLimits (newMinimumWidth, newMinimumHeight,
newMaximumWidth, newMaximumHeight);
setBoundsConstrained (getBounds());
}
void ResizableWindow::setDraggable (bool shouldBeDraggable) noexcept
{
canDrag = shouldBeDraggable;
}
void ResizableWindow::setConstrainer (ComponentBoundsConstrainer* newConstrainer)
{
if (constrainer != newConstrainer)
{
constrainer = newConstrainer;
bool useBottomRightCornerResizer = resizableCorner != nullptr;
bool shouldBeResizable = useBottomRightCornerResizer || resizableBorder != nullptr;
resizableCorner.reset();
resizableBorder.reset();
setResizable (shouldBeResizable, useBottomRightCornerResizer);
updatePeerConstrainer();
}
}
void ResizableWindow::setBoundsConstrained (const Rectangle<int>& newBounds)
{
if (constrainer != nullptr)
constrainer->setBoundsForComponent (this, newBounds, false, false, false, false);
else
setBounds (newBounds);
}
//==============================================================================
void ResizableWindow::paint (Graphics& g)
{
auto& lf = getLookAndFeel();
lf.fillResizableWindowBackground (g, getWidth(), getHeight(),
getBorderThickness(), *this);
if (! isFullScreen())
lf.drawResizableWindowBorder (g, getWidth(), getHeight(),
getBorderThickness(), *this);
#if JUCE_DEBUG
/* If this fails, then you've probably written a subclass with a resized()
callback but forgotten to make it call its parent class's resized() method.
It's important when you override methods like resized(), moved(),
etc., that you make sure the base class methods also get called.
Of course you shouldn't really be overriding ResizableWindow::resized() anyway,
because your content should all be inside the content component - and it's the
content component's resized() method that you should be using to do your
layout.
*/
jassert (hasBeenResized || (getWidth() == 0 && getHeight() == 0));
#endif
}
void ResizableWindow::lookAndFeelChanged()
{
resized();
if (isOnDesktop())
{
Component::addToDesktop (getDesktopWindowStyleFlags());
updatePeerConstrainer();
}
}
Colour ResizableWindow::getBackgroundColour() const noexcept
{
return findColour (backgroundColourId, false);
}
void ResizableWindow::setBackgroundColour (Colour newColour)
{
auto backgroundColour = newColour;
if (! Desktop::canUseSemiTransparentWindows())
backgroundColour = newColour.withAlpha (1.0f);
setColour (backgroundColourId, backgroundColour);
setOpaque (backgroundColour.isOpaque());
repaint();
}
//==============================================================================
bool ResizableWindow::isFullScreen() const
{
if (isOnDesktop())
{
auto* peer = getPeer();
return peer != nullptr && peer->isFullScreen();
}
return fullscreen;
}
void ResizableWindow::setFullScreen (const bool shouldBeFullScreen)
{
if (shouldBeFullScreen != isFullScreen())
{
updateLastPosIfShowing();
fullscreen = shouldBeFullScreen;
if (isOnDesktop())
{
if (auto* peer = getPeer())
{
// keep a copy of this intact in case the real one gets messed-up while we're un-maximising
auto lastPos = lastNonFullScreenPos;
peer->setFullScreen (shouldBeFullScreen);
if ((! shouldBeFullScreen) && ! lastPos.isEmpty())
setBounds (lastPos);
}
else
{
jassertfalse;
}
}
else
{
if (shouldBeFullScreen)
setBounds (0, 0, getParentWidth(), getParentHeight());
else
setBounds (lastNonFullScreenPos);
}
resized();
}
}
bool ResizableWindow::isMinimised() const
{
if (auto* peer = getPeer())
return peer->isMinimised();
return false;
}
void ResizableWindow::setMinimised (const bool shouldMinimise)
{
if (shouldMinimise != isMinimised())
{
if (auto* peer = getPeer())
{
updateLastPosIfShowing();
peer->setMinimised (shouldMinimise);
}
else
{
jassertfalse;
}
}
}
bool ResizableWindow::isKioskMode() const
{
if (isOnDesktop())
if (auto* peer = getPeer())
return peer->isKioskMode();
return Desktop::getInstance().getKioskModeComponent() == this;
}
void ResizableWindow::updateLastPosIfShowing()
{
if (isShowing())
{
updateLastPosIfNotFullScreen();
updatePeerConstrainer();
}
}
void ResizableWindow::updateLastPosIfNotFullScreen()
{
if (! (isFullScreen() || isMinimised() || isKioskMode()))
lastNonFullScreenPos = getBounds();
}
void ResizableWindow::updatePeerConstrainer()
{
if (isOnDesktop())
if (auto* peer = getPeer())
peer->setConstrainer (constrainer);
}
void ResizableWindow::parentSizeChanged()
{
if (isFullScreen() && getParentComponent() != nullptr)
setBounds (getParentComponent()->getLocalBounds());
}
//==============================================================================
String ResizableWindow::getWindowStateAsString()
{
updateLastPosIfShowing();
auto stateString = (isFullScreen() && ! isKioskMode() ? "fs " : "") + lastNonFullScreenPos.toString();
#if JUCE_LINUX
if (auto* peer = isOnDesktop() ? getPeer() : nullptr)
{
if (const auto optionalFrameSize = peer->getFrameSizeIfPresent())
{
const auto& frameSize = *optionalFrameSize;
stateString << " frame " << frameSize.getTop() << ' ' << frameSize.getLeft()
<< ' ' << frameSize.getBottom() << ' ' << frameSize.getRight();
}
}
#endif
return stateString;
}
bool ResizableWindow::restoreWindowStateFromString (const String& s)
{
StringArray tokens;
tokens.addTokens (s, false);
tokens.removeEmptyStrings();
tokens.trim();
const bool fs = tokens[0].startsWithIgnoreCase ("fs");
const int firstCoord = fs ? 1 : 0;
if (tokens.size() < firstCoord + 4)
return false;
Rectangle<int> newPos (tokens[firstCoord].getIntValue(),
tokens[firstCoord + 1].getIntValue(),
tokens[firstCoord + 2].getIntValue(),
tokens[firstCoord + 3].getIntValue());
if (newPos.isEmpty())
return false;
auto* peer = isOnDesktop() ? getPeer() : nullptr;
if (peer != nullptr)
{
if (const auto frameSize = peer->getFrameSizeIfPresent())
frameSize->addTo (newPos);
}
#if JUCE_LINUX
if (peer == nullptr || ! peer->getFrameSizeIfPresent())
{
// We need to adjust for the frame size before we create a peer, as X11
// doesn't provide this information at construction time.
if (tokens[firstCoord + 4] == "frame" && tokens.size() == firstCoord + 9)
{
BorderSize<int> frame { tokens[firstCoord + 5].getIntValue(),
tokens[firstCoord + 6].getIntValue(),
tokens[firstCoord + 7].getIntValue(),
tokens[firstCoord + 8].getIntValue() };
newPos.setX (newPos.getX() - frame.getLeft());
newPos.setY (newPos.getY() - frame.getTop());
setBounds (newPos);
}
}
#endif
{
auto& desktop = Desktop::getInstance();
auto allMonitors = desktop.getDisplays().getRectangleList (true);
allMonitors.clipTo (newPos);
auto onScreenArea = allMonitors.getBounds();
if (onScreenArea.getWidth() * onScreenArea.getHeight() < 32 * 32)
{
auto screen = desktop.getDisplays().getDisplayForRect (newPos)->userArea;
newPos.setSize (jmin (newPos.getWidth(), screen.getWidth()),
jmin (newPos.getHeight(), screen.getHeight()));
newPos.setPosition (jlimit (screen.getX(), screen.getRight() - newPos.getWidth(), newPos.getX()),
jlimit (screen.getY(), screen.getBottom() - newPos.getHeight(), newPos.getY()));
}
}
if (peer != nullptr)
{
if (const auto frameSize = peer->getFrameSizeIfPresent())
frameSize->subtractFrom (newPos);
peer->setNonFullScreenBounds (newPos);
}
updateLastPosIfNotFullScreen();
if (fs)
setBoundsConstrained (newPos);
setFullScreen (fs);
if (! fs)
setBoundsConstrained (newPos);
return true;
}
//==============================================================================
void ResizableWindow::mouseDown (const MouseEvent& e)
{
if (canDrag && ! isFullScreen())
{
dragStarted = true;
dragger.startDraggingComponent (this, e);
}
}
void ResizableWindow::mouseDrag (const MouseEvent& e)
{
if (dragStarted)
dragger.dragComponent (this, e, constrainer);
}
void ResizableWindow::mouseUp (const MouseEvent&)
{
dragStarted = false;
}
//==============================================================================
#if JUCE_DEBUG
void ResizableWindow::addChildComponent (Component* const child, int zOrder)
{
/* Agh! You shouldn't add components directly to a ResizableWindow - this class
manages its child components automatically, and if you add your own it'll cause
trouble. Instead, use setContentComponent() to give it a component which
will be automatically resized and kept in the right place - then you can add
subcomponents to the content comp. See the notes for the ResizableWindow class
for more info.
If you really know what you're doing and want to avoid this assertion, just call
Component::addChildComponent directly.
*/
jassertfalse;
Component::addChildComponent (child, zOrder);
}
void ResizableWindow::addAndMakeVisible (Component* const child, int zOrder)
{
/* Agh! You shouldn't add components directly to a ResizableWindow - this class
manages its child components automatically, and if you add your own it'll cause
trouble. Instead, use setContentComponent() to give it a component which
will be automatically resized and kept in the right place - then you can add
subcomponents to the content comp. See the notes for the ResizableWindow class
for more info.
If you really know what you're doing and want to avoid this assertion, just call
Component::addAndMakeVisible directly.
*/
jassertfalse;
Component::addAndMakeVisible (child, zOrder);
}
#endif
} // namespace juce
@@ -0,0 +1,413 @@
/*
==============================================================================
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
@@ -0,0 +1,58 @@
/*
==============================================================================
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
{
//==============================================================================
ScopedMessageBox::ScopedMessageBox() = default;
ScopedMessageBox::ScopedMessageBox (std::shared_ptr<detail::ScopedMessageBoxImpl> i)
: impl (std::move (i)) {}
ScopedMessageBox::~ScopedMessageBox() noexcept
{
close();
}
ScopedMessageBox::ScopedMessageBox (ScopedMessageBox&& other) noexcept
: impl (std::exchange (other.impl, nullptr)) {}
ScopedMessageBox& ScopedMessageBox::operator= (ScopedMessageBox&& other) noexcept
{
ScopedMessageBox temp (std::move (other));
std::swap (temp.impl, impl);
return *this;
}
void ScopedMessageBox::close()
{
if (impl != nullptr)
impl->close();
impl.reset();
}
} // namespace juce
@@ -0,0 +1,69 @@
/*
==============================================================================
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
{
/**
Objects of this type can be used to programmatically close message boxes.
@see NativeMessageBox::showScopedAsync(), AlertWindow::showScopedAsync()
@tags{GUI}
*/
class ScopedMessageBox
{
public:
/** @internal */
explicit ScopedMessageBox (std::shared_ptr<detail::ScopedMessageBoxImpl>);
/** Constructor */
ScopedMessageBox();
/** Destructor */
~ScopedMessageBox() noexcept;
/** Move constructor */
ScopedMessageBox (ScopedMessageBox&&) noexcept;
/** Move assignment operator */
ScopedMessageBox& operator= (ScopedMessageBox&&) noexcept;
/** Closes the message box, if it is currently showing.
This is also called automatically during ~ScopedMessageBox. This is useful if you want
to display a message corresponding to a particular view, and hide the message automatically
when the view is hidden. This situation commonly arises when displaying messages in plugin
editors.
*/
void close();
private:
std::shared_ptr<detail::ScopedMessageBoxImpl> impl;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ScopedMessageBox)
};
} // namespace juce
@@ -0,0 +1,119 @@
/*
==============================================================================
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 (Priority threadPriority)
{
JUCE_ASSERT_MESSAGE_THREAD
startThread (threadPriority);
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 (Priority threadPriority)
{
launchThread (threadPriority);
while (isTimerRunning())
MessageManager::getInstance()->runDispatchLoopUntil (5);
return ! wasCancelledByUser;
}
#endif
} // namespace juce
@@ -0,0 +1,175 @@
/*
==============================================================================
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::Priority for values
@returns true if the thread finished normally; false if the user pressed cancel
*/
bool runThread (Priority priority = Priority::normal);
#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::Priority for values
*/
void launchThread (Priority priority = Priority::normal);
/** 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
@@ -0,0 +1,255 @@
/*
==============================================================================
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 = detail::ScalingHelpers::scaledScreenPosToUnscaled (screenPos);
const auto scaledPos = detail::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 (detail::WindowingHelpers::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
@@ -0,0 +1,160 @@
/*
==============================================================================
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;
/** @internal */
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() 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);
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
@@ -0,0 +1,255 @@
/*
==============================================================================
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
{
//==============================================================================
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 = detail::TopLevelWindowManager::getInstance()->addWindow (this);
}
TopLevelWindow::~TopLevelWindow()
{
shadower = nullptr;
detail::TopLevelWindowManager::getInstance()->removeWindow (this);
}
//==============================================================================
void TopLevelWindow::focusOfChildComponentChanged (FocusChangeType)
{
auto* wm = detail::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)
{
detail::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();
const auto [targetCentre, parentArea] = [&]
{
const auto globalTargetCentre = c->localPointToGlobal (c->getLocalBounds().getCentre()) / scale;
if (auto* parent = getParentComponent())
return std::make_pair (parent->getLocalPoint (nullptr, globalTargetCentre), parent->getLocalBounds());
return std::make_pair (globalTargetCentre, c->getParentMonitorArea() / scale);
}();
setBounds (Rectangle<int> (targetCentre.x - width / 2,
targetCentre.y - height / 2,
width, height)
.constrainedWithin (parentArea.reduced (12, 12)));
}
}
//==============================================================================
int TopLevelWindow::getNumTopLevelWindows() noexcept
{
return detail::TopLevelWindowManager::getInstance()->windows.size();
}
TopLevelWindow* TopLevelWindow::getTopLevelWindow (const int index) noexcept
{
return detail::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
@@ -0,0 +1,167 @@
/*
==============================================================================
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;
/** @internal */
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() 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 detail::TopLevelWindowManager;
friend class ResizableWindow;
bool useDropShadow = true, useNativeTitleBar = false, isCurrentlyActive = false;
std::unique_ptr<DropShadower> shadower;
void setWindowActive (bool);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TopLevelWindow)
};
} // namespace juce
@@ -0,0 +1,113 @@
/*
==============================================================================
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
{
VBlankAttachment::VBlankAttachment (Component* c, std::function<void()> callbackIn)
: owner (c),
callback (std::move (callbackIn))
{
jassert (owner != nullptr && callback);
updateOwner();
updatePeer();
}
VBlankAttachment::VBlankAttachment (VBlankAttachment&& other)
: VBlankAttachment (other.owner, std::move (other.callback))
{
other.cleanup();
}
VBlankAttachment& VBlankAttachment::operator= (VBlankAttachment&& other)
{
cleanup();
owner = other.owner;
callback = std::move (other.callback);
updateOwner();
updatePeer();
other.cleanup();
return *this;
}
VBlankAttachment::~VBlankAttachment()
{
cleanup();
}
void VBlankAttachment::onVBlank()
{
callback();
}
void VBlankAttachment::componentParentHierarchyChanged (Component&)
{
updatePeer();
}
void VBlankAttachment::updateOwner()
{
if (auto previousLastOwner = std::exchange (lastOwner, owner); previousLastOwner != owner)
{
if (previousLastOwner != nullptr)
previousLastOwner->removeComponentListener (this);
if (owner != nullptr)
owner->addComponentListener (this);
}
}
void VBlankAttachment::updatePeer()
{
if (owner != nullptr)
{
if (auto* peer = owner->getPeer())
{
peer->addVBlankListener (this);
if (lastPeer != peer && ComponentPeer::isValidPeer (lastPeer))
lastPeer->removeVBlankListener (this);
lastPeer = peer;
}
}
else if (auto peer = std::exchange (lastPeer, nullptr); ComponentPeer::isValidPeer (peer))
{
peer->removeVBlankListener (this);
}
}
void VBlankAttachment::cleanup()
{
owner = nullptr;
updateOwner();
updatePeer();
}
} // namespace juce
@@ -0,0 +1,76 @@
/*
==============================================================================
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
{
/**
Helper class to synchronise Component updates to the vertical blank event of the display that
the Component is presented on. This is useful when animating the Component's contents.
@tags{GUI}
*/
class JUCE_API VBlankAttachment final : public ComponentPeer::VBlankListener,
public ComponentListener
{
public:
/** Default constructor for creating an empty object. */
VBlankAttachment() = default;
/** Constructor. Creates an attachment that will call the passed in function at every vertical
blank event of the display that the passed in Component is currently visible on.
The Component must be valid for the entire lifetime of the VBlankAttachment.
*/
VBlankAttachment (Component* c, std::function<void()> callbackIn);
VBlankAttachment (VBlankAttachment&& other);
VBlankAttachment& operator= (VBlankAttachment&& other);
/** Destructor. */
~VBlankAttachment() override;
/** Returns true for a default constructed object. */
bool isEmpty() const { return owner == nullptr; }
//==============================================================================
void onVBlank() override;
//==============================================================================
void componentParentHierarchyChanged (Component&) override;
private:
void updateOwner();
void updatePeer();
void cleanup();
Component* owner = nullptr;
Component* lastOwner = nullptr;
std::function<void()> callback;
ComponentPeer* lastPeer = nullptr;
JUCE_DECLARE_NON_COPYABLE (VBlankAttachment)
};
} // namespace juce
@@ -0,0 +1,42 @@
/*
==============================================================================
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
{
/**
Windowing helper functions.
@tags{GUI}
*/
struct WindowUtils
{
WindowUtils() = delete;
/** Returns true if any windows have a z order that is higher than normal. */
static bool areThereAnyAlwaysOnTopWindows();
};
} // namespace juce