git subrepo clone --branch=sono6good https://github.com/essej/JUCE.git deps/juce

subrepo:
  subdir:   "deps/juce"
  merged:   "b13f9084e"
upstream:
  origin:   "https://github.com/essej/JUCE.git"
  branch:   "sono6good"
  commit:   "b13f9084e"
git-subrepo:
  version:  "0.4.3"
  origin:   "https://github.com/ingydotnet/git-subrepo.git"
  commit:   "2f68596"
This commit is contained in:
essej
2022-04-18 17:51:22 -04:00
parent 63e175fee6
commit 25bd5d8adb
3210 changed files with 1045392 additions and 0 deletions

View File

@ -0,0 +1,799 @@
/*
==============================================================================
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

View File

@ -0,0 +1,531 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/** A window that displays a 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 runModalLoop() method is then used to
show it. The value returned by runModalLoop() shows which button the
user pressed to dismiss the box.
@see ThreadWithProgressWindow
@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;
/** 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
*/
void addProgressBarComponent (double& progressValue);
//==============================================================================
/** 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 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).
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
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 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).
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
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
//==============================================================================
#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;
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;
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
void exitAlert (Button* button);
void updateLayout (bool onlyIncreaseSize);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AlertWindow)
};
} // namespace juce

View File

@ -0,0 +1,274 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
CallOutBox::CallOutBox (Component& c, Rectangle<int> area, Component* const parent)
: content (c)
{
addAndMakeVisible (content);
if (parent != nullptr)
{
parent->addChildComponent (this);
updatePosition (area, parent->getLocalBounds());
setVisible (true);
}
else
{
setAlwaysOnTop (juce_areThereAnyAlwaysOnTopWindows());
updatePosition (area, Desktop::getInstance().getDisplays().getDisplayForRect (area)->userArea);
addToDesktop (ComponentPeer::windowIsTemporary);
startTimer (100);
}
creationTime = Time::getCurrentTime();
}
//==============================================================================
class CallOutBoxCallback : public ModalComponentManager::Callback,
private Timer
{
public:
CallOutBoxCallback (std::unique_ptr<Component> c, const Rectangle<int>& area, Component* parent, bool dismissIfBg)
: content (std::move (c)),
callout (*content, area, parent), dismissIfBackgrounded(dismissIfBg)
{
callout.setVisible (true);
callout.enterModalState (true, this);
if (dismissIfBackgrounded) {
startTimer (200);
}
}
void modalStateFinished (int) override {}
void timerCallback() override
{
if (! isForegroundOrEmbeddedProcess (&callout))
callout.dismiss();
}
std::unique_ptr<Component> content;
CallOutBox callout;
bool dismissIfBackgrounded = true;
JUCE_DECLARE_NON_COPYABLE (CallOutBoxCallback)
};
CallOutBox& CallOutBox::launchAsynchronously (std::unique_ptr<Component> content, Rectangle<int> area, Component* parent, bool dismissIfBackgrounded)
{
jassert (content != nullptr); // must be a valid content component!
return (new CallOutBoxCallback (std::move (content), area, parent, dismissIfBackgrounded))->callout;
}
//==============================================================================
void CallOutBox::setArrowSize (const float newSize)
{
arrowSize = newSize;
refreshPath();
}
int CallOutBox::getBorderSize() const noexcept
{
return jmax (getLookAndFeel().getCallOutBoxBorderSize (*this), (int) arrowSize);
}
void CallOutBox::lookAndFeelChanged()
{
resized();
repaint();
}
void CallOutBox::paint (Graphics& g)
{
getLookAndFeel().drawCallOutBoxBackground (*this, g, outline, background);
}
void CallOutBox::resized()
{
auto borderSpace = getBorderSize();
content.setTopLeftPosition (borderSpace, borderSpace);
refreshPath();
}
void CallOutBox::moved()
{
refreshPath();
}
void CallOutBox::childBoundsChanged (Component*)
{
updatePosition (targetArea, availableArea);
}
bool CallOutBox::hitTest (int x, int y)
{
return outline.contains ((float) x, (float) y);
}
void CallOutBox::inputAttemptWhenModal()
{
if (dismissalMouseClicksAreAlwaysConsumed
|| targetArea.contains (getMouseXYRelative() + getBounds().getPosition()))
{
// if you click on the area that originally popped-up the callout, you expect it
// to get rid of the box, but deleting the box here allows the click to pass through and
// probably re-trigger it, so we need to dismiss the box asynchronously to consume the click..
// For touchscreens, we make sure not to dismiss the CallOutBox immediately,
// as Windows still sends touch events before the CallOutBox had a chance
// to really open.
auto elapsed = Time::getCurrentTime() - creationTime;
if (elapsed.inMilliseconds() > 200)
dismiss();
}
else
{
exitModalState (0);
setVisible (false);
}
}
void CallOutBox::setDismissalMouseClicksAreAlwaysConsumed (bool b) noexcept
{
dismissalMouseClicksAreAlwaysConsumed = b;
}
static constexpr int callOutBoxDismissCommandId = 0x4f83a04b;
void CallOutBox::handleCommandMessage (int commandId)
{
Component::handleCommandMessage (commandId);
if (commandId == callOutBoxDismissCommandId)
{
exitModalState (0);
setVisible (false);
}
}
void CallOutBox::dismiss()
{
postCommandMessage (callOutBoxDismissCommandId);
}
bool CallOutBox::keyPressed (const KeyPress& key)
{
if (key.isKeyCode (KeyPress::escapeKey))
{
inputAttemptWhenModal();
return true;
}
return false;
}
void CallOutBox::updatePosition (const Rectangle<int>& newAreaToPointTo, const Rectangle<int>& newAreaToFitIn)
{
targetArea = newAreaToPointTo;
availableArea = newAreaToFitIn;
auto borderSpace = getBorderSize();
auto newBounds = getLocalArea (&content, Rectangle<int> (content.getWidth() + borderSpace * 2,
content.getHeight() + borderSpace * 2));
auto hw = newBounds.getWidth() / 2;
auto hh = newBounds.getHeight() / 2;
auto hwReduced = (float) (hw - borderSpace * 2);
auto hhReduced = (float) (hh - borderSpace * 2);
auto arrowIndent = (float) borderSpace - arrowSize;
Point<float> targets[4] = { { (float) targetArea.getCentreX(), (float) targetArea.getBottom() },
{ (float) targetArea.getRight(), (float) targetArea.getCentreY() },
{ (float) targetArea.getX(), (float) targetArea.getCentreY() },
{ (float) targetArea.getCentreX(), (float) targetArea.getY() } };
Line<float> lines[4] = { { targets[0].translated (-hwReduced, hh - arrowIndent), targets[0].translated (hwReduced, hh - arrowIndent) },
{ targets[1].translated (hw - arrowIndent, -hhReduced), targets[1].translated (hw - arrowIndent, hhReduced) },
{ targets[2].translated (-(hw - arrowIndent), -hhReduced), targets[2].translated (-(hw - arrowIndent), hhReduced) },
{ targets[3].translated (-hwReduced, -(hh - arrowIndent)), targets[3].translated (hwReduced, -(hh - arrowIndent)) } };
auto centrePointArea = newAreaToFitIn.reduced (hw, hh).toFloat();
auto targetCentre = targetArea.getCentre().toFloat();
float nearest = 1.0e9f;
for (int i = 0; i < 4; ++i)
{
Line<float> constrainedLine (centrePointArea.getConstrainedPoint (lines[i].getStart()),
centrePointArea.getConstrainedPoint (lines[i].getEnd()));
auto centre = constrainedLine.findNearestPointTo (targetCentre);
auto distanceFromCentre = centre.getDistanceFrom (targets[i]);
if (! centrePointArea.intersects (lines[i]))
distanceFromCentre += 1000.0f;
if (distanceFromCentre < nearest)
{
nearest = distanceFromCentre;
targetPoint = targets[i];
newBounds.setPosition ((int) (centre.x - (float) hw),
(int) (centre.y - (float) hh));
}
}
setBounds (newBounds);
}
void CallOutBox::refreshPath()
{
repaint();
background = {};
outline.clear();
const float gap = 4.5f;
outline.addBubble (getLocalArea (&content, content.getLocalBounds().toFloat()).expanded (gap, gap),
getLocalBounds().toFloat(),
targetPoint - getPosition().toFloat(),
getLookAndFeel().getCallOutBoxCornerSize (*this), arrowSize * 0.7f);
}
void CallOutBox::timerCallback()
{
toFront (true);
stopTimer();
}
//==============================================================================
std::unique_ptr<AccessibilityHandler> CallOutBox::createAccessibilityHandler()
{
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::window);
}
} // namespace juce

View File

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

View File

@ -0,0 +1,589 @@
/*
==============================================================================
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 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
{
Desktop::getInstance().peers.add (this);
}
ComponentPeer::~ComponentPeer()
{
auto& desktop = Desktop::getInstance();
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 (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();
}
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::dismissPendingTextInput() {}
//==============================================================================
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 = Component::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 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 (int index) { jassert (index == 0); ignoreUnused (index); }
//==============================================================================
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();
}
} // namespace juce

View File

@ -0,0 +1,440 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
The 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
{
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. This will prevent the window from
gaining keyboard focus. */
windowIsSemiTransparent = (1 << 30) /**< Not intended for public use - makes a window transparent. */
};
//==============================================================================
/** 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. */
virtual ~ComponentPeer();
//==============================================================================
/** 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;
/** 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.
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 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();
//==============================================================================
/** 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.
*/
virtual void textInputRequired (Point<int> position, TextInputTarget&) = 0;
/** If there's some kind of OS input-method in progress, this should dismiss it. */
virtual void dismissPendingTextInput();
/** 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); }
//==============================================================================
/** 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; }
protected:
//==============================================================================
static void forceDisplayUpdate();
Component& component;
const int styleFlags;
Rectangle<int> lastNonFullscreenBounds;
ComponentBoundsConstrainer* constrainer = nullptr;
static std::function<ModifierKeys()> getNativeRealtimeModifiers;
ListenerList<ScaleFactorListener> scaleFactorListeners;
private:
//==============================================================================
Component* getTargetForKeyPress();
WeakReference<Component> lastFocusedComponent, dragAndDropTargetComponent;
Component* lastDragAndDropCompUnderMouse = nullptr;
const uint32 uniqueID;
bool isWindowMinimised = false;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComponentPeer)
};
} // namespace juce

View File

@ -0,0 +1,181 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
DialogWindow::DialogWindow (const String& name, Colour colour,
const bool escapeCloses, const bool onDesktop,
const float scale)
: DocumentWindow (name, colour, DocumentWindow::closeButton, onDesktop),
desktopScale (scale),
escapeKeyTriggersCloseButton (escapeCloses)
{
}
DialogWindow::~DialogWindow() = default;
bool DialogWindow::escapeKeyPressed()
{
if (escapeKeyTriggersCloseButton)
{
setVisible (false);
return true;
}
return false;
}
bool DialogWindow::keyPressed (const KeyPress& key)
{
if (key == KeyPress::escapeKey && escapeKeyPressed())
return true;
return DocumentWindow::keyPressed (key);
}
void DialogWindow::resized()
{
DocumentWindow::resized();
if (escapeKeyTriggersCloseButton)
{
if (auto* close = getCloseButton())
{
const KeyPress esc (KeyPress::escapeKey, 0, 0);
if (! close->isRegisteredForShortcut (esc))
close->addShortcut (esc);
}
}
}
//==============================================================================
class DefaultDialogWindow : public DialogWindow
{
public:
DefaultDialogWindow (LaunchOptions& options)
: DialogWindow (options.dialogTitle, options.dialogBackgroundColour,
options.escapeKeyTriggersCloseButton, true,
options.componentToCentreAround != nullptr
? Component::getApproximateScaleFactorForComponent (options.componentToCentreAround)
: 1.0f)
{
setUsingNativeTitleBar (options.useNativeTitleBar);
setAlwaysOnTop (juce_areThereAnyAlwaysOnTopWindows());
if (options.content.willDeleteObject())
setContentOwned (options.content.release(), true);
else
setContentNonOwned (options.content.release(), true);
centreAroundComponent (options.componentToCentreAround, getWidth(), getHeight());
setResizable (options.resizable, options.useBottomRightCornerResizer);
}
void closeButtonPressed() override
{
setVisible (false);
}
private:
JUCE_DECLARE_NON_COPYABLE (DefaultDialogWindow)
};
DialogWindow::LaunchOptions::LaunchOptions() noexcept {}
DialogWindow* DialogWindow::LaunchOptions::create()
{
jassert (content != nullptr); // You need to provide some kind of content for the dialog!
return new DefaultDialogWindow (*this);
}
DialogWindow* DialogWindow::LaunchOptions::launchAsync()
{
auto* d = create();
d->enterModalState (true, nullptr, true);
return d;
}
#if JUCE_MODAL_LOOPS_PERMITTED
int DialogWindow::LaunchOptions::runModal()
{
return launchAsync()->runModalLoop();
}
#endif
//==============================================================================
void DialogWindow::showDialog (const String& dialogTitle,
Component* const contentComponent,
Component* const componentToCentreAround,
Colour backgroundColour,
const bool escapeKeyTriggersCloseButton,
const bool resizable,
const bool useBottomRightCornerResizer)
{
LaunchOptions o;
o.dialogTitle = dialogTitle;
o.content.setNonOwned (contentComponent);
o.componentToCentreAround = componentToCentreAround;
o.dialogBackgroundColour = backgroundColour;
o.escapeKeyTriggersCloseButton = escapeKeyTriggersCloseButton;
o.useNativeTitleBar = false;
o.resizable = resizable;
o.useBottomRightCornerResizer = useBottomRightCornerResizer;
o.launchAsync();
}
#if JUCE_MODAL_LOOPS_PERMITTED
int DialogWindow::showModalDialog (const String& dialogTitle,
Component* const contentComponent,
Component* const componentToCentreAround,
Colour backgroundColour,
const bool escapeKeyTriggersCloseButton,
const bool resizable,
const bool useBottomRightCornerResizer)
{
LaunchOptions o;
o.dialogTitle = dialogTitle;
o.content.setNonOwned (contentComponent);
o.componentToCentreAround = componentToCentreAround;
o.dialogBackgroundColour = backgroundColour;
o.escapeKeyTriggersCloseButton = escapeKeyTriggersCloseButton;
o.useNativeTitleBar = false;
o.resizable = resizable;
o.useBottomRightCornerResizer = useBottomRightCornerResizer;
return o.runModal();
}
#endif
//==============================================================================
std::unique_ptr<AccessibilityHandler> DialogWindow::createAccessibilityHandler()
{
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::dialogWindow);
}
} // namespace juce

View File

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

View File

@ -0,0 +1,360 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
class DocumentWindow::ButtonListenerProxy : public Button::Listener
{
public:
ButtonListenerProxy (DocumentWindow& w) : owner (w) {}
void buttonClicked (Button* button) override
{
if (button == owner.getMinimiseButton()) owner.minimiseButtonPressed();
else if (button == owner.getMaximiseButton()) owner.maximiseButtonPressed();
else if (button == owner.getCloseButton()) owner.closeButtonPressed();
}
private:
DocumentWindow& owner;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ButtonListenerProxy)
};
//==============================================================================
DocumentWindow::DocumentWindow (const String& title,
Colour backgroundColour,
int requiredButtons_,
bool addToDesktop_)
: ResizableWindow (title, backgroundColour, addToDesktop_),
requiredButtons (requiredButtons_),
#if JUCE_MAC
positionTitleBarButtonsOnLeft (true)
#else
positionTitleBarButtonsOnLeft (false)
#endif
{
setResizeLimits (128, 128, 32768, 32768);
DocumentWindow::lookAndFeelChanged();
}
DocumentWindow::~DocumentWindow()
{
// Don't delete or remove the resizer components yourself! They're managed by the
// DocumentWindow, and you should leave them alone! You may have deleted them
// accidentally by careless use of deleteAllChildren()..?
jassert (menuBar == nullptr || getIndexOfChildComponent (menuBar.get()) >= 0);
jassert (titleBarButtons[0] == nullptr || getIndexOfChildComponent (titleBarButtons[0].get()) >= 0);
jassert (titleBarButtons[1] == nullptr || getIndexOfChildComponent (titleBarButtons[1].get()) >= 0);
jassert (titleBarButtons[2] == nullptr || getIndexOfChildComponent (titleBarButtons[2].get()) >= 0);
for (auto& b : titleBarButtons)
b.reset();
menuBar.reset();
}
//==============================================================================
void DocumentWindow::repaintTitleBar()
{
repaint (getTitleBarArea());
}
void DocumentWindow::setName (const String& newName)
{
if (newName != getName())
{
Component::setName (newName);
repaintTitleBar();
}
}
void DocumentWindow::setIcon (const Image& imageToUse)
{
titleBarIcon = imageToUse;
repaintTitleBar();
}
void DocumentWindow::setTitleBarHeight (const int newHeight)
{
titleBarHeight = newHeight;
resized();
repaintTitleBar();
}
void DocumentWindow::setTitleBarButtonsRequired (const int buttons, const bool onLeft)
{
requiredButtons = buttons;
positionTitleBarButtonsOnLeft = onLeft;
lookAndFeelChanged();
}
void DocumentWindow::setTitleBarTextCentred (const bool textShouldBeCentred)
{
drawTitleTextCentred = textShouldBeCentred;
repaintTitleBar();
}
//==============================================================================
void DocumentWindow::setMenuBar (MenuBarModel* newMenuBarModel, const int newMenuBarHeight)
{
if (menuBarModel != newMenuBarModel)
{
menuBar.reset();
menuBarModel = newMenuBarModel;
menuBarHeight = newMenuBarHeight > 0 ? newMenuBarHeight
: getLookAndFeel().getDefaultMenuBarHeight();
if (menuBarModel != nullptr)
setMenuBarComponent (new MenuBarComponent (menuBarModel));
resized();
}
}
Component* DocumentWindow::getMenuBarComponent() const noexcept
{
return menuBar.get();
}
void DocumentWindow::setMenuBarComponent (Component* newMenuBarComponent)
{
menuBar.reset (newMenuBarComponent);
Component::addAndMakeVisible (menuBar.get()); // (call the superclass method directly to avoid the assertion in ResizableWindow)
if (menuBar != nullptr)
menuBar->setEnabled (isActiveWindow());
resized();
}
//==============================================================================
void DocumentWindow::closeButtonPressed()
{
/* If you've got a close button, you have to override this method to get
rid of your window!
If the window is just a pop-up, you should override this method and make
it delete the window in whatever way is appropriate for your app. E.g. you
might just want to call "delete this".
If your app is centred around this window such that the whole app should quit when
the window is closed, then you will probably want to use this method as an opportunity
to call JUCEApplicationBase::quit(), and leave the window to be deleted later by your
JUCEApplicationBase::shutdown() method. (Doing it this way means that your window will
still get cleaned-up if the app is quit by some other means (e.g. a cmd-Q on the mac
or closing it via the taskbar icon on Windows).
*/
jassertfalse;
}
void DocumentWindow::minimiseButtonPressed()
{
setMinimised (true);
}
void DocumentWindow::maximiseButtonPressed()
{
setFullScreen (! isFullScreen());
}
//==============================================================================
void DocumentWindow::paint (Graphics& g)
{
ResizableWindow::paint (g);
auto titleBarArea = getTitleBarArea();
g.reduceClipRegion (titleBarArea);
g.setOrigin (titleBarArea.getPosition());
int titleSpaceX1 = 6;
int titleSpaceX2 = titleBarArea.getWidth() - 6;
for (auto& b : titleBarButtons)
{
if (b != nullptr)
{
if (positionTitleBarButtonsOnLeft)
titleSpaceX1 = jmax (titleSpaceX1, b->getRight() + (getWidth() - b->getRight()) / 8);
else
titleSpaceX2 = jmin (titleSpaceX2, b->getX() - (b->getX() / 8));
}
}
getLookAndFeel().drawDocumentWindowTitleBar (*this, g,
titleBarArea.getWidth(),
titleBarArea.getHeight(),
titleSpaceX1,
jmax (1, titleSpaceX2 - titleSpaceX1),
titleBarIcon.isValid() ? &titleBarIcon : nullptr,
! drawTitleTextCentred);
}
void DocumentWindow::resized()
{
ResizableWindow::resized();
if (auto* b = getMaximiseButton())
b->setToggleState (isFullScreen(), dontSendNotification);
auto titleBarArea = getTitleBarArea();
getLookAndFeel()
.positionDocumentWindowButtons (*this,
titleBarArea.getX(), titleBarArea.getY(),
titleBarArea.getWidth(), titleBarArea.getHeight(),
titleBarButtons[0].get(),
titleBarButtons[1].get(),
titleBarButtons[2].get(),
positionTitleBarButtonsOnLeft);
if (menuBar != nullptr)
menuBar->setBounds (titleBarArea.getX(), titleBarArea.getBottom(),
titleBarArea.getWidth(), menuBarHeight);
}
BorderSize<int> DocumentWindow::getBorderThickness()
{
return ResizableWindow::getBorderThickness();
}
BorderSize<int> DocumentWindow::getContentComponentBorder()
{
auto border = getBorderThickness();
if (! isKioskMode())
border.setTop (border.getTop()
+ (isUsingNativeTitleBar() ? 0 : titleBarHeight)
+ (menuBar != nullptr ? menuBarHeight : 0));
return border;
}
int DocumentWindow::getTitleBarHeight() const
{
return isUsingNativeTitleBar() ? 0 : jmin (titleBarHeight, getHeight() - 4);
}
Rectangle<int> DocumentWindow::getTitleBarArea()
{
if (isKioskMode())
return {};
auto border = getBorderThickness();
return { border.getLeft(), border.getTop(), getWidth() - border.getLeftAndRight(), getTitleBarHeight() };
}
Button* DocumentWindow::getCloseButton() const noexcept { return titleBarButtons[2].get(); }
Button* DocumentWindow::getMinimiseButton() const noexcept { return titleBarButtons[0].get(); }
Button* DocumentWindow::getMaximiseButton() const noexcept { return titleBarButtons[1].get(); }
int DocumentWindow::getDesktopWindowStyleFlags() const
{
auto styleFlags = ResizableWindow::getDesktopWindowStyleFlags();
if ((requiredButtons & minimiseButton) != 0) styleFlags |= ComponentPeer::windowHasMinimiseButton;
if ((requiredButtons & maximiseButton) != 0) styleFlags |= ComponentPeer::windowHasMaximiseButton;
if ((requiredButtons & closeButton) != 0) styleFlags |= ComponentPeer::windowHasCloseButton;
return styleFlags;
}
void DocumentWindow::lookAndFeelChanged()
{
for (auto& b : titleBarButtons)
b.reset();
if (! isUsingNativeTitleBar())
{
auto& lf = getLookAndFeel();
if ((requiredButtons & minimiseButton) != 0) titleBarButtons[0].reset (lf.createDocumentWindowButton (minimiseButton));
if ((requiredButtons & maximiseButton) != 0) titleBarButtons[1].reset (lf.createDocumentWindowButton (maximiseButton));
if ((requiredButtons & closeButton) != 0) titleBarButtons[2].reset (lf.createDocumentWindowButton (closeButton));
for (auto& b : titleBarButtons)
{
if (b != nullptr)
{
if (buttonListener == nullptr)
buttonListener.reset (new ButtonListenerProxy (*this));
b->addListener (buttonListener.get());
b->setWantsKeyboardFocus (false);
// (call the Component method directly to avoid the assertion in ResizableWindow)
Component::addAndMakeVisible (b.get());
}
}
if (auto* b = getCloseButton())
{
#if JUCE_MAC
b->addShortcut (KeyPress ('w', ModifierKeys::commandModifier, 0));
#else
b->addShortcut (KeyPress (KeyPress::F4Key, ModifierKeys::altModifier, 0));
#endif
}
}
activeWindowStatusChanged();
ResizableWindow::lookAndFeelChanged();
}
void DocumentWindow::parentHierarchyChanged()
{
lookAndFeelChanged();
}
void DocumentWindow::activeWindowStatusChanged()
{
ResizableWindow::activeWindowStatusChanged();
bool isActive = isActiveWindow();
for (auto& b : titleBarButtons)
if (b != nullptr)
b->setEnabled (isActive);
if (menuBar != nullptr)
menuBar->setEnabled (isActive);
}
void DocumentWindow::mouseDoubleClick (const MouseEvent& e)
{
if (getTitleBarArea().contains (e.x, e.y))
if (auto* maximise = getMaximiseButton())
maximise->triggerClick();
}
void DocumentWindow::userTriedToCloseWindow()
{
closeButtonPressed();
}
} // namespace juce

