/*
  ==============================================================================

   This file is part of the JUCE library - "Jules' Utility Class Extensions"
   Copyright 2004-6 by Raw Material Software ltd.

  ------------------------------------------------------------------------------

   JUCE can be redistributed and/or modified under the terms of the
   GNU General Public License, as published by the Free Software Foundation;
   either version 2 of the License, or (at your option) any later version.

   JUCE is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with JUCE; if not, visit www.gnu.org/licenses or write to the
   Free Software Foundation, Inc., 59 Temple Place, Suite 330, 
   Boston, MA 02111-1307 USA

  ------------------------------------------------------------------------------

   If you'd like to release a closed-source product which uses JUCE, commercial
   licenses are also available: visit www.rawmaterialsoftware.com/juce for
   more information.

  ==============================================================================
*/

#include "../../../juce_core/basics/juce_StandardHeader.h"

BEGIN_JUCE_NAMESPACE


#include "juce_Component.h"
#include "juce_RepaintManager.h"
#include "juce_ComponentDeletionWatcher.h"
#include "juce_Desktop.h"
#include "keyboard/juce_KeyListener.h"
#include "lookandfeel/juce_LookAndFeel.h"
#include "../../application/juce_Application.h"
#include "../graphics/geometry/juce_RectangleList.h"
#include "../graphics/imaging/juce_Image.h"
#include "../graphics/contexts/juce_LowLevelGraphicsContext.h"
#include "../../events/juce_MessageManager.h"
#include "../../events/juce_Timer.h"
#include "../../../juce_core/basics/juce_Time.h"
#include "../../../juce_core/misc/juce_PlatformUtilities.h"


//==============================================================================
Component* Component::componentUnderMouse = 0;
Component* Component::currentlyFocusedComponent = 0;

static VoidArray modalComponentStack (4);
static int lastModalReturnValue = 0;

static const unsigned short defaultFocusOrder = 0xffff;

static const int customCommandMessage   = 0x7fff0001;
static const int exitModalStateMessage  = 0x7fff0002;

//==============================================================================
// these are also used by ComponentPeer
int64 juce_recentMouseDownTimes [4] = { 0, 0, 0, 0 };
int juce_recentMouseDownX [4] = { 0, 0, 0, 0 };
int juce_recentMouseDownY [4] = { 0, 0, 0, 0 };
int juce_LastMousePosX = 0;
int juce_LastMousePosY = 0;
int juce_MouseClickCounter = 0;
bool juce_MouseHasMovedSignificantlySincePressed = false;

static int countMouseClicks()
{
    int numClicks = 0;

    if (juce_recentMouseDownTimes[0] != 0)
    {
        if (! juce_MouseHasMovedSignificantlySincePressed)
            ++numClicks;

        for (int i = 1; i < 4; ++i)
        {
            if (juce_recentMouseDownTimes[0] - juce_recentMouseDownTimes [i]
                    < (int) (MouseEvent::getDoubleClickTimeout() * (1.0 + 0.25 * (i - 1)))
                && abs (juce_recentMouseDownX[0] - juce_recentMouseDownX[i]) < 8
                && abs (juce_recentMouseDownY[0] - juce_recentMouseDownY[i]) < 8)
            {
                ++numClicks;
            }
            else
            {
                break;
            }
        }
    }

    return numClicks;
}

static int unboundedMouseOffsetX = 0;
static int unboundedMouseOffsetY = 0;
static bool isUnboundedMouseModeOn = false;
static bool isCursorVisibleUntilOffscreen;

//==============================================================================
const int juce_windowIsSemiTransparentFlag = (1 << 31); // duplicated in native windowing code

#define checkMessageManagerIsLocked     jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager());

static uint32 nextComponentUID = 0;


//==============================================================================
Component::Component() throw()
  : parentComponent_ (0),
    componentUID (++nextComponentUID),
    compX_ (0),
    compY_ (0),
    compW_ (0),
    compH_ (0),
    focusOrderNumber_ (defaultFocusOrder),
    dragRepeatInterval_ (0),
    numDeepMouseListeners (0),
    childComponentList_ (16),
    lookAndFeel_ (0),
    effect_ (0),
    bufferedImage_ (0),
    mouseListeners_ (0),
    keyListeners_ (0),
    componentListeners_ (0),
    propertySet_ (0),
    componentFlags_ (0)
{
}

Component::Component (const String& name) throw()
  : componentName_ (name),
    parentComponent_ (0),
    componentUID (++nextComponentUID),
    compX_ (0),
    compY_ (0),
    compW_ (0),
    compH_ (0),
    focusOrderNumber_ (defaultFocusOrder),
    dragRepeatInterval_ (0),
    numDeepMouseListeners (0),
    childComponentList_ (16),
    lookAndFeel_ (0),
    effect_ (0),
    bufferedImage_ (0),
    mouseListeners_ (0),
    keyListeners_ (0),
    componentListeners_ (0),
    propertySet_ (0),
    componentFlags_ (0)
{
}

Component::~Component()
{
    if (parentComponent_ != 0)
    {
        parentComponent_->removeChildComponent (this);
    }
    else if ((currentlyFocusedComponent == this)
              || isParentOf (currentlyFocusedComponent))
    {
        giveAwayFocus();
    }

    if (componentUnderMouse == this)
        componentUnderMouse = 0;

    if (flags.hasHeavyweightPeerFlag)
        removeFromDesktop();

    modalComponentStack.removeValue (this);

    for (int i = childComponentList_.size(); --i >= 0;)
        ((Component*) childComponentList_.getUnchecked(i))->parentComponent_ = 0;

    delete bufferedImage_;
    delete mouseListeners_;
    delete keyListeners_;
    delete componentListeners_;
    delete propertySet_;
}

//==============================================================================
void Component::setName (const String& name)
{
    // if component methods are being called from threads other than the message
    // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe.
    checkMessageManagerIsLocked
    componentName_ = name;

    if (flags.hasHeavyweightPeerFlag)
    {
        ComponentPeer* const peer = getPeer();

        jassert (peer != 0);
        if (peer != 0)
            peer->setTitle (name);
    }
}

void Component::setVisible (bool shouldBeVisible)
{
    if (flags.visibleFlag != shouldBeVisible)
    {
        // if component methods are being called from threads other than the message
        // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe.
        checkMessageManagerIsLocked

        const ComponentDeletionWatcher deletionChecker (this);

        flags.visibleFlag = shouldBeVisible;

        internalRepaint (0, 0, compW_, compH_);

        sendFakeMouseMove();

        if (! shouldBeVisible)
        {
            if (currentlyFocusedComponent == this
                || isParentOf (currentlyFocusedComponent))
            {
                if (parentComponent_ != 0)
                    parentComponent_->grabKeyboardFocus();
                else
                    giveAwayFocus();
            }
        }

        sendVisibilityChangeMessage();

        if ((! deletionChecker.hasBeenDeleted()) && flags.hasHeavyweightPeerFlag)
        {
            ComponentPeer* const peer = getPeer();

            jassert (peer != 0);
            if (peer != 0)
                peer->setVisible (shouldBeVisible);
        }
    }
}

void Component::visibilityChanged()
{
}

void Component::sendVisibilityChangeMessage()
{
    const ComponentDeletionWatcher deletionChecker (this);

    visibilityChanged();

    if ((! deletionChecker.hasBeenDeleted()) && componentListeners_ != 0)
    {
        for (int i = componentListeners_->size(); --i >= 0;)
        {
            ((ComponentListener*) componentListeners_->getUnchecked (i))
                ->componentVisibilityChanged (*this);

            if (deletionChecker.hasBeenDeleted())
                return;

            i = jmin (i, componentListeners_->size());
        }
    }
}

bool Component::isShowing() const throw()
{
    if (flags.visibleFlag)
    {
        if (parentComponent_ != 0)
        {
            return parentComponent_->isShowing();
        }
        else
        {
            const ComponentPeer* const peer = getPeer();

            return peer != 0 && ! peer->isMinimised();
        }
    }

    return false;
}

//==============================================================================
class FadeOutProxyComponent  : public Component,
                               public Timer
{
public:
    FadeOutProxyComponent (Component* comp,
                           const int fadeLengthMs,
                           const int deltaXToMove,
                           const int deltaYToMove,
                           const float scaleFactorAtEnd)
       : lastTime (0),
         alpha (1.0f),
         scale (1.0f)
    {
        image = comp->createComponentSnapshot (Rectangle (0, 0, comp->getWidth(), comp->getHeight()));
        setBounds (comp->getBounds());
        comp->getParentComponent()->addAndMakeVisible (this);
        toBehind (comp);

        alphaChangePerMs = -1.0f / (float)fadeLengthMs;

        centreX = comp->getX() + comp->getWidth() * 0.5f;
        xChangePerMs = deltaXToMove / (float)fadeLengthMs;

        centreY = comp->getY() + comp->getHeight() * 0.5f;
        yChangePerMs = deltaYToMove / (float)fadeLengthMs;

        scaleChangePerMs = (scaleFactorAtEnd - 1.0f) / (float)fadeLengthMs;

        setInterceptsMouseClicks (false, false);

        // 30 fps is enough for a fade, but we need a higher rate if it's moving as well..
        startTimer (1000 / ((deltaXToMove == 0 && deltaYToMove == 0) ? 30 : 50));
    }

    ~FadeOutProxyComponent()
    {
        delete image;
    }

    void paint (Graphics& g)
    {
        g.setOpacity (alpha);

        g.drawImage (image,
                     0, 0, getWidth(), getHeight(),
                     0, 0, image->getWidth(), image->getHeight());
    }

    void timerCallback()
    {
        const uint32 now = Time::getMillisecondCounter();

        if (lastTime == 0)
            lastTime = now;

        const int msPassed = (now > lastTime) ? now - lastTime : 0;
        lastTime = now;

        alpha += alphaChangePerMs * msPassed;

        if (alpha > 0)
        {
            if (xChangePerMs != 0.0f || yChangePerMs != 0.0f || scaleChangePerMs != 0.0f)
            {
                centreX += xChangePerMs * msPassed;
                centreY += yChangePerMs * msPassed;
                scale += scaleChangePerMs * msPassed;

                const int w = roundFloatToInt (image->getWidth() * scale);
                const int h = roundFloatToInt (image->getHeight() * scale);

                setBounds (roundFloatToInt (centreX) - w / 2,
                           roundFloatToInt (centreY) - h / 2,
                           w, h);
            }

            repaint();
        }
        else
        {
            delete this;
        }
    }

    juce_UseDebuggingNewOperator

private:
    Image* image;
    uint32 lastTime;
    float alpha, alphaChangePerMs;
    float centreX, xChangePerMs;
    float centreY, yChangePerMs;
    float scale, scaleChangePerMs;

    FadeOutProxyComponent (const FadeOutProxyComponent&);
    const FadeOutProxyComponent& operator= (const FadeOutProxyComponent&);
};

void Component::fadeOutComponent (const int millisecondsToFade,
                                  const int deltaXToMove,
                                  const int deltaYToMove,
                                  const float scaleFactorAtEnd)
{
    //xxx won't work for comps without parents
    if (isShowing() && millisecondsToFade > 0)
        new FadeOutProxyComponent (this, millisecondsToFade,
                                   deltaXToMove, deltaYToMove, scaleFactorAtEnd);

    setVisible (false);
}


