text:
/*
  ==============================================================================

   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
{

//==============================================================================
/**
    A component that resizes its parent component when dragged.

    This component forms a frame around the edge of a component, allowing it to
    be dragged by the edges or corners to resize it - like the way windows are
    resized in MSWindows or Linux.

    To use it, just add it to your component, making it fill the entire parent component
    (there's a mouse hit-test that only traps mouse-events which land around the
    edge of the component, so it's even ok to put it on top of any other components
    you're using). Make sure you rescale the resizer component to fill the parent
    each time the parent's size changes.

    @see ResizableCornerComponent
*/
class JUCE_API  ResizableBorderComponent  : public Component
{
public:
    //==============================================================================
    /** Creates a resizer.

        Pass in the target component which you want to be resized when this one is
        dragged.

        The target component will usually be a parent of the resizer component, but this
        isn't mandatory.

        Remember that when the target component is resized, it'll need to move and
        resize this component to keep it in place, as this won't happen automatically.

        If the constrainer parameter is not a nullptr, then this object will be used to
        enforce limits on the size and position that the component can be stretched to.
        Make sure that the constrainer isn't deleted while still in use by this object.

        @see ComponentBoundsConstrainer
    */
    ResizableBorderComponent (Component* componentToResize,
                              ComponentBoundsConstrainer* constrainer);

    /** Destructor. */
    ~ResizableBorderComponent();

    //==============================================================================
    /** Specifies how many pixels wide the draggable edges of this component are.

        @see getBorderThickness
    */
    void setBorderThickness (const BorderSize<int>& newBorderSize);

    /** Returns the number of pixels wide that the draggable edges of this component are.

        @see setBorderThickness
    */
    BorderSize<int> getBorderThickness() const;

    //==============================================================================
    /** Represents the different sections of a resizable border, which allow it to
        resized in different ways.
    */
    class Zone
    {
    public:
        //==============================================================================
        enum Zones
        {
            centre  = 0,
            left    = 1,
            top     = 2,
            right   = 4,
            bottom  = 8
        };

        //==============================================================================
        /** Creates a Zone from a combination of the flags in \enum Zones. */
        explicit Zone (int zoneFlags) noexcept;

        Zone() noexcept;
        Zone (const Zone&) noexcept;
        Zone& operator= (const Zone&) noexcept;

        bool operator== (const Zone&) const noexcept;
        bool operator!= (const Zone&) const noexcept;

        //==============================================================================
        /** Given a point within a rectangle with a resizable border, this returns the
            zone that the point lies within.
        */
        static Zone fromPositionOnBorder (const Rectangle<int>& totalSize,
                                          const BorderSize<int>& border,
                                          Point<int> position);

        /** Returns an appropriate mouse-cursor for this resize zone. */
        MouseCursor getMouseCursor() const noexcept;

        /** Returns true if dragging this zone will move the enire object without resizing it. */
        bool isDraggingWholeObject() const noexcept     { return zone == centre; }
        /** Returns true if dragging this zone will move the object's left edge. */
        bool isDraggingLeftEdge() const noexcept        { return (zone & left) != 0; }
        /** Returns true if dragging this zone will move the object's right edge. */
        bool isDraggingRightEdge() const noexcept       { return (zone & right) != 0; }
        /** Returns true if dragging this zone will move the object's top edge. */
        bool isDraggingTopEdge() const noexcept         { return (zone & top) != 0; }
        /** Returns true if dragging this zone will move the object's bottom edge. */
        bool isDraggingBottomEdge() const noexcept      { return (zone & bottom) != 0; }

        /** Resizes this rectangle by the given amount, moving just the edges that this zone
            applies to.
        */
        template <typename ValueType>
        Rectangle<ValueType> resizeRectangleBy (Rectangle<ValueType> original,
                                                const Point<ValueType>& distance) const noexcept
        {
            if (isDraggingWholeObject())
                return original + distance;

            if (isDraggingLeftEdge())   original.setLeft (jmin (original.getRight(), original.getX() + distance.x));
            if (isDraggingRightEdge())  original.setWidth (jmax (ValueType(), original.getWidth() + distance.x));
            if (isDraggingTopEdge())    original.setTop (jmin (original.getBottom(), original.getY() + distance.y));
            if (isDraggingBottomEdge()) original.setHeight (jmax (ValueType(), original.getHeight() + distance.y));

            return original;
        }

        /** Returns the raw flags for this zone. */
        int getZoneFlags() const noexcept               { return zone; }

    private:
        //==============================================================================
        int zone;
    };

    /** Returns the zone in which the mouse was last seen. */
    Zone getCurrentZone() const noexcept                 { return mouseZone; }

protected:
    /** @internal */
    void paint (Graphics&) override;
    /** @internal */
    void mouseEnter (const MouseEvent&) override;
    /** @internal */
    void mouseMove (const MouseEvent&) override;
    /** @internal */
    void mouseDown (const MouseEvent&) override;
    /** @internal */
    void mouseDrag (const MouseEvent&) override;
    /** @internal */
    void mouseUp (const MouseEvent&) override;
    /** @internal */
    bool hitTest (int x, int y) override;

private:
    WeakReference<Component> component;
    ComponentBoundsConstrainer* constrainer;
    BorderSize<int> borderSize;
    Rectangle<int> originalBounds;
    Zone mouseZone;

    void updateMouseZone (const MouseEvent&);

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ResizableBorderComponent)
};

} // namespace juce