View File

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

View File

@ -0,0 +1,143 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
/** The type of icon to show in the dialog box. */
enum class MessageBoxIconType
{
NoIcon, /**< No icon will be shown on the dialog box. */
QuestionIcon, /**< A question-mark icon, for dialog boxes that need the
user to answer a question. */
WarningIcon, /**< An exclamation mark to indicate that the dialog is a
warning about something and shouldn't be ignored. */
InfoIcon /**< An icon that indicates that the dialog box is just
giving the user some information, which doesn't require
a response from them. */
};
//==============================================================================
/** Class used to create a set of options to pass to the AlertWindow and NativeMessageBox
methods for showing dialog boxes.
You can chain together a series of calls to this class's methods to create
a set of whatever options you want to specify.
E.g. @code
AlertWindow::showAsync (MessageBoxOptions()
.withIconType (MessageBoxIconType::InfoIcon)
.withTitle ("A Title")
.withMessage ("A message.")
.withButton ("OK")
.withButton ("Cancel")
.withAssociatedComponent (myComp),
myCallback);
@endcode
@tags{GUI}
*/
class JUCE_API MessageBoxOptions
{
public:
MessageBoxOptions() = default;
MessageBoxOptions (const MessageBoxOptions&) = default;
MessageBoxOptions& operator= (const MessageBoxOptions&) = default;
//==============================================================================
/** Sets the type of icon that should be used for the dialog box. */
MessageBoxOptions withIconType (MessageBoxIconType type) const { return with (*this, &MessageBoxOptions::iconType, type); }
/** Sets the title of the dialog box. */
MessageBoxOptions withTitle (const String& boxTitle) const { return with (*this, &MessageBoxOptions::title, boxTitle); }
/** Sets the message that should be displayed in the dialog box. */
MessageBoxOptions withMessage (const String& boxMessage) const { return with (*this, &MessageBoxOptions::message, boxMessage); }
/** If the string passed in is not empty, this will add a button to the
dialog box with the specified text.
Generally up to 3 buttons are supported for dialog boxes, so adding any more
than this may have no effect.
*/
MessageBoxOptions withButton (const String& text) const { auto copy = *this; copy.buttons.add (text); return copy; }
/** The component that the dialog box should be associated with. */
MessageBoxOptions withAssociatedComponent (Component* component) const { return with (*this, &MessageBoxOptions::associatedComponent, component); }
//==============================================================================
/** Returns the icon type of the dialog box.
@see withIconType
*/
MessageBoxIconType getIconType() const noexcept { return iconType; }
/** Returns the title of the dialog box.
@see withTitle
*/
String getTitle() const { return title; }
/** Returns the message of the dialog box.
@see withMessage
*/
String getMessage() const { return message; }
/** Returns the number of buttons that have been added to the dialog box.
@see withButtonText
*/
int getNumButtons() const noexcept { return buttons.size(); }
/** Returns the text that has been set for one of the buttons of the dialog box.
@see withButtonText, getNumButtons
*/
String getButtonText (int buttonIndex) const { return buttons[buttonIndex]; }
/** Returns the component that the dialog box is associated with.
@see withAssociatedComponent
*/
Component* getAssociatedComponent() const noexcept { return associatedComponent; }
private:
//==============================================================================
template <typename Member, typename Item>
static MessageBoxOptions with (MessageBoxOptions options, Member&& member, Item&& item)
{
options.*member = std::forward<Item> (item);
return options;
}
//==============================================================================
MessageBoxIconType iconType = MessageBoxIconType::InfoIcon;
String title, message;
StringArray buttons;
WeakReference<Component> associatedComponent;
};
} // namespace juce

