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

   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.

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

#ifdef _MSC_VER
  #pragma warning (disable: 4514)
  #pragma warning (push)
#endif

#include "win32_headers.h"
#include <float.h>
#include <windowsx.h>
#include <shlobj.h>

#ifdef _MSC_VER
  #define USE_GDIPLUS 0
#endif

#if USE_GDIPLUS
  #include <gdiplus.h>
#endif

#if JUCE_OPENGL
  #include <gl/gl.h>
#endif

#ifdef _MSC_VER
  #pragma warning (pop)
  #pragma warning (disable: 4312 4244)
#endif

#undef GetSystemMetrics // multimon overrides this for some reason and causes a mess..

// these are in the windows SDK, but need to be repeated here for GCC..
#ifndef GET_APPCOMMAND_LPARAM
  #define FAPPCOMMAND_MASK                  0xF000
  #define GET_APPCOMMAND_LPARAM(lParam)     ((short) (HIWORD(lParam) & ~FAPPCOMMAND_MASK))
  #define APPCOMMAND_MEDIA_NEXTTRACK        11
  #define APPCOMMAND_MEDIA_PREVIOUSTRACK    12
  #define APPCOMMAND_MEDIA_STOP             13
  #define APPCOMMAND_MEDIA_PLAY_PAUSE       14
  #define WM_APPCOMMAND                     0x0319
#endif


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

BEGIN_JUCE_NAMESPACE

#include "../../../src/juce_core/text/juce_StringArray.h"
#include "../../../src/juce_core/basics/juce_SystemStats.h"
#include "../../../src/juce_core/basics/juce_Logger.h"
#include "../../../src/juce_core/threads/juce_Process.h"
#include "../../../src/juce_core/misc/juce_PlatformUtilities.h"
#include "../../../src/juce_appframework/events/juce_Timer.h"
#include "../../../src/juce_appframework/events/juce_MessageManager.h"
#include "../../../src/juce_appframework/application/juce_DeletedAtShutdown.h"
#include "../../../src/juce_appframework/gui/components/keyboard/juce_KeyPress.h"
#include "../../../src/juce_appframework/gui/components/mouse/juce_DragAndDropContainer.h"
#include "../../../src/juce_appframework/gui/components/juce_Desktop.h"
#include "../../../src/juce_appframework/gui/components/lookandfeel/juce_LookAndFeel.h"
#include "../../../src/juce_appframework/gui/components/special/juce_OpenGLComponent.h"
#include "../../../src/juce_appframework/gui/components/special/juce_DropShadower.h"
#include "../../../src/juce_appframework/gui/components/juce_RepaintManager.h"
#include "../../../src/juce_appframework/gui/components/juce_ComponentDeletionWatcher.h"
#include "../../../src/juce_appframework/gui/components/layout/juce_ComponentBoundsConstrainer.h"
#include "../../../src/juce_appframework/gui/graphics/imaging/juce_ImageFileFormat.h"
#include "../../../src/juce_appframework/gui/graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.h"
#include "../../../src/juce_appframework/gui/graphics/geometry/juce_PathIterator.h"
#include "juce_win32_DynamicLibraryLoader.h"

extern void repeatLastProcessPriority(); // in juce_win32_Threads.cpp

const int juce_windowIsSemiTransparentFlag = (1 << 31); // also in component.cpp

#ifndef ULW_ALPHA
  #define ULW_ALPHA     0x00000002
#endif

#ifndef AC_SRC_ALPHA
  #define AC_SRC_ALPHA  0x01
#endif

#define DEBUG_REPAINT_TIMES 0

static HPALETTE palette = 0;
static bool createPaletteIfNeeded = true;
static bool shouldDeactivateTitleBar = true;

//==============================================================================
typedef BOOL (WINAPI* UpdateLayeredWinFunc) (HWND, HDC, POINT*, SIZE*, HDC, POINT*, COLORREF, BLENDFUNCTION*, DWORD);
static UpdateLayeredWinFunc updateLayeredWindow = 0;

bool Desktop::canUseSemiTransparentWindows()
{
    if (updateLayeredWindow == 0)
    {
        HMODULE user32Mod = GetModuleHandle (_T("user32.dll"));
        updateLayeredWindow = (UpdateLayeredWinFunc) GetProcAddress (user32Mod, "UpdateLayeredWindow");
    }

    return updateLayeredWindow != 0;
}


//==============================================================================
#if JUCE_QUICKTIME
  extern void OfferMessageToQuickTime (HWND h, UINT message, WPARAM wParam, LPARAM lParam, Component* component);
#endif


//==============================================================================
UNICODE_FUNCTION (SetWindowTextW, BOOL, (HWND, LPCWSTR))

void juce_initialiseUnicodeWindowFunctions()
{
    static bool initialised = false;

    if (! initialised)
    {
        initialised = true;

        HMODULE h = LoadLibraryA ("user32.dll");
        UNICODE_FUNCTION_LOAD (SetWindowTextW)
    }
}


//==============================================================================
static const int nonAsciiKeyFlag    = 0x10000;

const int KeyPress::spaceKey        = VK_SPACE;
const int KeyPress::returnKey       = VK_RETURN;
const int KeyPress::escapeKey       = VK_ESCAPE;
const int KeyPress::backspaceKey    = VK_BACK;
const int KeyPress::deleteKey       = VK_DELETE | nonAsciiKeyFlag;
const int KeyPress::insertKey       = VK_INSERT | nonAsciiKeyFlag;
const int KeyPress::tabKey          = VK_TAB;
const int KeyPress::leftKey         = VK_LEFT   | nonAsciiKeyFlag;
const int KeyPress::rightKey        = VK_RIGHT  | nonAsciiKeyFlag;
const int KeyPress::upKey           = VK_UP     | nonAsciiKeyFlag;
const int KeyPress::downKey         = VK_DOWN   | nonAsciiKeyFlag;
const int KeyPress::homeKey         = VK_HOME   | nonAsciiKeyFlag;
const int KeyPress::endKey          = VK_END    | nonAsciiKeyFlag;
const int KeyPress::pageUpKey       = VK_PRIOR  | nonAsciiKeyFlag;
const int KeyPress::pageDownKey     = VK_NEXT   | nonAsciiKeyFlag;
const int KeyPress::F1Key           = VK_F1     | nonAsciiKeyFlag;
const int KeyPress::F2Key           = VK_F2     | nonAsciiKeyFlag;
const int KeyPress::F3Key           = VK_F3     | nonAsciiKeyFlag;
const int KeyPress::F4Key           = VK_F4     | nonAsciiKeyFlag;
const int KeyPress::F5Key           = VK_F5     | nonAsciiKeyFlag;
const int KeyPress::F6Key           = VK_F6     | nonAsciiKeyFlag;
const int KeyPress::F7Key           = VK_F7     | nonAsciiKeyFlag;
const int KeyPress::F8Key           = VK_F8     | nonAsciiKeyFlag;
const int KeyPress::F9Key           = VK_F9     | nonAsciiKeyFlag;
const int KeyPress::F10Key          = VK_F10    | nonAsciiKeyFlag;
const int KeyPress::F11Key          = VK_F11    | nonAsciiKeyFlag;
const int KeyPress::F12Key          = VK_F12    | nonAsciiKeyFlag;
const int KeyPress::playKey         = 0x30000;
const int KeyPress::stopKey         = 0x30001;
const int KeyPress::fastForwardKey  = 0x30002;
const int KeyPress::rewindKey       = 0x30003;

//==============================================================================
class WindowsBitmapImage  : public Image
{
public:
    //==============================================================================
    HBITMAP hBitmap;
    BITMAPV4HEADER bitmapInfo;
    HDC hdc;
    unsigned char* bitmapData;

    //==============================================================================
    WindowsBitmapImage (const PixelFormat format_,
                        const int w, const int h, const bool clearImage)
        : Image (format_, w, h)
    {
        jassert (format_ == RGB || format_ == ARGB);

        pixelStride = (format_ == RGB) ? 3 : 4;

        zerostruct (bitmapInfo);
        bitmapInfo.bV4Size = sizeof (BITMAPV4HEADER);
        bitmapInfo.bV4Width = w;
        bitmapInfo.bV4Height = h;
        bitmapInfo.bV4Planes = 1;
        bitmapInfo.bV4BitCount = (unsigned short) (pixelStride * 8);

        if (format_ == ARGB)
        {
            bitmapInfo.bV4AlphaMask        = 0xff000000;
            bitmapInfo.bV4RedMask          = 0xff0000;
            bitmapInfo.bV4GreenMask        = 0xff00;
            bitmapInfo.bV4BlueMask         = 0xff;
            bitmapInfo.bV4V4Compression    = BI_BITFIELDS;
        }
        else
        {
            bitmapInfo.bV4V4Compression    = BI_RGB;
        }

        lineStride = -((w * pixelStride + 3) & ~3);

        HDC dc = GetDC (0);
        hdc = CreateCompatibleDC (dc);
        ReleaseDC (0, dc);

        SetMapMode (hdc, MM_TEXT);

        hBitmap = CreateDIBSection (hdc,
                                    (BITMAPINFO*) &(bitmapInfo),
                                    DIB_RGB_COLORS,
                                    (void**) &bitmapData,
                                    0, 0);

        SelectObject (hdc, hBitmap);

        static int needsClearing = 0;

        if (needsClearing == 0)
        {
            const String os (SystemStats::getOSType());

            if (os == SystemStats::Win95 || os == SystemStats::Win98)
                needsClearing = 2;
            else
                needsClearing = 1;
        }

        if (format_ == ARGB && needsClearing == 2 && clearImage)
            zeromem (bitmapData, abs (h * lineStride));

        imageData = bitmapData - (lineStride * (h - 1));
    }

    ~WindowsBitmapImage()
    {
        DeleteDC (hdc);
        DeleteObject (hBitmap);
        imageData = 0; // to stop the base class freeing this

    }

    void blitToWindow (HWND hwnd, HDC dc, const bool transparent,
                       int x, int y, const RectangleList& maskedRegion)
    {
        static HDRAWDIB hdd = 0;
        static bool needToCreateDrawDib = true;

        if (needToCreateDrawDib)
        {
            needToCreateDrawDib = false;

            HDC dc = GetDC (0);
            const int n = GetDeviceCaps (dc, BITSPIXEL);
            ReleaseDC (0, dc);

            // only open if we're not palettised
            if (n > 8)
                hdd = DrawDibOpen();
        }

        if (createPaletteIfNeeded)
        {
            HDC dc = GetDC (0);
            const int n = GetDeviceCaps (dc, BITSPIXEL);
            ReleaseDC (0, dc);

            if (n <= 8)
                palette = CreateHalftonePalette (dc);

            createPaletteIfNeeded = false;
        }

        if (palette != 0)
        {
            SelectPalette (dc, palette, FALSE);
            RealizePalette (dc);
            SetStretchBltMode (dc, HALFTONE);
        }

        SetMapMode (dc, MM_TEXT);

        if (transparent)
        {
            POINT p, pos;
            SIZE size;

            RECT windowBounds;
            GetWindowRect (hwnd, &windowBounds);

            p.x = -x;
            p.y = -y;
            pos.x = windowBounds.left;
            pos.y = windowBounds.top;
            size.cx = windowBounds.right - windowBounds.left;
            size.cy = windowBounds.bottom - windowBounds.top;

            BLENDFUNCTION bf;
            bf.AlphaFormat = AC_SRC_ALPHA;
            bf.BlendFlags = 0;
            bf.BlendOp = AC_SRC_OVER;
            bf.SourceConstantAlpha = 0xff;

            updateLayeredWindow (hwnd, 0, &pos, &size, hdc, &p, 0, &bf, ULW_ALPHA);
        }
        else
        {
            int savedDC = 0;

            if (! maskedRegion.isEmpty())
            {
                savedDC = SaveDC (dc);

                for (RectangleList::Iterator i (maskedRegion); i.next();)
                {
                    const Rectangle& r = i.getRectangle();
                    ExcludeClipRect (dc, r.getX(), r.getY(), r.getRight(), r.getBottom());
                }
            }

            const int w = getWidth();
            const int h = getHeight();

            if (hdd == 0)
            {
                StretchDIBits (dc,
                               x, y, w, h,
                               0, 0, w, h,
                               bitmapData, (const BITMAPINFO*) &bitmapInfo,
                               DIB_RGB_COLORS, SRCCOPY);
            }
            else
            {
                DrawDibDraw (hdd, dc, x, y, -1, -1,
                             (BITMAPINFOHEADER*) &bitmapInfo, bitmapData,
                             0, 0, w, h, 0);
            }

            if (! maskedRegion.isEmpty())
                RestoreDC (dc, savedDC);
        }
    }