text:
/*
  ==============================================================================

   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
{

ResizableCornerComponent::ResizableCornerComponent (Component* const componentToResize,
                                                    ComponentBoundsConstrainer* const constrainer_)
   : component (componentToResize),
     constrainer (constrainer_)
{
    setRepaintsOnMouseActivity (true);
    setMouseCursor (MouseCursor::BottomRightCornerResizeCursor);
}

ResizableCornerComponent::~ResizableCornerComponent()
{
}

//==============================================================================
void ResizableCornerComponent::paint (Graphics& g)
{
    getLookAndFeel()
        .drawCornerResizer (g, getWidth(), getHeight(),
                            isMouseOverOrDragging(),
                            isMouseButtonDown());
}

void ResizableCornerComponent::mouseDown (const MouseEvent&)
{
    if (component == nullptr)
    {
        jassertfalse; // You've deleted the component that this resizer is supposed to be controlling!
        return;
    }

    originalBounds = component->getBounds();

    if (constrainer != nullptr)
        constrainer->resizeStart();
}

void ResizableCornerComponent::mouseDrag (const MouseEvent& e)
{
    if (component == nullptr)
    {
        jassertfalse; // You've deleted the component that this resizer is supposed to be controlling!
        return;
    }

    Rectangle<int> r (originalBounds.withSize (originalBounds.getWidth() + e.getDistanceFromDragStartX(),
                                               originalBounds.getHeight() + e.getDistanceFromDragStartY()));

    if (constrainer != nullptr)
    {
        constrainer->setBoundsForComponent (component, r, false, false, true, true);
    }
    else
    {
        if (Component::Positioner* const pos = component->getPositioner())
            pos->applyNewBounds (r);
        else
            component->setBounds (r);
    }
}

void ResizableCornerComponent::mouseUp (const MouseEvent&)
{
    if (constrainer != nullptr)
        constrainer->resizeEnd();
}

bool ResizableCornerComponent::hitTest (int x, int y)
{
    if (getWidth() <= 0)
        return false;

    const int yAtX = getHeight() - (getHeight() * x / getWidth());

    return y >= yAtX - getHeight() / 4;
}

} // namespace juce

repo_name: fedden/RenderMan

mime_type: text/x-c++


text:
/*
  ==============================================================================

   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
{

//==============================================================================
/**
    A scrollbar component.

    To use a scrollbar, set up its total range using the setRangeLimits() method - this
    sets the range of values it can represent. Then you can use setCurrentRange() to
    change the position and size of the scrollbar's 'thumb'.

    Registering a ScrollBar::Listener with the scrollbar will allow you to find out when
    the user moves it, and you can use the getCurrentRangeStart() to find out where
    they moved it to.

    The scrollbar will adjust its own visibility according to whether its thumb size
    allows it to actually be scrolled.

    For most purposes, it's probably easier to use a Viewport or ListBox
    instead of handling a scrollbar directly.

    @see ScrollBar::Listener
*/
class JUCE_API  ScrollBar  : public Component,
                             public AsyncUpdater,
                             private Timer
{
public:
    //==============================================================================
    /** Creates a Scrollbar.
        @param isVertical      specifies whether the bar should be a vertical or horizontal
    */
    ScrollBar (bool isVertical);

    /** Destructor. */
    ~ScrollBar();

    //==============================================================================
    /** Returns true if the scrollbar is vertical, false if it's horizontal. */
    bool isVertical() const noexcept                                { return vertical; }

    /** Changes the scrollbar's direction.

        You'll also need to resize the bar appropriately - this just changes its internal
        layout.

        @param shouldBeVertical     true makes it vertical; false makes it horizontal.
    */
    void setOrientation (bool shouldBeVertical);

    /** Tells the scrollbar whether to make itself invisible when not needed.

        The default behaviour is for a scrollbar to become invisible when the thumb
        fills the whole of its range (i.e. when it can't be moved). Setting this
        value to false forces the bar to always be visible.
        @see autoHides()
    */
    void setAutoHide (bool shouldHideWhenFullRange);

    /** Returns true if this scrollbar is set to auto-hide when its thumb is as big
        as its maximum range.
        @see setAutoHide
    */
    bool autoHides() const noexcept;

    //==============================================================================
    /** Sets the minimum and maximum values that the bar will move between.

        The bar's thumb will always be constrained so that the entire thumb lies
        within this range.

        @see setCurrentRange
    */
    void setRangeLimits (Range<double> newRangeLimit,
                         NotificationType notification = sendNotificationAsync);

    /** Sets the minimum and maximum values that the bar will move between.

        The bar's thumb will always be constrained so that the entire thumb lies
        within this range.

        @see setCurrentRange
    */
    void setRangeLimits (double minimum, double maximum,
                         NotificationType notification = sendNotificationAsync);

    /** Returns the current limits on the thumb position.
        @see setRangeLimits
    */
    Range<double> getRangeLimit() const noexcept                    { return totalRange; }

    /** Returns the lower value that the thumb can be set to.

        This is the value set by setRangeLimits().
    */
    double getMinimumRangeLimit() const noexcept                    { return totalRange.getStart(); }

    /** Returns the upper value that the thumb can be set to.

        This is the value set by setRangeLimits().
    */
    double getMaximumRangeLimit() const noexcept                    { return totalRange.getEnd(); }

    //==============================================================================
    /** Changes the position of the scrollbar's 'thumb'.

        This sets both the position and size of the thumb - to just set the position without
        changing the size, you can use setCurrentRangeStart().

        If this method call actually changes the scrollbar's position, it will trigger an
        asynchronous call to ScrollBar::Listener::scrollBarMoved() for all the listeners that
        are registered.

        The notification parameter can be used to optionally send or inhibit a callback to
        any scrollbar listeners.

        @returns true if the range was changed, or false if nothing was changed.
        @see getCurrentRange. setCurrentRangeStart
    */
    bool setCurrentRange (Range<double> newRange,
                          NotificationType notification = sendNotificationAsync);

    /** Changes the position of the scrollbar's 'thumb'.

        This sets both the position and size of the thumb - to just set the position without
        changing the size, you can use setCurrentRangeStart().

        @param newStart     the top (or left) of the thumb, in the range
                            getMinimumRangeLimit() <= newStart <= getMaximumRangeLimit(). If the
                            value is beyond these limits, it will be clipped.
        @param newSize      the size of the thumb, such that
                            getMinimumRangeLimit() <= newStart + newSize <= getMaximumRangeLimit(). If the
                            size is beyond these limits, it will be clipped.
        @param notification specifies if and how a callback should be made to any listeners
                            if the range actually changes
        @see setCurrentRangeStart, getCurrentRangeStart, getCurrentRangeSize
    */
    void setCurrentRange (double newStart, double newSize,
                          NotificationType notification = sendNotificationAsync);

    /** Moves the bar's thumb position.

        This will move the thumb position without changing the thumb size. Note
        that the maximum thumb start position is (getMaximumRangeLimit() - getCurrentRangeSize()).

        If this method call actually changes the scrollbar's position, it will trigger an
        asynchronous call to ScrollBar::Listener::scrollBarMoved() for all the listeners that
        are registered.

        @see setCurrentRange
    */
    void setCurrentRangeStart (double newStart,
                               NotificationType notification = sendNotificationAsync);

    /** Returns the current thumb range.
        @see getCurrentRange, setCurrentRange
    */
    Range<double> getCurrentRange() const noexcept                  { return visibleRange; }

    /** Returns the position of the top of the thumb.
        @see getCurrentRange, setCurrentRangeStart
    */
    double getCurrentRangeStart() const noexcept                    { return visibleRange.getStart(); }

    /** Returns the current size of the thumb.
        @see getCurrentRange, setCurrentRange
    */
    double getCurrentRangeSize() const noexcept                     { return visibleRange.getLength(); }

    //==============================================================================
    /** Sets the amount by which the up and down buttons will move the bar.

        The value here is in terms of the total range, and is added or subtracted
        from the thumb position when the user clicks an up/down (or left/right) button.
    */
    void setSingleStepSize (double newSingleStepSize) noexcept;

    /** Moves the scrollbar by a number of single-steps.

        This will move the bar by a multiple of its single-step interval (as
        specified using the setSingleStepSize() method).

        A positive value here will move the bar down or to the right, a negative
        value moves it up or to the left.
        @returns true if the scrollbar's position actually changed.
    */
    bool moveScrollbarInSteps (int howManySteps,
                               NotificationType notification = sendNotificationAsync);

    /** Moves the scroll bar up or down in pages.

        This will move the bar by a multiple of its current thumb size, effectively
        doing a page-up or down.

        A positive value here will move the bar down or to the right, a negative
        value moves it up or to the left.
        @returns true if the scrollbar's position actually changed.
    */
    bool moveScrollbarInPages (int howManyPages,
                               NotificationType notification = sendNotificationAsync);

    /** Scrolls to the top (or left).
        This is the same as calling setCurrentRangeStart (getMinimumRangeLimit());
        @returns true if the scrollbar's position actually changed.
    */
    bool scrollToTop (NotificationType notification = sendNotificationAsync);

    /** Scrolls to the bottom (or right).
        This is the same as calling setCurrentRangeStart (getMaximumRangeLimit() - getCurrentRangeSize());
        @returns true if the scrollbar's position actually changed.
    */
    bool scrollToBottom (NotificationType notification = sendNotificationAsync);

    /** Changes the delay before the up and down buttons autorepeat when they are held
        down.

        For an explanation of what the parameters are for, see Button::setRepeatSpeed().

        @see Button::setRepeatSpeed
    */
    void setButtonRepeatSpeed (int initialDelayInMillisecs,
                               int repeatDelayInMillisecs,
                               int minimumDelayInMillisecs = -1);

    //==============================================================================
    /** A set of colour IDs to use to change the colour of various aspects of the component.

        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          = 0x1000300,    /**< The background colour of the scrollbar. */
        thumbColourId               = 0x1000400,    /**< A base colour to use for the thumb. The look and feel will probably use variations on this colour. */
        trackColourId               = 0x1000401     /**< A base colour to use for the slot area of the bar. The look and feel will probably use variations on this colour. */
    };

    //==============================================================================
    /**
        A class for receiving events from a ScrollBar.

        You can register a ScrollBar::Listener with a ScrollBar using the ScrollBar::addListener()
        method, and it will be called when the bar's position changes.

        @see ScrollBar::addListener, ScrollBar::removeListener
    */
    class JUCE_API  Listener
    {
    public:
        /** Destructor. */
        virtual ~Listener() {}

        /** Called when a ScrollBar is moved.

            @param scrollBarThatHasMoved    the bar that has moved
            @param newRangeStart            the new range start of this bar
        */
        virtual void scrollBarMoved (ScrollBar* scrollBarThatHasMoved,
                                     double newRangeStart) = 0;
    };

    /** Registers a listener that will be called when the scrollbar is moved. */
    void addListener (Listener* listener);

    /** Deregisters a previously-registered listener. */
    void removeListener (Listener* listener);

    //==============================================================================
    /** This abstract base class is implemented by LookAndFeel classes to provide
        scrollbar-drawing functionality.
    */
    struct JUCE_API  LookAndFeelMethods
    {
        virtual ~LookAndFeelMethods() {}

        virtual bool areScrollbarButtonsVisible() = 0;

        /** Draws one of the buttons on a scrollbar.

            @param g                    the context to draw into
            @param scrollbar            the bar itself
            @param width                the width of the button
            @param height               the height of the button
            @param buttonDirection      the direction of the button, where 0 = up, 1 = right, 2 = down, 3 = left
            @param isScrollbarVertical  true if it's a vertical bar, false if horizontal
            @param isMouseOverButton    whether the mouse is currently over the button (also true if it's held down)
            @param isButtonDown         whether the mouse button's held down
        */
        virtual void drawScrollbarButton (Graphics& g,
                                          ScrollBar& scrollbar,
                                          int width, int height,
                                          int buttonDirection,
                                          bool isScrollbarVertical,
                                          bool isMouseOverButton,
                                          bool isButtonDown) = 0;

        /** Draws the thumb area of a scrollbar.

            @param g                    the context to draw into
            @param scrollbar            the bar itself
            @param x                    the x position of the left edge of the thumb area to draw in
            @param y                    the y position of the top edge of the thumb area to draw in
            @param width                the width of the thumb area to draw in
            @param height               the height of the thumb area to draw in
            @param isScrollbarVertical  true if it's a vertical bar, false if horizontal
            @param thumbStartPosition   for vertical bars, the y coordinate of the top of the
                                        thumb, or its x position for horizontal bars
            @param thumbSize            for vertical bars, the height of the thumb, or its width for
                                        horizontal bars. This may be 0 if the thumb shouldn't be drawn.
            @param isMouseOver          whether the mouse is over the thumb area, also true if the mouse is
                                        currently dragging the thumb
            @param isMouseDown          whether the mouse is currently dragging the scrollbar
        */
        virtual void drawScrollbar (Graphics& g, ScrollBar& scrollbar,
                                    int x, int y, int width, int height,
                                    bool isScrollbarVertical,
                                    int thumbStartPosition,
                                    int thumbSize,
                                    bool isMouseOver,
                                    bool isMouseDown) = 0;

        /** Returns the component effect to use for a scrollbar */
        virtual ImageEffectFilter* getScrollbarEffect() = 0;

        /** Returns the minimum length in pixels to use for a scrollbar thumb. */
        virtual int getMinimumScrollbarThumbSize (ScrollBar&) = 0;

        /** Returns the default thickness to use for a scrollbar. */
        virtual int getDefaultScrollbarWidth() = 0;

        /** Returns the length in pixels to use for a scrollbar button. */
        virtual int getScrollbarButtonSize (ScrollBar&) = 0;
    };

    //==============================================================================
    /** @internal */
    bool keyPressed (const KeyPress&) override;
    /** @internal */
    void mouseWheelMove (const MouseEvent&, const MouseWheelDetails&) override;
    /** @internal */
    void lookAndFeelChanged() override;
    /** @internal */
    void mouseDown (const MouseEvent&) override;
    /** @internal */
    void mouseDrag (const MouseEvent&) override;
    /** @internal */
    void mouseUp   (const MouseEvent&) override;
    /** @internal */
    void paint (Graphics&) override;
    /** @internal */
    void resized() override;
    /** @internal */
    void parentHierarchyChanged() override;