//==============================================================================
bool Component::isValidComponent() const throw()
{
    return (this != 0) && isValidMessageListener();
}

void* Component::getWindowHandle() const
{
    const ComponentPeer* const peer = getPeer();

    if (peer != 0)
        return peer->getNativeHandle();

    return 0;
}

//==============================================================================
void Component::addToDesktop (int desktopWindowStyleFlags, void* nativeWindowToAttachTo)
{
    // if component methods are being called from threads other than the message
    // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe.
    checkMessageManagerIsLocked

    if (! isOpaque())
        desktopWindowStyleFlags |= juce_windowIsSemiTransparentFlag;

    int currentStyleFlags = 0;
    ComponentBoundsConstrainer* currentConstainer = 0;

    if (isOnDesktop())
    {
        const ComponentPeer* const peer = getPeer();

        if (peer != 0)
        {
            currentStyleFlags = peer->getStyleFlags();
            currentConstainer = peer->getConstrainer();
        }
    }

    if ((! isOnDesktop()) || desktopWindowStyleFlags != currentStyleFlags)
    {
        const ComponentDeletionWatcher deletionChecker (this);

#ifdef JUCE_LINUX
        // it's wise to give the component a non-zero size before
        // putting it on the desktop, as X windows get confused by this, and
        // a (1, 1) minimum size is enforced here.
        setSize (jmax (1, getWidth()),
                 jmax (1, getHeight()));
#endif

        const int x = getScreenX();
        const int y = getScreenY();

        if (isOnDesktop())
            removeFromDesktop();

        if (parentComponent_ != 0)
            parentComponent_->removeChildComponent (this);

        if (! deletionChecker.hasBeenDeleted())
        {
            flags.hasHeavyweightPeerFlag = true;

            ComponentPeer* const peer
                = createNewPeer (desktopWindowStyleFlags, nativeWindowToAttachTo);

            Desktop::getInstance().addDesktopComponent (this);

            compX_ = x;
            compY_ = y;
            peer->setBounds (x, y, getWidth(), getHeight(), false);
            peer->setVisible (isVisible());

            if (isAlwaysOnTop())
                peer->setAlwaysOnTop (true);

            peer->setConstrainer (currentConstainer);

            repaint();
        }

        internalHierarchyChanged();
    }
}

void Component::removeFromDesktop()
{
    // if component methods are being called from threads other than the message
    // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe.
    checkMessageManagerIsLocked

    if (isOnDesktop())
    {
        ComponentPeer* const peer = ComponentPeer::getPeerFor (this);

        flags.hasHeavyweightPeerFlag = false;

        jassert (peer != 0);
        delete peer;

        Desktop::getInstance().removeDesktopComponent (this);
    }
}

bool Component::isOnDesktop() const
{
    return flags.hasHeavyweightPeerFlag;
}

void Component::userTriedToCloseWindow()
{
    /* This means that the user's trying to get rid of your window with the 'close window' system
       menu option (on windows) or possibly the task manager - you should really handle this
       and delete or hide your component in an appropriate way.

       If you want to ignore the event and don't want to trigger this assertion, just override
       this method and do nothing.
    */
    jassertfalse
}

void Component::minimisationStateChanged (bool)
{
}

//==============================================================================
void Component::setOpaque (const bool shouldBeOpaque)
{
    if (shouldBeOpaque != flags.opaqueFlag)
    {
        flags.opaqueFlag = shouldBeOpaque;

        if (isOnDesktop())
        {
            const ComponentPeer* const peer = getPeer();

            if (peer != 0)
            {
                // to make it recreate the heavyweight window
                addToDesktop (peer->getStyleFlags());
            }
        }

        repaint();
    }
}

bool Component::isOpaque() const throw()
{
    return flags.opaqueFlag;
}

//==============================================================================
void Component::setBufferedToImage (const bool shouldBeBuffered)
{
    if (shouldBeBuffered != flags.bufferToImageFlag)
    {
        deleteAndZero (bufferedImage_);
        flags.bufferToImageFlag = shouldBeBuffered;
    }
}

//==============================================================================
void Component::toFront (const bool setAsForeground)
{
    // if component methods are being called from threads other than the message
    // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe.
    checkMessageManagerIsLocked

    if (flags.hasHeavyweightPeerFlag)
    {
        ComponentPeer* const peer = getPeer();

        if (peer != 0)
        {
            peer->toFront (setAsForeground);

            if (setAsForeground)
                grabKeyboardFocus();
        }
    }
    else if (parentComponent_ != 0
              && parentComponent_->childComponentList_.getLast() != this)
    {
        const int index = parentComponent_->childComponentList_.indexOf (this);

        if (index >= 0)
        {
            parentComponent_->childComponentList_.remove (index);

            int insertIndex = -1;

            if (! flags.alwaysOnTopFlag)
            {
                insertIndex = parentComponent_->childComponentList_.size();

                while (insertIndex > 0)
                {
                    if (! ((Component*)(parentComponent_->childComponentList_
                                          .getUnchecked (insertIndex - 1)))->isAlwaysOnTop())
                        break;

                    --insertIndex;
                }
            }

            parentComponent_->childComponentList_.insert (insertIndex, this);
            sendFakeMouseMove();

            repaintParent();

            if (setAsForeground)
                internalBroughtToFront();
        }
    }
}

void Component::toBehind (Component* const other)
{
    if (other != 0)
    {
        // the two components must belong to the same parent..
        jassert (parentComponent_ == other->parentComponent_);

        if (parentComponent_ != 0)
        {
            const int index = parentComponent_->childComponentList_.indexOf (this);

            if (parentComponent_->childComponentList_.contains (other)
                 && (index >= 0)
                 && (other != this))
            {
                parentComponent_->childComponentList_.remove (index);
                parentComponent_->childComponentList_.insert (parentComponent_->childComponentList_.indexOf (other), this);

                sendFakeMouseMove();
                repaintParent();
            }
        }
        else if (isOnDesktop())
        {
            jassert (other->isOnDesktop());

            if (other->isOnDesktop())
            {
                ComponentPeer* const us = getPeer();
                ComponentPeer* const them = other->getPeer();

                jassert (us != 0 && them != 0);
                if (us != 0 && them != 0)
                    us->toBehind (them);
            }
        }
    }
}

void Component::toBack()
{
    if (isOnDesktop())
    {
        jassertfalse //xxx need to add this to native window
    }
    else if (parentComponent_ != 0
         && parentComponent_->childComponentList_.getFirst() != this)
    {
        const int index = parentComponent_->childComponentList_.indexOf (this);

        if (index > 0)
        {
            parentComponent_->childComponentList_.remove (index);
            parentComponent_->childComponentList_.insert (0, this);

            sendFakeMouseMove();
            repaintParent();
        }
    }
}

void Component::setAlwaysOnTop (const bool shouldStayOnTop)
{
    if (shouldStayOnTop != flags.alwaysOnTopFlag)
    {
        flags.alwaysOnTopFlag = shouldStayOnTop;

        if (isOnDesktop())
        {
            ComponentPeer* const peer = getPeer();

            jassert (peer != 0);
            if (peer != 0)
            {
                if (! peer->setAlwaysOnTop (shouldStayOnTop))
                {
                    // some kinds of peer can't change their always-on-top status, so
                    // for these, we'll need to create a new window
                    const int oldFlags = peer->getStyleFlags();
                    removeFromDesktop();
                    addToDesktop (oldFlags);
                }
            }
        }

        if (shouldStayOnTop)
            toFront (false);

        internalHierarchyChanged();
    }
}

bool Component::isAlwaysOnTop() const
{
    return flags.alwaysOnTopFlag;
}

//==============================================================================
int Component::getXRelativeTo (Component* otherComponent) const
{
    return getScreenX() - ((otherComponent != 0) ? otherComponent->getScreenX() : 0);
}

int Component::getYRelativeTo (Component* otherComponent) const
{
    return getScreenY() - ((otherComponent != 0) ? otherComponent->getScreenY() : 0);
}

const Rectangle Component::getBounds() const throw()
{
    return Rectangle (compX_, compY_, compW_, compH_);
}

int Component::getRight() const throw()
{
    return compX_ + compW_;
}

int Component::getBottom() const throw()
{
    return compY_ + compH_;
}

int Component::proportionOfWidth (const float proportion) const throw()
{
    return roundDoubleToInt (proportion * compW_);
}

int Component::proportionOfHeight (const float proportion) const throw()
{
    return roundDoubleToInt (proportion * compH_);
}

int Component::getParentWidth() const throw()
{
    return (parentComponent_ != 0) ? parentComponent_->compW_
                                   : getParentMonitorArea().getWidth();
}

int Component::getParentHeight() const throw()
{
    return (parentComponent_ != 0) ? parentComponent_->compH_
                                   : getParentMonitorArea().getHeight();
}

int Component::getScreenX() const throw()
{
    return (parentComponent_ != 0) ? parentComponent_->getScreenX() + compX_
                                   : (flags.hasHeavyweightPeerFlag ? getPeer()->getScreenX()
                                                                   : compX_);
}

int Component::getScreenY() const throw()
{
    return (parentComponent_ != 0) ? parentComponent_->getScreenY() + compY_
                                   : (flags.hasHeavyweightPeerFlag ? getPeer()->getScreenY()
                                                                   : compY_);
}

//==============================================================================
void Component::setBounds (int x, int y, int w, int h)
{
    // if component methods are being called from threads other than the message
    // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe.
    checkMessageManagerIsLocked

    if (w < 0) w = 0;
    if (h < 0) h = 0;

    const bool wasResized  = (compW_ != w || compH_ != h);
    const bool wasMoved    = (compX_ != x || compY_ != y);

    if (wasMoved || wasResized)
    {
        if (flags.visibleFlag)
        {
            // send a fake mouse move to trigger enter/exit messages if needed..
            sendFakeMouseMove();

            if (! isOnDesktop())
                repaintParent();
        }

        compX_ = x;
        compY_ = y;
        compW_ = w;
        compH_ = h;

        if (wasResized)
            repaint();
        else if (! isOnDesktop())
            repaintParent();

        if (flags.hasHeavyweightPeerFlag)
        {
            ComponentPeer* const peer = getPeer();

            if (peer != 0)
            {
                if (wasMoved && wasResized)
                    peer->setBounds (getX(), getY(), getWidth(), getHeight(), false);
                else if (wasMoved)
                    peer->setPosition (getX(), getY());
                else if (wasResized)
                    peer->setSize (getWidth(), getHeight());
            }
        }

        sendMovedResizedMessages (wasMoved, wasResized);
    }
}

void Component::sendMovedResizedMessages (const bool wasMoved, const bool wasResized)
{
    JUCE_TRY
    {
        if (wasMoved)
            moved();

        if (wasResized)
        {
            resized();

            for (int i = childComponentList_.size(); --i >= 0;)
            {
                ((Component*) childComponentList_.getUnchecked(i))->parentSizeChanged();

                i = jmin (i, childComponentList_.size());
            }
        }

        if (parentComponent_ != 0)
            parentComponent_->childBoundsChanged (this);

        if (componentListeners_ != 0)
        {
            const ComponentDeletionWatcher deletionChecker (this);

            for (int i = componentListeners_->size(); --i >= 0;)
            {
                ((ComponentListener*) componentListeners_->getUnchecked (i))
                    ->componentMovedOrResized (*this, wasMoved, wasResized);

                if (deletionChecker.hasBeenDeleted())
                    return;

                i = jmin (i, componentListeners_->size());
            }
        }
    }
    JUCE_CATCH_EXCEPTION
}