View File

@ -0,0 +1,265 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
This class contains some static methods for showing native alert windows.
@tags{GUI}
*/
class NativeMessageBox
{
public:
#if JUCE_MODAL_LOOPS_PERMITTED
/** Shows a dialog box that just has a message and a single 'ok' button to close it.
The box is shown modally, and the method will block until the user has clicked its
button (or pressed the escape or return keys).
@param iconType the type of icon to show.
@param title the headline to show at the top of the box.
@param message a longer, more descriptive message to show underneath the title.
@param associatedComponent if this is non-null, it specifies the component that the
alert window should be associated with. Depending on the look
and feel, this might be used for positioning of the alert window.
*/
static void JUCE_CALLTYPE showMessageBox (MessageBoxIconType iconType,
const String& title,
const String& message,
Component* associatedComponent = nullptr);
/** Shows a dialog box using the specified options.
The box is shown modally, and the method will block until the user dismisses it.
@param options the options to use when creating the dialog.
@returns the index of the button that was clicked.
@see MessageBoxOptions
*/
static int JUCE_CALLTYPE show (const MessageBoxOptions& options);
#endif
/** Shows a dialog box using the specified options.
The box will be displayed and placed into a modal state, but this method will return
immediately, and the callback will be invoked later when the user dismisses the box.
@param options the options to use when creating the dialog.
@param callback if this is non-null, the callback will receive a call to its
modalStateFinished() when the box is dismissed with the index of the
button that was clicked as its argument.
The callback object will be owned and deleted by the system, so make sure
that it works safely and doesn't keep any references to objects that might
be deleted before it gets called.
@see MessageBoxOptions
*/
static void JUCE_CALLTYPE showAsync (const MessageBoxOptions& options,
ModalComponentManager::Callback* callback);
/** Shows a dialog box using the specified options.
The box will be displayed and placed into a modal state, but this method will return
immediately, and the callback will be invoked later when the user dismisses the box.
@param options the options to use when creating the dialog.
@param callback if this is non-null, the callback will be called when the box is
dismissed with the index of the button that was clicked as its argument.
@see MessageBoxOptions
*/
static void JUCE_CALLTYPE showAsync (const MessageBoxOptions& options,
std::function<void (int)> callback);
/** Shows a dialog box that just has a message and a single 'ok' button to close it.
The box will be displayed and placed into a modal state, but this method will return
immediately, and the callback will be invoked later when the user dismisses the box.
@param iconType the type of icon to show.
@param title the headline to show at the top of the box.
@param message a longer, more descriptive message to show underneath the title.
@param associatedComponent if this is non-null, it specifies the component that the
alert window should be associated with. Depending on the look
and feel, this might be used for positioning of the alert window.
@param callback if this is non-null, the callback will receive a call to its
modalStateFinished() when the box is dismissed. The callback object
will be owned and deleted by the system, so make sure that it works
safely and doesn't keep any references to objects that might be deleted
before it gets called. You can use the ModalCallbackFunction to easily
pass in a lambda for this parameter.
@see ModalCallbackFunction
*/
static void JUCE_CALLTYPE showMessageBoxAsync (MessageBoxIconType iconType,
const String& title,
const String& message,
Component* associatedComponent = nullptr,
ModalComponentManager::Callback* callback = nullptr);
/** Shows a dialog box with two buttons.
Ideal for ok/cancel or yes/no choices. The return key can also be used
to trigger the first button, and the escape key for the second button.
If the callback parameter is null and modal loops are enabled, the box is shown modally,
and the method will block until the user has clicked the button (or pressed the escape or
return keys). If the callback parameter is non-null, the box will be displayed and placed
into a modal state, but this method will return immediately, and the callback will be invoked
later when the user dismisses the box.
@param iconType the type of icon to show.
@param title the headline to show at the top of the box.
@param message a longer, more descriptive message to show underneath the title.
@param associatedComponent if this is non-null, it specifies the component that the
alert window should be associated with. Depending on the look
and feel, this might be used for positioning of the alert window.
@param callback if this is non-null, the box will be launched asynchronously,
returning immediately, and the callback will receive a call to its
modalStateFinished() when the box is dismissed, with its parameter
being 1 if the ok button was pressed, or 0 for cancel, The callback object
will be owned and deleted by the system, so make sure that it works
safely and doesn't keep any references to objects that might be deleted
before it gets called. You can use the ModalCallbackFunction to easily
pass in a lambda for this parameter.
@returns true if button 1 was clicked, false if it was button 2. If the callback parameter
is not null, the method always returns false, and the user's choice is delivered
later by the callback.
@see ModalCallbackFunction
*/
static bool JUCE_CALLTYPE showOkCancelBox (MessageBoxIconType iconType,
const String& title,
const String& message,
#if JUCE_MODAL_LOOPS_PERMITTED
Component* associatedComponent = nullptr,
ModalComponentManager::Callback* callback = nullptr);
#else
Component* associatedComponent,
ModalComponentManager::Callback* callback);
#endif
/** Shows a dialog box with three buttons.
Ideal for yes/no/cancel boxes.
The escape key can be used to trigger the third button.
If the callback parameter is null and modal loops are enabled, the box is shown modally,
and the method will block until the user has clicked the button (or pressed the escape or
return keys). If the callback parameter is non-null, the box will be displayed and placed
into a modal state, but this method will return immediately, and the callback will be invoked
later when the user dismisses the box.
@param iconType the type of icon to show.
@param title the headline to show at the top of the box.
@param message a longer, more descriptive message to show underneath the title.
@param associatedComponent if this is non-null, it specifies the component that the
alert window should be associated with. Depending on the look
and feel, this might be used for positioning of the alert window.
@param callback if this is non-null, the box will be launched asynchronously,
returning immediately, and the callback will receive a call to its
modalStateFinished() when the box is dismissed, with its parameter
being 1 if the "yes" button was pressed, 2 for the "no" button, or 0
if it was cancelled, The callback object will be owned and deleted by the
system, so make sure that it works safely and doesn't keep any references
to objects that might be deleted before it gets called. You can use the
ModalCallbackFunction to easily pass in a lambda for this parameter.
@returns If the callback parameter has been set, this returns 0. Otherwise, it returns one
of the following values:
- 0 if 'cancel' was pressed
- 1 if 'yes' was pressed
- 2 if 'no' was pressed
@see ModalCallbackFunction
*/
static int JUCE_CALLTYPE showYesNoCancelBox (MessageBoxIconType iconType,
const String& title,
const String& message,
#if JUCE_MODAL_LOOPS_PERMITTED
Component* associatedComponent = nullptr,
ModalComponentManager::Callback* callback = nullptr);
#else
Component* associatedComponent,
ModalComponentManager::Callback* callback);
#endif
/** Shows a dialog box with two buttons.
Ideal for yes/no boxes.
The escape key can be used to trigger the no button.
If the callback parameter is null and modal loops are enabled, the box is shown modally,
and the method will block until the user has clicked the button (or pressed the escape or
return keys). If the callback parameter is non-null, the box will be displayed and placed
into a modal state, but this method will return immediately, and the callback will be invoked
later when the user dismisses the box.
@param iconType the type of icon to show.
@param title the headline to show at the top of the box.
@param message a longer, more descriptive message to show underneath the title.
@param associatedComponent if this is non-null, it specifies the component that the
alert window should be associated with. Depending on the look
and feel, this might be used for positioning of the alert window.
@param callback if this is non-null, the box will be launched asynchronously,
returning immediately, and the callback will receive a call to its
modalStateFinished() when the box is dismissed, with its parameter
being 1 if the "yes" button was pressed or 0 for the "no" button was
pressed. The callback object will be owned and deleted by the
system, so make sure that it works safely and doesn't keep any references
to objects that might be deleted before it gets called. You can use the
ModalCallbackFunction to easily pass in a lambda for this parameter.
@returns If the callback parameter has been set, this returns 0. Otherwise, it returns one
of the following values:
- 0 if 'no' was pressed
- 1 if 'yes' was pressed
@see ModalCallbackFunction
*/
static int JUCE_CALLTYPE showYesNoBox (MessageBoxIconType iconType,
const String& title,
const String& message,
#if JUCE_MODAL_LOOPS_PERMITTED
Component* associatedComponent = nullptr,
ModalComponentManager::Callback* callback = nullptr);
#else
Component* associatedComponent,
ModalComponentManager::Callback* callback);
#endif
private:
NativeMessageBox() = delete;
JUCE_DECLARE_NON_COPYABLE (NativeMessageBox)
};
} // namespace juce