private:
    //==============================================================================
    Range<double> totalRange, visibleRange;
    double singleStepSize, dragStartRange;
    int thumbAreaStart, thumbAreaSize, thumbStart, thumbSize;
    int dragStartMousePos, lastMousePos;
    int initialDelayInMillisecs, repeatDelayInMillisecs, minimumDelayInMillisecs;
    bool vertical, isDraggingThumb, autohides;
    class ScrollbarButton;
    friend struct ContainerDeletePolicy<ScrollbarButton>;
    ScopedPointer<ScrollbarButton> upButton, downButton;
    ListenerList<Listener> listeners;

    void handleAsyncUpdate() override;
    void updateThumbPosition();
    void timerCallback() override;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ScrollBar)
};

/** This typedef is just for compatibility with old code - newer code should use the ScrollBar::Listener class directly. */
typedef ScrollBar::Listener ScrollBarListener;

} // namespace juce


text:
/*
  ==============================================================================

   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
{

GroupComponent::GroupComponent (const String& name,
                                const String& labelText)
    : Component (name),
      text (labelText),
      justification (Justification::left)
{
    setInterceptsMouseClicks  (false, true);
}

GroupComponent::~GroupComponent() {}

void GroupComponent::setText (const String& newText)
{
    if (text != newText)
    {
        text = newText;
        repaint();
    }
}

String GroupComponent::getText() const
{
    return text;
}

void GroupComponent::setTextLabelPosition (Justification newJustification)
{
    if (justification != newJustification)
    {
        justification = newJustification;
        repaint();
    }
}

void GroupComponent::paint (Graphics& g)
{
    getLookAndFeel().drawGroupComponentOutline (g, getWidth(), getHeight(),
                                                text, justification, *this);
}

void GroupComponent::enablementChanged()    { repaint(); }
void GroupComponent::colourChanged()        { repaint(); }

} // namespace juce


text:
/*
  ==============================================================================

   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
{

class ComponentAnimator::AnimationTask
{
public:
    AnimationTask (Component* c) noexcept  : component (c) {}

    void reset (const Rectangle<int>& finalBounds,
                float finalAlpha,
                int millisecondsToSpendMoving,
                bool useProxyComponent,
                double startSpd, double endSpd)
    {
        msElapsed = 0;
        msTotal = jmax (1, millisecondsToSpendMoving);
        lastProgress = 0;
        destination = finalBounds;
        destAlpha = finalAlpha;

        isMoving = (finalBounds != component->getBounds());
        isChangingAlpha = (finalAlpha != component->getAlpha());

        left    = component->getX();
        top     = component->getY();
        right   = component->getRight();
        bottom  = component->getBottom();
        alpha   = component->getAlpha();

        const double invTotalDistance = 4.0 / (startSpd + endSpd + 2.0);
        startSpeed = jmax (0.0, startSpd * invTotalDistance);
        midSpeed = invTotalDistance;
        endSpeed = jmax (0.0, endSpd * invTotalDistance);

        if (useProxyComponent)
            proxy = new ProxyComponent (*component);
        else
            proxy = nullptr;

        component->setVisible (! useProxyComponent);
    }

    bool useTimeslice (const int elapsed)
    {
        if (auto* c = proxy != nullptr ? static_cast<Component*> (proxy)
                                       : static_cast<Component*> (component))
        {
            msElapsed += elapsed;
            double newProgress = msElapsed / (double) msTotal;

            if (newProgress >= 0 && newProgress < 1.0)
            {
                const WeakReference<AnimationTask> weakRef (this);
                newProgress = timeToDistance (newProgress);
                const double delta = (newProgress - lastProgress) / (1.0 - lastProgress);
                jassert (newProgress >= lastProgress);
                lastProgress = newProgress;

                if (delta < 1.0)
                {
                    bool stillBusy = false;

                    if (isMoving)
                    {
                        left   += (destination.getX()      - left)   * delta;
                        top    += (destination.getY()      - top)    * delta;
                        right  += (destination.getRight()  - right)  * delta;
                        bottom += (destination.getBottom() - bottom) * delta;

                        const Rectangle<int> newBounds (roundToInt (left),
                                                        roundToInt (top),
                                                        roundToInt (right - left),
                                                        roundToInt (bottom - top));

                        if (newBounds != destination)
                        {
                            c->setBounds (newBounds);
                            stillBusy = true;
                        }
                    }

                    // Check whether the animation was cancelled/deleted during
                    // a callback during the setBounds method
                    if (weakRef.wasObjectDeleted())
                        return false;

                    if (isChangingAlpha)
                    {
                        alpha += (destAlpha - alpha) * delta;
                        c->setAlpha ((float) alpha);
                        stillBusy = true;
                    }

                    if (stillBusy)
                        return true;
                }
            }
        }

        moveToFinalDestination();
        return false;
    }

    void moveToFinalDestination()
    {
        if (component != nullptr)
        {
            const WeakReference<AnimationTask> weakRef (this);
            component->setAlpha ((float) destAlpha);
            component->setBounds (destination);

            if (! weakRef.wasObjectDeleted())
                if (proxy != nullptr)
                    component->setVisible (destAlpha > 0);
        }
    }

    //==============================================================================
    struct ProxyComponent  : public Component
    {
        ProxyComponent (Component& c)
        {
            setWantsKeyboardFocus (false);
            setBounds (c.getBounds());
            setTransform (c.getTransform());
            setAlpha (c.getAlpha());
            setInterceptsMouseClicks (false, false);

            if (auto* parent = c.getParentComponent())
                parent->addAndMakeVisible (this);
            else if (c.isOnDesktop() && c.getPeer() != nullptr)
                addToDesktop (c.getPeer()->getStyleFlags() | ComponentPeer::windowIgnoresKeyPresses);
            else
                jassertfalse; // seem to be trying to animate a component that's not visible..

            auto scale = (float) Desktop::getInstance().getDisplays()
                                  .getDisplayContaining (getScreenBounds().getCentre()).scale;

            image = c.createComponentSnapshot (c.getLocalBounds(), false, scale);

            setVisible (true);
            toBehind (&c);
        }

        void paint (Graphics& g) override
        {
            g.setOpacity (1.0f);
            g.drawImageTransformed (image, AffineTransform::scale (getWidth()  / (float) image.getWidth(),
                                                                   getHeight() / (float) image.getHeight()), false);
        }

    private:
        Image image;

        JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProxyComponent)
    };

    WeakReference<Component> component;
    ScopedPointer<Component> proxy;

    Rectangle<int> destination;
    double destAlpha;

    int msElapsed, msTotal;
    double startSpeed, midSpeed, endSpeed, lastProgress;
    double left, top, right, bottom, alpha;
    bool isMoving, isChangingAlpha;

private:
    double timeToDistance (const double time) const noexcept
    {
        return (time < 0.5) ? time * (startSpeed + time * (midSpeed - startSpeed))
                            : 0.5 * (startSpeed + 0.5 * (midSpeed - startSpeed))
                                + (time - 0.5) * (midSpeed + (time - 0.5) * (endSpeed - midSpeed));
    }

    JUCE_DECLARE_WEAK_REFERENCEABLE (AnimationTask)
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AnimationTask)
};

//==============================================================================
ComponentAnimator::ComponentAnimator()  : lastTime (0) {}
ComponentAnimator::~ComponentAnimator() {}

//==============================================================================
ComponentAnimator::AnimationTask* ComponentAnimator::findTaskFor (Component* const component) const noexcept
{
    for (int i = tasks.size(); --i >= 0;)
        if (component == tasks.getUnchecked(i)->component.get())
            return tasks.getUnchecked(i);

    return nullptr;
}

void ComponentAnimator::animateComponent (Component* const component,
                                          const Rectangle<int>& finalBounds,
                                          const float finalAlpha,
                                          const int millisecondsToSpendMoving,
                                          const bool useProxyComponent,
                                          const double startSpeed,
                                          const double endSpeed)
{
    // the speeds must be 0 or greater!
    jassert (startSpeed >= 0 && endSpeed >= 0);

    if (component != nullptr)
    {
        auto* at = findTaskFor (component);

        if (at == nullptr)
        {
            at = new AnimationTask (component);
            tasks.add (at);
            sendChangeMessage();
        }

        at->reset (finalBounds, finalAlpha, millisecondsToSpendMoving,
                   useProxyComponent, startSpeed, endSpeed);

        if (! isTimerRunning())
        {
            lastTime = Time::getMillisecondCounter();
            startTimerHz (50);
        }
    }
}

void ComponentAnimator::fadeOut (Component* component, int millisecondsToTake)
{
    if (component != nullptr)
    {
        if (component->isShowing() && millisecondsToTake > 0)
            animateComponent (component, component->getBounds(), 0.0f, millisecondsToTake, true, 1.0, 1.0);

        component->setVisible (false);
    }
}

void ComponentAnimator::fadeIn (Component* component, int millisecondsToTake)
{
    if (component != nullptr && ! (component->isVisible() && component->getAlpha() == 1.0f))
    {
        component->setAlpha (0.0f);
        component->setVisible (true);
        animateComponent (component, component->getBounds(), 1.0f, millisecondsToTake, false, 1.0, 1.0);
    }
}

void ComponentAnimator::cancelAllAnimations (const bool moveComponentsToTheirFinalPositions)
{
    if (tasks.size() > 0)
    {
        if (moveComponentsToTheirFinalPositions)
            for (int i = tasks.size(); --i >= 0;)
                tasks.getUnchecked(i)->moveToFinalDestination();

        tasks.clear();
        sendChangeMessage();
    }
}

void ComponentAnimator::cancelAnimation (Component* const component,
                                         const bool moveComponentToItsFinalPosition)
{
    if (auto* at = findTaskFor (component))
    {
        if (moveComponentToItsFinalPosition)
            at->moveToFinalDestination();

        tasks.removeObject (at);
        sendChangeMessage();
    }
}

Rectangle<int> ComponentAnimator::getComponentDestination (Component* const component)
{
    jassert (component != nullptr);

    if (auto* at = findTaskFor (component))
        return at->destination;

    return component->getBounds();
}

bool ComponentAnimator::isAnimating (Component* component) const noexcept
{
    return findTaskFor (component) != nullptr;
}

bool ComponentAnimator::isAnimating() const noexcept
{
    return tasks.size() != 0;
}

void ComponentAnimator::timerCallback()
{
    auto timeNow = Time::getMillisecondCounter();

    if (lastTime == 0)
        lastTime = timeNow;

    auto elapsed = (int) (timeNow - lastTime);

    for (auto* task : Array<AnimationTask*> (tasks.begin(), tasks.size()))
    {
        if (tasks.contains (task) && ! task->useTimeslice (elapsed))
        {
            tasks.removeObject (task);
            sendChangeMessage();
        }
    }

    lastTime = timeNow;

    if (tasks.size() == 0)
        stopTimer();
}

} // namespace juce


text:
/*
  ==============================================================================

   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
{

//==============================================================================
/**
    A component that resizes its parent component when dragged.

    This component forms a bar along one edge of a component, allowing it to
    be dragged by that edges to resize it.

    To use it, just add it to your component, positioning it along the appropriate
    edge. Make sure you reposition the resizer component each time the parent's size
    changes, to keep it in the correct position.

    @see ResizbleBorderComponent, ResizableCornerComponent
*/
class JUCE_API  ResizableEdgeComponent  : public Component
{
public:
    //==============================================================================
    enum Edge
    {
        leftEdge,   /**< Indicates a vertical bar that can be dragged left/right to move the component's left-hand edge. */
        rightEdge,  /**< Indicates a vertical bar that can be dragged left/right to move the component's right-hand edge. */
        topEdge,    /**< Indicates a horizontal bar that can be dragged up/down to move the top of the component. */
        bottomEdge  /**< Indicates a horizontal bar that can be dragged up/down to move the bottom of the component. */
    };