void Component::setSize (const int w, const int h)
{
    setBounds (getX(), getY(), w, h);
}

void Component::setTopLeftPosition (const int x, const int y)
{
    setBounds (x, y, getWidth(), getHeight());
}

void Component::setTopRightPosition (const int x, const int y)
{
    setTopLeftPosition (x - getWidth(), y);
}

void Component::setBounds (const Rectangle& r)
{
    setBounds (r.getX(),
               r.getY(),
               r.getWidth(),
               r.getHeight());
}

void Component::setBoundsRelative (const float x, const float y,
                                   const float w, const float h)
{
    const int pw = getParentWidth();
    const int ph = getParentHeight();

    setBounds (roundFloatToInt (x * pw),
               roundFloatToInt (y * ph),
               roundFloatToInt (w * pw),
               roundFloatToInt (h * ph));
}

void Component::setCentrePosition (const int x, const int y)
{
    setTopLeftPosition (x - getWidth() / 2,
                        y - getHeight() / 2);
}

void Component::setCentreRelative (const float x, const float y)
{
    setCentrePosition (roundFloatToInt (getParentWidth() * x),
                       roundFloatToInt (getParentHeight() * y));
}

void Component::centreWithSize (const int width, const int height)
{
    setBounds ((getParentWidth() - width) / 2,
               (getParentHeight() - height) / 2,
               width,
               height);
}

void Component::setBoundsInset (const BorderSize& borders)
{
    setBounds (borders.getLeft(),
               borders.getTop(),
               getParentWidth() - (borders.getLeftAndRight()),
               getParentHeight() - (borders.getTopAndBottom()));
}

void Component::setBoundsToFit (int x, int y, int width, int height,
                                const Justification& justification,
                                const bool onlyReduceInSize)
{
    // it's no good calling this method unless both the component and
    // target rectangle have a finite size.
    jassert (compW_ > 0 && compH_ > 0 && width > 0 && height > 0);

    if (compW_ > 0 && compH_ > 0
         && width > 0 && height > 0)
    {
        int newW, newH;

        if (onlyReduceInSize && compW_ <= width && compH_ <= height)
        {
            newW = compW_;
            newH = compH_;
        }
        else
        {
            const double imageRatio = compH_ / (double) compW_;
            const double targetRatio = height / (double) width;

            if (imageRatio <= targetRatio)
            {
                newW = width;
                newH = jmin (height, roundDoubleToInt (newW * imageRatio));
            }
            else
            {
                newH = height;
                newW = jmin (width, roundDoubleToInt (newH / imageRatio));
            }
        }

        if (newW > 0 && newH > 0)
        {
            int newX, newY;
            justification.applyToRectangle (newX, newY, newW, newH,
                                            x, y, width, height);

            setBounds (newX, newY, newW, newH);
        }
    }
}

//==============================================================================
bool Component::hitTest (int x, int y)
{
    if (! flags.ignoresMouseClicksFlag)
        return true;

    if (flags.allowChildMouseClicksFlag)
    {
        for (int i = getNumChildComponents(); --i >= 0;)
        {
            Component* const c = getChildComponent (i);

            if (c->isVisible()
                && c->getBounds().contains (x, y)
                && c->hitTest (x - c->getX(),
                               y - c->getY()))
            {
                return true;
            }
        }
    }

    return false;
}

void Component::setInterceptsMouseClicks (const bool allowClicks,
                                          const bool allowClicksOnChildComponents)
{
    flags.ignoresMouseClicksFlag = ! allowClicks;
    flags.allowChildMouseClicksFlag = allowClicksOnChildComponents;
}

bool Component::contains (const int x, const int y)
{
    if (x >= 0 && y >= 0 && x < compW_ && y < compH_
         && hitTest (x, y))
    {
        if (parentComponent_ != 0)
        {
            return parentComponent_->contains (x + compX_,
                                               y + compY_);
        }
        else if (flags.hasHeavyweightPeerFlag)
        {
            const ComponentPeer* const peer = getPeer();

            if (peer != 0)
                return peer->contains (x, y, true);
        }
    }

    return false;
}

bool Component::reallyContains (int x, int y,
                                const bool returnTrueIfWithinAChild)
{
    if (! contains (x, y))
        return false;

    Component* p = this;

    while (p->parentComponent_ != 0)
    {
        x += p->compX_;
        y += p->compY_;

        p = p->parentComponent_;
    }

    const Component* const c = p->getComponentAt (x, y);

    return (c == this) || (returnTrueIfWithinAChild && isParentOf (c));
}

Component* Component::getComponentAt (const int x, const int y)
{
    if (flags.visibleFlag
         && x >= 0 && y >= 0 && x < compW_ && y < compH_
         && hitTest (x, y))
    {
        for (int i = childComponentList_.size(); --i >= 0;)
        {
            Component& child = *(Component*) (childComponentList_.getUnchecked(i));

            Component* const c = child.getComponentAt (x - child.compX_,
                                                       y - child.compY_);

            if (c != 0)
                return c;
        }

        return this;
    }

    return 0;
}

//==============================================================================
void Component::addChildComponent (Component* const child,
                                   int zOrder)
{
    // if component methods are being called from threads other than the message
    // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe.
    checkMessageManagerIsLocked

    if (child != 0 && child->parentComponent_ != this)
    {
        if (child->parentComponent_ != 0)
            child->parentComponent_->removeChildComponent (child);
        else
            child->removeFromDesktop();

        child->parentComponent_ = this;

        if (child->isVisible())
            child->repaintParent();

        if (! child->isAlwaysOnTop())
        {
            if (zOrder < 0)
                zOrder = childComponentList_.size();

            while (zOrder > 0)
            {
                if (! ((const Component*) childComponentList_.getUnchecked (zOrder - 1))->isAlwaysOnTop())
                    break;

                --zOrder;
            }
        }

        childComponentList_.insert (zOrder, child);

        child->internalHierarchyChanged();
        internalChildrenChanged();
    }
}

void Component::addAndMakeVisible (Component* const child,
                                   int zOrder)
{
    if (child != 0)
    {
        child->setVisible (true);
        addChildComponent (child, zOrder);
    }
}

void Component::removeChildComponent (Component* const child)
{
    removeChildComponent (childComponentList_.indexOf (child));
}

Component* Component::removeChildComponent (const int index)
{
    // if component methods are being called from threads other than the message
    // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe.
    checkMessageManagerIsLocked

    Component* const child = (Component*) (childComponentList_ [index]);

    if (child != 0)
    {
        child->repaintParent();

        childComponentList_.remove (index);
        child->parentComponent_ = 0;

        JUCE_TRY
        {
            if ((currentlyFocusedComponent == child)
                || child->isParentOf (currentlyFocusedComponent))
            {
                // get rid first to force the grabKeyboardFocus to change to us.
                giveAwayFocus();
                grabKeyboardFocus();
            }
        }
#if JUCE_CATCH_UNHANDLED_EXCEPTIONS
        catch (const std::exception& e)
        {
            currentlyFocusedComponent = 0;
            JUCEApplication::sendUnhandledException (&e, __FILE__, __LINE__);
        }
        catch (...)
        {
            currentlyFocusedComponent = 0;
            JUCEApplication::sendUnhandledException (0, __FILE__, __LINE__);
        }
#endif

        child->internalHierarchyChanged();
        internalChildrenChanged();
    }

    return child;
}

//==============================================================================
void Component::removeAllChildren()
{
    for (int i = childComponentList_.size(); --i >= 0;)
        removeChildComponent (i);
}

void Component::deleteAllChildren()
{
    for (int i = childComponentList_.size(); --i >= 0;)
        delete (removeChildComponent (i));
}

//==============================================================================
int Component::getNumChildComponents() const throw()
{
    return childComponentList_.size();
}

Component* Component::getChildComponent (const int index) const throw()
{
    return (Component*) childComponentList_ [index];
}

Component* Component::getTopLevelComponent() const
{
    const Component* comp = this;

    while (comp->parentComponent_ != 0)
        comp = comp->parentComponent_;

    return (Component*) comp;
}

bool Component::isParentOf (const Component* possibleChild) const
{
    while (possibleChild->isValidComponent())
    {
        possibleChild = possibleChild->parentComponent_;

        if (possibleChild == this)
            return true;
    }

    return false;
}

//==============================================================================
void Component::parentHierarchyChanged()
{
}

void Component::childrenChanged()
{
}

void Component::internalChildrenChanged()
{
    const ComponentDeletionWatcher deletionChecker (this);
    const bool hasListeners = componentListeners_ != 0;

    childrenChanged();

    if (hasListeners)
    {
        if (deletionChecker.hasBeenDeleted())
            return;

        for (int i = componentListeners_->size(); --i >= 0;)
        {
            ((ComponentListener*) componentListeners_->getUnchecked (i))
                ->componentChildrenChanged (*this);

            if (deletionChecker.hasBeenDeleted())
                return;

            i = jmin (i, componentListeners_->size());
        }
    }
}

void Component::internalHierarchyChanged()
{
    parentHierarchyChanged();

    const ComponentDeletionWatcher deletionChecker (this);

    if (componentListeners_ != 0)
    {
        for (int i = componentListeners_->size(); --i >= 0;)
        {
            ((ComponentListener*) componentListeners_->getUnchecked (i))
                ->componentParentHierarchyChanged (*this);

            if (deletionChecker.hasBeenDeleted())
                return;

            i = jmin (i, componentListeners_->size());
        }
    }

    for (int i = childComponentList_.size(); --i >= 0;)
    {
        ((Component*) childComponentList_.getUnchecked (i))->internalHierarchyChanged();

        // you really shouldn't delete the parent component during a callback telling you
        // that it's changed..
        jassert (! deletionChecker.hasBeenDeleted());
        if (deletionChecker.hasBeenDeleted())
            return;

        i = jmin (i, childComponentList_.size());
    }
}

//==============================================================================
void* Component::runModalLoopCallback (void* userData)
{
    return (void*) (pointer_sized_int) ((Component*) userData)->runModalLoopInt();
}

int Component::runModalLoop()
{
    // uses a callback so this can be called from non-gui threads
    return (int) (pointer_sized_int)
                MessageManager::getInstance()
                   ->callFunctionOnMessageThread (&runModalLoopCallback, (void*) this);
}

int Component::runModalLoopInt()
{
    Component* const prevFocused = getCurrentlyFocusedComponent();

    ComponentDeletionWatcher* deletionChecker = 0;
    if (prevFocused != 0)
        deletionChecker = new ComponentDeletionWatcher (prevFocused);

    if (! isCurrentlyModal())
        enterModalState();

    JUCE_TRY
    {
        while (flags.currentlyModalFlag && flags.visibleFlag)
        {
            if (! MessageManager::getInstance()->dispatchNextMessage())
                break;

            // check whether this component was deleted during the last message
            if (! isValidMessageListener())
                break;
        }
    }
#if JUCE_CATCH_UNHANDLED_EXCEPTIONS
    catch (const std::exception& e)
    {
        JUCEApplication::sendUnhandledException (&e, __FILE__, __LINE__);
        return 0;
    }
    catch (...)
    {
        JUCEApplication::sendUnhandledException (0, __FILE__, __LINE__);
        return 0;
    }
#endif

    modalComponentStack.removeValue (this);

    if (deletionChecker != 0)
    {
        if (! deletionChecker->hasBeenDeleted())
            prevFocused->grabKeyboardFocus();

        delete deletionChecker;
    }

    return lastModalReturnValue;
}

