656 lines
20 KiB
C++
656 lines
20 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
This file is part of the JUCE library.
|
|
Copyright (c) 2017 - ROLI Ltd.
|
|
|
|
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 5 End-User License
|
|
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
|
27th April 2017).
|
|
|
|
End User License Agreement: www.juce.com/juce-5-licence
|
|
Privacy Policy: www.juce.com/juce-5-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 5 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-5-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();
|
|
return (isFullScreen() && ! isKioskMode() ? "fs " : "") + lastNonFullScreenPos.toString();
|
|
}
|
|
|
|
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);
|
|
|
|
{
|
|
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().findDisplayForRect (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
|