    /** Creates a resizer bar.

        Pass in the target component which you want to be resized when this one is
        dragged. The target component will usually be this component's parent, but this
        isn't mandatory.

        Remember that when the target component is resized, it'll need to move and
        resize this component to keep it in place, as this won't happen automatically.

        If the constrainer parameter is not a nullptr, then this object will be used to
        enforce limits on the size and position that the component can be stretched to.
        Make sure that the constrainer isn't deleted while still in use by this object.

        @see ComponentBoundsConstrainer
    */
    ResizableEdgeComponent (Component* componentToResize,
                            ComponentBoundsConstrainer* constrainer,
                            Edge edgeToResize);

    /** Destructor. */
    ~ResizableEdgeComponent();

    bool isVertical() const noexcept;

protected:
    //==============================================================================
    /** @internal */
    void paint (Graphics&) override;
    /** @internal */
    void mouseDown (const MouseEvent&) override;
    /** @internal */
    void mouseDrag (const MouseEvent&) override;
    /** @internal */
    void mouseUp (const MouseEvent&) override;

private:
    WeakReference<Component> component;
    ComponentBoundsConstrainer* constrainer;
    Rectangle<int> originalBounds;
    const Edge edge;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ResizableEdgeComponent)
};

} // namespace juce


text:
/*
  ==============================================================================

   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
{

//==============================================================================
/**
    Loads and maintains a tree of Components from a ValueTree that represents them.

    To allow the state of a tree of components to be saved as a ValueTree and re-loaded,
    this class lets you register a set of type-handlers for the different components that
    are involved, and then uses these types to re-create a set of components from its
    stored state.

    Essentially, to use this, you need to create a ComponentBuilder with your ValueTree,
    then use registerTypeHandler() to give it a set of type handlers that can cope with
    all the items in your tree. Then you can call getComponent() to build the component.
    Once you've got the component you can either take it and delete the ComponentBuilder
    object, or if you keep the ComponentBuilder around, it'll monitor any changes in the
    ValueTree and automatically update the component to reflect these changes.
*/
class JUCE_API  ComponentBuilder  : private ValueTree::Listener
{
public:
    /** Creates a ComponentBuilder that will use the given state.
        Once you've created your builder, you should use registerTypeHandler() to register some
        type handlers for it, and then you can call createComponent() or getManagedComponent()
        to get the actual component.
    */
    explicit ComponentBuilder (const ValueTree& state);