void Component::enterModalState()
{
    // if component methods are being called from threads other than the message
    // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe.
    checkMessageManagerIsLocked

    // Check for an attempt to make a component modal when it already is!
    // This can cause nasty problems..
    jassert (! flags.currentlyModalFlag);

    if (! isCurrentlyModal())
    {
        lastModalReturnValue = 0;

        modalComponentStack.add (this);

        flags.currentlyModalFlag = true;
        setVisible (true);
        toFront (true);
    }
}

void Component::exitModalState (const int returnValue)
{
    if (isCurrentlyModal())
    {
        if (MessageManager::getInstance()->isThisTheMessageThread())
        {
            lastModalReturnValue = returnValue;
            flags.currentlyModalFlag = false;

            modalComponentStack.removeValue (this);
        }
        else
        {
            postMessage (new Message (exitModalStateMessage, returnValue, 0, 0));
        }
    }
}

bool Component::isCurrentlyModal() const
{
    return flags.currentlyModalFlag
            && getCurrentlyModalComponent() == this;
}

bool Component::isCurrentlyBlockedByAnotherModalComponent() const
{
    Component* const mc = getCurrentlyModalComponent();

    return mc != 0
            && mc != this
            && ! mc->isParentOf (this);
}

Component* Component::getCurrentlyModalComponent()
{
    Component* const c = (Component*) modalComponentStack.getLast();

    return c->isValidComponent() ? c : 0;
}

//==============================================================================
void Component::setBroughtToFrontOnMouseClick (const bool shouldBeBroughtToFront)
{
    flags.dontRaiseOnClickFlag = ! shouldBeBroughtToFront;
}

bool Component::isBroughtToFrontOnMouseClick() const throw()
{
    return ! flags.dontRaiseOnClickFlag;
}

//==============================================================================
void Component::setMouseCursor (const MouseCursor& cursor)
{
    cursor_ = cursor;

    if (flags.visibleFlag
         && (flags.draggingFlag || reallyContains (getMouseXRelative(),
                                                   getMouseYRelative(),
                                                   false)))
    {
        internalUpdateMouseCursor (false);
    }
}

const MouseCursor Component::getMouseCursor()
{
    return cursor_;
}

void Component::updateMouseCursor() const
{
    sendFakeMouseMove();
}

void Component::internalUpdateMouseCursor (const bool forcedUpdate)
{
    ComponentPeer* const peer = getPeer();

    if (peer != 0)
    {
        MouseCursor mc (getMouseCursor());

        if (isUnboundedMouseModeOn && (unboundedMouseOffsetX != 0
                                        || unboundedMouseOffsetY != 0
                                        || ! isCursorVisibleUntilOffscreen))
        {
            mc = MouseCursor::NoCursor;
        }

        static void* currentCursorHandle = 0;

        if (forcedUpdate || mc.getHandle() != currentCursorHandle)
        {
            currentCursorHandle = mc.getHandle();
            mc.showInWindow (peer);
        }
    }
}

//==============================================================================
void Component::setDragRepeatInterval (const int millisecondInterval)
{
    dragRepeatInterval_ = (unsigned short) jlimit (1, 32767, millisecondInterval);
}

void Component::setRepaintsOnMouseActivity (const bool shouldRepaint)
{
    flags.repaintOnMouseActivityFlag = shouldRepaint;
}

//==============================================================================
void Component::repaintParent()
{
    if (flags.visibleFlag)
        internalRepaint (0, 0, compW_, compH_);
}

void Component::repaint() throw()
{
    repaint (0, 0, compW_, compH_);
}

void Component::repaint (const int x, const int y,
                         const int w, const int h) throw()
{
    deleteAndZero (bufferedImage_);

    if (flags.visibleFlag)
        internalRepaint (x, y, w, h);
}

void Component::internalRepaint (int x, int y, int w, int h)
{
    // if component methods are being called from threads other than the message
    // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe.
    checkMessageManagerIsLocked

    if (x < 0)
    {
        w += x;
        x = 0;
    }

    if (x + w > compW_)
        w = compW_ - x;

    if (w > 0)
    {
        if (y < 0)
        {
            h += y;
            y = 0;
        }

        if (y + h > compH_)
            h = compH_ - y;

        if (h > 0)
        {
            if (parentComponent_ != 0)
            {
                x += compX_;
                y += compY_;

                if (parentComponent_->flags.visibleFlag)
                    parentComponent_->internalRepaint (x, y, w, h);
            }
            else if (flags.hasHeavyweightPeerFlag)
            {
                ComponentPeer* const peer = getPeer();

                if (peer != 0)
                    peer->repaint (x, y, w, h);
            }
        }
    }
}

//==============================================================================
void Component::paintEntireComponent (Graphics& originalContext)
{
    jassert (! originalContext.isClipEmpty());

    Graphics* g = &originalContext;
    Image* effectImage = 0;

    if (effect_ != 0)
    {
        effectImage = new Image (flags.opaqueFlag ? Image::RGB : Image::ARGB,
                                 getWidth(), getHeight(),
                                 ! flags.opaqueFlag);

        g = new Graphics (*effectImage);
    }

    const RectangleList originalClipRegion (g->getClipRegion());
    int originalOriginX, originalOriginY;
    g->getInternalContext()->getAbsoluteOrigin (originalOriginX, originalOriginY);

    clipObscuredRegions (*g, getUnclippedArea(), 0, 0);

    if (! g->isClipEmpty())
    {
        if (bufferedImage_ != 0)
        {
            g->drawImageAt (bufferedImage_, 0, 0);
        }
        else
        {
            if (flags.bufferToImageFlag)
            {
                if (bufferedImage_ == 0)
                {
                    bufferedImage_ = new Image (flags.opaqueFlag ? Image::RGB : Image::ARGB,
                                                compW_, compH_, ! flags.opaqueFlag);

                    Graphics imG (*bufferedImage_);
                    paint (imG);
                }

                g->drawImageAt (bufferedImage_, 0, 0);
            }
            else
            {
                paint (*g);

                g->getInternalContext()->setAbsoluteOrigin (originalOriginX, originalOriginY);
                g->resetToDefaultState();
            }
        }
    }

    RectangleList clip;

    for (int i = 0; i < childComponentList_.size(); ++i)
    {
        Component& child = *(Component*) childComponentList_.getUnchecked (i);

        if (child.isVisible()
             && originalClipRegion.getIntersectionWith (child.getBounds(), clip))
        {
            g->setClipRegion (clip);
            g->setOrigin (child.compX_, child.compY_);

            child.paintEntireComponent (*g);

            g->getInternalContext()->setAbsoluteOrigin (originalOriginX, originalOriginY);
            g->resetToDefaultState();
        }
    }

    JUCE_TRY
    {
        g->setClipRegion (originalClipRegion);
        paintOverChildren (*g);
    }
    JUCE_CATCH_EXCEPTION

    if (effect_ != 0)
    {
        delete g;

        effect_->applyEffect (*effectImage, originalContext);
        delete effectImage;
    }
}

//==============================================================================
Image* Component::createComponentSnapshot (const Rectangle& areaToGrab)
{
    Rectangle r (areaToGrab.getIntersection (Rectangle (0, 0, getWidth(), getHeight())));

    Image* const componentImage = new Image (flags.opaqueFlag ? Image::RGB : Image::ARGB,
                                             jmax (1, r.getWidth()),
                                             jmax (1, r.getHeight()),
                                             true);

    Graphics imageContext (*componentImage);
    imageContext.setOrigin (-r.getX(),
                            -r.getY());

    paintEntireComponent (imageContext);

    return componentImage;
}

void Component::setComponentEffect (ImageEffectFilter* const effect)
{
    if (effect_ != effect)
    {
        effect_ = effect;
        repaint();
    }
}

//==============================================================================
LookAndFeel& Component::getLookAndFeel() const throw()
{
    const Component* c = this;

    do
    {
        if (c->lookAndFeel_ != 0)
            return *(c->lookAndFeel_);

        c = c->parentComponent_;
    }
    while (c != 0);

    return LookAndFeel::getDefaultLookAndFeel();
}

void Component::setLookAndFeel (LookAndFeel* const newLookAndFeel)
{
    if (lookAndFeel_ != newLookAndFeel)
    {
        lookAndFeel_ = newLookAndFeel;

        sendLookAndFeelChange();
    }
}

void Component::lookAndFeelChanged()
{
}

void Component::sendLookAndFeelChange()
{
    repaint();

    lookAndFeelChanged();

    // (it's not a great idea to do anything that would delete this component
    //  during the lookAndFeelChanged() callback)
    jassert (isValidComponent());

    const ComponentDeletionWatcher deletionChecker (this);

    for (int i = childComponentList_.size(); --i >= 0;)
    {
        ((Component*) childComponentList_.getUnchecked (i))->sendLookAndFeelChange();

        if (deletionChecker.hasBeenDeleted())
            return;

        i = jmin (i, childComponentList_.size());
    }
}

//==============================================================================
const Rectangle Component::getUnclippedArea() const
{
    int x = 0, y = 0, w = compW_, h = compH_;

    Component* p = parentComponent_;
    int px = compX_;
    int py = compY_;

    while (p != 0)
    {
        if (! Rectangle::intersectRectangles (x, y, w, h, -px, -py, p->getWidth(), p->getHeight()))
            return Rectangle();

        px += p->getX();
        py += p->getY();
        p = p->parentComponent_;
    }

    return Rectangle (x, y, w, h);
}

void Component::clipObscuredRegions (Graphics& g, const Rectangle& clipRect,
                                     const int deltaX, const int deltaY) const
{
    for (int i = childComponentList_.size(); --i >= 0;)
    {
        const Component* const c = (const Component*) childComponentList_.getUnchecked(i);

        if (c->isVisible())
        {
            Rectangle newClip (clipRect.getIntersection (c->getBounds()));

            if (! newClip.isEmpty())
            {
                if (c->isOpaque())
                {
                    g.excludeClipRegion (deltaX + newClip.getX(),
                                         deltaY + newClip.getY(),
                                         newClip.getWidth(),
                                         newClip.getHeight());
                }
                else
                {
                    newClip.translate (-c->compX_, -c->compY_);
                    c->clipObscuredRegions (g, newClip,
                                            c->compX_ + deltaX,
                                            c->compY_ + deltaY);
                }
            }
        }
    }
}

void Component::getVisibleArea (RectangleList& result,
                                const bool includeSiblings) const
{
    result.clear();
    const Rectangle unclipped (getUnclippedArea());

    if (! unclipped.isEmpty())
    {
        result.add (unclipped);

        if (includeSiblings)
        {
            const Component* const c = getTopLevelComponent();

            c->subtractObscuredRegions (result,
                                        c->getScreenX() - getScreenX(),
                                        c->getScreenY() - getScreenY(),
                                        Rectangle (0, 0, c->getWidth(), c->getHeight()),
                                        this);
        }

        subtractObscuredRegions (result, 0, 0, unclipped, 0);
        result.consolidate();
    }
}