View File

@ -0,0 +1,684 @@
/*
==============================================================================
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
{
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 6 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-6-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)
{
const auto frameSize = peer->getFrameSize();
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)
{
peer->getFrameSize().addTo (newPos);
}
#if JUCE_LINUX
else
{
// 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() };
frame.addTo (newPos);
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)
{
peer->getFrameSize().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

View File

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

View File

@ -0,0 +1,119 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
ThreadWithProgressWindow::ThreadWithProgressWindow (const String& title,
const bool hasProgressBar,
const bool hasCancelButton,
const int cancellingTimeOutMs,
const String& cancelButtonText,
Component* componentToCentreAround)
: Thread ("ThreadWithProgressWindow"),
progress (0.0),
timeOutMsWhenCancelling (cancellingTimeOutMs),
wasCancelledByUser (false)
{
alertWindow.reset (LookAndFeel::getDefaultLookAndFeel()
.createAlertWindow (title, {},
cancelButtonText.isEmpty() ? TRANS("Cancel")
: cancelButtonText,
{}, {}, MessageBoxIconType::NoIcon, hasCancelButton ? 1 : 0,
componentToCentreAround));
// if there are no buttons, we won't allow the user to interrupt the thread.
alertWindow->setEscapeKeyCancels (false);
if (hasProgressBar)
alertWindow->addProgressBarComponent (progress);
}
ThreadWithProgressWindow::~ThreadWithProgressWindow()
{
stopThread (timeOutMsWhenCancelling);
}
void ThreadWithProgressWindow::launchThread (int priority)
{
JUCE_ASSERT_MESSAGE_THREAD
startThread (priority);
startTimer (100);
{
const ScopedLock sl (messageLock);
alertWindow->setMessage (message);
}
alertWindow->enterModalState();
}
void ThreadWithProgressWindow::setProgress (const double newProgress)
{
progress = newProgress;
}
void ThreadWithProgressWindow::setStatusMessage (const String& newStatusMessage)
{
const ScopedLock sl (messageLock);
message = newStatusMessage;
}
void ThreadWithProgressWindow::timerCallback()
{
bool threadStillRunning = isThreadRunning();
if (! (threadStillRunning && alertWindow->isCurrentlyModal (false)))
{
stopTimer();
stopThread (timeOutMsWhenCancelling);
alertWindow->exitModalState (1);
alertWindow->setVisible (false);
wasCancelledByUser = threadStillRunning;
threadComplete (threadStillRunning);
return; // (this may be deleted now)
}
const ScopedLock sl (messageLock);
alertWindow->setMessage (message);
}
void ThreadWithProgressWindow::threadComplete (bool) {}
#if JUCE_MODAL_LOOPS_PERMITTED
bool ThreadWithProgressWindow::runThread (const int priority)
{
launchThread (priority);
while (isTimerRunning())
MessageManager::getInstance()->runDispatchLoopUntil (5);
return ! wasCancelledByUser;
}
#endif
} // namespace juce

View File

@ -0,0 +1,175 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
A thread that automatically pops up a modal dialog box with a progress bar
and cancel button while it's busy running.
These are handy for performing some sort of task while giving the user feedback
about how long there is to go, etc.
E.g. @code
class MyTask : public ThreadWithProgressWindow
{
public:
MyTask() : ThreadWithProgressWindow ("busy...", true, true)
{
}
void run()
{
for (int i = 0; i < thingsToDo; ++i)
{
// must check this as often as possible, because this is
// how we know if the user's pressed 'cancel'
if (threadShouldExit())
break;
// this will update the progress bar on the dialog box
setProgress (i / (double) thingsToDo);
// ... do the business here...
}
}
};
void doTheTask()
{
MyTask m;
if (m.runThread())
{
// thread finished normally..
}
else
{
// user pressed the cancel button..
}
}
@endcode
@see Thread, AlertWindow
@tags{GUI}
*/
class JUCE_API ThreadWithProgressWindow : public Thread,
private Timer
{
public:
//==============================================================================
/** Creates the thread.
Initially, the dialog box won't be visible, it'll only appear when the
runThread() method is called.
@param windowTitle the title to go at the top of the dialog box
@param hasProgressBar whether the dialog box should have a progress bar (see
setProgress() )
@param hasCancelButton whether the dialog box should have a cancel button
@param timeOutMsWhenCancelling when 'cancel' is pressed, this is how long to wait for
the thread to stop before killing it forcibly (see
Thread::stopThread() )
@param cancelButtonText the text that should be shown in the cancel button
(if it has one). Leave this empty for the default "Cancel"
@param componentToCentreAround if this is non-null, the window will be positioned
so that it's centred around this component.
*/
ThreadWithProgressWindow (const String& windowTitle,
bool hasProgressBar,
bool hasCancelButton,
int timeOutMsWhenCancelling = 10000,
const String& cancelButtonText = String(),
Component* componentToCentreAround = nullptr);
/** Destructor. */
~ThreadWithProgressWindow() override;
//==============================================================================
#if JUCE_MODAL_LOOPS_PERMITTED
/** Starts the thread and waits for it to finish.
This will start the thread, make the dialog box appear, and wait until either
the thread finishes normally, or until the cancel button is pressed.
Before returning, the dialog box will be hidden.
@param priority the priority to use when starting the thread - see
Thread::startThread() for values
@returns true if the thread finished normally; false if the user pressed cancel
*/
bool runThread (int priority = 5);
#endif
/** Starts the thread and returns.
This will start the thread and make the dialog box appear in a modal state. When
the thread finishes normally, or the cancel button is pressed, the window will be
hidden and the threadComplete() method will be called.
@param priority the priority to use when starting the thread - see
Thread::startThread() for values
*/
void launchThread (int priority = 5);
/** The thread should call this periodically to update the position of the progress bar.
@param newProgress the progress, from 0.0 to 1.0
@see setStatusMessage
*/
void setProgress (double newProgress);
/** The thread can call this to change the message that's displayed in the dialog box. */
void setStatusMessage (const String& newStatusMessage);
/** Returns the AlertWindow that is being used. */
AlertWindow* getAlertWindow() const noexcept { return alertWindow.get(); }
//==============================================================================
/** This method is called (on the message thread) when the operation has finished.
You may choose to use this callback to delete the ThreadWithProgressWindow object.
*/
virtual void threadComplete (bool userPressedCancel);
private:
//==============================================================================
void timerCallback() override;
double progress;
std::unique_ptr<AlertWindow> alertWindow;
String message;
CriticalSection messageLock;
const int timeOutMsWhenCancelling;
bool wasCancelledByUser;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ThreadWithProgressWindow)
};
} // namespace juce