    juce_UseDebuggingNewOperator

private:
    WindowsBitmapImage (const WindowsBitmapImage&);
    const WindowsBitmapImage& operator= (const WindowsBitmapImage&);
};


//==============================================================================
#if USE_GDIPLUS

xxx sorry - I've broken all the GDI+ stuff in this build.. it's rubbish and slow anyway, but
I'll fix it in the near future. Just needs the new clip region adding, and the new gradient stuff

#define GDIPLUS_FUNCTION(functionName, params) \
    typedef Gdiplus::GpStatus (WINGDIPAPI *type##functionName) params; \
    static type##functionName j##functionName = 0;

#define GDIPLUS_FUNCTION_LOAD(functionName) \
    j##functionName = (type##functionName) GetProcAddress (h, #functionName);  \
    jassert (j##functionName != 0);

GDIPLUS_FUNCTION (GdiplusStartup, (OUT ULONG_PTR *token, const Gdiplus::GdiplusStartupInput *input, OUT Gdiplus::GdiplusStartupOutput *output))
GDIPLUS_FUNCTION (GdipCreateFromHDC, (HDC hdc, Gdiplus::GpGraphics **graphics))
GDIPLUS_FUNCTION (GdipDeleteGraphics, (Gdiplus::GpGraphics **graphics))
GDIPLUS_FUNCTION (GdipSetCompositingMode, (Gdiplus::GpGraphics *graphics, Gdiplus::CompositingMode compositingMode))
GDIPLUS_FUNCTION (GdipFillRectangleI, (Gdiplus::GpGraphics *graphics, Gdiplus::GpBrush *brush, INT x, INT y, INT width, INT height))
GDIPLUS_FUNCTION (GdipCreateSolidFill, (Gdiplus::ARGB color, Gdiplus::GpSolidFill **brush))
GDIPLUS_FUNCTION (GdipDeleteBrush, (Gdiplus::GpBrush *brush))
GDIPLUS_FUNCTION (GdipCreateBitmapFromHBITMAP, (HBITMAP hbm, HPALETTE hpal, Gdiplus::GpBitmap** bitmap))
GDIPLUS_FUNCTION (GdipCreateBitmapFromScan0, (INT width, INT height, INT stride, Gdiplus::PixelFormat format, BYTE* scan0, Gdiplus::GpBitmap** bitmap))
GDIPLUS_FUNCTION (GdipDisposeImage, (Gdiplus::GpImage *image))
GDIPLUS_FUNCTION (GdipDrawImageRectRectI, (Gdiplus::GpGraphics *graphics, Gdiplus::GpImage *image, INT dstx, INT dsty, INT dstwidth, INT dstheight, INT srcx, INT srcy, INT srcwidth, INT srcheight, Gdiplus::GpUnit srcUnit, GDIPCONST Gdiplus::GpImageAttributes* imageAttributes, Gdiplus::DrawImageAbort callback, VOID * callbackData))
GDIPLUS_FUNCTION (GdipCreateImageAttributes, (Gdiplus::GpImageAttributes **imageattr))
GDIPLUS_FUNCTION (GdipDisposeImageAttributes, (Gdiplus::GpImageAttributes *imageattr))
GDIPLUS_FUNCTION (GdipSetImageAttributesColorMatrix, (Gdiplus::GpImageAttributes *imageattr, Gdiplus::ColorAdjustType type, BOOL enableFlag, GDIPCONST Gdiplus::ColorMatrix* colorMatrix, GDIPCONST Gdiplus::ColorMatrix* grayMatrix, Gdiplus::ColorMatrixFlags flags))
GDIPLUS_FUNCTION (GdipSetClipRectI, (Gdiplus::GpGraphics *graphics, INT x, INT y, INT width, INT height, Gdiplus::CombineMode combineMode))
GDIPLUS_FUNCTION (GdipCreateMatrix2, (Gdiplus::REAL m11, Gdiplus::REAL m12, Gdiplus::REAL m21, Gdiplus::REAL m22, Gdiplus::REAL dx, Gdiplus::REAL dy, Gdiplus::GpMatrix **matrix))
GDIPLUS_FUNCTION (GdipDeleteMatrix, (Gdiplus::GpMatrix *matrix))
GDIPLUS_FUNCTION (GdipSetWorldTransform, (Gdiplus::GpGraphics *graphics, Gdiplus::GpMatrix *matrix))
GDIPLUS_FUNCTION (GdipResetWorldTransform, (Gdiplus::GpGraphics *graphics))
GDIPLUS_FUNCTION (GdipSetInterpolationMode, (Gdiplus::GpGraphics *graphics, Gdiplus::InterpolationMode interpolationMode))
GDIPLUS_FUNCTION (GdipSetSmoothingMode, (Gdiplus::GpGraphics *graphics, Gdiplus::SmoothingMode smoothingMode))

static int canUseGdiPlus = 0;

static void initialiseGdiPlus()
{
    if (canUseGdiPlus == 0)
    {
        HMODULE h = LoadLibrary ("GdiPlus.dll");

        if (h != 0)
        {
            GDIPLUS_FUNCTION_LOAD (GdiplusStartup)
            GDIPLUS_FUNCTION_LOAD (GdipCreateFromHDC)
            GDIPLUS_FUNCTION_LOAD (GdipDeleteGraphics)
            GDIPLUS_FUNCTION_LOAD (GdipSetCompositingMode)
            GDIPLUS_FUNCTION_LOAD (GdipFillRectangleI)
            GDIPLUS_FUNCTION_LOAD (GdipCreateSolidFill)
            GDIPLUS_FUNCTION_LOAD (GdipDeleteBrush)
            GDIPLUS_FUNCTION_LOAD (GdipCreateBitmapFromHBITMAP)
            GDIPLUS_FUNCTION_LOAD (GdipCreateBitmapFromScan0)
            GDIPLUS_FUNCTION_LOAD (GdipDisposeImage)
            GDIPLUS_FUNCTION_LOAD (GdipDrawImageRectRectI)
            GDIPLUS_FUNCTION_LOAD (GdipCreateImageAttributes)
            GDIPLUS_FUNCTION_LOAD (GdipDisposeImageAttributes)
            GDIPLUS_FUNCTION_LOAD (GdipSetImageAttributesColorMatrix)
            GDIPLUS_FUNCTION_LOAD (GdipSetClipRectI)
            GDIPLUS_FUNCTION_LOAD (GdipCreateMatrix2)
            GDIPLUS_FUNCTION_LOAD (GdipDeleteMatrix)
            GDIPLUS_FUNCTION_LOAD (GdipSetWorldTransform)
            GDIPLUS_FUNCTION_LOAD (GdipResetWorldTransform)
            GDIPLUS_FUNCTION_LOAD (GdipSetInterpolationMode)
            GDIPLUS_FUNCTION_LOAD (GdipSetSmoothingMode)

            Gdiplus::GdiplusStartupInput gdiStartupIn;
            ULONG token = 0;
            jGdiplusStartup (&token, &gdiStartupIn, 0);
        }

        canUseGdiPlus = (h != 0) ? -1 : 1;
    }
}

class WindowsBitmapDeviceContext  : public LowLevelGraphicsSoftwareRenderer
{
public:
    //==============================================================================
    WindowsBitmapDeviceContext (WindowsBitmapImage& image_)
        : LowLevelGraphicsSoftwareRenderer (image_)
    {
        graphics = 0;
        hdc = image_.hdc;
        imageAtts = 0;

        jassert (canUseGdiPlus < 0);
        init();
    }

    WindowsBitmapDeviceContext (const WindowsBitmapDeviceContext& other)
        : LowLevelGraphicsSoftwareRenderer (other)
    {
        graphics = 0;
        hdc = other.hdc;
        init();
    }

    ~WindowsBitmapDeviceContext()
    {
        if (graphics != 0)
        {
            jGdipDeleteGraphics (&graphics);
            jGdipDisposeImageAttributes (imageAtts);
        }
    }

    void init()
    {
        if (jGdipCreateFromHDC != 0)
        {
            jGdipCreateFromHDC (hdc, &graphics);

            const Gdiplus::ColorMatrix m = { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
                                             0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
                                             0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
                                             0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
                                             0.0f, 0.0f, 0.0f, 0.0f, 1.0f };
            colourMatrix = m;

            jGdipCreateImageAttributes (&imageAtts);
            jGdipSetSmoothingMode (graphics, Gdiplus::SmoothingModeNone);
        }
    }

    LowLevelGraphicsContext* createCopy()
    {
        return new WindowsBitmapDeviceContext (*this);
    }

    void fillRectWithColour (int x, int y, int w, int h, const Colour& colour, const bool replaceExistingContents)
    {
        x += xOffset;
        y += yOffset;

        if (Rectangle::intersectRectangles (x, y, w, h, clipX, clipY, clipW, clipH))
        {
            if (replaceExistingContents)
                jGdipSetCompositingMode (graphics, Gdiplus::CompositingModeSourceCopy);

            jGdipSetClipRectI (graphics, clipX, clipY, clipW, clipH, Gdiplus::CombineModeReplace);

            Gdiplus::GpSolidFill* brush = 0;
            jGdipCreateSolidFill (colour.getARGB(), &brush);

            jGdipFillRectangleI (graphics, brush, x, y, w, h);

            jGdipDeleteBrush (brush);

            if (replaceExistingContents)
                jGdipSetCompositingMode (graphics, Gdiplus::CompositingModeSourceOver);
        }
    }

    void blendImage (const Image& sourceImage,
                     int dx, int dy,
                     int dw, int dh,
                     int sx, int sy,
                     float opacity)
    {
        jassert (sx >= 0 && sx + dw <= sourceImage.getWidth());
        jassert (sy >= 0 && sy + dh <= sourceImage.getHeight());

        dx += xOffset;
        dy += yOffset;

        jGdipSetClipRectI (graphics, clipX, clipY, clipW, clipH, Gdiplus::CombineModeReplace);

        if (opacity < 1.0f)
        {
            colourMatrix.m[3][3] = opacity;
            jGdipSetImageAttributesColorMatrix (imageAtts, Gdiplus::ColorAdjustTypeBitmap, TRUE,
                                                &colourMatrix, 0, Gdiplus::ColorMatrixFlagsDefault);
        }

        int stride, pixStride;
        const uint8* srcPixels = sourceImage.lockPixelDataReadOnly (sx, sy, dw, dh, stride, pixStride);

        Gdiplus::GpBitmap* bitmap = 0;
        jGdipCreateBitmapFromScan0 (dw, dh, stride,
                                    pixStride == 3 ? PixelFormat24bppRGB
                                                   : PixelFormat32bppPARGB,
                                    (BYTE*) srcPixels, &bitmap);

        jGdipDrawImageRectRectI (graphics, bitmap,
                                 dx, dy, dw, dh,
                                 0, 0, dw, dh,
                                 Gdiplus::UnitPixel, imageAtts, 0, 0);

        jGdipDisposeImage (bitmap);
        sourceImage.releasePixelDataReadOnly (srcPixels);
    }

    void setQuality (const Graphics::ResamplingQuality quality) const
    {
        Gdiplus::InterpolationMode mode = Gdiplus::InterpolationModeNearestNeighbor;

        if (quality == Graphics::mediumResamplingQuality)
            mode = Gdiplus::InterpolationModeBilinear;
        else if (quality == Graphics::highResamplingQuality)
            mode = Gdiplus::InterpolationModeBicubic;

        jGdipSetInterpolationMode (graphics, mode);
    }

    void blendImageRescaling (const Image& sourceImage,
                              int dx, int dy, int dw, int dh,
                              int sx, int sy, int sw, int sh,
                              float opacity,
                              const Graphics::ResamplingQuality quality)
    {
        jassert (sx >= 0 && sx + sw <= sourceImage.getWidth());
        jassert (sy >= 0 && sy + sh <= sourceImage.getHeight());

        setQuality (quality);
        jGdipSetClipRectI (graphics, clipX, clipY, clipW, clipH, Gdiplus::CombineModeReplace);

        dx += xOffset;
        dy += yOffset;

        if (sx + sw > sourceImage.getWidth())
            sw = sourceImage.getWidth() - sx;

        if (sy + sh > sourceImage.getHeight())
            sh = sourceImage.getHeight() - sy;

        if (sw <= 0 || sh <= 0)
            return;

        if (opacity < 1.0f)
        {
            colourMatrix.m[3][3] = opacity;
            jGdipSetImageAttributesColorMatrix (imageAtts, Gdiplus::ColorAdjustTypeBitmap, TRUE,
                                                &colourMatrix, 0, Gdiplus::ColorMatrixFlagsDefault);
        }

        int stride, pixStride;
        const uint8* srcPixels = sourceImage.lockPixelDataReadOnly (sx, sy, sw, sh, stride, pixStride);

        Gdiplus::GpBitmap* bitmap = 0;
        jGdipCreateBitmapFromScan0 (sw, sh, stride,
                                    pixStride == 3 ? PixelFormat24bppRGB
                                                   : PixelFormat32bppPARGB,
                                    (BYTE*) srcPixels, &bitmap);

        jGdipDrawImageRectRectI (graphics, bitmap,
                                 dx, dy, dw, dh,
                                 0, 0, sw, sh,
                                 Gdiplus::UnitPixel, imageAtts, 0, 0);

        jGdipDisposeImage (bitmap);
        sourceImage.releasePixelDataReadOnly (srcPixels);
    }

    void blendImageWarping (const Image& sourceImage, int sx, int sy, int sw, int sh,
                            const AffineTransform& transform,
                            float opacity, const Graphics::ResamplingQuality quality)
    {
        setQuality (quality);
        jGdipSetClipRectI (graphics, clipX, clipY, clipW, clipH, Gdiplus::CombineModeReplace);

        const AffineTransform t (transform.translated (xOffset, yOffset));

        Gdiplus::GpMatrix* matrix = 0;
        jGdipCreateMatrix2 (t.mat00, t.mat10, t.mat01, t.mat11, t.mat02, t.mat12, &matrix);
        jGdipSetWorldTransform (graphics, matrix);

        if (opacity < 1.0f)
        {
            colourMatrix.m[3][3] = opacity;
            jGdipSetImageAttributesColorMatrix (imageAtts, Gdiplus::ColorAdjustTypeBitmap, TRUE,
                                                &colourMatrix, 0,
                                                Gdiplus::ColorMatrixFlagsDefault);

        }

        int stride, pixStride;
        const uint8* srcPixels = sourceImage.lockPixelDataReadOnly (sx, sy, sw, sh, stride, pixStride);

        Gdiplus::GpBitmap* bitmap = 0;
        jGdipCreateBitmapFromScan0 (sw, sh, stride,
                                    pixStride == 3 ? PixelFormat24bppRGB
                                                   : PixelFormat32bppPARGB,
                                    (BYTE*) srcPixels, &bitmap);

        jGdipDrawImageRectRectI (graphics, bitmap,
                                 sx, sy, sw, sh,
                                 0, 0, sw, sh,
                                 Gdiplus::UnitPixel, imageAtts, 0, 0);

        jGdipDisposeImage (bitmap);
        sourceImage.releasePixelDataReadOnly (srcPixels);

        jGdipResetWorldTransform (graphics);
        jGdipDeleteMatrix (matrix);
    }

private:
    HDC hdc;
    Gdiplus::GpGraphics* graphics;
    Gdiplus::ColorMatrix colourMatrix;
    Gdiplus::GpImageAttributes* imageAtts;
};
#endif


//==============================================================================
long improbableWindowNumber = 0xf965aa01; // also referenced by messaging.cpp


//==============================================================================
static int currentModifiers = 0;

static void updateKeyModifiers()
{
    currentModifiers &= ~(ModifierKeys::shiftModifier
                          | ModifierKeys::ctrlModifier
                          | ModifierKeys::altModifier);

    if ((GetKeyState (VK_SHIFT) & 0x8000) != 0)
        currentModifiers |= ModifierKeys::shiftModifier;

    if ((GetKeyState (VK_CONTROL) & 0x8000) != 0)
        currentModifiers |= ModifierKeys::ctrlModifier;

    if ((GetKeyState (VK_MENU) & 0x8000) != 0)
        currentModifiers |= ModifierKeys::altModifier;
}

void ModifierKeys::updateCurrentModifiers()
{
    currentModifierFlags = currentModifiers;
}

bool KeyPress::isKeyCurrentlyDown (int keyCode)
{
    int k = keyCode & 0xffff;

    if ((keyCode & nonAsciiKeyFlag) == 0
         && (k >= T('a') && k <= T('z')))
        k += T('A') - T('a');

    return (GetKeyState (k) & 0x8000) != 0;
}

const ModifierKeys ModifierKeys::getCurrentModifiersRealtime()
{
    updateKeyModifiers();

    currentModifiers &= ~ModifierKeys::allMouseButtonModifiers;

    if ((GetKeyState (VK_LBUTTON) & 0x8000) != 0)
        currentModifiers |= ModifierKeys::leftButtonModifier;

    if ((GetKeyState (VK_RBUTTON) & 0x8000) != 0)
        currentModifiers |= ModifierKeys::rightButtonModifier;

    if ((GetKeyState (VK_MBUTTON) & 0x8000) != 0)
        currentModifiers |= ModifierKeys::middleButtonModifier;

    return ModifierKeys (currentModifiers);
}


//==============================================================================
class Win32ComponentPeer  : public ComponentPeer
{
public:
    //==============================================================================
    Win32ComponentPeer (Component* const component,
                        const int windowStyleFlags)
        : ComponentPeer (component, windowStyleFlags),
          dontRepaint (false),
          fullScreen (false),
          isDragging (false)
    {
        repainter = new Win32RepaintManager (this, component, 3000);

        juce_initialiseUnicodeWindowFunctions();

        MessageManager::getInstance()
           ->callFunctionOnMessageThread (&createWindowCallback, (void*) this);

        setTitle (component->getName());

        if ((windowStyleFlags & windowHasDropShadow) != 0
             && Desktop::canUseSemiTransparentWindows())
        {
            shadower = component->getLookAndFeel().createDropShadowerForComponent (component);

            if (shadower != 0)
                shadower->setOwner (component);
        }
        else
        {
            shadower = 0;
        }
    }

    ~Win32ComponentPeer()
    {
        deleteAndZero (shadower);

        MessageManager::getInstance()
            ->callFunctionOnMessageThread (&destroyWindowCallback, (void*) hwnd);

        delete repainter;
    }

    //==============================================================================
    void* getNativeHandle() const
    {
        return (void*) hwnd;
    }

    void setVisible (bool shouldBeVisible)
    {
        ShowWindow (hwnd, shouldBeVisible ? SW_SHOWNA : SW_HIDE);

        if (shouldBeVisible)
            InvalidateRect (hwnd, 0, 0);
        else
            lastPaintTime = 0;
    }

    void setTitle (const String& title)
    {
        if (wSetWindowTextW != 0)
            wSetWindowTextW (hwnd, title);
        else
            SetWindowText (hwnd, title);
    }

    void setPosition (int x, int y)
    {
        offsetWithinParent (x, y);
        SetWindowPos (hwnd, 0,
                      x - windowBorder.getLeft(),
                      y - windowBorder.getTop(),
                      0, 0,
                      SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOSENDCHANGING);
    }

    void repaintNowIfTransparent()
    {
        if (isTransparent() && lastPaintTime > 0 && Time::getMillisecondCounter() > lastPaintTime + 30)
            handlePaintMessage();
    }

    void updateBorderSize()
    {
        WINDOWINFO info;
        info.cbSize = sizeof (info);

        if (GetWindowInfo (hwnd, &info))
        {
            windowBorder = BorderSize (info.rcClient.top - info.rcWindow.top,
                                       info.rcClient.left - info.rcWindow.left,
                                       info.rcWindow.bottom - info.rcClient.bottom,
                                       info.rcWindow.right - info.rcClient.right);
        }
    }

    void setSize (int w, int h)
    {
        SetWindowPos (hwnd, 0, 0, 0,
                      w + windowBorder.getLeftAndRight(),
                      h + windowBorder.getTopAndBottom(),
                      SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOSENDCHANGING);

        updateBorderSize();

        repaintNowIfTransparent();
    }

    void setBounds (int x, int y, int w, int h, const bool isNowFullScreen)
    {
        fullScreen = isNowFullScreen;
        offsetWithinParent (x, y);

        SetWindowPos (hwnd, 0,
                      x - windowBorder.getLeft(),
                      y - windowBorder.getTop(),
                      w + windowBorder.getLeftAndRight(),
                      h + windowBorder.getTopAndBottom(),
                      SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOSENDCHANGING);

        updateBorderSize();

        repaintNowIfTransparent();
    }

    void getBounds (int& x, int& y, int& w, int& h) const
    {
        RECT r;
        GetWindowRect (hwnd, &r);

        x = r.left;
        y = r.top;
        w = r.right - x;
        h = r.bottom - y;

        HWND parentH = GetParent (hwnd);
        if (parentH != 0)
        {
            GetWindowRect (parentH, &r);
            x -= r.left;
            y -= r.top;
        }

        x += windowBorder.getLeft();
        y += windowBorder.getTop();
        w -= windowBorder.getLeftAndRight();
        h -= windowBorder.getTopAndBottom();
    }

    int getScreenX() const
    {
        RECT r;
        GetWindowRect (hwnd, &r);
        return r.left + windowBorder.getLeft();
    }

    int getScreenY() const
    {
        RECT r;
        GetWindowRect (hwnd, &r);
        return r.top + windowBorder.getTop();
    }

    void setMinimised (bool shouldBeMinimised)
    {
        if (shouldBeMinimised != isMinimised())
            ShowWindow (hwnd, shouldBeMinimised ? SW_MINIMIZE : SW_SHOWNORMAL);
    }

    bool isMinimised() const
    {
        WINDOWPLACEMENT wp;
        wp.length = sizeof (WINDOWPLACEMENT);
        GetWindowPlacement (hwnd, &wp);

        return wp.showCmd == SW_SHOWMINIMIZED;
    }

    void setFullScreen (bool shouldBeFullScreen)
    {
        setMinimised (false);

        if (fullScreen != shouldBeFullScreen)
        {
            fullScreen = shouldBeFullScreen;
            const ComponentDeletionWatcher deletionChecker (component);

            if (! fullScreen)
            {
                if (! lastNonFullscreenBounds.isEmpty())
                {
                    setBounds (lastNonFullscreenBounds.getX(),
                               lastNonFullscreenBounds.getY(),
                               lastNonFullscreenBounds.getWidth(),
                               lastNonFullscreenBounds.getHeight(),
                               false);
                }
            }
            else
            {
                if ((styleFlags & windowHasTitleBar) != 0)
                    ShowWindow (hwnd, SW_SHOWMAXIMIZED);
                else
                    SendMessage (hwnd, WM_SETTINGCHANGE, 0, 0);
            }

            if (! deletionChecker.hasBeenDeleted())
                handleMovedOrResized();
        }
    }

    bool isFullScreen() const
    {
        if ((styleFlags & windowHasTitleBar) == 0)
            return fullScreen;

        WINDOWPLACEMENT wp;
        wp.length = sizeof (wp);
        GetWindowPlacement (hwnd, &wp);

        return wp.showCmd == SW_SHOWMAXIMIZED;
    }

    bool contains (int x, int y, bool trueIfInAChildWindow) const
    {
        RECT r;
        GetWindowRect (hwnd, &r);

        POINT p;
        p.x = x + r.left;
        p.y = y + r.top;

        HWND w = WindowFromPoint (p);

        return w == hwnd || (trueIfInAChildWindow && (IsChild (hwnd, w) != 0));
    }

    const BorderSize getFrameSize() const
    {
        return windowBorder;
    }

    bool setAlwaysOnTop (bool alwaysOnTop)
    {
        const bool oldDeactivate = shouldDeactivateTitleBar;
        shouldDeactivateTitleBar = ((styleFlags & windowIsTemporary) == 0);

        SetWindowPos (hwnd, alwaysOnTop ? HWND_TOPMOST : HWND_NOTOPMOST,
                      0, 0, 0, 0,
                      SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOSENDCHANGING);

        shouldDeactivateTitleBar = oldDeactivate;

        if (shadower != 0)
            shadower->componentBroughtToFront (*component);

        return true;
    }

    void toFront (bool makeActive)
    {
        setMinimised (false);

        const bool oldDeactivate = shouldDeactivateTitleBar;
        shouldDeactivateTitleBar = ((styleFlags & windowIsTemporary) == 0);

        MessageManager::getInstance()
            ->callFunctionOnMessageThread (makeActive ? &toFrontCallback1
                                                      : &toFrontCallback2,
                                           (void*) hwnd);

        shouldDeactivateTitleBar = oldDeactivate;

        if (! makeActive)
        {
            // in this case a broughttofront call won't have occured, so do it now..
            handleBroughtToFront();
        }
    }

    void toBehind (ComponentPeer* other)
    {
        Win32ComponentPeer* const otherPeer = dynamic_cast <Win32ComponentPeer*> (other);

        jassert (otherPeer != 0); // wrong type of window?

        if (otherPeer != 0)
        {
            setMinimised (false);

            SetWindowPos (hwnd, otherPeer->hwnd, 0, 0, 0, 0,
                          SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOSENDCHANGING);
        }
    }

    bool isFocused() const
    {
        return MessageManager::getInstance()
                 ->callFunctionOnMessageThread (&getFocusCallback, 0) == (void*) hwnd;
    }

    void grabFocus()
    {
        const bool oldDeactivate = shouldDeactivateTitleBar;
        shouldDeactivateTitleBar = ((styleFlags & windowIsTemporary) == 0);

        MessageManager::getInstance()
            ->callFunctionOnMessageThread (&setFocusCallback, (void*) hwnd);

        shouldDeactivateTitleBar = oldDeactivate;
    }

    void repaint (int x, int y, int w, int h)
    {
        const RECT r = { x, y, x + w, y + h };
        InvalidateRect (hwnd, &r, FALSE);

        repainter->invalidateCache (x, y, w, h);
    }

    //==============================================================================
    static Win32ComponentPeer* getOwnerOfWindow (HWND h)
    {
        if (h != 0 && GetWindowLongPtr (h, GWLP_USERDATA) == improbableWindowNumber)
            return (Win32ComponentPeer*) GetWindowLongPtr (h, 8);

        return 0;
    }

    //==============================================================================
    juce_UseDebuggingNewOperator

    bool dontRepaint;

private:
    friend class Win32RepaintManager;
    HWND hwnd;
    DropShadower* shadower;
    bool fullScreen, isDragging;
    BorderSize windowBorder;

    //==============================================================================
    class Win32RepaintManager : public RepaintManager
    {
        Win32ComponentPeer* const peer;

        Win32RepaintManager (const Win32RepaintManager&);
        const Win32RepaintManager& operator= (const Win32RepaintManager&);

    public:

        Win32RepaintManager (Win32ComponentPeer* const peer_,
                             Component* const component,
                             const int timeBeforeReleasingImage)
            : RepaintManager (component, timeBeforeReleasingImage),
              peer (peer_)
        {
        }

        Image* createNewImage (int w, int h)
        {
            return new WindowsBitmapImage (peer->getComponent()->isOpaque() ? Image::RGB : Image::ARGB,
                                           w, h, false);
        }

        /*void repaintNow (const RectangleList& areasToPaint)
        {
            HDC dc = GetWindowDC (peer->hwnd);

            SetMapMode (dc, MM_TEXT);

            int x, y;
            WindowsBitmapImage* paintingBuffer = (WindowsBitmapImage*) getImage (x, y);

            if (paintingBuffer != 0 && ! peer->dontRepaint)
            {
                RectangleList mask (Rectangle (0, 0, component->getWidth(), component->getHeight()));
                mask.subtract (areasToPaint);

                paintingBuffer->blitToWindow (peer->hwnd, dc, peer->isTransparent(), x, y, mask);
            }

            ReleaseDC (peer->hwnd, dc);
            peer->lastPaintTime = Time::getMillisecondCounter();
        }*/
    };

    Win32RepaintManager* repainter;

    //==============================================================================
    static void* createWindowCallback (void* userData)
    {
        ((Win32ComponentPeer*) userData)->createWindow();
        return 0;
    }

    void createWindow()
    {
        static int windowClassNameNum = 0;

        const bool needToCreateWindowClass = (windowClassNameNum == 0);

        if (windowClassNameNum == 0)
            windowClassNameNum = (int) (Time::currentTimeMillis() & 0x7fffffff);

        // this name has to be different for each app/dll instance because otherwise
        // poor old Win32 can get a bit confused (even despite it not being a process-global
        // window class).
        String windowClassName (T("JUCE_"));
        windowClassName << windowClassNameNum;

        if (needToCreateWindowClass)
        {
            HINSTANCE moduleHandle = (HINSTANCE) PlatformUtilities::getCurrentModuleInstanceHandle();

            TCHAR moduleFile [1024];
            moduleFile[0] = 0;
            GetModuleFileName (moduleHandle, moduleFile, 1024);
            WORD iconNum = 0;

            WNDCLASSEX wcex;
            wcex.cbSize         = sizeof (wcex);
            wcex.style          = CS_OWNDC;
            wcex.lpfnWndProc    = (WNDPROC) windowProc;
            wcex.lpszClassName  = windowClassName;
            wcex.cbClsExtra     = 0;
            wcex.cbWndExtra     = 32;
            wcex.hInstance      = moduleHandle;
            wcex.hIcon          = ExtractAssociatedIcon (moduleHandle, moduleFile, &iconNum);
            iconNum = 1;
            wcex.hIconSm        = ExtractAssociatedIcon (moduleHandle, moduleFile, &iconNum);
            wcex.hCursor        = 0;
            wcex.hbrBackground  = 0;
            wcex.lpszMenuName   = 0;

            RegisterClassEx (&wcex);
        }

        DWORD exstyle = WS_EX_NOPARENTNOTIFY | WS_EX_ACCEPTFILES;
        DWORD type = WS_CLIPSIBLINGS | WS_CLIPCHILDREN;

        if ((styleFlags & windowHasTitleBar) != 0)
        {
            type |= WS_OVERLAPPED;
            exstyle |= WS_EX_APPWINDOW;

            if ((styleFlags & windowHasCloseButton) != 0)
            {
                type |= WS_SYSMENU;
            }
            else
            {
                // annoyingly, windows won't let you have a min/max button without a close button
                jassert ((styleFlags & (windowHasMinimiseButton | windowHasMaximiseButton)) == 0);
            }

            if ((styleFlags & windowHasMinimiseButton) != 0)
                type |= WS_MINIMIZEBOX;

            if ((styleFlags & windowHasMaximiseButton) != 0)
                type |= WS_MAXIMIZEBOX;

            if ((styleFlags & windowIsResizable) != 0)
                type |= WS_THICKFRAME;
        }
        else
        {
            type |= WS_MINIMIZEBOX | WS_POPUP | WS_SYSMENU;

            if ((styleFlags & windowAppearsOnTaskbar) == 0)
                exstyle |= WS_EX_TOOLWINDOW;
            else
                exstyle |= WS_EX_APPWINDOW;
        }

        if ((styleFlags & windowIgnoresMouseClicks) != 0)
            exstyle |= WS_EX_TRANSPARENT;

        if ((styleFlags & juce_windowIsSemiTransparentFlag) != 0
              && Desktop::canUseSemiTransparentWindows())
            exstyle |= WS_EX_LAYERED;

        hwnd = CreateWindowEx (exstyle,
                               windowClassName,
                               _T(""),
                               type,
                               0, 0, 0, 0,
                               0, 0, 0, 0);

        if (hwnd != 0)
        {
            SetWindowLongPtr (hwnd, 0, 0);
            SetWindowLongPtr (hwnd, 8, (LONG_PTR) this);
            SetWindowLongPtr (hwnd, GWLP_USERDATA, improbableWindowNumber);

            DragAcceptFiles (hwnd, TRUE);

            updateBorderSize();
        }
        else
        {
            jassertfalse
        }
    }

    static void* destroyWindowCallback (void* handle)
    {
        SetWindowLongPtr ((HWND) handle, GWLP_USERDATA, 0);
        DestroyWindow ((HWND) handle);
        return 0;
    }

    static void* toFrontCallback1 (void* h)
    {
        SetForegroundWindow ((HWND) h);
        return 0;
    }

    static void* toFrontCallback2 (void* h)
    {
        SetWindowPos ((HWND) h, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOSENDCHANGING);
        return 0;
    }

    static void* setFocusCallback (void* h)
    {
        SetFocus ((HWND) h);
        return 0;
    }

    static void* getFocusCallback (void*)
    {
        return (void*) GetFocus();
    }

    void offsetWithinParent (int& x, int& y) const
    {
        if (isTransparent())
        {
            HWND parentHwnd = GetParent (hwnd);

            if (parentHwnd != 0)
            {
                RECT parentRect;
                GetWindowRect (parentHwnd, &parentRect);
                x += parentRect.left;
                y += parentRect.top;
            }
        }
    }

    bool isTransparent() const
    {
        return (GetWindowLong (hwnd, GWL_EXSTYLE) & WS_EX_LAYERED) != 0;
    }

    //==============================================================================
    void handlePaintMessage()
    {
#if DEBUG_REPAINT_TIMES
        const double paintStart = Time::getMillisecondCounterHiRes();
#endif
        HRGN rgn = CreateRectRgn (0, 0, 0, 0);
        const int regionType = GetUpdateRgn (hwnd, rgn, false);

        PAINTSTRUCT paintStruct;
        HDC dc = BeginPaint (hwnd, &paintStruct); // Note this can immediately generate a WM_NCPAINT
                                                  // message and become re-entrant, but that's OK

        // if something in a paint handler calls, e.g. a message box, this can become reentrant and
        // corrupt the image it's using to paint into, so do a check here.
        static bool reentrant = false;
        if (reentrant)
        {
            DeleteObject (rgn);
            EndPaint (hwnd, &paintStruct);
            return;
        }

        reentrant = true;

        const bool transparent = isTransparent();

        // this is the rectangle to update..
        int x = paintStruct.rcPaint.left;
        int y = paintStruct.rcPaint.top;
        int w = paintStruct.rcPaint.right - x;
        int h = paintStruct.rcPaint.bottom - y;

        if (transparent)
        {
            RECT r;
            GetWindowRect (hwnd, &r);
            x = y = 0;
            w = r.right - r.left;
            h = r.bottom - r.top;
        }

        if (w > 0 && h > 0)
        {
            clearMaskedRegion();

            bool needToPaintAll = true;

            if (regionType == COMPLEXREGION && ! transparent)
            {
                HRGN clipRgn = CreateRectRgnIndirect (&paintStruct.rcPaint);
                CombineRgn (rgn, rgn, clipRgn, RGN_AND);
                DeleteObject (clipRgn);

                char rgnData [8192];
                const DWORD res = GetRegionData (rgn, sizeof (rgnData), (RGNDATA*) rgnData);

                if (res > 0 && res <= sizeof (rgnData))
                {
                    const RGNDATAHEADER* const hdr = &(((const RGNDATA*) rgnData)->rdh);

                    if (hdr->iType == RDH_RECTANGLES
                         && hdr->rcBound.right - hdr->rcBound.left >= w
                         && hdr->rcBound.bottom - hdr->rcBound.top >= h)
                    {
                        needToPaintAll = false;

                        const RECT* rects = (const RECT*) (rgnData + sizeof (RGNDATAHEADER));
                        int num = ((RGNDATA*) rgnData)->rdh.nCount;

                        while (--num >= 0)
                        {
                            // (need to move this one pixel to the left because of a win32 bug)
                            const int cx = jmax (x, rects->left - 1);
                            const int cy = rects->top;
                            const int cw = rects->right - cx;
                            const int ch = rects->bottom - rects->top;

                            if (cx + cw - x <= w && cy + ch - y <= h)
                            {
                                repainter->repaint (cx, cy, cw, ch);
                            }
                            else
                            {
                                needToPaintAll = true;
                                break;
                            }

                            ++rects;
                        }
                    }
                }
            }

            if (needToPaintAll)
                repainter->repaint (x, y, w, h);

            // if the component's not opaque, this won't draw properly unless the platform can support this
            jassert (Desktop::canUseSemiTransparentWindows() || component->isOpaque());

            updateCurrentModifiers();

            repainter->renderCacheAreasNeedingRepaint();
            repainter->clearPendingRepaints();

            WindowsBitmapImage* const paintingBuffer
                = (WindowsBitmapImage*) repainter->getImage (x, y);

            if (paintingBuffer != 0 && ! dontRepaint)
                paintingBuffer->blitToWindow (hwnd, dc, transparent, x, y, maskedRegion);
        }

        DeleteObject (rgn);
        EndPaint (hwnd, &paintStruct);
        reentrant = false;

#ifndef __GNUC__ //xxx should add this fn for gcc..
        _fpreset(); // because some graphics cards can unmask FP exceptions
#endif

        lastPaintTime = Time::getMillisecondCounter();

#if DEBUG_REPAINT_TIMES
        const double elapsed = Time::getMillisecondCounterHiRes() - paintStart;
        Logger::outputDebugString (T("repaint time: ") + String (elapsed, 2));
#endif
    }

    static int64 getMouseEventTime()
    {
        static int64 eventTimeOffset = 0;
        static DWORD lastMessageTime = 0;
        const DWORD thisMessageTime = GetMessageTime();

        if (thisMessageTime < lastMessageTime || lastMessageTime == 0)
        {
            lastMessageTime = thisMessageTime;
            eventTimeOffset = Time::currentTimeMillis() - thisMessageTime;
        }

        return eventTimeOffset + thisMessageTime;
    }

    void doMouseMove (const int x, const int y)
    {
        static uint32 lastMouseTime = 0;
        // this can be set to throttle the mouse-messages to less than a
        // certain number per second, as things can get unresponsive
        // if each drag or move callback has to do a lot of work.
        const int maxMouseMovesPerSecond = 60;

        // grab the mouse..
        if (GetCapture() != hwnd)
        {
            SetCapture (hwnd);

            updateKeyModifiers();
            handleMouseEnter (x, y, getMouseEventTime());
        }
        else if (! isDragging)
        {
            if (x < 0 || y < 0
                || x >= getComponent()->getWidth()
                || y >= getComponent() ->getHeight())
            {
                ReleaseCapture();
            }
            else
            {
                RECT r;
                GetWindowRect (hwnd, &r);

                POINT p;
                p.x = x + r.left + windowBorder.getLeft();
                p.y = y + r.top + windowBorder.getTop();

                if (WindowFromPoint (p) == hwnd)
                {
                    const uint32 now = Time::getMillisecondCounter();

                    if (now > lastMouseTime + 1000 / maxMouseMovesPerSecond)
                    {
                        lastMouseTime = now;
                        handleMouseMove (x, y, getMouseEventTime());
                    }
                }
                else
                {
                    ReleaseCapture();
                }
            }
        }
        else
        {
            const uint32 now = Time::getMillisecondCounter();

            if (now > lastMouseTime + 1000 / maxMouseMovesPerSecond)
            {
                lastMouseTime = now;
                handleMouseDrag (x, y, getMouseEventTime());
            }
        }
    }

    void doMouseDown (const int x, const int y, const WPARAM wParam)
    {
        doMouseMove (x, y);

        currentModifiers &= ~ModifierKeys::allMouseButtonModifiers;

        if ((wParam & MK_LBUTTON) != 0)
            currentModifiers |= ModifierKeys::leftButtonModifier;

        if ((wParam & MK_RBUTTON) != 0)
            currentModifiers |= ModifierKeys::rightButtonModifier;

        if ((wParam & MK_MBUTTON) != 0)
            currentModifiers |= ModifierKeys::middleButtonModifier;

        updateKeyModifiers();
        isDragging = true;

        handleMouseDown (x, y, getMouseEventTime());
    }

    void doMouseUp (const int x, const int y, const WPARAM wParam)
    {
        int numButtons = 0;

        if ((wParam & MK_LBUTTON) != 0)
            ++numButtons;

        if ((wParam & MK_RBUTTON) != 0)
            ++numButtons;

        if ((wParam & MK_MBUTTON) != 0)
            ++numButtons;

        const int oldModifiers = currentModifiers;

        // update the currentmodifiers only after the callback, so the callback
        // knows which button was released.
        currentModifiers &= ~ModifierKeys::allMouseButtonModifiers;

        if ((wParam & MK_LBUTTON) != 0)
            currentModifiers |= ModifierKeys::leftButtonModifier;

        if ((wParam & MK_RBUTTON) != 0)
            currentModifiers |= ModifierKeys::rightButtonModifier;

        if ((wParam & MK_MBUTTON) != 0)
            currentModifiers |= ModifierKeys::middleButtonModifier;

        updateKeyModifiers();
        isDragging = false;

        // release the mouse capture if the user's not still got a button down
        if (numButtons == 0 && ! contains (x, y, false))
            ReleaseCapture();
        else
            handleMouseUp (oldModifiers, x, y, getMouseEventTime());
    }

    void doCaptureChanged()
    {
        RECT wr;
        GetWindowRect (hwnd, &wr);

        currentModifiers &= ~ModifierKeys::allMouseButtonModifiers;
        isDragging = false;

        const DWORD mp = GetMessagePos();

        handleMouseExit (GET_X_LPARAM (mp) - wr.left,
                         GET_Y_LPARAM (mp) - wr.top,
                         getMouseEventTime());
    }

    void doMouseWheel (const WPARAM wParam)
    {
        updateKeyModifiers();
        handleMouseWheel (jlimit (-1000, 1000, (int) (0.75f * (short) HIWORD (wParam))),
                          getMouseEventTime());
    }

    //==============================================================================
    void doKeyUp (const WPARAM key)
    {
        updateKeyModifiers();

        switch (key)
        {
            case VK_SHIFT:
            case VK_CONTROL:
            case VK_MENU:
            case VK_CAPITAL:
            case VK_LWIN:
            case VK_RWIN:
            case VK_APPS:
            case VK_NUMLOCK:
            case VK_SCROLL:
            case VK_LSHIFT:
            case VK_RSHIFT:
            case VK_LCONTROL:
            case VK_LMENU:
            case VK_RCONTROL:
            case VK_RMENU:
                handleModifierKeysChange();
        }

        handleKeyUpOrDown();
    }

    void doKeyDown (const WPARAM key)
    {
        updateKeyModifiers();

        switch (key)
        {
            case VK_SHIFT:
            case VK_LSHIFT:
            case VK_RSHIFT:
            case VK_CONTROL:
            case VK_LCONTROL:
            case VK_RCONTROL:
            case VK_MENU:
            case VK_LMENU:
            case VK_RMENU:
            case VK_LWIN:
            case VK_RWIN:
            case VK_CAPITAL:
            case VK_NUMLOCK:
            case VK_SCROLL:
            case VK_APPS:
                handleModifierKeysChange();
                break;

            case VK_LEFT:
            case VK_RIGHT:
            case VK_UP:
            case VK_DOWN:
            case VK_PRIOR:
            case VK_NEXT:
            case VK_HOME:
            case VK_END:
            case VK_DELETE:
            case VK_INSERT:
            case VK_F1:
            case VK_F2:
            case VK_F3:
            case VK_F4:
            case VK_F5:
            case VK_F6:
            case VK_F7:
            case VK_F8:
            case VK_F9:
            case VK_F10:
            case VK_F11:
            case VK_F12:
                handleKeyUpOrDown();
                handleKeyPress (nonAsciiKeyFlag | (int) key);
                break;

            default:
                handleKeyUpOrDown();

                if ((currentModifiers & (ModifierKeys::ctrlModifier | ModifierKeys::altModifier)) != 0)
                    handleKeyPress ((int) LOWORD (MapVirtualKey (key, 2)));

                break;
        }
    }

    bool doAppCommand (const LPARAM lParam)
    {
        int key = 0;

        switch (GET_APPCOMMAND_LPARAM (lParam))
        {
        case APPCOMMAND_MEDIA_PLAY_PAUSE:
            key = KeyPress::playKey;
            break;

        case APPCOMMAND_MEDIA_STOP:
            key = KeyPress::stopKey;
            break;

        case APPCOMMAND_MEDIA_NEXTTRACK:
            key = KeyPress::fastForwardKey;
            break;

        case APPCOMMAND_MEDIA_PREVIOUSTRACK:
            key = KeyPress::rewindKey;
            break;
        }

        if (key != 0)
        {
            updateKeyModifiers();

            if (hwnd == GetActiveWindow())
            {
                handleKeyPress (key);
                return true;
            }
        }

        return false;
    }

    void doKeyChar (const WPARAM key)
    {
        const bool isAltGr = (currentModifiers & (ModifierKeys::ctrlModifier | ModifierKeys::altModifier))
                                == (ModifierKeys::ctrlModifier | ModifierKeys::altModifier);

        if ((currentModifiers & (ModifierKeys::ctrlModifier | ModifierKeys::altModifier)) == 0
             || isAltGr
             || ! String::isLetterOrDigit ((juce_wchar) key))
        {
            // if alt + ctrl are both down, this seems to indicate "alt gr", so we have
            // to temporarily disable these modifiers to make sure the characters typed
            // will be treated as normal keystrokes.
            if (isAltGr)
            {
                currentModifiers &= ~(ModifierKeys::ctrlModifier | ModifierKeys::altModifier);
            }
            else if ((currentModifiers & ModifierKeys::ctrlModifier) != 0
                      && key <= 26)
            {
                return;
            }

            handleKeyPress ((int) key);

            updateKeyModifiers();
        }
    }

    //==============================================================================
    void doDroppedFiles (HDROP hdrop)
    {
        POINT p;
        DragQueryPoint (hdrop, &p);

        const int numFiles = DragQueryFile (hdrop, 0xffffffff, 0, 0);
        StringArray files;

        for (int i = 0; i < numFiles; ++i)
        {
            const int size = sizeof (TCHAR) * MAX_PATH * 2 + 8;
            TCHAR* name = (TCHAR*) juce_calloc (size);

            DragQueryFile (hdrop, i, name, MAX_PATH);
            files.add (name);

            juce_free (name);
        }

        DragFinish (hdrop);

        handleFilesDropped (p.x, p.y, files);
    }

    void doSettingChange()
    {
        Desktop::getInstance().refreshMonitorSizes();

        if (fullScreen && ! isMinimised())
        {
            const Rectangle r (component->getParentMonitorArea());

            SetWindowPos (hwnd, 0,
                          r.getX(), r.getY(), r.getWidth(), r.getHeight(),
                          SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_NOSENDCHANGING);
        }
    }

    //==============================================================================
    static LRESULT CALLBACK windowProc (HWND h, UINT message, WPARAM wParam, LPARAM lParam)
    {
        Win32ComponentPeer* const peer = getOwnerOfWindow (h);

        if (peer != 0)
            return peer->peerWindowProc (h, message, wParam, lParam);

        return DefWindowProc (h, message, wParam, lParam);
    }

    LRESULT peerWindowProc (HWND h, UINT message, WPARAM wParam, LPARAM lParam)
    {
#if JUCE_QUICKTIME
        OfferMessageToQuickTime (h, message, wParam, lParam, getComponent());
#endif

        switch (message)
        {
            case WM_NCHITTEST:
                if ((styleFlags & windowHasTitleBar) != 0)
                    break;

                return HTCLIENT;

            //==============================================================================
            case WM_PAINT:
                handlePaintMessage();
                return 0;

            case WM_NCPAINT:
                if ((styleFlags & windowHasTitleBar) != 0)
                    break;

                if (wParam != 1)
                    handlePaintMessage();

                return 0;

            case WM_ERASEBKGND:
            case WM_NCCALCSIZE:
                if ((styleFlags & windowHasTitleBar) != 0)
                    break;

                return 1;

            //==============================================================================
            case WM_MOUSEMOVE:
                doMouseMove (GET_X_LPARAM (lParam), GET_Y_LPARAM (lParam));
                return 0;

            case WM_LBUTTONDOWN:
            case WM_MBUTTONDOWN:
            case WM_RBUTTONDOWN:
                doMouseDown (GET_X_LPARAM (lParam), GET_Y_LPARAM (lParam), wParam);
                return 0;

            case WM_LBUTTONUP:
            case WM_MBUTTONUP:
            case WM_RBUTTONUP:
                doMouseUp (GET_X_LPARAM (lParam), GET_Y_LPARAM (lParam), wParam);
                return 0;

            case WM_CAPTURECHANGED:
                doCaptureChanged();
                return 0;

            case WM_NCMOUSEMOVE:
                if ((styleFlags & windowHasTitleBar) != 0)
                    return DefWindowProc (h, message, TRUE, lParam);

                return 0;

            case 0x020A: // mouse-wheel
                doMouseWheel (wParam);
                return 0;

            //==============================================================================
            case WM_WINDOWPOSCHANGING:
                if ((styleFlags & (windowHasTitleBar | windowIsResizable)) == (windowHasTitleBar | windowIsResizable))
                {
                    WINDOWPOS* const wp = (WINDOWPOS*) lParam;

                    if ((wp->flags & (SWP_NOMOVE | SWP_NOSIZE)) != (SWP_NOMOVE | SWP_NOSIZE))
                    {
                        if (constrainer != 0)
                        {
                            const Rectangle current (getComponent()->getX() - windowBorder.getLeft(),
                                                     getComponent()->getY() - windowBorder.getTop(),
                                                     getComponent()->getWidth() + windowBorder.getLeftAndRight(),
                                                     getComponent()->getHeight() + windowBorder.getTopAndBottom());

                            constrainer->checkBounds (wp->x, wp->y, wp->cx, wp->cy,
                                                      current,
                                                      Desktop::getInstance().getAllMonitorDisplayAreas().getBounds(),
                                                      wp->y != current.getY() && wp->y + wp->cy == current.getBottom(),
                                                      wp->x != current.getX() && wp->x + wp->cx == current.getRight(),
                                                      wp->y == current.getY() && wp->y + wp->cy != current.getBottom(),
                                                      wp->x == current.getX() && wp->x + wp->cx != current.getRight());
                        }
                    }
                }

                return 0;

            case WM_WINDOWPOSCHANGED:
                handleMovedOrResized();
                return 0;

            //==============================================================================
            case WM_KEYDOWN:
            case WM_SYSKEYDOWN:
                doKeyDown (wParam);
                break;

            case WM_KEYUP:
            case WM_SYSKEYUP:
                doKeyUp (wParam);
                return 0;

            case WM_APPCOMMAND:
                if (doAppCommand (lParam))
                    return TRUE;
                else
                    break;

            case WM_CHAR:
                updateKeyModifiers();
                doKeyChar (wParam);
                break;

            //==============================================================================
            case WM_SETFOCUS:
                updateKeyModifiers();
                handleFocusGain();
                break;

            case WM_KILLFOCUS:
                handleFocusLoss();
                break;

            case WM_ACTIVATEAPP:
                // Windows does weird things to process priority when you swap apps,
                // so this forces an update when the app is brought to the front
                if (wParam != FALSE)
                    repeatLastProcessPriority();

                return 0;

            case WM_ACTIVATE:
                if (LOWORD (wParam) == WA_ACTIVE || LOWORD (wParam) == WA_CLICKACTIVE)
                {
                    updateKeyModifiers();

                    if (isMinimised())
                    {
                        getComponent()->repaint();
                        handleMovedOrResized();

                        if (! isValidMessageListener())
                            return 0;
                    }

                    handleBroughtToFront();
                    return 0;
                }

                break;

            case WM_NCACTIVATE:
                // while a temporary window is being shown, prevent Windows from deactivating the
                // title bars of our main windows.
                if (wParam == 0 && ! shouldDeactivateTitleBar)
                    return DefWindowProc (h, message, TRUE, lParam);

                break;

            case WM_SHOWWINDOW:
                if (wParam != 0)
                    handleBroughtToFront();

                break;

            case WM_CLOSE:
                handleUserClosingWindow();
                return 0;

            //==============================================================================
            case WM_DROPFILES:
                doDroppedFiles ((HDROP) wParam);
                break;

            //==============================================================================
            case WM_SYNCPAINT:
                return 0;

            case WM_PALETTECHANGED:
                InvalidateRect (h, 0, 0);
                break;

            case WM_DISPLAYCHANGE:
                InvalidateRect (h, 0, 0);
                createPaletteIfNeeded = true;
                handleScreenSizeChange();
                // intentional fall-through...
            case WM_SETTINGCHANGE:  // note the fall-through in the previous case!
                doSettingChange();
                break;

            case WM_NCLBUTTONDOWN:
            case WM_NCRBUTTONDOWN:
            case WM_NCMBUTTONDOWN:
                if (getComponent()->isCurrentlyBlockedByAnotherModalComponent())
                {
                    Component* const current = Component::getCurrentlyModalComponent();

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

                break;

            default:
                break;
        }

        return DefWindowProc (h, message, wParam, lParam);
    }

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


ComponentPeer* Component::createNewPeer (int styleFlags, void* /*nativeWindowToAttachTo*/)
{
    return new Win32ComponentPeer (this, styleFlags);
}


//==============================================================================
void juce_setWindowStyleBit (HWND h, int styleType, int feature, bool bitIsSet)
{
    DWORD val = GetWindowLong (h, styleType);

    if (bitIsSet)
        val |= feature;
    else
        val &= ~feature;

    SetWindowLongPtr (h, styleType, val);
    SetWindowPos (h, 0, 0, 0, 0, 0,
                  SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER
                   | SWP_NOOWNERZORDER | SWP_FRAMECHANGED | SWP_NOSENDCHANGING);
}


//==============================================================================
bool Process::isForegroundProcess()
{
    HWND fg = GetForegroundWindow();

    if (fg == 0)
        return true;

    DWORD processId = 0;
    GetWindowThreadProcessId (fg, &processId);

    return processId == GetCurrentProcessId();
}

//==============================================================================
void Desktop::getMousePosition (int& x, int& y)
{
    POINT mousePos;
    GetCursorPos (&mousePos);
    x = mousePos.x;
    y = mousePos.y;
}

void Desktop::setMousePosition (int x, int y)
{
    SetCursorPos (x, y);
}

//==============================================================================
BOOL CALLBACK enumMonitorsProc (HMONITOR, HDC, LPRECT r, LPARAM userInfo)
{
    Array <Rectangle>* const monitorCoords = (Array <Rectangle>*) userInfo;

    monitorCoords->add (Rectangle (r->left, r->top, r->right - r->left, r->bottom - r->top));

    return TRUE;
}

void juce_updateMultiMonitorInfo (Array <Rectangle>& monitorCoords, const bool clipToWorkArea)
{
    DynamicLibraryLoader user32Dll ("user32.dll");
    DynamicLibraryImport (EnumDisplayMonitors, enumDisplayMonitors, BOOL, user32Dll,
                          (HDC hdc, LPCRECT lprcClip, MONITORENUMPROC, LPARAM))

    if (enumDisplayMonitors != 0)
        enumDisplayMonitors (0, 0, &enumMonitorsProc, (LPARAM) &monitorCoords);

    // make sure the first in the list is the main monitor
    for (int i = 1; i < monitorCoords.size(); ++i)
        if (monitorCoords[i].getX() == 0 && monitorCoords[i].getY() == 0)
            monitorCoords.swap (i, 0);

    if (monitorCoords.size() == 0)
    {
        RECT r;
        GetWindowRect (GetDesktopWindow(), &r);

        monitorCoords.add (Rectangle (r.left, r.top, r.right - r.left, r.bottom - r.top));
    }

    if (clipToWorkArea)
    {
        // clip the main monitor to the active non-taskbar area
        RECT r;
        SystemParametersInfo (SPI_GETWORKAREA, 0, &r, 0);

        Rectangle& screen = monitorCoords.getReference (0);
        screen.setBounds (jmax (screen.getX(), r.left),
                          jmax (screen.getY(), r.top),
                          jmin (screen.getRight(), r.right) - screen.getX(),
                          jmin (screen.getBottom(), r.bottom) - screen.getY());
    }
}


//==============================================================================
void* juce_createMouseCursorFromImage (const Image& image, int hotspotX, int hotspotY)
{
    const int maxW = GetSystemMetrics (SM_CXCURSOR);
    const int maxH = GetSystemMetrics (SM_CYCURSOR);

    const Image* im = &image;
    Image* newIm = 0;

    if (image.getWidth() > maxW || image.getHeight() > maxH)
    {
        im = newIm = image.createCopy (maxW, maxH);

        hotspotX = (hotspotX * maxW) / image.getWidth();
        hotspotY = (hotspotY * maxH) / image.getHeight();
    }

    const int stride = (maxW + 7) >> 3;
    uint8* const andPlane = (uint8*) juce_calloc (stride * maxH);
    uint8* const xorPlane = (uint8*) juce_calloc (stride * maxH);
    int index = 0;

    for (int y = 0; y < maxH; ++y)
    {
        for (int x = 0; x < maxW; ++x)
        {
            const unsigned char bit = (unsigned char)(1 << (7 - (x & 7)));

            const Colour pixelColour (im->getPixelAt (x, y));

            if (pixelColour.getAlpha() < 127)
            {
                andPlane [index + (x >> 3)] |= bit;
            }
            else if (pixelColour.getBrightness() >= 0.5f)
            {
                xorPlane [index + (x >> 3)] |= bit;
            }
        }

        index += stride;
    }

    if (newIm != 0)
        delete newIm;

    void* const cursorH = CreateCursor (0, hotspotX, hotspotY, maxW, maxH, andPlane, xorPlane);

    juce_free (andPlane);
    juce_free (xorPlane);

    return cursorH;
}

void juce_deleteMouseCursor (void* cursorHandle, bool isStandard)
{
    if (! isStandard && cursorHandle != 0)
        DestroyCursor ((HCURSOR) cursorHandle);
}

void* juce_createStandardMouseCursor (MouseCursor::StandardCursorType type)
{
    LPCTSTR cursorName = IDC_ARROW;

    switch (type)
    {
    case MouseCursor::NormalCursor:
        cursorName = IDC_ARROW;
        break;

    case MouseCursor::NoCursor:
        return 0;

    case MouseCursor::DraggingHandCursor:
    {
        static void* dragHandCursor = 0;

        if (dragHandCursor == 0)
        {
            const unsigned char dragHandData[] =
                { 71,73,70,56,57,97,16,0,16,0,145,2,0,0,0,0,255,255,255,0,0,0,0,0,0,33,249,4,1,0,0,2,0,44,0,0,0,0,16,0,
                  16,0,0,2,52,148,47,0,200,185,16,130,90,12,74,139,107,84,123,39, 132,117,151,116,132,146,248,60,209,138,
                  98,22,203,114,34,236,37,52,77,217,247,154,191,119,110,240,193,128,193,95,163,56,60,234,98,135,2,0,59 };

            Image* const image = ImageFileFormat::loadFrom ((const char*) dragHandData, sizeof (dragHandData));
            dragHandCursor = juce_createMouseCursorFromImage (*image, 8, 7);
            delete image;
        }

        return dragHandCursor;
    }

    case MouseCursor::WaitCursor:
        cursorName = IDC_WAIT;
        break;

    case MouseCursor::IBeamCursor:
        cursorName = IDC_IBEAM;
        break;

    case MouseCursor::PointingHandCursor:
        cursorName = MAKEINTRESOURCE(32649);
        break;

    case MouseCursor::LeftRightResizeCursor:
    case MouseCursor::LeftEdgeResizeCursor:
    case MouseCursor::RightEdgeResizeCursor:
        cursorName = IDC_SIZEWE;
        break;

    case MouseCursor::UpDownResizeCursor:
    case MouseCursor::TopEdgeResizeCursor:
    case MouseCursor::BottomEdgeResizeCursor:
        cursorName = IDC_SIZENS;
        break;

    case MouseCursor::TopLeftCornerResizeCursor:
    case MouseCursor::BottomRightCornerResizeCursor:
        cursorName = IDC_SIZENWSE;
        break;

    case MouseCursor::TopRightCornerResizeCursor:
    case MouseCursor::BottomLeftCornerResizeCursor:
        cursorName = IDC_SIZENESW;
        break;

    case MouseCursor::UpDownLeftRightResizeCursor:
        cursorName = IDC_SIZEALL;
        break;

    case MouseCursor::CrosshairCursor:
        cursorName = IDC_CROSS;
        break;
    }

    HCURSOR cursorH = LoadCursor (0, cursorName);

    if (cursorH == 0)
        cursorH = LoadCursor (0, IDC_ARROW);

    return (void*) cursorH;
}

//==============================================================================
void MouseCursor::showInWindow (ComponentPeer*) const
{
    SetCursor ((HCURSOR) getHandle());
}

void MouseCursor::showInAllWindows() const
{
    showInWindow (0);
}

//==============================================================================
static Image* createImageFromHBITMAP (HBITMAP bitmap)
{
    Image* im = 0;

    if (bitmap != 0)
    {
        BITMAP bm;

        if (GetObject (bitmap, sizeof (BITMAP), &bm)
             && bm.bmWidth > 0 && bm.bmHeight > 0)
        {
            HDC tempDC = GetDC (0);
            HDC dc = CreateCompatibleDC (tempDC);
            ReleaseDC (0, tempDC);

            SelectObject (dc, bitmap);

            im = new Image (Image::ARGB, bm.bmWidth, bm.bmHeight, true);

            for (int y = bm.bmHeight; --y >= 0;)
            {
                for (int x = bm.bmWidth; --x >= 0;)
                {
                    COLORREF col = GetPixel (dc, x, y);

                    im->setPixelAt (x, y, Colour ((uint8) GetRValue (col),
                                                  (uint8) GetGValue (col),
                                                  (uint8) GetBValue (col)));
                }
            }

            DeleteDC (dc);
        }
    }

    return im;
}

static Image* createImageFromHICON (HICON icon)
{
    ICONINFO info;

    if (GetIconInfo (icon, &info))
    {
        Image* const mask = createImageFromHBITMAP (info.hbmMask);

        if (mask == 0)
            return 0;

        Image* const image = createImageFromHBITMAP (info.hbmColor);

        if (image == 0)
            return mask;

        for (int y = image->getHeight(); --y >= 0;)
        {
            for (int x = image->getWidth(); --x >= 0;)
            {
                const float brightness = mask->getPixelAt (x, y).getBrightness();

                if (brightness > 0.0f)
                    image->multiplyAlphaAt (x, y, 1.0f - brightness);
            }
        }

        delete mask;
        return image;
    }

    return 0;
}

Image* juce_createIconForFile (const File& file)
{
    Image* image = 0;

    TCHAR filename [1024];
    file.getFullPathName().copyToBuffer (filename, 1023);
    WORD iconNum = 0;

    HICON icon = ExtractAssociatedIcon ((HINSTANCE) PlatformUtilities::getCurrentModuleInstanceHandle(),
                                        filename, &iconNum);

    if (icon != 0)
    {
        image = createImageFromHICON (icon);
        DestroyIcon (icon);
    }

    return image;
}


//==============================================================================
class JuceDropSource   : public IDropSource
{
    int refCount;

public:
    JuceDropSource()
        : refCount (1)
    {
    }

    ~JuceDropSource()
    {
        jassert (refCount == 0);
    }

    HRESULT __stdcall QueryInterface (REFIID id, void __RPC_FAR* __RPC_FAR* result)
    {
        if (id == IID_IUnknown || id == IID_IDropSource)
        {
            AddRef();
            *result = this;
            return S_OK;
        }

        *result = 0;
        return E_NOINTERFACE;
    }

    ULONG __stdcall AddRef()    { return ++refCount; }
    ULONG __stdcall Release()   { jassert (refCount > 0); const int r = --refCount; if (r == 0) delete this; return r; }

    HRESULT __stdcall QueryContinueDrag (BOOL escapePressed, DWORD keys)
    {
        if (escapePressed)
            return DRAGDROP_S_CANCEL;

        if ((keys & (MK_LBUTTON | MK_RBUTTON)) == 0)
            return DRAGDROP_S_DROP;

        return S_OK;
    }

    HRESULT __stdcall GiveFeedback (DWORD)
    {
        return DRAGDROP_S_USEDEFAULTCURSORS;
    }
};


class JuceEnumFormatEtc   : public IEnumFORMATETC
{
private:
    int refCount;
    FORMATETC* formats;
    int numFormats, index;

    static void copyFormatEtc (FORMATETC& dest, FORMATETC& source)
    {
        dest = source;

        if (source.ptd != 0)
        {
            dest.ptd = (DVTARGETDEVICE*) CoTaskMemAlloc (sizeof (DVTARGETDEVICE));
            *(dest.ptd) = *(source.ptd);
        }
    }

public:
    JuceEnumFormatEtc (FORMATETC* const formats_,
                       const int numFormats_)
        : refCount (1),
          formats (formats_),
          numFormats (numFormats_),
          index (0)
    {
    }

    ~JuceEnumFormatEtc()
    {
        jassert (refCount == 0);
    }

    HRESULT __stdcall QueryInterface (REFIID id, void __RPC_FAR* __RPC_FAR* result)
    {
        if (id == IID_IUnknown || id == IID_IEnumFORMATETC)
        {
            AddRef();
            *result = this;
            return S_OK;
        }

        *result = 0;
        return E_NOINTERFACE;
    }

    ULONG __stdcall AddRef()    { return ++refCount; }
    ULONG __stdcall Release()   { jassert (refCount > 0); const int r = --refCount; if (r == 0) delete this; return r; }

    HRESULT __stdcall Clone (IEnumFORMATETC FAR* FAR* result)
    {
        if (result == 0)
            return E_POINTER;

        JuceEnumFormatEtc* const newOne = new JuceEnumFormatEtc (formats, numFormats);
        newOne->index = index;

        *result = newOne;
        return S_OK;
    }

    HRESULT __stdcall Next (ULONG celt, LPFORMATETC lpFormatEtc, ULONG FAR* pceltFetched)
    {
        if (pceltFetched != 0)
            *pceltFetched = 0;

        if (celt <= 0 || lpFormatEtc == 0 || index >= numFormats
             || (pceltFetched == 0 && celt != 1))
            return S_FALSE;

        int numDone = 0;

        while (index < numFormats && numDone < (int) celt)
            copyFormatEtc (lpFormatEtc [numDone++], formats [index++]);

        if (pceltFetched != 0)
            *pceltFetched = numDone;

        return (numDone != 0) ? S_OK : S_FALSE;
    }

    HRESULT __stdcall Skip (ULONG celt)
    {
        if (index + (int) celt >= numFormats)
            return S_FALSE;

        index += celt;
        return S_OK;
    }

    HRESULT __stdcall Reset()
    {
        index = 0;
        return S_OK;
    }
};

class JuceDataObject  : public IDataObject
{
    int refCount;
    JuceDropSource* dropSource;

    FORMATETC* formats;
    STGMEDIUM* mediums;
    int numFormats;

    int indexOfFormat (const FORMATETC* const f) const
    {
        for (int i = 0; i < numFormats; ++i)
        {
            if (f->tymed == formats[i].tymed
                && f->cfFormat == formats[i].cfFormat
                && f->dwAspect == formats[i].dwAspect)
            {
                return i;
            }
        }

        return -1;
    }

public:
    JuceDataObject (JuceDropSource* const dropSource_,
                    FORMATETC* const formats_,
                    STGMEDIUM* const mediums_,
                    const int numFormats_)
        : refCount (1),
          dropSource (dropSource_),
          formats (formats_),
          mediums (mediums_),
          numFormats (numFormats_)
    {
    }

    ~JuceDataObject()
    {
        jassert (refCount == 0);
    }

    HRESULT __stdcall QueryInterface (REFIID id, void __RPC_FAR* __RPC_FAR* result)
    {
        if (id == IID_IUnknown || id == IID_IDataObject)
        {
            AddRef();
            *result = this;
            return S_OK;
        }

        *result = 0;
        return E_NOINTERFACE;
    }

    ULONG __stdcall AddRef()    { return ++refCount; }
    ULONG __stdcall Release()   { jassert (refCount > 0); const int r = --refCount; if (r == 0) delete this; return r; }

    HRESULT __stdcall GetData (FORMATETC __RPC_FAR* pFormatEtc, STGMEDIUM __RPC_FAR* pMedium)
    {
        const int i = indexOfFormat (pFormatEtc);

        if (i >= 0)
        {
            pMedium->tymed = formats[i].tymed;
            pMedium->pUnkForRelease = 0;

            if (formats[i].tymed == TYMED_HGLOBAL)
            {
                const SIZE_T len = GlobalSize (mediums[i].hGlobal);
                void* const src = GlobalLock (mediums[i].hGlobal);
                void* const dst = GlobalAlloc (GMEM_FIXED, len);

                memcpy (dst, src, len);

                GlobalUnlock (mediums[i].hGlobal);

                pMedium->hGlobal = dst;
                return S_OK;
            }
        }

        return DV_E_FORMATETC;
    }

    HRESULT __stdcall QueryGetData (FORMATETC __RPC_FAR* result)
    {
        if (result == 0)
            return E_INVALIDARG;

        return (indexOfFormat (result) >= 0) ? S_OK : DV_E_FORMATETC;
    }

    HRESULT __stdcall GetCanonicalFormatEtc (FORMATETC __RPC_FAR*, FORMATETC __RPC_FAR* pFormatEtcOut)
    {
        pFormatEtcOut->ptd = 0;
        return E_NOTIMPL;
    }

    HRESULT __stdcall EnumFormatEtc (DWORD direction, IEnumFORMATETC __RPC_FAR *__RPC_FAR *result)
    {
        if (result == 0)
            return E_POINTER;

        if (direction == DATADIR_GET)
        {
            *result = new JuceEnumFormatEtc (formats, numFormats);
            return S_OK;
        }

        *result = 0;
        return E_NOTIMPL;
    }

    HRESULT __stdcall GetDataHere (FORMATETC __RPC_FAR*, STGMEDIUM __RPC_FAR*)                          { return DATA_E_FORMATETC; }
    HRESULT __stdcall SetData (FORMATETC __RPC_FAR*, STGMEDIUM __RPC_FAR*, BOOL)                        { return E_NOTIMPL; }
    HRESULT __stdcall DAdvise (FORMATETC __RPC_FAR*, DWORD, IAdviseSink __RPC_FAR*, DWORD __RPC_FAR*)   { return OLE_E_ADVISENOTSUPPORTED; }
    HRESULT __stdcall DUnadvise (DWORD)                                                                 { return E_NOTIMPL; }
    HRESULT __stdcall EnumDAdvise (IEnumSTATDATA __RPC_FAR *__RPC_FAR *)                                { return OLE_E_ADVISENOTSUPPORTED; }
};

static HDROP createHDrop (const StringArray& fileNames)
{
    int totalChars = 0;
    for (int i = fileNames.size(); --i >= 0;)
        totalChars += fileNames[i].length() + 1;

    HDROP hDrop = (HDROP) GlobalAlloc (GMEM_MOVEABLE | GMEM_ZEROINIT,
                                       sizeof (DROPFILES)
                                         + sizeof (WCHAR) * (totalChars + 2));

    if (hDrop != 0)
    {
        LPDROPFILES pDropFiles = (LPDROPFILES) GlobalLock (hDrop);
        pDropFiles->pFiles = sizeof (DROPFILES);

        pDropFiles->fWide = SystemStats::getOSType() != SystemStats::Win95
                             && SystemStats::getOSType() != SystemStats::Win98;

        if (pDropFiles->fWide)
        {
            WCHAR* fname = (WCHAR*) (((char*) pDropFiles) + sizeof (DROPFILES));

            for (int i = 0; i < fileNames.size(); ++i)
            {
                fileNames[i].copyToBuffer (fname, 2048);
                fname += fileNames[i].length() + 1;
            }

            *fname = 0;
        }
        else
        {
            char* fname = ((char*) pDropFiles) + sizeof (DROPFILES);

            for (int i = 0; i < fileNames.size(); ++i)
            {
                fileNames[i].copyToBuffer (fname, 2048);
                fname += fileNames[i].length() + 1;
            }

            *fname = 0;
        }

        GlobalUnlock (hDrop);
    }

    return hDrop;
}

static bool performDragDrop (FORMATETC* format, STGMEDIUM* medium, const DWORD whatToDo)
{
    JuceDropSource* const source = new JuceDropSource();
    JuceDataObject* const data = new JuceDataObject (source, format, medium, 1);

    DWORD effect;
    HRESULT res = DoDragDrop (data, source, whatToDo, &effect);

    data->Release();
    source->Release();

    return res == DRAGDROP_S_DROP;
}

bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& files, const bool canMove)
{
    FORMATETC format = { CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
    STGMEDIUM medium = { TYMED_HGLOBAL, { 0 }, 0 };

    medium.hGlobal = createHDrop (files);

    return performDragDrop (&format, &medium, canMove ? (DROPEFFECT_COPY | DROPEFFECT_MOVE)
                                                      : DROPEFFECT_COPY);
}

bool DragAndDropContainer::performExternalDragDropOfText (const String& text)
{
    FORMATETC format = { CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
    STGMEDIUM medium = { TYMED_HGLOBAL, { 0 }, 0 };

    const int numChars = text.length();

    medium.hGlobal = GlobalAlloc (GMEM_MOVEABLE | GMEM_ZEROINIT, (numChars + 2) * sizeof (WCHAR));
    char* d = (char*) GlobalLock (medium.hGlobal);

    if (SystemStats::getOSType() != SystemStats::Win95
         && SystemStats::getOSType() != SystemStats::Win98)
    {
        text.copyToBuffer ((WCHAR*) d, numChars + 1);
        format.cfFormat = CF_UNICODETEXT;
    }
    else
    {
        text.copyToBuffer (d, numChars + 1);
    }

    GlobalUnlock (medium.hGlobal);

    return performDragDrop (&format, &medium, DROPEFFECT_COPY | DROPEFFECT_MOVE);
}


//==============================================================================
#if JUCE_OPENGL

struct OpenGLContextInfo
{
    Win32ComponentPeer* nativeWindow;

    HDC dc;
    HGLRC renderContext;
};

void* juce_createOpenGLContext (OpenGLComponent* component, void* sharedContext)
{
    jassert (component != 0);

    Win32ComponentPeer* peer = dynamic_cast <Win32ComponentPeer*> (component->getTopLevelComponent()->getPeer());

    if (peer == 0)
        return 0;

    OpenGLContextInfo* const oc = new OpenGLContextInfo();

    oc->nativeWindow = new Win32ComponentPeer (component, 0);
    oc->nativeWindow->dontRepaint = true;
    oc->nativeWindow->setVisible (true);
    HWND hwnd = (HWND) oc->nativeWindow->getNativeHandle();

    SetParent (hwnd, (HWND) peer->getNativeHandle());
    juce_setWindowStyleBit (hwnd, GWL_STYLE, WS_CHILD, true);
    juce_setWindowStyleBit (hwnd, GWL_STYLE, WS_POPUP, false);

    oc->dc = GetDC (hwnd);

    PIXELFORMATDESCRIPTOR pfd =
    {
        sizeof (PIXELFORMATDESCRIPTOR), // Size of this structure
        1,                              // Version of this structure
        PFD_DRAW_TO_WINDOW |            // Draw to Window (not to bitmap)
        PFD_SUPPORT_OPENGL |            // Support OpenGL calls in window
        PFD_DOUBLEBUFFER,               // Double buffered mode
        PFD_TYPE_RGBA,                  // RGBA Color mode
        32,                             // Want 32 bit color
        0,0,0,0,0,0,
        0,0,
        0,0,0,0,0,
        32,                             // Size of depth buffer
        0,
        0,
        PFD_MAIN_PLANE,
        0,
        0,0,0
    };

    SetPixelFormat (oc->dc, ChoosePixelFormat (oc->dc, &pfd), &pfd);

    oc->renderContext = wglCreateContext (oc->dc);

    if (sharedContext != 0)
        wglShareLists (oc->renderContext, ((OpenGLContextInfo*) sharedContext)->renderContext);

    return oc;
}

void juce_updateOpenGLWindowPos (void* context, Component* owner, Component* topComp)
{
    jassert (context != 0);
    OpenGLContextInfo* const oc = (OpenGLContextInfo*) context;

    SetWindowPos ((HWND) oc->nativeWindow->getNativeHandle(), 0,
                  owner->getScreenX() - topComp->getScreenX(),
                  owner->getScreenY() - topComp->getScreenY(),
                  owner->getWidth(),
                  owner->getHeight(),
                  SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOSENDCHANGING);
}

void juce_deleteOpenGLContext (void* context)
{
    OpenGLContextInfo* const oc = (OpenGLContextInfo*) context;

    if (oc != 0)
    {
        wglDeleteContext (oc->renderContext);
        ReleaseDC ((HWND) oc->nativeWindow->getNativeHandle(), oc->dc);

        deleteAndZero (oc->nativeWindow);

        delete oc;
    }
}

bool juce_makeOpenGLContextCurrent (void* context)
{
    OpenGLContextInfo* const oc = (OpenGLContextInfo*) context;

    if (oc != 0)
        return wglMakeCurrent (oc->dc, oc->renderContext) != 0;
    else
        return wglMakeCurrent (0, 0) != 0;
}

void juce_swapOpenGLBuffers (void* context)
{
    OpenGLContextInfo* const oc = (OpenGLContextInfo*) context;

    if (oc != 0)
        SwapBuffers (oc->dc);
}

void juce_repaintOpenGLWindow (void* context)
{
    OpenGLContextInfo* const oc = (OpenGLContextInfo*) context;

    if (oc != 0)
    {
        int x, y, w, h;
        oc->nativeWindow->getBounds (x, y, w, h);
        oc->nativeWindow->repaint (0, 0, w, h);
    }
}

#endif

END_JUCE_NAMESPACE