void Component::subtractObscuredRegions (RectangleList& result,
                                         const int deltaX,
                                         const int deltaY,
                                         const Rectangle& clipRect,
                                         const Component* const compToAvoid) const
{
    for (int i = childComponentList_.size(); --i >= 0;)
    {
        const Component* const c = (const Component*) childComponentList_.getUnchecked(i);

        if (c != compToAvoid && c->isVisible())
        {
            if (c->isOpaque())
            {
                Rectangle childBounds (c->getBounds().getIntersection (clipRect));
                childBounds.translate (deltaX, deltaY);

                result.subtract (childBounds);
            }
            else
            {
                Rectangle newClip (clipRect.getIntersection (c->getBounds()));
                newClip.translate (-c->compX_, -c->compY_);

                c->subtractObscuredRegions (result,
                                            c->compX_ + deltaX,
                                            c->compY_ + deltaY,
                                            newClip,
                                            compToAvoid);
            }
        }
    }
}

//==============================================================================
void Component::mouseEnter (const MouseEvent&)
{
    // base class does nothing
}

void Component::mouseExit (const MouseEvent&)
{
    // base class does nothing
}

void Component::mouseDown (const MouseEvent&)
{
    // base class does nothing
}

void Component::mouseUp (const MouseEvent&)
{
    // base class does nothing
}

void Component::mouseDrag (const MouseEvent&)
{
    // base class does nothing
}

void Component::mouseMove (const MouseEvent&)
{
    // base class does nothing
}

void Component::mouseDoubleClick (const MouseEvent&)
{
    // base class does nothing
}

void Component::mouseWheelMove (const MouseEvent& e, float wheelIncrement)
{
    // the base class just passes this event up to its parent..

    if (parentComponent_ != 0)
        parentComponent_->mouseWheelMove (e.getEventRelativeTo (parentComponent_),
                                          wheelIncrement);
}


//==============================================================================
void Component::resized()
{
    // base class does nothing
}

void Component::moved()
{
    // base class does nothing
}

void Component::childBoundsChanged (Component*)
{
    // base class does nothing
}

void Component::parentSizeChanged()
{
    // base class does nothing
}

void Component::addComponentListener (ComponentListener* const newListener)
{
    if (componentListeners_ == 0)
        componentListeners_ = new VoidArray (4);

    componentListeners_->addIfNotAlreadyThere (newListener);
}

void Component::removeComponentListener (ComponentListener* const listenerToRemove)
{
    jassert (isValidComponent());

    if (componentListeners_ != 0)
        componentListeners_->removeValue (listenerToRemove);
}

//==============================================================================
void Component::inputAttemptWhenModal()
{
    getTopLevelComponent()->toFront (true);

    PlatformUtilities::beep();
}

void Component::internalModalInputAttempt()
{
    Component* const current = getCurrentlyModalComponent();

    if (current != 0)
        current->inputAttemptWhenModal();
}


//==============================================================================
void Component::paint (Graphics&)
{
    // all painting is done in the subclasses

    jassert (! isOpaque()); // if your component's opaque, you've gotta paint it!
}

void Component::paintOverChildren (Graphics&)
{
    // all painting is done in the subclasses
}

//==============================================================================
void Component::handleMessage (const Message& message)
{
    if (message.intParameter1 == exitModalStateMessage)
    {
        exitModalState (message.intParameter2);
    }
    else if (message.intParameter1 == customCommandMessage)
    {
        handleCommandMessage (message.intParameter2);
    }
}

//==============================================================================
void Component::postCommandMessage (const int commandId)
{
    postMessage (new Message (customCommandMessage, commandId, 0, 0));
}

void Component::handleCommandMessage (int)
{
    // used by subclasses
}

//==============================================================================
void Component::addMouseListener (MouseListener* const newListener,
                                  const bool wantsEventsForAllNestedChildComponents)
{
    // if component methods are being called from threads other than the message
    // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe.
    checkMessageManagerIsLocked

    if (mouseListeners_ == 0)
        mouseListeners_ = new VoidArray (4);

    if (! mouseListeners_->contains (newListener))
    {
        if (wantsEventsForAllNestedChildComponents)
        {
            mouseListeners_->insert (0, newListener);
            ++numDeepMouseListeners;
        }
        else
        {
            mouseListeners_->add (newListener);
        }
    }
}

void Component::removeMouseListener (MouseListener* const listenerToRemove)
{
    // if component methods are being called from threads other than the message
    // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe.
    checkMessageManagerIsLocked

    if (mouseListeners_ != 0)
    {
        const int index = mouseListeners_->indexOf (listenerToRemove);

        if (index >= 0)
        {
            if (index < numDeepMouseListeners)
                --numDeepMouseListeners;

            mouseListeners_->remove (index);
        }
    }
}

//==============================================================================
void Component::internalMouseEnter (int x, int y, int64 time)
{
    if (isCurrentlyBlockedByAnotherModalComponent())
    {
        // if something else is modal, always just show a normal mouse cursor
        if (componentUnderMouse == this)
        {
            ComponentPeer* const peer = getPeer();

            if (peer != 0)
            {
                MouseCursor mc (MouseCursor::NormalCursor);
                mc.showInWindow (peer);
            }
        }

        return;
    }

    if (! flags.mouseInsideFlag)
    {
        flags.mouseInsideFlag = true;
        flags.mouseOverFlag = true;
        flags.draggingFlag = false;

        if (isValidComponent())
        {
            const ComponentDeletionWatcher deletionChecker (this);

            if (flags.repaintOnMouseActivityFlag)
                repaint();

            const MouseEvent me (x, y,
                                 ModifierKeys::getCurrentModifiers(),
                                 this,
                                 Time (time),
                                 x, y,
                                 Time (time),
                                 0, false);

            mouseEnter (me);

            if (deletionChecker.hasBeenDeleted())
                return;

            Desktop::getInstance().resetTimer();

            for (int i = Desktop::getInstance().mouseListeners.size(); --i >= 0;)
            {
                ((MouseListener*) Desktop::getInstance().mouseListeners[i])->mouseEnter (me);

                if (deletionChecker.hasBeenDeleted())
                    return;

                i = jmin (i, Desktop::getInstance().mouseListeners.size());
            }

            if (mouseListeners_ != 0)
            {
                for (int i = mouseListeners_->size(); --i >= 0;)
                {
                    ((MouseListener*) mouseListeners_->getUnchecked(i))->mouseEnter (me);

                    if (deletionChecker.hasBeenDeleted())
                        return;

                    i = jmin (i, mouseListeners_->size());
                }
            }

            const Component* p = parentComponent_;

            while (p != 0)
            {
                const ComponentDeletionWatcher parentDeletionChecker (p);

                for (int i = p->numDeepMouseListeners; --i >= 0;)
                {
                    ((MouseListener*) (p->mouseListeners_->getUnchecked(i)))->mouseEnter (me);

                    if (deletionChecker.hasBeenDeleted() || parentDeletionChecker.hasBeenDeleted())
                        return;

                    i = jmin (i, p->numDeepMouseListeners);
                }

                p = p->parentComponent_;
            }
        }
    }

    if (componentUnderMouse == this)
        internalUpdateMouseCursor (true);
}

void Component::internalMouseExit (int x, int y, int64 time)
{
    const ComponentDeletionWatcher deletionChecker (this);

    if (flags.draggingFlag)
    {
        internalMouseUp (ModifierKeys::getCurrentModifiers().getRawFlags(), x, y, time);

        if (deletionChecker.hasBeenDeleted())
            return;
    }

    enableUnboundedMouseMovement (false);

    if (flags.mouseInsideFlag || flags.mouseOverFlag)
    {
        flags.mouseInsideFlag = false;
        flags.mouseOverFlag = false;
        flags.draggingFlag = false;

        if (flags.repaintOnMouseActivityFlag)
            repaint();

        const MouseEvent me (x, y,
                             ModifierKeys::getCurrentModifiers(),
                             this,
                             Time (time),
                             x, y,
                             Time (time),
                             0, false);
        mouseExit (me);

        if (deletionChecker.hasBeenDeleted())
            return;

        Desktop::getInstance().resetTimer();

        for (int i = Desktop::getInstance().mouseListeners.size(); --i >= 0;)
        {
            ((MouseListener*) Desktop::getInstance().mouseListeners[i])->mouseExit (me);

            if (deletionChecker.hasBeenDeleted())
                return;

            i = jmin (i, Desktop::getInstance().mouseListeners.size());
        }

        if (mouseListeners_ != 0)
        {
            for (int i = mouseListeners_->size(); --i >= 0;)
            {
                ((MouseListener*) mouseListeners_->getUnchecked (i))->mouseExit (me);

                if (deletionChecker.hasBeenDeleted())
                    return;

                i = jmin (i, mouseListeners_->size());
            }
        }

        const Component* p = parentComponent_;

        while (p != 0)
        {
            const ComponentDeletionWatcher parentDeletionChecker (p);

            for (int i = p->numDeepMouseListeners; --i >= 0;)
            {
                ((MouseListener*) (p->mouseListeners_->getUnchecked (i)))->mouseExit (me);

                if (deletionChecker.hasBeenDeleted() || parentDeletionChecker.hasBeenDeleted())
                    return;

                i = jmin (i, p->numDeepMouseListeners);
            }

            p = p->parentComponent_;
        }
    }
}

//==============================================================================
class InternalDragRepeater  : public Timer
{
public:
    InternalDragRepeater()
    {}

    ~InternalDragRepeater()
    {}

    void timerCallback()
    {
        Component* const c = Component::getComponentUnderMouse();

        if (c->isValidComponent())
        {
            // the offsets are added on in these functions, so must be taken off before calling the
            // drag.. otherwise they'll be added twice
            const int x = c->getMouseXRelative() - unboundedMouseOffsetX;
            const int y = c->getMouseYRelative() - unboundedMouseOffsetY;

            c->internalMouseDrag (x, y, Time::currentTimeMillis());
        }
    }

    juce_UseDebuggingNewOperator
};

static InternalDragRepeater* dragRepeater = 0;