    /** Creates a builder that doesn't have a state object. */
    ComponentBuilder();

    /** Destructor. */
    ~ComponentBuilder();

    /** This is the ValueTree data object that the builder is working with. */
    ValueTree state;

    //==============================================================================
    /** Returns the builder's component (creating it if necessary).

        The first time that this method is called, the builder will attempt to create a component
        from the ValueTree, so you must have registered some suitable type handlers before calling
        this. If there's a problem and the component can't be created, this method returns nullptr.

        The component that is returned is owned by this ComponentBuilder, so you can put it inside
        your own parent components, but don't delete it! The ComponentBuilder will delete it automatically
        when the builder is destroyed. If you want to get a component that you can delete yourself,
        call createComponent() instead.

        The ComponentBuilder will update this component if any changes are made to the ValueTree, so if
        there's a chance that the tree might change, be careful not to keep any pointers to sub-components,
        as they may be changed or removed.
    */
    Component* getManagedComponent();

    /** Creates and returns a new instance of the component that the ValueTree represents.
        The caller is responsible for using and deleting the object that is returned. Unlike
        getManagedComponent(), the component that is returned will not be updated by the builder.
    */
    Component* createComponent();

    //==============================================================================
    /**
        The class is a base class for objects that manage the loading of a type of component
        from a ValueTree.

        To store and re-load a tree of components as a ValueTree, each component type must have
        a TypeHandler to represent it.

        @see ComponentBuilder::registerTypeHandler(), Drawable::registerDrawableTypeHandlers()
    */
    class JUCE_API  TypeHandler
    {
    public:
        //==============================================================================
        /** Creates a TypeHandler.
            The valueTreeType must be the type name of the ValueTrees that this handler can parse.
        */
        explicit TypeHandler (const Identifier& valueTreeType);

        /** Destructor. */
        virtual ~TypeHandler();

        /** Returns the type of the ValueTrees that this handler can parse. */
        const Identifier type;

        /** Returns the builder that this type is registered with. */
        ComponentBuilder* getBuilder() const noexcept;

        //==============================================================================
        /** This method must create a new component from the given state, add it to the specified
            parent component (which may be null), and return it.

            The ValueTree will have been pre-checked to make sure that its type matches the type
            that this handler supports.

            There's no need to set the new Component's ID to match that of the state - the builder
            will take care of that itself.
        */
        virtual Component* addNewComponentFromState (const ValueTree& state, Component* parent) = 0;

        /** This method must update an existing component from a new ValueTree state.

            A component that has been created with addNewComponentFromState() may need to be updated
            if the ValueTree changes, so this method is used to do that. Your implementation must do
            whatever's necessary to update the component from the new state provided.

            The ValueTree will have been pre-checked to make sure that its type matches the type
            that this handler supports, and the component will have been created by this type's
            addNewComponentFromState() method.
        */
        virtual void updateComponentFromState (Component* component, const ValueTree& state) = 0;

    private:
        //==============================================================================
        friend class ComponentBuilder;
        ComponentBuilder* builder;

        JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TypeHandler)
    };

    //==============================================================================
    /** Adds a type handler that the builder can use when trying to load components.
        @see Drawable::registerDrawableTypeHandlers()
    */
    void registerTypeHandler (TypeHandler* type);

    /** Tries to find a registered type handler that can load a component from the given ValueTree. */
    TypeHandler* getHandlerForState (const ValueTree& state) const;

    /** Returns the number of registered type handlers.
        @see getHandler, registerTypeHandler
    */
    int getNumHandlers() const noexcept;

    /** Returns one of the registered type handlers.
        @see getNumHandlers, registerTypeHandler
    */
    TypeHandler* getHandler (int index) const noexcept;

    /** Registers handlers for various standard juce components. */
    void registerStandardComponentTypes();

    //==============================================================================
    /** This class is used when references to images need to be stored in ValueTrees.

        An instance of an ImageProvider provides a mechanism for converting an Image to/from
        a reference, which may be a file, URL, ID string, or whatever system is appropriate in
        your app.

        When you're loading components from a ValueTree that may need a way of loading images, you
        should call ComponentBuilder::setImageProvider() to supply a suitable provider before
        trying to load the component.

        @see ComponentBuilder::setImageProvider()
    */
    class JUCE_API  ImageProvider
    {
    public:
        ImageProvider() {}
        virtual ~ImageProvider() {}

        /** Retrieves the image associated with this identifier, which could be any
            kind of string, number, filename, etc.

            The image that is returned will be owned by the caller, but it may come
            from the ImageCache.
        */
        virtual Image getImageForIdentifier (const var& imageIdentifier) = 0;

        /** Returns an identifier to be used to refer to a given image.
            This is used when a reference to an image is stored in a ValueTree.
        */
        virtual var getIdentifierForImage (const Image& image) = 0;
    };

    //==============================================================================
    /** Gives the builder an ImageProvider object that the type handlers can use when
        loading images from stored references.

        The object that is passed in is not owned by the builder, so the caller must delete
        it when it is no longer needed, but not while the builder may still be using it. To
        clear the image provider, just call setImageProvider (nullptr).
    */
    void setImageProvider (ImageProvider* newImageProvider) noexcept;

    /** Returns the current image provider that this builder is using, or nullptr if none has been set. */
    ImageProvider* getImageProvider() const noexcept;

    //==============================================================================
    /** Updates the children of a parent component by updating them from the children of
        a given ValueTree.
    */
    void updateChildComponents (Component& parent, const ValueTree& children);

    /** An identifier for the property of the ValueTrees that is used to store a unique ID
        for that component.
    */
    static const Identifier idProperty;

private:
    //==============================================================================
    OwnedArray<TypeHandler> types;
    ScopedPointer<Component> component;
    ImageProvider* imageProvider;
   #if JUCE_DEBUG
    WeakReference<Component> componentRef;
   #endif

    void valueTreePropertyChanged (ValueTree&, const Identifier&) override;
    void valueTreeChildAdded (ValueTree&, ValueTree&) override;
    void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override;
    void valueTreeChildOrderChanged (ValueTree&, int, int) override;
    void valueTreeParentChanged (ValueTree&) override;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComponentBuilder)
};

} // namespace juce


