800 lines
26 KiB
C++
800 lines
26 KiB
C++
|
/*
|
||
|
==============================================================================
|
||
|
|
||
|
This file is part of the JUCE library.
|
||
|
Copyright (c) 2020 - Raw Material Software Limited
|
||
|
|
||
|
JUCE is an open source library subject to commercial or open-source
|
||
|
licensing.
|
||
|
|
||
|
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||
|
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||
|
|
||
|
End User License Agreement: www.juce.com/juce-6-licence
|
||
|
Privacy Policy: www.juce.com/juce-privacy-policy
|
||
|
|
||
|
Or: You may also use this code under the terms of the GPL v3 (see
|
||
|
www.gnu.org/licenses).
|
||
|
|
||
|
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||
|
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||
|
DISCLAIMED.
|
||
|
|
||
|
==============================================================================
|
||
|
*/
|
||
|
|
||
|
namespace juce
|
||
|
{
|
||
|
|
||
|
static juce_wchar getDefaultPasswordChar() noexcept
|
||
|
{
|
||
|
#if JUCE_LINUX || JUCE_BSD
|
||
|
return 0x2022;
|
||
|
#else
|
||
|
return 0x25cf;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
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 (juce_areThereAnyAlwaysOnTopWindows());
|
||
|
|
||
|
accessibleMessageLabel.setColour (Label::textColourId, Colours::transparentBlack);
|
||
|
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();
|
||
|
}
|
||
|
|
||
|
void AlertWindow::triggerButtonClick (const String& buttonName)
|
||
|
{
|
||
|
for (auto* b : buttons)
|
||
|
{
|
||
|
if (buttonName == b->getName())
|
||
|
{
|
||
|
b->triggerClick();
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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 : 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)
|
||
|
{
|
||
|
auto* pb = new ProgressBar (progressValue);
|
||
|
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();
|
||
|
}
|
||
|
|
||
|
enum class Async { no, yes };
|
||
|
|
||
|
//==============================================================================
|
||
|
class AlertWindowInfo
|
||
|
{
|
||
|
public:
|
||
|
AlertWindowInfo (const MessageBoxOptions& opts,
|
||
|
std::unique_ptr<ModalComponentManager::Callback>&& cb,
|
||
|
Async showAsync)
|
||
|
: options (opts),
|
||
|
callback (std::move (cb)),
|
||
|
async (showAsync)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
int invoke() const
|
||
|
{
|
||
|
MessageManager::getInstance()->callFunctionOnMessageThread (showCallback, (void*) this);
|
||
|
return returnValue;
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
static void* showCallback (void* userData)
|
||
|
{
|
||
|
static_cast<AlertWindowInfo*> (userData)->show();
|
||
|
return nullptr;
|
||
|
}
|
||
|
|
||
|
void show()
|
||
|
{
|
||
|
auto* component = options.getAssociatedComponent();
|
||
|
|
||
|
auto& lf = (component != nullptr ? component->getLookAndFeel()
|
||
|
: LookAndFeel::getDefaultLookAndFeel());
|
||
|
|
||
|
std::unique_ptr<AlertWindow> alertBox (lf.createAlertWindow (options.getTitle(), options.getMessage(),
|
||
|
options.getButtonText (0), options.getButtonText (1), options.getButtonText (2),
|
||
|
options.getIconType(), options.getNumButtons(), component));
|
||
|
|
||
|
jassert (alertBox != nullptr); // you have to return one of these!
|
||
|
|
||
|
alertBox->setAlwaysOnTop (juce_areThereAnyAlwaysOnTopWindows());
|
||
|
|
||
|
#if JUCE_MODAL_LOOPS_PERMITTED
|
||
|
if (async == Async::no)
|
||
|
returnValue = alertBox->runModalLoop();
|
||
|
else
|
||
|
#endif
|
||
|
{
|
||
|
ignoreUnused (async);
|
||
|
|
||
|
alertBox->enterModalState (true, callback.release(), true);
|
||
|
alertBox.release();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
MessageBoxOptions options;
|
||
|
std::unique_ptr<ModalComponentManager::Callback> callback;
|
||
|
const Async async;
|
||
|
int returnValue = 0;
|
||
|
|
||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AlertWindowInfo)
|
||
|
};
|
||
|
|
||
|
namespace AlertWindowMappings
|
||
|
{
|
||
|
using MapFn = int (*) (int);
|
||
|
|
||
|
static inline int noMapping (int buttonIndex) { return buttonIndex; }
|
||
|
static inline int messageBox (int) { return 0; }
|
||
|
static inline int okCancel (int buttonIndex) { return buttonIndex == 0 ? 1 : 0; }
|
||
|
static inline int yesNoCancel (int buttonIndex) { return buttonIndex == 2 ? 0 : buttonIndex + 1; }
|
||
|
|
||
|
static std::unique_ptr<ModalComponentManager::Callback> getWrappedCallback (ModalComponentManager::Callback* callbackIn,
|
||
|
MapFn mapFn)
|
||
|
{
|
||
|
jassert (mapFn != nullptr);
|
||
|
|
||
|
if (callbackIn == nullptr)
|
||
|
return nullptr;
|
||
|
|
||
|
auto wrappedCallback = [innerCallback = rawToUniquePtr (callbackIn), mapFn] (int buttonIndex)
|
||
|
{
|
||
|
innerCallback->modalStateFinished (mapFn (buttonIndex));
|
||
|
};
|
||
|
|
||
|
return rawToUniquePtr (ModalCallbackFunction::create (std::move (wrappedCallback)));
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
#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);
|
||
|
|
||
|
AlertWindowInfo info (options, nullptr, Async::no);
|
||
|
return info.invoke();
|
||
|
}
|
||
|
|
||
|
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
|
||
|
{
|
||
|
AlertWindowInfo info (options, rawToUniquePtr (callback), Async::yes);
|
||
|
info.invoke();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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)
|
||
|
{
|
||
|
showAsync (MessageBoxOptions()
|
||
|
.withIconType (iconType)
|
||
|
.withTitle (title)
|
||
|
.withMessage (message)
|
||
|
.withButton (buttonText.isEmpty() ? TRANS("OK") : buttonText)
|
||
|
.withAssociatedComponent (associatedComponent),
|
||
|
callback);
|
||
|
}
|
||
|
|
||
|
static int showMaybeAsync (const MessageBoxOptions& options,
|
||
|
ModalComponentManager::Callback* callbackIn,
|
||
|
AlertWindowMappings::MapFn mapFn)
|
||
|
{
|
||
|
const auto showAsync = (callbackIn != nullptr ? Async::yes
|
||
|
: Async::no);
|
||
|
|
||
|
auto callback = AlertWindowMappings::getWrappedCallback (callbackIn, mapFn);
|
||
|
|
||
|
if (LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows())
|
||
|
{
|
||
|
#if JUCE_MODAL_LOOPS_PERMITTED
|
||
|
if (showAsync == Async::no)
|
||
|
return mapFn (NativeMessageBox::show (options));
|
||
|
#endif
|
||
|
|
||
|
NativeMessageBox::showAsync (options, callback.release());
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
AlertWindowInfo info (options, std::move (callback), showAsync);
|
||
|
return info.invoke();
|
||
|
}
|
||
|
|
||
|
bool AlertWindow::showOkCancelBox (MessageBoxIconType iconType,
|
||
|
const String& title,
|
||
|
const String& message,
|
||
|
const String& button1Text,
|
||
|
const String& button2Text,
|
||
|
Component* associatedComponent,
|
||
|
ModalComponentManager::Callback* callback)
|
||
|
{
|
||
|
return showMaybeAsync (MessageBoxOptions()
|
||
|
.withIconType (iconType)
|
||
|
.withTitle (title)
|
||
|
.withMessage (message)
|
||
|
.withButton (button1Text.isEmpty() ? TRANS("OK") : button1Text)
|
||
|
.withButton (button2Text.isEmpty() ? TRANS("Cancel") : button2Text)
|
||
|
.withAssociatedComponent (associatedComponent),
|
||
|
callback,
|
||
|
LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows()
|
||
|
? AlertWindowMappings::okCancel
|
||
|
: AlertWindowMappings::noMapping) == 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)
|
||
|
{
|
||
|
return showMaybeAsync (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),
|
||
|
callback,
|
||
|
LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows()
|
||
|
? AlertWindowMappings::yesNoCancel
|
||
|
: AlertWindowMappings::noMapping);
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
std::unique_ptr<AccessibilityHandler> AlertWindow::createAccessibilityHandler()
|
||
|
{
|
||
|
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::dialogWindow);
|
||
|
}
|
||
|
|
||
|
} // namespace juce
|