//==============================================================================
void Component::internalMouseDown (int x, int y)
{
    const ComponentDeletionWatcher deletionChecker (this);

    if (isCurrentlyBlockedByAnotherModalComponent())
    {
        internalModalInputAttempt();

        // allow blocked mouse-events to go to global listeners..
        const MouseEvent me (x, y,
                             ModifierKeys::getCurrentModifiers(),
                             this,
                             Time (juce_recentMouseDownTimes[0]),
                             x, y,
                             Time (juce_recentMouseDownTimes[0]),
                             countMouseClicks(),
                             false);

        Desktop::getInstance().resetTimer();

        for (int i = Desktop::getInstance().mouseListeners.size(); --i >= 0;)
        {
            ((MouseListener*) Desktop::getInstance().mouseListeners[i])->mouseDown (me);

            if (deletionChecker.hasBeenDeleted())
                return;

            i = jmin (i, Desktop::getInstance().mouseListeners.size());
        }

        return;
    }

    if (getTopLevelComponent()->isBroughtToFrontOnMouseClick())
    {
        ComponentPeer* const peer = getPeer();

        if (peer != 0)
        {
            peer->toFront (true);

            getTopLevelComponent()->internalBroughtToFront();
        }

        if (deletionChecker.hasBeenDeleted())
            return;
    }

    if (! flags.dontFocusOnMouseClickFlag)
        grabFocusInternal (focusChangedByMouseClick);

    if (! deletionChecker.hasBeenDeleted())
    {
        flags.draggingFlag = true;
        flags.mouseOverFlag = true;

        if (flags.repaintOnMouseActivityFlag)
            repaint();

        const MouseEvent me (x, y,
                             ModifierKeys::getCurrentModifiers(),
                             this,
                             Time (juce_recentMouseDownTimes[0]),
                             x, y,
                             Time (juce_recentMouseDownTimes[0]),
                             countMouseClicks(),
                             false);
        mouseDown (me);

        if (deletionChecker.hasBeenDeleted())
            return;

        Desktop::getInstance().resetTimer();

        for (int i = Desktop::getInstance().mouseListeners.size(); --i >= 0;)
        {
            ((MouseListener*) Desktop::getInstance().mouseListeners[i])->mouseDown (me);

            if (deletionChecker.hasBeenDeleted())
                return;

            i = jmin (i, Desktop::getInstance().mouseListeners.size());
        }

        if (mouseListeners_ != 0)
        {
            for (int i = mouseListeners_->size(); --i >= 0;)
            {
                ((MouseListener*) mouseListeners_->getUnchecked (i))->mouseDown (me);

                if (deletionChecker.hasBeenDeleted())
                    return;

                i = jmin (i, mouseListeners_->size());
            }
        }

        if (dragRepeatInterval_ > 0)
        {
            if (dragRepeater == 0)
                dragRepeater = new InternalDragRepeater();

            dragRepeater->startTimer ((int) dragRepeatInterval_);
        }

        const Component* p = parentComponent_;

        while (p != 0)
        {
            const ComponentDeletionWatcher parentDeletionChecker (p);

            for (int i = p->numDeepMouseListeners; --i >= 0;)
            {
                ((MouseListener*) (p->mouseListeners_->getUnchecked (i)))->mouseDown (me);

                if (deletionChecker.hasBeenDeleted() || parentDeletionChecker.hasBeenDeleted())
                    return;

                i = jmin (i, p->numDeepMouseListeners);
            }

            p = p->parentComponent_;
        }
    }
}

//==============================================================================
void Component::internalMouseUp (const int oldModifiers, int x, int y, int64 time)
{
    if (isValidComponent() && flags.draggingFlag)
    {
        flags.draggingFlag = false;
        deleteAndZero (dragRepeater);

        const int ux = x + unboundedMouseOffsetX;
        const int uy = y + unboundedMouseOffsetY;
        juce_LastMousePosX = ux + getScreenX();
        juce_LastMousePosY = uy + getScreenY();

        const ComponentDeletionWatcher deletionChecker (this);

        if (flags.repaintOnMouseActivityFlag)
            repaint();

        const MouseEvent me (ux, uy,
                             oldModifiers,
                             this,
                             Time (time),
                             juce_recentMouseDownX[0] - getScreenX(),
                             juce_recentMouseDownY[0] - getScreenY(),
                             Time (juce_recentMouseDownTimes [0]),
                             countMouseClicks(),
                             juce_MouseHasMovedSignificantlySincePressed
                               || juce_recentMouseDownTimes[0] + 300 < time);

        mouseUp (me);

        if (deletionChecker.hasBeenDeleted())
            return;

        Desktop::getInstance().resetTimer();

        for (int i = Desktop::getInstance().mouseListeners.size(); --i >= 0;)
        {
            ((MouseListener*) Desktop::getInstance().mouseListeners[i])->mouseUp (me);

            if (deletionChecker.hasBeenDeleted())
                return;

            i = jmin (i, Desktop::getInstance().mouseListeners.size());
        }

        if (mouseListeners_ != 0)
        {
            for (int i = mouseListeners_->size(); --i >= 0;)
            {
                ((MouseListener*) mouseListeners_->getUnchecked (i))->mouseUp (me);

                if (deletionChecker.hasBeenDeleted())
                    return;

                i = jmin (i, mouseListeners_->size());
            }
        }

        const Component* p = parentComponent_;

        while (p != 0)
        {
            const ComponentDeletionWatcher parentDeletionChecker (p);

            for (int i = p->numDeepMouseListeners; --i >= 0;)
            {
                ((MouseListener*) (p->mouseListeners_->getUnchecked (i)))->mouseUp (me);

                if (deletionChecker.hasBeenDeleted() || parentDeletionChecker.hasBeenDeleted())
                    return;

                i = jmin (i, p->numDeepMouseListeners);
            }

            p = p->parentComponent_;
        }

        // check for double-click
        if (me.getNumberOfClicks() >= 2)
        {
            const int numListeners = (mouseListeners_ != 0) ? mouseListeners_->size() : 0;

            mouseDoubleClick (me);

            int i;
            for (i = Desktop::getInstance().mouseListeners.size(); --i >= 0;)
            {
                ((MouseListener*) Desktop::getInstance().mouseListeners[i])->mouseDoubleClick (me);

                if (deletionChecker.hasBeenDeleted())
                    return;

                i = jmin (i, Desktop::getInstance().mouseListeners.size());
            }

            for (i = numListeners; --i >= 0;)
            {
                if (deletionChecker.hasBeenDeleted() || mouseListeners_ == 0)
                    return;

                MouseListener* const ml = (MouseListener*)((*mouseListeners_)[i]);
                if (ml != 0)
                    ml->mouseDoubleClick (me);
            }

            if (deletionChecker.hasBeenDeleted())
                return;

            const Component* p = parentComponent_;

            while (p != 0)
            {
                const ComponentDeletionWatcher parentDeletionChecker (p);

                for (i = p->numDeepMouseListeners; --i >= 0;)
                {
                    ((MouseListener*) (p->mouseListeners_->getUnchecked (i)))->mouseDoubleClick (me);

                    if (deletionChecker.hasBeenDeleted() || parentDeletionChecker.hasBeenDeleted())
                        return;

                    i = jmin (i, p->numDeepMouseListeners);
                }

                p = p->parentComponent_;
            }
        }
    }

    enableUnboundedMouseMovement (false);
}

void Component::internalMouseDrag (int x, int y, int64 time)
{
    if (isValidComponent() && flags.draggingFlag)
    {
        flags.mouseOverFlag = reallyContains (x, y, false);

        juce_LastMousePosX = x + getScreenX() + unboundedMouseOffsetX;
        juce_LastMousePosY = y + getScreenY() + unboundedMouseOffsetY;

        juce_MouseHasMovedSignificantlySincePressed
            = juce_MouseHasMovedSignificantlySincePressed
               || abs (juce_recentMouseDownX[0] - juce_LastMousePosX) >= 4
               || abs (juce_recentMouseDownY[0] - juce_LastMousePosY) >= 4;

        const ComponentDeletionWatcher deletionChecker (this);

        const MouseEvent me (x + unboundedMouseOffsetX,
                             y + unboundedMouseOffsetY,
                             ModifierKeys::getCurrentModifiers(),
                             this,
                             Time (time),
                             juce_recentMouseDownX[0] - getScreenX(),
                             juce_recentMouseDownY[0] - getScreenY(),
                             Time (juce_recentMouseDownTimes[0]),
                             countMouseClicks(),
                             juce_MouseHasMovedSignificantlySincePressed
                               || juce_recentMouseDownTimes[0] + 300 < time);
        mouseDrag (me);

        if (deletionChecker.hasBeenDeleted())
            return;

        Desktop::getInstance().resetTimer();

        for (int i = Desktop::getInstance().mouseListeners.size(); --i >= 0;)
        {
            ((MouseListener*) Desktop::getInstance().mouseListeners[i])->mouseDrag (me);

            if (deletionChecker.hasBeenDeleted())
                return;

            i = jmin (i, Desktop::getInstance().mouseListeners.size());
        }

        if (mouseListeners_ != 0)
        {
            for (int i = mouseListeners_->size(); --i >= 0;)
            {
                ((MouseListener*) mouseListeners_->getUnchecked (i))->mouseDrag (me);

                if (deletionChecker.hasBeenDeleted())
                    return;

                i = jmin (i, mouseListeners_->size());
            }
        }

        const Component* p = parentComponent_;

        while (p != 0)
        {
            const ComponentDeletionWatcher parentDeletionChecker (p);

            for (int i = p->numDeepMouseListeners; --i >= 0;)
            {
                ((MouseListener*) (p->mouseListeners_->getUnchecked (i)))->mouseDrag (me);

                if (deletionChecker.hasBeenDeleted() || parentDeletionChecker.hasBeenDeleted())
                    return;

                i = jmin (i, p->numDeepMouseListeners);
            }

            p = p->parentComponent_;
        }

        if (this == componentUnderMouse)
        {
            if (isUnboundedMouseModeOn)
            {
                Rectangle screenArea (getParentMonitorArea());
                screenArea.expand (-2, -2);

                int mx, my;
                Desktop::getMousePosition (mx, my);

                if (! screenArea.contains (mx, my))
                {
                    int deltaX = 0, deltaY = 0;

                    if (mx <= screenArea.getX() || mx >= screenArea.getRight())
                        deltaX = getScreenX() + getWidth() / 2 - mx;

                    if (my <= screenArea.getY() || my >= screenArea.getBottom())
                        deltaY = getScreenY() + getHeight() / 2 - my;

                    unboundedMouseOffsetX -= deltaX;
                    unboundedMouseOffsetY -= deltaY;

                    Desktop::setMousePosition (mx + deltaX,
                                               my + deltaY);
                }
                else if (isCursorVisibleUntilOffscreen
                          && (unboundedMouseOffsetX != 0 || unboundedMouseOffsetY != 0)
                          && screenArea.contains (mx + unboundedMouseOffsetX,
                                                  my + unboundedMouseOffsetY))
                {
                    mx += unboundedMouseOffsetX;
                    my += unboundedMouseOffsetY;
                    unboundedMouseOffsetX = 0;
                    unboundedMouseOffsetY = 0;

                    Desktop::setMousePosition (mx, my);
                }
            }

            internalUpdateMouseCursor (false);
        }
    }
}

void Component::internalMouseMove (int x, int y, int64 time)
{
    const ComponentDeletionWatcher deletionChecker (this);

    if (isValidComponent())
    {
        const MouseEvent me (x, y,
                             ModifierKeys::getCurrentModifiers(),
                             this,
                             Time (time),
                             x, y,
                             Time (time),
                             0, false);

        if (isCurrentlyBlockedByAnotherModalComponent())
        {
            // allow blocked mouse-events to go to global listeners..
            Desktop::getInstance().sendMouseMove();
        }
        else
        {
            if (this == componentUnderMouse)
                internalUpdateMouseCursor (false);

            flags.mouseOverFlag = true;

            mouseMove (me);

            if (deletionChecker.hasBeenDeleted())
                return;

            Desktop::getInstance().resetTimer();

            for (int i = Desktop::getInstance().mouseListeners.size(); --i >= 0;)
            {
                ((MouseListener*) Desktop::getInstance().mouseListeners[i])->mouseMove (me);

                if (deletionChecker.hasBeenDeleted())
                    return;

                i = jmin (i, Desktop::getInstance().mouseListeners.size());
            }

            if (mouseListeners_ != 0)
            {
                for (int i = mouseListeners_->size(); --i >= 0;)
                {
                    ((MouseListener*) mouseListeners_->getUnchecked (i))->mouseMove (me);

                    if (deletionChecker.hasBeenDeleted())
                        return;

                    i = jmin (i, mouseListeners_->size());
                }
            }

            const Component* p = parentComponent_;

            while (p != 0)
            {
                const ComponentDeletionWatcher parentDeletionChecker (p);

                for (int i = p->numDeepMouseListeners; --i >= 0;)
                {
                    ((MouseListener*) (p->mouseListeners_->getUnchecked (i)))->mouseMove (me);

                    if (deletionChecker.hasBeenDeleted() || parentDeletionChecker.hasBeenDeleted())
                        return;

                    i = jmin (i, p->numDeepMouseListeners);
                }

                p = p->parentComponent_;
            }
        }
    }
}