View File

@ -0,0 +1,232 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
TooltipWindow::TooltipWindow (Component* parentComp, int delayMs)
: Component ("tooltip"),
millisecondsBeforeTipAppears (delayMs)
{
setAlwaysOnTop (true);
setOpaque (true);
setAccessible (false);
if (parentComp != nullptr)
parentComp->addChildComponent (this);
if (Desktop::getInstance().getMainMouseSource().canHover())
startTimer (123);
}
TooltipWindow::~TooltipWindow()
{
hideTip();
}
void TooltipWindow::setMillisecondsBeforeTipAppears (const int newTimeMs) noexcept
{
millisecondsBeforeTipAppears = newTimeMs;
}
void TooltipWindow::paint (Graphics& g)
{
getLookAndFeel().drawTooltip (g, tipShowing, getWidth(), getHeight());
}
void TooltipWindow::mouseEnter (const MouseEvent&)
{
hideTip();
}
void TooltipWindow::updatePosition (const String& tip, Point<int> pos, Rectangle<int> parentArea)
{
setBounds (getLookAndFeel().getTooltipBounds (tip, pos, parentArea));
setVisible (true);
}
#if JUCE_DEBUG
static Array<TooltipWindow*> activeTooltipWindows;
#endif
void TooltipWindow::displayTip (Point<int> screenPos, const String& tip)
{
jassert (tip.isNotEmpty());
if (! reentrant)
{
ScopedValueSetter<bool> setter (reentrant, true, false);
if (tipShowing != tip)
{
tipShowing = tip;
repaint();
}
if (auto* parent = getParentComponent())
{
updatePosition (tip, parent->getLocalPoint (nullptr, screenPos),
parent->getLocalBounds());
}
else
{
const auto physicalPos = ScalingHelpers::scaledScreenPosToUnscaled (screenPos);
const auto scaledPos = ScalingHelpers::unscaledScreenPosToScaled (*this, physicalPos);
updatePosition (tip, scaledPos, Desktop::getInstance().getDisplays().getDisplayForPoint (screenPos)->userArea);
addToDesktop (ComponentPeer::windowHasDropShadow
| ComponentPeer::windowIsTemporary
| ComponentPeer::windowIgnoresKeyPresses
| ComponentPeer::windowIgnoresMouseClicks);
}
#if JUCE_DEBUG
activeTooltipWindows.addIfNotAlreadyThere (this);
auto* parent = getParentComponent();
for (auto* w : activeTooltipWindows)
{
if (w != nullptr && w != this && w->tipShowing == tipShowing && w->getParentComponent() == parent)
{
// Looks like you have more than one TooltipWindow showing the same tip..
// Be careful not to create more than one instance of this class with the
// same parent component!
jassertfalse;
}
}
#endif
toFront (false);
}
}
String TooltipWindow::getTipFor (Component& c)
{
if (isForegroundOrEmbeddedProcess (&c)
&& ! ModifierKeys::currentModifiers.isAnyMouseButtonDown())
{
if (auto* ttc = dynamic_cast<TooltipClient*> (&c))
if (! c.isCurrentlyBlockedByAnotherModalComponent())
return ttc->getTooltip();
}
return {};
}
void TooltipWindow::hideTip()
{
if (! reentrant)
{
tipShowing.clear();
removeFromDesktop();
setVisible (false);
#if JUCE_DEBUG
activeTooltipWindows.removeAllInstancesOf (this);
#endif
}
}
float TooltipWindow::getDesktopScaleFactor() const
{
if (lastComponentUnderMouse != nullptr)
return Component::getApproximateScaleFactorForComponent (lastComponentUnderMouse);
return Component::getDesktopScaleFactor();
}
std::unique_ptr<AccessibilityHandler> TooltipWindow::createAccessibilityHandler()
{
return createIgnoredAccessibilityHandler (*this);
}
void TooltipWindow::timerCallback()
{
auto& desktop = Desktop::getInstance();
auto mouseSource = desktop.getMainMouseSource();
auto now = Time::getApproximateMillisecondCounter();
auto* newComp = mouseSource.isTouch() ? nullptr : mouseSource.getComponentUnderMouse();
if (newComp == nullptr || getParentComponent() == nullptr || newComp->getPeer() == getPeer())
{
auto newTip = newComp != nullptr ? getTipFor (*newComp) : String();
bool tipChanged = (newTip != lastTipUnderMouse || newComp != lastComponentUnderMouse);
lastComponentUnderMouse = newComp;
lastTipUnderMouse = newTip;
auto clickCount = desktop.getMouseButtonClickCounter();
auto wheelCount = desktop.getMouseWheelMoveCounter();
bool mouseWasClicked = (clickCount > mouseClicks || wheelCount > mouseWheelMoves);
mouseClicks = clickCount;
mouseWheelMoves = wheelCount;
auto mousePos = mouseSource.getScreenPosition();
bool mouseMovedQuickly = mousePos.getDistanceFrom (lastMousePos) > 12;
lastMousePos = mousePos;
if (tipChanged || mouseWasClicked || mouseMovedQuickly)
lastCompChangeTime = now;
auto showTip = [this, &mouseSource, &mousePos, &newTip]
{
bool mouseHasMovedSinceClick = mouseSource.getLastMouseDownPosition() != lastMousePos;
if (mouseHasMovedSinceClick)
displayTip (mousePos.roundToInt(), newTip);
};
if (isVisible() || now < lastHideTime + 500)
{
// if a tip is currently visible (or has just disappeared), update to a new one
// immediately if needed..
if (newComp == nullptr || mouseWasClicked || newTip.isEmpty())
{
if (isVisible())
{
lastHideTime = now;
hideTip();
}
}
else if (tipChanged)
{
showTip();
}
}
else
{
// if there isn't currently a tip, but one is needed, only let it appear after a timeout
if (newTip.isNotEmpty()
&& newTip != tipShowing
&& now > lastCompChangeTime + (uint32) millisecondsBeforeTipAppears)
{
showTip();
}
}
}
}
} // namespace juce