text:
/*
  ==============================================================================

   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
{

ResizableBorderComponent::Zone::Zone() noexcept  : zone (0) {}

ResizableBorderComponent::Zone::Zone (const int zoneFlags) noexcept  : zone (zoneFlags) {}

ResizableBorderComponent::Zone::Zone (const ResizableBorderComponent::Zone& other) noexcept  : zone (other.zone) {}

ResizableBorderComponent::Zone& ResizableBorderComponent::Zone::operator= (const ResizableBorderComponent::Zone& other) noexcept
{
    zone = other.zone;
    return *this;
}

bool ResizableBorderComponent::Zone::operator== (const ResizableBorderComponent::Zone& other) const noexcept      { return zone == other.zone; }
bool ResizableBorderComponent::Zone::operator!= (const ResizableBorderComponent::Zone& other) const noexcept      { return zone != other.zone; }

ResizableBorderComponent::Zone ResizableBorderComponent::Zone::fromPositionOnBorder (const Rectangle<int>& totalSize,
                                                                                const BorderSize<int>& border,
                                                                                Point<int> position)
{
    int z = 0;

    if (totalSize.contains (position)
         && ! border.subtractedFrom (totalSize).contains (position))
    {
        const int minW = jmax (totalSize.getWidth() / 10, jmin (10, totalSize.getWidth() / 3));
        if (position.x < jmax (border.getLeft(), minW) && border.getLeft() > 0)
            z |= left;
        else if (position.x >= totalSize.getWidth() - jmax (border.getRight(), minW) && border.getRight() > 0)
            z |= right;

        const int minH = jmax (totalSize.getHeight() / 10, jmin (10, totalSize.getHeight() / 3));
        if (position.y < jmax (border.getTop(), minH) && border.getTop() > 0)
            z |= top;
        else if (position.y >= totalSize.getHeight() - jmax (border.getBottom(), minH) && border.getBottom() > 0)
            z |= bottom;
    }

    return Zone (z);
}

MouseCursor ResizableBorderComponent::Zone::getMouseCursor() const noexcept
{
    MouseCursor::StandardCursorType mc = MouseCursor::NormalCursor;

    switch (zone)
    {
        case (left | top):      mc = MouseCursor::TopLeftCornerResizeCursor; break;
        case top:               mc = MouseCursor::TopEdgeResizeCursor; break;
        case (right | top):     mc = MouseCursor::TopRightCornerResizeCursor; break;
        case left:              mc = MouseCursor::LeftEdgeResizeCursor; break;
        case right:             mc = MouseCursor::RightEdgeResizeCursor; break;
        case (left | bottom):   mc = MouseCursor::BottomLeftCornerResizeCursor; break;
        case bottom:            mc = MouseCursor::BottomEdgeResizeCursor; break;
        case (right | bottom):  mc = MouseCursor::BottomRightCornerResizeCursor; break;
        default:                break;
    }

    return mc;
}

//==============================================================================
ResizableBorderComponent::ResizableBorderComponent (Component* const componentToResize,
                                                    ComponentBoundsConstrainer* const constrainer_)
   : component (componentToResize),
     constrainer (constrainer_),
     borderSize (5),
     mouseZone (0)
{
}

ResizableBorderComponent::~ResizableBorderComponent()
{
}

//==============================================================================
void ResizableBorderComponent::paint (Graphics& g)
{
    getLookAndFeel().drawResizableFrame (g, getWidth(), getHeight(), borderSize);
}

void ResizableBorderComponent::mouseEnter (const MouseEvent& e)
{
    updateMouseZone (e);
}

void ResizableBorderComponent::mouseMove (const MouseEvent& e)
{
    updateMouseZone (e);
}

void ResizableBorderComponent::mouseDown (const MouseEvent& e)
{
    if (component == nullptr)
    {
        jassertfalse; // You've deleted the component that this resizer was supposed to be using!
        return;
    }

    updateMouseZone (e);

    originalBounds = component->getBounds();

    if (constrainer != nullptr)
        constrainer->resizeStart();
}

void ResizableBorderComponent::mouseDrag (const MouseEvent& e)
{
    if (component == nullptr)
    {
        jassertfalse; // You've deleted the component that this resizer was supposed to be using!
        return;
    }

    const Rectangle<int> newBounds (mouseZone.resizeRectangleBy (originalBounds, e.getOffsetFromDragStart()));

    if (constrainer != nullptr)
    {
        constrainer->setBoundsForComponent (component, newBounds,
                                            mouseZone.isDraggingTopEdge(),
                                            mouseZone.isDraggingLeftEdge(),
                                            mouseZone.isDraggingBottomEdge(),
                                            mouseZone.isDraggingRightEdge());
    }
    else
    {
        if (Component::Positioner* const pos = component->getPositioner())
            pos->applyNewBounds (newBounds);
        else
            component->setBounds (newBounds);
    }
}

void ResizableBorderComponent::mouseUp (const MouseEvent&)
{
    if (constrainer != nullptr)
        constrainer->resizeEnd();
}

bool ResizableBorderComponent::hitTest (int x, int y)
{
    return x < borderSize.getLeft()
            || x >= getWidth() - borderSize.getRight()
            || y < borderSize.getTop()
            || y >= getHeight() - borderSize.getBottom();
}

void ResizableBorderComponent::setBorderThickness (const BorderSize<int>& newBorderSize)
{
    if (borderSize != newBorderSize)
    {
        borderSize = newBorderSize;
        repaint();
    }
}

BorderSize<int> ResizableBorderComponent::getBorderThickness() const
{
    return borderSize;
}

void ResizableBorderComponent::updateMouseZone (const MouseEvent& e)
{
    Zone newZone (Zone::fromPositionOnBorder (getLocalBounds(), borderSize, e.getPosition()));

    if (mouseZone != newZone)
    {
        mouseZone = newZone;
        setMouseCursor (newZone.getMouseCursor());
    }
}

} // namespace juce


text:
/*
  ==============================================================================

   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
{

//==============================================================================
/**
    A utility class for fitting a set of objects whose sizes can vary between
    a minimum and maximum size, into a space.

    This is a trickier algorithm than it would first seem, so I've put it in this
    class to allow it to be shared by various bits of code.

    To use it, create one of these objects, call addItem() to add the list of items
    you need, then call resizeToFit(), which will change all their sizes. You can
    then retrieve the new sizes with getItemSize() and getNumItems().

    It's currently used by the TableHeaderComponent for stretching out the table
    headings to fill the table's width.
*/
class StretchableObjectResizer
{
public:
    //==============================================================================
    /** Creates an empty object resizer. */
    StretchableObjectResizer();

    /** Destructor. */
    ~StretchableObjectResizer();

    //==============================================================================
    /** Adds an item to the list.

        The order parameter lets you specify groups of items that are resized first when some
        space needs to be found. Those items with an order of 0 will be the first ones to be
        resized, and if that doesn't provide enough space to meet the requirements, the algorithm
        will then try resizing the items with an order of 1, then 2, and so on.
    */
    void addItem (double currentSize,
                  double minSize,
                  double maxSize,
                  int order = 0);

    /** Resizes all the items to fit this amount of space.

        This will attempt to fit them in without exceeding each item's miniumum and
        maximum sizes. In cases where none of the items can be expanded or enlarged any
        further, the final size may be greater or less than the size passed in.

        After calling this method, you can retrieve the new sizes with the getItemSize()
        method.
    */
    void resizeToFit (double targetSize);

    /** Returns the number of items that have been added. */
    int getNumItems() const noexcept                        { return items.size(); }

    /** Returns the size of one of the items. */
    double getItemSize (int index) const noexcept;

private:
    //==============================================================================
    struct Item
    {
        double size;
        double minSize;
        double maxSize;
        int order;
    };

    Array<Item> items;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StretchableObjectResizer)
};

} // namespace juce