void Component::internalMouseWheel (const int intAmount, int64 time)
{
    const ComponentDeletionWatcher deletionChecker (this);

    const float wheelIncrement = intAmount * (1.0f / 256.0f);

    const MouseEvent me (getMouseXRelative(),
                         getMouseYRelative(),
                         ModifierKeys::getCurrentModifiers(),
                         this,
                         Time (time),
                         getMouseXRelative(),
                         getMouseYRelative(),
                         Time (time),
                         0, false);

    if (isCurrentlyBlockedByAnotherModalComponent())
    {
        // allow blocked mouse-events to go to global listeners..
        for (int i = Desktop::getInstance().mouseListeners.size(); --i >= 0;)
        {
            ((MouseListener*) Desktop::getInstance().mouseListeners[i])->mouseWheelMove (me, wheelIncrement);

            if (deletionChecker.hasBeenDeleted())
                return;

            i = jmin (i, Desktop::getInstance().mouseListeners.size());
        }
    }
    else
    {
        mouseWheelMove (me, wheelIncrement);

        if (deletionChecker.hasBeenDeleted())
            return;

        for (int i = Desktop::getInstance().mouseListeners.size(); --i >= 0;)
        {
            ((MouseListener*) Desktop::getInstance().mouseListeners[i])->mouseWheelMove (me, wheelIncrement);

            if (deletionChecker.hasBeenDeleted())
                return;

            i = jmin (i, Desktop::getInstance().mouseListeners.size());
        }

        if (mouseListeners_ != 0)
        {
            for (int i = mouseListeners_->size(); --i >= 0;)
            {
                ((MouseListener*) mouseListeners_->getUnchecked (i))->mouseWheelMove (me, wheelIncrement);

                if (deletionChecker.hasBeenDeleted())
                    return;

                i = jmin (i, mouseListeners_->size());
            }
        }

        const Component* p = parentComponent_;

        while (p != 0)
        {
            const ComponentDeletionWatcher parentDeletionChecker (p);

            for (int i = p->numDeepMouseListeners; --i >= 0;)
            {
                ((MouseListener*) (p->mouseListeners_->getUnchecked (i)))->mouseWheelMove (me, wheelIncrement);

                if (deletionChecker.hasBeenDeleted() || parentDeletionChecker.hasBeenDeleted())
                    return;

                i = jmin (i, p->numDeepMouseListeners);
            }

            p = p->parentComponent_;
        }

        sendFakeMouseMove();
    }
}

void Component::sendFakeMouseMove() const
{
    ComponentPeer* const peer = getPeer();

    if (peer != 0)
        peer->sendFakeMouseMove();
}

void Component::broughtToFront()
{
}

void Component::internalBroughtToFront()
{
    if (isValidComponent())
    {
        if (flags.hasHeavyweightPeerFlag)
            Desktop::getInstance().componentBroughtToFront (this);

        const ComponentDeletionWatcher deletionChecker (this);
        broughtToFront();

        if (deletionChecker.hasBeenDeleted())
            return;

        if (componentListeners_ != 0)
        {
            for (int i = componentListeners_->size(); --i >= 0;)
            {
                ((ComponentListener*) componentListeners_->getUnchecked (i))
                    ->componentBroughtToFront (*this);

                if (deletionChecker.hasBeenDeleted())
                    return;

                i = jmin (i, componentListeners_->size());
            }
        }

        // when brought to the front and there's a modal component blocking this one,
        // we need to bring the modal one to the front instead..

        Component* const cm = getCurrentlyModalComponent();

        if (cm != 0 && cm->getTopLevelComponent() != getTopLevelComponent())
        {
            cm->getTopLevelComponent()->toFront (true);
        }
    }
}

void Component::focusGained (FocusChangeType)
{
    // base class does nothing
}

void Component::internalFocusGain (const FocusChangeType cause)
{
    const ComponentDeletionWatcher deletionChecker (this);

    focusGained (cause);

    if (! deletionChecker.hasBeenDeleted())
        internalChildFocusChange (cause);
}

void Component::focusLost (FocusChangeType)
{
    // base class does nothing
}

void Component::internalFocusLoss (const FocusChangeType cause)
{
    const ComponentDeletionWatcher deletionChecker (this);

    focusLost (focusChangedDirectly);

    if (! deletionChecker.hasBeenDeleted())
        internalChildFocusChange (cause);
}

void Component::focusOfChildComponentChanged (FocusChangeType /*cause*/)
{
    // base class does nothing
}

void Component::internalChildFocusChange (FocusChangeType cause)
{
    const bool childIsNowFocused = hasKeyboardFocus (true);

    if (flags.childCompFocusedFlag != childIsNowFocused)
    {
        flags.childCompFocusedFlag = childIsNowFocused;

        const ComponentDeletionWatcher deletionChecker (this);
        focusOfChildComponentChanged (cause);

        if (deletionChecker.hasBeenDeleted())
            return;
    }

    if (parentComponent_ != 0)
        parentComponent_->internalChildFocusChange (cause);
}

//==============================================================================
bool Component::isEnabled() const
{
    return (! flags.isDisabledFlag)
            && (parentComponent_ == 0 || parentComponent_->isEnabled());
}

void Component::setEnabled (const bool shouldBeEnabled)
{
    if (flags.isDisabledFlag == shouldBeEnabled)
    {
        flags.isDisabledFlag = ! shouldBeEnabled;

        // if any parent components are disabled, setting our flag won't make a difference,
        // so no need to send a change message
        if (parentComponent_ == 0 || parentComponent_->isEnabled())
            sendEnablementChangeMessage();
    }
}

void Component::sendEnablementChangeMessage()
{
    const ComponentDeletionWatcher deletionChecker (this);

    enablementChanged();

    if (deletionChecker.hasBeenDeleted())
        return;

    for (int i = getNumChildComponents(); --i >= 0;)
    {
        Component* const c = getChildComponent (i);

        if (c != 0)
        {
            c->sendEnablementChangeMessage();

            if (deletionChecker.hasBeenDeleted())
                return;
        }
    }
}

void Component::enablementChanged()
{
}

//==============================================================================
void Component::setWantsKeyboardFocus (const bool wantsFocus)
{
    flags.wantsFocusFlag = wantsFocus;
}

void Component::setMouseClickGrabsKeyboardFocus (const bool shouldGrabFocus)
{
    flags.dontFocusOnMouseClickFlag = ! shouldGrabFocus;
}

bool Component::getWantsKeyboardFocus() const throw()
{
    return flags.wantsFocusFlag && ! flags.isDisabledFlag;
}

void Component::setFocusOrder (unsigned short orderNumber)
{
    focusOrderNumber_ = orderNumber;
}

uint32 Component::getFocusOrder() const
{
    if (flags.wantsFocusFlag)
        return focusOrderNumber_;

    // if we don't want it ourselves, return a multiple of how much any
    // children want it..
    uint32 best = 0x7fffffff;

    for (int i = childComponentList_.size(); --i >= 0;)
    {
        Component& child = *(Component*)(childComponentList_.getUnchecked(i));

        if (child.flags.visibleFlag)
        {
            const uint32 childOrder = child.getFocusOrder();

            if (childOrder < best)
                best = childOrder;
        }
    }

    if (best < 0x6fffffff)
        best += 0x10000;

    return best;
}

void Component::takeKeyboardFocus (FocusChangeType cause)
{
    // give the focus to this component
    if (currentlyFocusedComponent != this)
    {
        JUCE_TRY
        {
            // get the focus onto our desktop window
            ComponentPeer* const peer = getPeer();

            if (peer != 0)
            {
                peer->grabFocus();

                if (peer->isFocused() && currentlyFocusedComponent != this)
                {
                    Component* const componentLosingFocus = currentlyFocusedComponent;

                    currentlyFocusedComponent = this;

                    // call this after setting currentlyFocusedComponent so that the one that's
                    // losing it has a chance to see where focus is going
                    if (componentLosingFocus->isValidComponent())
                        componentLosingFocus->internalFocusLoss (cause);

                    if (currentlyFocusedComponent == this)
                        internalFocusGain (cause);
                }
            }
        }
#if JUCE_CATCH_UNHANDLED_EXCEPTIONS
        catch (const std::exception& e)
        {
            currentlyFocusedComponent = 0;
            JUCEApplication::sendUnhandledException (&e, __FILE__, __LINE__);
        }
        catch (...)
        {
            currentlyFocusedComponent = 0;
            JUCEApplication::sendUnhandledException (0, __FILE__, __LINE__);
        }
#endif
    }
}

void Component::grabFocusInternal (FocusChangeType cause, const bool canTryParent)
{
    if (isShowing())
    {
        if (flags.wantsFocusFlag && (isEnabled() || parentComponent_ == 0))
        {
            takeKeyboardFocus (cause);
        }
        else
        {
            if (isParentOf (currentlyFocusedComponent)
                 && currentlyFocusedComponent->isShowing())
            {
                // do nothing if the focused component is actually a child of ours..
            }
            else
            {
                // look for the most interested of our child components..
                uint32 mostInterest = 0x7fffffff;
                Component* mostInterested = 0;

                for (int i = 0; i < childComponentList_.size(); ++i)
                {
                    Component* const child = (Component*) (childComponentList_.getUnchecked(i));

                    if (child->isVisible() && child->isEnabled())
                    {
                        const uint32 howMuch = child->getFocusOrder();

                        if (howMuch < mostInterest)
                        {
                            mostInterest = howMuch;
                            mostInterested = child;
                        }
                    }
                }

                if (mostInterested != 0)
                {
                    mostInterested->grabFocusInternal (cause, false);
                }
                else if (canTryParent && parentComponent_ != 0)
                {
                    // if no children want it and we're allowed to try our parent comp,
                    // then pass up to parent, which will try our siblings.
                    parentComponent_->grabFocusInternal (cause, true);
                }
            }
        }
    }
}

void Component::grabKeyboardFocus()
{
    // if component methods are being called from threads other than the message
    // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe.
    checkMessageManagerIsLocked

    grabFocusInternal (focusChangedDirectly);
}

void Component::moveKeyboardFocusToSibling (const bool moveToNext)
{
    // if component methods are being called from threads other than the message
    // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe.
    checkMessageManagerIsLocked

    if (parentComponent_ != 0)
    {
        const uint32 ourOrder = (getFocusOrder() << 12) + parentComponent_->childComponentList_.indexOf (this);

        Component* bestComp = 0;
        uint32 bestOrder = 0;
        Component* wrapAroundComp = 0;
        uint32 wrapAroundOrder = 0;

        for (int i = parentComponent_->getNumChildComponents(); --i >= 0;)
        {
            Component* const sibling = parentComponent_->getChildComponent (i);

            if (sibling != this && sibling->getWantsKeyboardFocus())
            {
                const int siblingOrder = sibling->getFocusOrder();

                if (siblingOrder < 0x7fffffff)
                {
                    const uint32 order = (sibling->getFocusOrder() << 12) + i;
                    bool isBest, isWrap;

                    if (moveToNext)
                    {
                        isBest = order > ourOrder && (bestComp == 0 || order <= bestOrder);
                        isWrap = wrapAroundComp == 0 || order <= wrapAroundOrder;
                    }
                    else
                    {
                        isBest = order < ourOrder && (bestComp == 0 || order >= bestOrder);
                        isWrap = wrapAroundComp == 0 || order > wrapAroundOrder;
                    }

                    if (isBest)
                    {
                        bestComp = sibling;
                        bestOrder = order;
                    }

                    if (isWrap)
                    {
                        wrapAroundComp = sibling;
                        wrapAroundOrder = order;
                    }
                }
            }
        }

        if (bestComp == 0)
            bestComp = wrapAroundComp;

        if (bestComp != 0)
        {
            bestComp->grabFocusInternal (focusChangedByTabKey);
        }
        else
        {
            parentComponent_->moveKeyboardFocusToSibling (moveToNext);
        }
    }
}