View File

@ -0,0 +1,149 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
A window that displays a pop-up tooltip when the mouse hovers over another component.
To enable tooltips in your app, just create a single instance of a TooltipWindow
object. Note that if you instantiate more than one instance of this class with the
same parentComponent (even if both TooltipWindow's parentComponent is nil), you'll
end up with multiple tooltips being shown! To avoid this use a SharedResourcePointer
to instantiate the TooltipWindow only once.
For audio plug-ins (which should not be opening native windows) it is better
to add a TooltipWindow as a member variable to the editor and ensure that the
editor is the parentComponent of your TooltipWindow. This will ensure that your
TooltipWindow is scaled according to your editor and the DAWs scaling setting.
The TooltipWindow object will then stay invisible, waiting until the mouse
hovers for the specified length of time - it will then see if it's currently
over a component which implements the TooltipClient interface, and if so,
it will make itself visible to show the tooltip in the appropriate place.
@see TooltipClient, SettableTooltipClient, SharedResourcePointer
@tags{GUI}
*/
class JUCE_API TooltipWindow : public Component,
private Timer
{
public:
//==============================================================================
/** Creates a tooltip window.
Make sure your app only creates one instance of this class, otherwise you'll
get multiple overlaid tooltips appearing. The window will initially be invisible
and will make itself visible when it needs to display a tip.
To change the style of tooltips, see the LookAndFeel class for its tooltip
methods.
@param parentComponent if set to nullptr, the TooltipWindow will appear on the desktop,
otherwise the tooltip will be added to the given parent
component.
@param millisecondsBeforeTipAppears the time for which the mouse has to stay still
before a tooltip will be shown
@see TooltipClient, LookAndFeel::drawTooltip, LookAndFeel::getTooltipBounds
*/
explicit TooltipWindow (Component* parentComponent = nullptr,
int millisecondsBeforeTipAppears = 700);
/** Destructor. */
~TooltipWindow() override;
//==============================================================================
/** Changes the time before the tip appears.
This lets you change the value that was set in the constructor.
*/
void setMillisecondsBeforeTipAppears (int newTimeMs = 700) noexcept;
/** Can be called to manually force a tip to be shown at a particular location. */
void displayTip (Point<int> screenPosition, const String& text);
/** Can be called to manually hide the tip if it's showing. */
void hideTip();
/** Asks a component for its tooltip.
This can be overridden if you need custom lookup behaviour or to modify the strings.
*/
virtual String getTipFor (Component&);
//==============================================================================
/** A set of colour IDs to use to change the colour of various aspects of the tooltip.
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
methods.
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
*/
enum ColourIds
{
backgroundColourId = 0x1001b00, /**< The colour to fill the background with. */
textColourId = 0x1001c00, /**< The colour to use for the text. */
outlineColourId = 0x1001c10 /**< The colour to use to draw an outline around the tooltip. */
};
//==============================================================================
/** This abstract base class is implemented by LookAndFeel classes to provide
window drawing functionality.
*/
struct JUCE_API LookAndFeelMethods
{
virtual ~LookAndFeelMethods() = default;
/** returns the bounds for a tooltip at the given screen coordinate, constrained within the given desktop area. */
virtual Rectangle<int> getTooltipBounds (const String& tipText, Point<int> screenPos, Rectangle<int> parentArea) = 0;
virtual void drawTooltip (Graphics&, const String& text, int width, int height) = 0;
};
//==============================================================================
/** @internal */
float getDesktopScaleFactor() const override;
private:
//==============================================================================
Point<float> lastMousePos;
Component* lastComponentUnderMouse = nullptr;
String tipShowing, lastTipUnderMouse;
int millisecondsBeforeTipAppears;
int mouseClicks = 0, mouseWheelMoves = 0;
unsigned int lastCompChangeTime = 0, lastHideTime = 0;
bool reentrant = false;
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
void paint (Graphics&) override;
void mouseEnter (const MouseEvent&) override;
void timerCallback() override;
void updatePosition (const String&, Point<int>, Rectangle<int>);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TooltipWindow)
};
} // namespace juce