text:
/*
  ==============================================================================

   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
{

ButtonPropertyComponent::ButtonPropertyComponent (const String& name,
                                                  const bool triggerOnMouseDown)
    : PropertyComponent (name)
{
    addAndMakeVisible (button);
    button.setTriggeredOnMouseDown (triggerOnMouseDown);
    button.addListener (this);
}

ButtonPropertyComponent::~ButtonPropertyComponent()
{
}

void ButtonPropertyComponent::refresh()
{
    button.setButtonText (getButtonText());
}

void ButtonPropertyComponent::buttonClicked (Button*)
{
    buttonClicked();
}

} // namespace juce


text:
/*
  ==============================================================================

   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
{

//==============================================================================
/**
    A PropertyComponent that contains an on/off toggle button.

    This type of property component can be used if you have a boolean value to
    toggle on/off.

    @see PropertyComponent
*/
class JUCE_API  BooleanPropertyComponent  : public PropertyComponent,
                                            private Button::Listener
{
protected:
    //==============================================================================
    /** Creates a button component.

        If you use this constructor, you must override the getState() and setState()
        methods.

        @param propertyName         the property name to be passed to the PropertyComponent
        @param buttonTextWhenTrue   the text shown in the button when the value is true
        @param buttonTextWhenFalse  the text shown in the button when the value is false
    */
    BooleanPropertyComponent (const String& propertyName,
                              const String& buttonTextWhenTrue,
                              const String& buttonTextWhenFalse);

public:
    /** Creates a button component.

        Note that if you call this constructor then you must use the Value to interact with the
        button state, and you can't override the class with your own setState or getState methods.
        If you want to use getState and setState, call the other constructor instead.

        @param valueToControl       a Value object that this property should refer to.
        @param propertyName         the property name to be passed to the PropertyComponent
        @param buttonText           the text shown in the ToggleButton component
    */
    BooleanPropertyComponent (const Value& valueToControl,
                              const String& propertyName,
                              const String& buttonText);

    /** Destructor. */
    ~BooleanPropertyComponent();

    //==============================================================================
    /** Called to change the state of the boolean value. */
    virtual void setState (bool newState);

    /** Must return the current value of the property. */
    virtual bool getState() const;

    //==============================================================================
    /** A set of colour IDs to use to change the colour of various aspects of the component.

        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          = 0x100e801,    /**< The colour to fill the background of the button area. */
        outlineColourId             = 0x100e803,    /**< The colour to use to draw an outline around the text area. */
    };

    //==============================================================================
    /** @internal */
    void paint (Graphics&) override;
    /** @internal */
    void refresh() override;
    /** @internal */
    void buttonClicked (Button*) override;

private:
    ToggleButton button;
    String onText, offText;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BooleanPropertyComponent)
};

} // namespace juce


text:
/*
  ==============================================================================

   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
{

SliderPropertyComponent::SliderPropertyComponent (const String& name,
                                                  const double rangeMin,
                                                  const double rangeMax,
                                                  const double interval,
                                                  const double skewFactor,
                                                  bool symmetricSkew)
    : PropertyComponent (name)
{
    addAndMakeVisible (slider);

    slider.setRange (rangeMin, rangeMax, interval);
    slider.setSkewFactor (skewFactor, symmetricSkew);
    slider.setSliderStyle (Slider::LinearBar);

    slider.addListener (this);
}

SliderPropertyComponent::SliderPropertyComponent (const Value& valueToControl,
                                                  const String& name,
                                                  const double rangeMin,
                                                  const double rangeMax,
                                                  const double interval,
                                                  const double skewFactor,
                                                  bool symmetricSkew)
    : PropertyComponent (name)
{
    addAndMakeVisible (slider);

    slider.setRange (rangeMin, rangeMax, interval);
    slider.setSkewFactor (skewFactor, symmetricSkew);
    slider.setSliderStyle (Slider::LinearBar);

    slider.getValueObject().referTo (valueToControl);
}

SliderPropertyComponent::~SliderPropertyComponent()
{
}

void SliderPropertyComponent::setValue (const double /*newValue*/)
{
}

double SliderPropertyComponent::getValue() const
{
    return slider.getValue();
}

void SliderPropertyComponent::refresh()
{
    slider.setValue (getValue(), dontSendNotification);
}

void SliderPropertyComponent::sliderValueChanged (Slider*)
{
    if (getValue() != slider.getValue())
        setValue (slider.getValue());
}

} // namespace juce


text:
/*
  ==============================================================================

   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
{

//==============================================================================
/**
    A PropertyComponent that shows its value as editable text.

    @see PropertyComponent
*/
class JUCE_API  TextPropertyComponent  : public PropertyComponent
{
protected:
    //==============================================================================
    /** Creates a text property component.

        @param propertyName  The name of the property
        @param maxNumChars   If not zero, then this specifies the maximum allowable length of
                             the string. If zero, then the string will have no length limit.
        @param isMultiLine   isMultiLine sets whether the text editor allows carriage returns.

        @see TextEditor
    */
    TextPropertyComponent (const String& propertyName,
                           int maxNumChars,
                           bool isMultiLine);

public:
    /** Creates a text property component.

        @param valueToControl The Value that is controlled by the TextPropertyComponent
        @param propertyName   The name of the property
        @param maxNumChars    If not zero, then this specifies the maximum allowable length of
                              the string. If zero, then the string will have no length limit.
        @param isMultiLine    isMultiLine sets whether the text editor allows carriage returns.

        @see TextEditor
    */
    TextPropertyComponent (const Value& valueToControl,
                           const String& propertyName,
                           int maxNumChars,
                           bool isMultiLine);

    /** Destructor. */
    ~TextPropertyComponent();

    //==============================================================================
    /** Called when the user edits the text.

        Your subclass must use this callback to change the value of whatever item
        this property component represents.
    */
    virtual void setText (const String& newText);

    /** Returns the text that should be shown in the text editor. */
    virtual String getText() const;

    /** Returns the text that should be shown in the text editor as a Value object. */
    Value& getValue() const;

    //==============================================================================
    /** A set of colour IDs to use to change the colour of various aspects of the component.

        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          = 0x100e401,    /**< The colour to fill the background of the text area. */
        textColourId                = 0x100e402,    /**< The colour to use for the editable text. */
        outlineColourId             = 0x100e403,    /**< The colour to use to draw an outline around the text area. */
    };

    void colourChanged() override;

    //==============================================================================
    class JUCE_API Listener
    {
    public:
        /** Destructor. */
        virtual ~Listener() {}

        /** Called when text has finished being entered (i.e. not per keypress) has changed. */
        virtual void textPropertyComponentChanged (TextPropertyComponent*) = 0;
    };

    /** Registers a listener to receive events when this button's state changes.
        If the listener is already registered, this will not register it again.
        @see removeListener
    */
    void addListener (Listener* newListener);

    /** Removes a previously-registered button listener
        @see addListener
    */
    void removeListener (Listener* listener);

    //==============================================================================
    /** Sets whether the text property component can have files dropped onto it by an external application.

        The default setting for this is true but you may want to disable this behaviour if you derive
        from this class and want your subclass to respond to the file drag.
    */
    void setInterestedInFileDrag (bool isInterested);

    //==============================================================================
    /** @internal */
    void refresh() override;
    /** @internal */
    virtual void textWasEdited();

private:
    class LabelComp;
    friend class LabelComp;

    ScopedPointer<LabelComp> textEditor;
    ListenerList<Listener> listenerList;

    void callListeners();
    void createEditor (int maxNumChars, bool isMultiLine);

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TextPropertyComponent)
};

#ifndef DOXYGEN
 /** This typedef is just for compatibility with old code and VC6 - newer code should use TextPropertyComponent::Listener instead. */
 typedef TextPropertyComponent::Listener TextPropertyComponentListener;
#endif

} // namespace juce