bool Component::hasKeyboardFocus (const bool trueIfChildIsFocused) const
{
    return (currentlyFocusedComponent == this)
            || (trueIfChildIsFocused && isParentOf (currentlyFocusedComponent));
}

Component* Component::getCurrentlyFocusedComponent()
{
    return currentlyFocusedComponent;
}

void Component::giveAwayFocus()
{
    // use a copy so we can clear the value before the call
    Component* const componentLosingFocus = currentlyFocusedComponent;
    currentlyFocusedComponent = 0;

    if (componentLosingFocus->isValidComponent())
        componentLosingFocus->internalFocusLoss (focusChangedDirectly);
}

//==============================================================================
bool Component::isMouseOver() const
{
    return flags.mouseOverFlag;
}

bool Component::isMouseButtonDown() const throw()
{
    return flags.draggingFlag;
}

bool Component::isMouseOverOrDragging() const throw()
{
    return flags.mouseOverFlag || flags.draggingFlag;
}

bool Component::isMouseButtonDownAnywhere() throw()
{
    return ModifierKeys::getCurrentModifiers().isAnyMouseButtonDown();
}

int Component::getMouseX() throw()
{
    int mx, my;
    Desktop::getMousePosition (mx, my);

    return mx + unboundedMouseOffsetX;
}

int Component::getMouseY() throw()
{
    int mx, my;
    Desktop::getMousePosition (mx, my);

    return my + unboundedMouseOffsetY;
}

int Component::getMouseXRelative() const throw()
{
    return getMouseX() - getScreenX();
}

int Component::getMouseYRelative() const throw()
{
    return getMouseY() - getScreenY();
}

void Component::enableUnboundedMouseMovement (bool enable,
                                              bool keepCursorVisibleUntilOffscreen)
{
    enable = enable && isMouseButtonDown();
    isCursorVisibleUntilOffscreen = keepCursorVisibleUntilOffscreen;

    if (enable != isUnboundedMouseModeOn)
    {
        if ((! enable) && ((! isCursorVisibleUntilOffscreen)
                            || unboundedMouseOffsetX != 0
                            || unboundedMouseOffsetY != 0))
        {
            // when released, return the mouse to within the component's bounds
            Desktop::setMousePosition (jlimit (getScreenX(), getScreenX() + getWidth(), getMouseX()),
                                       jlimit (getScreenY(), getScreenY() + getHeight(), getMouseY()));
        }

        isUnboundedMouseModeOn = enable;
        unboundedMouseOffsetX = 0;
        unboundedMouseOffsetY = 0;

        internalUpdateMouseCursor (true);
    }
}

Component* Component::getComponentUnderMouse() throw()
{
    return componentUnderMouse;
}

//==============================================================================
const Rectangle Component::getParentMonitorArea() const throw()
{
    const int centreX = getScreenX() + getWidth() / 2;
    const int centreY = getScreenY() + getHeight() / 2;

    return Desktop::getInstance().getMonitorAreaContaining (centreX, centreY);
}

//==============================================================================
void Component::addKeyListener (KeyListener* const newListener)
{
    if (keyListeners_ == 0)
        keyListeners_ = new VoidArray (4);

    keyListeners_->addIfNotAlreadyThere (newListener);
}

void Component::removeKeyListener (KeyListener* const listenerToRemove)
{
    if (keyListeners_ != 0)
        keyListeners_->removeValue (listenerToRemove);
}

void Component::internalKeyPress (const int key)
{
    if (isCurrentlyBlockedByAnotherModalComponent())
    {
        internalModalInputAttempt();
    }
    else
    {
        keyPressed (KeyPress (key,
                              ModifierKeys::getCurrentModifiers().getRawFlags()
                                 & (ModifierKeys::commandModifier | ModifierKeys::ctrlModifier
                                     | ModifierKeys::shiftModifier | ModifierKeys::altModifier)));
    }
}

void Component::keyPressed (const KeyPress& key)
{
    const ComponentDeletionWatcher deletionChecker (this);

    if (key.isKeyCode (KeyPress::tabKey)
         && getCurrentlyFocusedComponent()->isValidComponent())
    {
        getCurrentlyFocusedComponent()->moveKeyboardFocusToSibling (! key.getModifiers().isShiftDown());
    }
    else if (parentComponent_ != 0)
    {
        parentComponent_->keyPressed (key);
    }

    if ((! deletionChecker.hasBeenDeleted()) && keyListeners_ != 0)
    {
        for (int i = keyListeners_->size(); --i >= 0;)
        {
            ((KeyListener*) keyListeners_->getUnchecked(i))->keyPressed (key, this);

            if (deletionChecker.hasBeenDeleted())
                return;

            i = jmin (i, keyListeners_->size());
        }
    }
}

void Component::internalKeyStateChanged()
{
    keyStateChanged();
}

void Component::keyStateChanged()
{
    const ComponentDeletionWatcher deletionChecker (this);

    if (parentComponent_ != 0)
        parentComponent_->keyStateChanged();

    if ((! deletionChecker.hasBeenDeleted()) && keyListeners_ != 0)
    {
        for (int i = keyListeners_->size(); --i >= 0;)
        {
            ((KeyListener*) keyListeners_->getUnchecked(i))->keyStateChanged (this);

            if (deletionChecker.hasBeenDeleted())
                return;

            i = jmin (i, keyListeners_->size());
        }
    }
}

void Component::internalModifierKeysChanged()
{
    sendFakeMouseMove();

    modifierKeysChanged (ModifierKeys::getCurrentModifiers());
}

void Component::modifierKeysChanged (const ModifierKeys& modifiers)
{
    if (parentComponent_ != 0)
        parentComponent_->modifierKeysChanged (modifiers);
}

//==============================================================================
bool Component::filesDropped (const StringArray&, int, int)
{
    // base class returns false to let the message get passed up to its parent
    return false;
}

void Component::internalFilesDropped (const int x,
                                      const int y,
                                      const StringArray& files)
{
    if (isCurrentlyBlockedByAnotherModalComponent())
    {
        internalModalInputAttempt();
    }
    else
    {
        Component* c = getComponentAt (x, y);

        while (c->isValidComponent())
        {
            if (c->filesDropped (files,
                                 x - (c->getScreenX() - getScreenX()),
                                 y - (c->getScreenY() - getScreenY())))
            {
                break;
            }

            c = c->getParentComponent();
        }
    }
}

ComponentPeer* Component::getPeer() const
{
    if (flags.hasHeavyweightPeerFlag)
        return ComponentPeer::getPeerFor (this);
    else if (parentComponent_ != 0)
        return parentComponent_->getPeer();
    else
        return 0;
}

//==============================================================================
const String Component::getComponentProperty (const String& keyName,
                                              const bool useParentComponentIfNotFound,
                                              const String& defaultReturnValue) const throw()
{
    if (propertySet_ != 0 && ((! useParentComponentIfNotFound) || propertySet_->containsKey (keyName)))
        return propertySet_->getValue (keyName, defaultReturnValue);

    if (useParentComponentIfNotFound && (parentComponent_ != 0))
        return parentComponent_->getComponentProperty (keyName, true, defaultReturnValue);

    return defaultReturnValue;
}

int Component::getComponentPropertyInt (const String& keyName,
                                        const bool useParentComponentIfNotFound,
                                        const int defaultReturnValue) const throw()
{
    if (propertySet_ != 0 && ((! useParentComponentIfNotFound) || propertySet_->containsKey (keyName)))
        return propertySet_->getIntValue (keyName, defaultReturnValue);

    if (useParentComponentIfNotFound && (parentComponent_ != 0))
        return parentComponent_->getComponentPropertyInt (keyName, true, defaultReturnValue);

    return defaultReturnValue;
}

double Component::getComponentPropertyDouble (const String& keyName,
                                              const bool useParentComponentIfNotFound,
                                              const double defaultReturnValue) const throw()
{
    if (propertySet_ != 0 && ((! useParentComponentIfNotFound) || propertySet_->containsKey (keyName)))
        return propertySet_->getDoubleValue (keyName, defaultReturnValue);

    if (useParentComponentIfNotFound && (parentComponent_ != 0))
        return parentComponent_->getComponentPropertyDouble (keyName, true, defaultReturnValue);

    return defaultReturnValue;
}

bool Component::getComponentPropertyBool (const String& keyName,
                                          const bool useParentComponentIfNotFound,
                                          const bool defaultReturnValue) const throw()
{
    if (propertySet_ != 0 && ((! useParentComponentIfNotFound) || propertySet_->containsKey (keyName)))
        return propertySet_->getBoolValue (keyName, defaultReturnValue);

    if (useParentComponentIfNotFound && (parentComponent_ != 0))
        return parentComponent_->getComponentPropertyBool (keyName, true, defaultReturnValue);

    return defaultReturnValue;
}

void Component::setComponentProperty (const String& keyName, const String& value) throw()
{
    if (propertySet_ == 0)
        propertySet_ = new PropertySet();

    propertySet_->setValue (keyName, value);
}

void Component::setComponentProperty (const String& keyName, const int value) throw()
{
    if (propertySet_ == 0)
        propertySet_ = new PropertySet();

    propertySet_->setValue (keyName, value);
}

void Component::setComponentProperty (const String& keyName, const double value) throw()
{
    if (propertySet_ == 0)
        propertySet_ = new PropertySet();

    propertySet_->setValue (keyName, value);
}

void Component::setComponentProperty (const String& keyName, const bool value) throw()
{
    if (propertySet_ == 0)
        propertySet_ = new PropertySet();

    propertySet_->setValue (keyName, value);
}

void Component::removeComponentProperty (const String& keyName) throw()
{
    if (propertySet_ != 0)
        propertySet_->removeValue (keyName);
}

//==============================================================================
ComponentDeletionWatcher::ComponentDeletionWatcher (const Component* const componentToWatch_)
    : componentToWatch (componentToWatch_),
      componentUID (componentToWatch_->getComponentUID())
{
    // not possible to check on an already-deleted object..
    jassert (componentToWatch_->isValidComponent());
}

ComponentDeletionWatcher::~ComponentDeletionWatcher() {}

bool ComponentDeletionWatcher::hasBeenDeleted() const throw()
{
    return ! (componentToWatch->isValidComponent()
                && componentToWatch->getComponentUID() == componentUID);
}

const Component* ComponentDeletionWatcher::getComponent() const throw()
{
    return hasBeenDeleted() ? 0 : componentToWatch;
}


END_JUCE_NAMESPACE