View File

@ -0,0 +1,358 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
/** Keeps track of the active top level window. */
class TopLevelWindowManager : private Timer,
private DeletedAtShutdown
{
public:
TopLevelWindowManager() {}
~TopLevelWindowManager() override { clearSingletonInstance(); }
JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (TopLevelWindowManager)
void checkFocusAsync()
{
startTimer (10);
}
void checkFocus()
{
startTimer (jmin (1731, getTimerInterval() * 2));
auto* newActive = findCurrentlyActiveWindow();
if (newActive != currentActive)
{
currentActive = newActive;
for (int i = windows.size(); --i >= 0;)
if (auto* tlw = windows[i])
tlw->setWindowActive (isWindowActive (tlw));
Desktop::getInstance().triggerFocusCallback();
}
}
bool addWindow (TopLevelWindow* const w)
{
windows.add (w);
checkFocusAsync();
return isWindowActive (w);
}
void removeWindow (TopLevelWindow* const w)
{
checkFocusAsync();
if (currentActive == w)
currentActive = nullptr;
windows.removeFirstMatchingValue (w);
if (windows.isEmpty())
deleteInstance();
}
Array<TopLevelWindow*> windows;
private:
TopLevelWindow* currentActive = nullptr;
void timerCallback() override
{
checkFocus();
}
bool isWindowActive (TopLevelWindow* const tlw) const
{
return (tlw == currentActive
|| tlw->isParentOf (currentActive)
|| tlw->hasKeyboardFocus (true))
&& tlw->isShowing();
}
TopLevelWindow* findCurrentlyActiveWindow() const
{
if (Process::isForegroundProcess())
{
auto* focusedComp = Component::getCurrentlyFocusedComponent();
auto* w = dynamic_cast<TopLevelWindow*> (focusedComp);
if (w == nullptr && focusedComp != nullptr)
w = focusedComp->findParentComponentOfClass<TopLevelWindow>();
if (w == nullptr)
w = currentActive;
if (w != nullptr && w->isShowing())
return w;
}
return nullptr;
}
JUCE_DECLARE_NON_COPYABLE (TopLevelWindowManager)
};
JUCE_IMPLEMENT_SINGLETON (TopLevelWindowManager)
void juce_checkCurrentlyFocusedTopLevelWindow();
void juce_checkCurrentlyFocusedTopLevelWindow()
{
if (auto* wm = TopLevelWindowManager::getInstanceWithoutCreating())
wm->checkFocusAsync();
}
//==============================================================================
TopLevelWindow::TopLevelWindow (const String& name, const bool shouldAddToDesktop)
: Component (name)
{
setTitle (name);
setOpaque (true);
if (shouldAddToDesktop)
Component::addToDesktop (TopLevelWindow::getDesktopWindowStyleFlags());
else
setDropShadowEnabled (true);
setWantsKeyboardFocus (true);
setBroughtToFrontOnMouseClick (true);
isCurrentlyActive = TopLevelWindowManager::getInstance()->addWindow (this);
}
TopLevelWindow::~TopLevelWindow()
{
shadower.reset();
TopLevelWindowManager::getInstance()->removeWindow (this);
}
//==============================================================================
void TopLevelWindow::focusOfChildComponentChanged (FocusChangeType)
{
auto* wm = TopLevelWindowManager::getInstance();
if (hasKeyboardFocus (true))
wm->checkFocus();
else
wm->checkFocusAsync();
}
void TopLevelWindow::setWindowActive (const bool isNowActive)
{
if (isCurrentlyActive != isNowActive)
{
isCurrentlyActive = isNowActive;
activeWindowStatusChanged();
}
}
void TopLevelWindow::activeWindowStatusChanged()
{
}
bool TopLevelWindow::isUsingNativeTitleBar() const noexcept
{
return useNativeTitleBar && (isOnDesktop() || ! isShowing());
}
void TopLevelWindow::visibilityChanged()
{
if (isShowing())
if (auto* p = getPeer())
if ((p->getStyleFlags() & (ComponentPeer::windowIsTemporary
| ComponentPeer::windowIgnoresKeyPresses)) == 0)
toFront (true);
}
void TopLevelWindow::parentHierarchyChanged()
{
setDropShadowEnabled (useDropShadow);
}
int TopLevelWindow::getDesktopWindowStyleFlags() const
{
int styleFlags = ComponentPeer::windowAppearsOnTaskbar;
if (useDropShadow) styleFlags |= ComponentPeer::windowHasDropShadow;
if (useNativeTitleBar) styleFlags |= ComponentPeer::windowHasTitleBar;
return styleFlags;
}
void TopLevelWindow::setDropShadowEnabled (const bool useShadow)
{
useDropShadow = useShadow;
if (isOnDesktop())
{
shadower.reset();
Component::addToDesktop (getDesktopWindowStyleFlags());
}
else
{
if (useShadow && isOpaque())
{
if (shadower == nullptr)
{
shadower.reset (getLookAndFeel().createDropShadowerForComponent (this));
if (shadower != nullptr)
shadower->setOwner (this);
}
}
else
{
shadower.reset();
}
}
}
void TopLevelWindow::setUsingNativeTitleBar (const bool shouldUseNativeTitleBar)
{
if (useNativeTitleBar != shouldUseNativeTitleBar)
{
FocusRestorer focusRestorer;
useNativeTitleBar = shouldUseNativeTitleBar;
recreateDesktopWindow();
sendLookAndFeelChange();
}
}
void TopLevelWindow::recreateDesktopWindow()
{
if (isOnDesktop())
{
Component::addToDesktop (getDesktopWindowStyleFlags());
toFront (true);
}
}
void TopLevelWindow::addToDesktop()
{
shadower.reset();
Component::addToDesktop (getDesktopWindowStyleFlags());
setDropShadowEnabled (isDropShadowEnabled()); // force an update to clear away any fake shadows if necessary.
}
void TopLevelWindow::addToDesktop (int windowStyleFlags, void* nativeWindowToAttachTo)
{
/* It's not recommended to change the desktop window flags directly for a TopLevelWindow,
because this class needs to make sure its layout corresponds with settings like whether
it's got a native title bar or not.
If you need custom flags for your window, you can override the getDesktopWindowStyleFlags()
method. If you do this, it's best to call the base class's getDesktopWindowStyleFlags()
method, then add or remove whatever flags are necessary from this value before returning it.
*/
jassert ((windowStyleFlags & ~ComponentPeer::windowIsSemiTransparent)
== (getDesktopWindowStyleFlags() & ~ComponentPeer::windowIsSemiTransparent));
Component::addToDesktop (windowStyleFlags, nativeWindowToAttachTo);
if (windowStyleFlags != getDesktopWindowStyleFlags())
sendLookAndFeelChange();
}
std::unique_ptr<AccessibilityHandler> TopLevelWindow::createAccessibilityHandler()
{
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::window);
}
//==============================================================================
void TopLevelWindow::centreAroundComponent (Component* c, const int width, const int height)
{
if (c == nullptr)
c = TopLevelWindow::getActiveTopLevelWindow();
if (c == nullptr || c->getBounds().isEmpty())
{
centreWithSize (width, height);
}
else
{
const auto scale = getDesktopScaleFactor() / Desktop::getInstance().getGlobalScaleFactor();
auto targetCentre = c->localPointToGlobal (c->getLocalBounds().getCentre()) / scale;
auto parentArea = c->getParentMonitorArea();
if (auto* parent = getParentComponent())
{
targetCentre = parent->getLocalPoint (nullptr, targetCentre);
parentArea = parent->getLocalBounds();
}
setBounds (Rectangle<int> (targetCentre.x - width / 2,
targetCentre.y - height / 2,
width, height)
.constrainedWithin (parentArea.reduced (12, 12)));
}
}
//==============================================================================
int TopLevelWindow::getNumTopLevelWindows() noexcept
{
return TopLevelWindowManager::getInstance()->windows.size();
}
TopLevelWindow* TopLevelWindow::getTopLevelWindow (const int index) noexcept
{
return TopLevelWindowManager::getInstance()->windows [index];
}
TopLevelWindow* TopLevelWindow::getActiveTopLevelWindow() noexcept
{
TopLevelWindow* best = nullptr;
int bestNumTWLParents = -1;
for (int i = TopLevelWindow::getNumTopLevelWindows(); --i >= 0;)
{
auto* tlw = TopLevelWindow::getTopLevelWindow (i);
if (tlw->isActiveWindow())
{
int numTWLParents = 0;
for (auto* c = tlw->getParentComponent(); c != nullptr; c = c->getParentComponent())
if (dynamic_cast<const TopLevelWindow*> (c) != nullptr)
++numTWLParents;
if (bestNumTWLParents < numTWLParents)
{
best = tlw;
bestNumTWLParents = numTWLParents;
}
}
}
return best;
}
} // namespace juce