text:
/*
  ==============================================================================

   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
{

//==============================================================================
/**
    A PropertyComponent that contains a button.

    This type of property component can be used if you need a button to trigger some
    kind of action.

    @see PropertyComponent
*/
class JUCE_API  ButtonPropertyComponent  : public PropertyComponent,
                                           private Button::Listener
{
public:
    //==============================================================================
    /** Creates a button component.

        @param propertyName         the property name to be passed to the PropertyComponent
        @param triggerOnMouseDown   this is passed to the Button::setTriggeredOnMouseDown() method
    */
    ButtonPropertyComponent (const String& propertyName,
                             bool triggerOnMouseDown);

    /** Destructor. */
    ~ButtonPropertyComponent();

    //==============================================================================
    /** Called when the user clicks the button.
    */
    virtual void buttonClicked() = 0;

    /** Returns the string that should be displayed in the button.

        If you need to change this string, call refresh() to update the component.
    */
    virtual String getButtonText() const = 0;

    //==============================================================================
    /** @internal */
    void refresh();
    /** @internal */
    void buttonClicked (Button*);

private:
    TextButton button;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ButtonPropertyComponent)
};

} // namespace juce


text:
/*
  ==============================================================================

   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
{

class ChoicePropertyComponent::RemapperValueSource    : public Value::ValueSource,
                                                        private Value::Listener
{
public:
    RemapperValueSource (const Value& source, const Array<var>& map)
       : sourceValue (source), mappings (map)
    {
        sourceValue.addListener (this);
    }

    var getValue() const
    {
        const var targetValue (sourceValue.getValue());

        for (int i = 0; i < mappings.size(); ++i)
            if (mappings.getReference(i).equalsWithSameType (targetValue))
                return i + 1;

        return mappings.indexOf (targetValue) + 1;
    }

    void setValue (const var& newValue)
    {
        const var remappedVal (mappings [static_cast<int> (newValue) - 1]);

        if (! remappedVal.equalsWithSameType (sourceValue))
            sourceValue = remappedVal;
    }

protected:
    Value sourceValue;
    Array<var> mappings;

    void valueChanged (Value&)
    {
        sendChangeMessage (true);
    }

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RemapperValueSource)
};

//==============================================================================
ChoicePropertyComponent::ChoicePropertyComponent (const String& name)
    : PropertyComponent (name),
      isCustomClass (true)
{
}

ChoicePropertyComponent::ChoicePropertyComponent (const Value& valueToControl,
                                                  const String& name,
                                                  const StringArray& choiceList,
                                                  const Array<var>& correspondingValues)
    : PropertyComponent (name),
      choices (choiceList),
      isCustomClass (false)
{
    // The array of corresponding values must contain one value for each of the items in
    // the choices array!
    jassert (correspondingValues.size() == choices.size());

    createComboBox();

    comboBox.getSelectedIdAsValue().referTo (Value (new RemapperValueSource (valueToControl,
                                                                             correspondingValues)));
}

ChoicePropertyComponent::~ChoicePropertyComponent()
{
}

//==============================================================================
void ChoicePropertyComponent::createComboBox()
{
    addAndMakeVisible (comboBox);

    for (int i = 0; i < choices.size(); ++i)
    {
        if (choices[i].isNotEmpty())
            comboBox.addItem (choices[i], i + 1);
        else
            comboBox.addSeparator();
    }

    comboBox.setEditableText (false);
}

void ChoicePropertyComponent::setIndex (const int /*newIndex*/)
{
    jassertfalse; // you need to override this method in your subclass!
}

int ChoicePropertyComponent::getIndex() const
{
    jassertfalse; // you need to override this method in your subclass!
    return -1;
}

const StringArray& ChoicePropertyComponent::getChoices() const
{
    return choices;
}

//==============================================================================
void ChoicePropertyComponent::refresh()
{
    if (isCustomClass)
    {
        if (! comboBox.isVisible())
        {
            createComboBox();
            comboBox.addListener (this);
        }

        comboBox.setSelectedId (getIndex() + 1, dontSendNotification);
    }
}

void ChoicePropertyComponent::comboBoxChanged (ComboBox*)
{
    if (isCustomClass)
    {
        const int newIndex = comboBox.getSelectedId() - 1;

        if (newIndex != getIndex())
            setIndex (newIndex);
    }
}

} // namespace juce


text:
/*
  ==============================================================================

   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
{

class TextPropertyComponent::LabelComp  : public Label,
                                          public FileDragAndDropTarget
{
public:
    LabelComp (TextPropertyComponent& tpc, const int charLimit, const bool multiline)
        : Label (String(), String()),
          owner (tpc),
          maxChars (charLimit),
          isMultiline (multiline)
    {
        setEditable (true, true, false);

        updateColours();
    }

    bool isInterestedInFileDrag (const StringArray&) override
    {
        return interestedInFileDrag;
    }

    void filesDropped (const StringArray& files, int, int) override
    {
        setText (getText() + files.joinIntoString (isMultiline ? "\n" : ", "), sendNotificationSync);
        showEditor();
    }

    TextEditor* createEditorComponent() override
    {
        TextEditor* const ed = Label::createEditorComponent();
        ed->setInputRestrictions (maxChars);

        if (isMultiline)
        {
            ed->setMultiLine (true, true);
            ed->setReturnKeyStartsNewLine (true);
        }

        return ed;
    }

    void textWasEdited() override
    {
        owner.textWasEdited();
    }

    void updateColours()
    {
        setColour (backgroundColourId, owner.findColour (TextPropertyComponent::backgroundColourId));
        setColour (outlineColourId,    owner.findColour (TextPropertyComponent::outlineColourId));
        setColour (textColourId,       owner.findColour (TextPropertyComponent::textColourId));
        repaint();
    }

    void setInterestedInFileDrag (bool isInterested)
    {
        interestedInFileDrag = isInterested;
    }

private:
    TextPropertyComponent& owner;
    int maxChars;
    bool isMultiline;
    bool interestedInFileDrag = true;
};

//==============================================================================
TextPropertyComponent::TextPropertyComponent (const String& name,
                                              const int maxNumChars,
                                              const bool isMultiLine)
    : PropertyComponent (name)
{
    createEditor (maxNumChars, isMultiLine);
}

TextPropertyComponent::TextPropertyComponent (const Value& valueToControl,
                                              const String& name,
                                              const int maxNumChars,
                                              const bool isMultiLine)
    : PropertyComponent (name)
{
    createEditor (maxNumChars, isMultiLine);

    textEditor->getTextValue().referTo (valueToControl);
}

TextPropertyComponent::~TextPropertyComponent()
{
}

void TextPropertyComponent::setText (const String& newText)
{
    textEditor->setText (newText, sendNotificationSync);
}

String TextPropertyComponent::getText() const
{
    return textEditor->getText();
}

Value& TextPropertyComponent::getValue() const
{
    return textEditor->getTextValue();
}

void TextPropertyComponent::createEditor (const int maxNumChars, const bool isMultiLine)
{
    addAndMakeVisible (textEditor = new LabelComp (*this, maxNumChars, isMultiLine));

    if (isMultiLine)
    {
        textEditor->setJustificationType (Justification::topLeft);
        preferredHeight = 100;
    }
}

void TextPropertyComponent::refresh()
{
    textEditor->setText (getText(), dontSendNotification);
}

void TextPropertyComponent::textWasEdited()
{
    const String newText (textEditor->getText());

    if (getText() != newText)
        setText (newText);

    callListeners();
}

void TextPropertyComponent::addListener (TextPropertyComponentListener* const listener)
{
    listenerList.add (listener);
}

void TextPropertyComponent::removeListener (TextPropertyComponentListener* const listener)
{
    listenerList.remove (listener);
}

void TextPropertyComponent::callListeners()
{
    Component::BailOutChecker checker (this);
    listenerList.callChecked (checker, &TextPropertyComponentListener::textPropertyComponentChanged, this);
}

void TextPropertyComponent::colourChanged()
{
    PropertyComponent::colourChanged();
    textEditor->updateColours();
}

void TextPropertyComponent::setInterestedInFileDrag (bool isInterested)
{
    if (textEditor != nullptr)
        textEditor->setInterestedInFileDrag (isInterested);
}

} // namespace juce