View File

@ -0,0 +1,166 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
A base class for top-level windows.
This class is used for components that are considered a major part of your
application - e.g. ResizableWindow, DocumentWindow, DialogWindow, AlertWindow,
etc. Things like menus that pop up briefly aren't derived from it.
A TopLevelWindow is probably on the desktop, but this isn't mandatory - it
could itself be the child of another component.
The class manages a list of all instances of top-level windows that are in use,
and each one is also given the concept of being "active". The active window is
one that is actively being used by the user. This isn't quite the same as the
component with the keyboard focus, because there may be a popup menu or other
temporary window which gets keyboard focus while the active top level window is
unchanged.
A top-level window also has an optional drop-shadow.
@see ResizableWindow, DocumentWindow, DialogWindow
@tags{GUI}
*/
class JUCE_API TopLevelWindow : public Component
{
public:
//==============================================================================
/** Creates a TopLevelWindow.
@param name the name to give the component
@param addToDesktop if true, the window will be automatically added to the
desktop; if false, you can use it as a child component
*/
TopLevelWindow (const String& name, bool addToDesktop);
/** Destructor. */
~TopLevelWindow() override;
//==============================================================================
/** True if this is currently the TopLevelWindow that is actively being used.
This isn't quite the same as having keyboard focus, because the focus may be
on a child component or a temporary pop-up menu, etc, while this window is
still considered to be active.
@see activeWindowStatusChanged
*/
bool isActiveWindow() const noexcept { return isCurrentlyActive; }
//==============================================================================
/** This will set the bounds of the window so that it's centred in front of another
window.
If your app has a few windows open and want to pop up a dialog box for one of
them, you can use this to show it in front of the relevant parent window, which
is a bit neater than just having it appear in the middle of the screen.
If componentToCentreAround is nullptr, then the currently active TopLevelWindow will
be used instead. If no window is focused, it'll just default to the middle of the
screen.
*/
void centreAroundComponent (Component* componentToCentreAround,
int width, int height);
//==============================================================================
/** Turns the drop-shadow on and off. */
void setDropShadowEnabled (bool useShadow);
/** True if drop-shadowing is enabled. */
bool isDropShadowEnabled() const noexcept { return useDropShadow; }
/** Sets whether an OS-native title bar will be used, or a JUCE one.
@see isUsingNativeTitleBar
*/
void setUsingNativeTitleBar (bool useNativeTitleBar);
/** Returns true if the window is currently using an OS-native title bar.
@see setUsingNativeTitleBar
*/
bool isUsingNativeTitleBar() const noexcept;
//==============================================================================
/** Returns the number of TopLevelWindow objects currently in use.
@see getTopLevelWindow
*/
static int getNumTopLevelWindows() noexcept;
/** Returns one of the TopLevelWindow objects currently in use.
The index is 0 to (getNumTopLevelWindows() - 1).
*/
static TopLevelWindow* getTopLevelWindow (int index) noexcept;
/** Returns the currently-active top level window.
There might not be one, of course, so this can return nullptr.
*/
static TopLevelWindow* getActiveTopLevelWindow() noexcept;
/** Adds the window to the desktop using the default flags. */
void addToDesktop();
//==============================================================================
/** @internal */
void addToDesktop (int windowStyleFlags, void* nativeWindowToAttachTo = nullptr) override;
protected:
//==============================================================================
/** This callback happens when this window becomes active or inactive.
@see isActiveWindow
*/
virtual void activeWindowStatusChanged();
//==============================================================================
/** @internal */
void focusOfChildComponentChanged (FocusChangeType) override;
/** @internal */
void parentHierarchyChanged() override;
/** @internal */
virtual int getDesktopWindowStyleFlags() const;
/** @internal */
void recreateDesktopWindow();
/** @internal */
void visibilityChanged() override;
private:
friend class TopLevelWindowManager;
friend class ResizableWindow;
bool useDropShadow = true, useNativeTitleBar = false, isCurrentlyActive = false;
std::unique_ptr<DropShadower> shadower;
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
void setWindowActive (bool);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TopLevelWindow)
};
} // namespace juce