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

   This file is part of the JUCE library - "Jules' Utility Class Extensions"
   Copyright 2004-7 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.

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

// (This file gets included by juce_win32_NativeCode.cpp, rather than being
// compiled on its own).
#if JUCE_INCLUDED_FILE


//==============================================================================
#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

extern void juce_repeatLastProcessPriority() throw(); // in juce_win32_Threads.cpp
extern void juce_CheckCurrentlyFocusedTopLevelWindow() throw();  // in juce_TopLevelWindow.cpp
extern bool juce_IsRunningInWine() throw();

#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;

static HICON createHICONFromImage (const Image& image, const BOOL isIcon, int hotspotX, int hotspotY) throw();
#define WM_TRAYNOTIFY WM_USER + 100

using ::abs;

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

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

    return updateLayeredWindow != 0;
}

//==============================================================================
#undef DefWindowProc
#define DefWindowProc DefWindowProcW

//==============================================================================
const int extendedKeyModifier               = 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         | extendedKeyModifier;
const int KeyPress::insertKey               = VK_INSERT         | extendedKeyModifier;
const int KeyPress::tabKey                  = VK_TAB;
const int KeyPress::leftKey                 = VK_LEFT           | extendedKeyModifier;
const int KeyPress::rightKey                = VK_RIGHT          | extendedKeyModifier;
const int KeyPress::upKey                   = VK_UP             | extendedKeyModifier;
const int KeyPress::downKey                 = VK_DOWN           | extendedKeyModifier;
const int KeyPress::homeKey                 = VK_HOME           | extendedKeyModifier;
const int KeyPress::endKey                  = VK_END            | extendedKeyModifier;
const int KeyPress::pageUpKey               = VK_PRIOR          | extendedKeyModifier;
const int KeyPress::pageDownKey             = VK_NEXT           | extendedKeyModifier;
const int KeyPress::F1Key                   = VK_F1             | extendedKeyModifier;
const int KeyPress::F2Key                   = VK_F2             | extendedKeyModifier;
const int KeyPress::F3Key                   = VK_F3             | extendedKeyModifier;
const int KeyPress::F4Key                   = VK_F4             | extendedKeyModifier;
const int KeyPress::F5Key                   = VK_F5             | extendedKeyModifier;
const int KeyPress::F6Key                   = VK_F6             | extendedKeyModifier;
const int KeyPress::F7Key                   = VK_F7             | extendedKeyModifier;
const int KeyPress::F8Key                   = VK_F8             | extendedKeyModifier;
const int KeyPress::F9Key                   = VK_F9             | extendedKeyModifier;
const int KeyPress::F10Key                  = VK_F10            | extendedKeyModifier;
const int KeyPress::F11Key                  = VK_F11            | extendedKeyModifier;
const int KeyPress::F12Key                  = VK_F12            | extendedKeyModifier;
const int KeyPress::F13Key                  = VK_F13            | extendedKeyModifier;
const int KeyPress::F14Key                  = VK_F14            | extendedKeyModifier;
const int KeyPress::F15Key                  = VK_F15            | extendedKeyModifier;
const int KeyPress::F16Key                  = VK_F16            | extendedKeyModifier;
const int KeyPress::numberPad0              = VK_NUMPAD0        | extendedKeyModifier;
const int KeyPress::numberPad1              = VK_NUMPAD1        | extendedKeyModifier;
const int KeyPress::numberPad2              = VK_NUMPAD2        | extendedKeyModifier;
const int KeyPress::numberPad3              = VK_NUMPAD3        | extendedKeyModifier;
const int KeyPress::numberPad4              = VK_NUMPAD4        | extendedKeyModifier;
const int KeyPress::numberPad5              = VK_NUMPAD5        | extendedKeyModifier;
const int KeyPress::numberPad6              = VK_NUMPAD6        | extendedKeyModifier;
const int KeyPress::numberPad7              = VK_NUMPAD7        | extendedKeyModifier;
const int KeyPress::numberPad8              = VK_NUMPAD8        | extendedKeyModifier;
const int KeyPress::numberPad9              = VK_NUMPAD9        | extendedKeyModifier;
const int KeyPress::numberPadAdd            = VK_ADD            | extendedKeyModifier;
const int KeyPress::numberPadSubtract       = VK_SUBTRACT       | extendedKeyModifier;
const int KeyPress::numberPadMultiply       = VK_MULTIPLY       | extendedKeyModifier;
const int KeyPress::numberPadDivide         = VK_DIVIDE         | extendedKeyModifier;
const int KeyPress::numberPadSeparator      = VK_SEPARATOR      | extendedKeyModifier;
const int KeyPress::numberPadDecimalPoint   = VK_DECIMAL        | extendedKeyModifier;
const int KeyPress::numberPadEquals         = 0x92 /*VK_OEM_NEC_EQUAL*/  | extendedKeyModifier;
const int KeyPress::numberPadDelete         = VK_DELETE         | extendedKeyModifier;
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.bV4CSType = 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);

        if (format_ == ARGB && 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,
                       const int x, const int y,
                       const RectangleList& maskedRegion) throw()
    {
        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;

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

            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&);
};

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


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

static void updateKeyModifiers() throw()
{
    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;

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

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

bool KeyPress::isKeyCurrentlyDown (const int keyCode) throw()
{
    SHORT k = (SHORT) keyCode;

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

    const SHORT translatedValues[] = { (SHORT) ',', VK_OEM_COMMA,
                                       (SHORT) '+', VK_OEM_PLUS,
                                       (SHORT) '-', VK_OEM_MINUS,
                                       (SHORT) '.', VK_OEM_PERIOD,
                                       (SHORT) ';', VK_OEM_1,
                                       (SHORT) ':', VK_OEM_1,
                                       (SHORT) '/', VK_OEM_2,
                                       (SHORT) '?', VK_OEM_2,
                                       (SHORT) '[', VK_OEM_4,
                                       (SHORT) ']', VK_OEM_6 };

    for (int i = 0; i < numElementsInArray (translatedValues); i += 2)
        if (k == translatedValues [i])
            k = translatedValues [i + 1];

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

const ModifierKeys ModifierKeys::getCurrentModifiersRealtime() throw()
{
    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);
}

static int64 getMouseEventTime() throw()
{
    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;
}

static void* callFunctionIfNotLocked (MessageCallbackFunction* callback, void* userData)
{
    if (MessageManager::getInstance()->currentThreadHasLockedMessageManager())
        return callback (userData);
    else
        return MessageManager::getInstance()->callFunctionOnMessageThread (callback, userData);
}

//==============================================================================
class Win32ComponentPeer  : public ComponentPeer
{
public:
    //==============================================================================
    Win32ComponentPeer (Component* const component,
                        const int windowStyleFlags)
        : ComponentPeer (component, windowStyleFlags),
          dontRepaint (false),
          fullScreen (false),
          isDragging (false),
          isMouseOver (false),
          hasCreatedCaret (false),
          currentWindowIcon (0),
          taskBarIcon (0),
          dropTarget (0)
    {
        callFunctionIfNotLocked (&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()
    {
        setTaskBarIcon (0);
        deleteAndZero (shadower);

        // do this before the next bit to avoid messages arriving for this window
        // before it's destroyed
        SetWindowLongPtr (hwnd, GWLP_USERDATA, 0);

        callFunctionIfNotLocked (&destroyWindowCallback, (void*) hwnd);

        if (currentWindowIcon != 0)
            DestroyIcon (currentWindowIcon);

        if (dropTarget != 0)
        {
            dropTarget->Release();
            dropTarget = 0;
        }
    }

    //==============================================================================
    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)
    {
        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);
    }

    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);

        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);

        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 relativePositionToGlobal (int& x, int& y)
    {
        RECT r;
        GetWindowRect (hwnd, &r);

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

    void globalPositionToRelative (int& x, int& y)
    {
        RECT r;
        GetWindowRect (hwnd, &r);

        x -= r.left + windowBorder.getLeft();
        y -= 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)
            {
                const Rectangle boundsCopy (lastNonFullscreenBounds);

                if (hasTitleBar())
                    ShowWindow (hwnd, SW_SHOWNORMAL);

                if (! boundsCopy.isEmpty())
                {
                    setBounds (boundsCopy.getX(),
                               boundsCopy.getY(),
                               boundsCopy.getWidth(),
                               boundsCopy.getHeight(),
                               false);
                }
            }
            else
            {
                if (hasTitleBar())
                    ShowWindow (hwnd, SW_SHOWMAXIMIZED);
                else
                    SendMessageW (hwnd, WM_SETTINGCHANGE, 0, 0);
            }

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

    bool isFullScreen() const
    {
        if (! hasTitleBar())
            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 + windowBorder.getLeft();
        p.y = y + r.top + windowBorder.getTop();

        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);

        callFunctionIfNotLocked (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);

            // must be careful not to try to put a topmost window behind a normal one, or win32
            // promotes the normal one to be topmost!
            if (getComponent()->isAlwaysOnTop() == otherPeer->getComponent()->isAlwaysOnTop())
                SetWindowPos (hwnd, otherPeer->hwnd, 0, 0, 0, 0,
                              SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOSENDCHANGING);
            else if (otherPeer->getComponent()->isAlwaysOnTop())
                SetWindowPos (hwnd, HWND_TOP, 0, 0, 0, 0,
                              SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOSENDCHANGING);
        }
    }

    bool isFocused() const
    {
        return callFunctionIfNotLocked (&getFocusCallback, 0) == (void*) hwnd;
    }

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

        callFunctionIfNotLocked (&setFocusCallback, (void*) hwnd);

        shouldDeactivateTitleBar = oldDeactivate;
    }

    void textInputRequired (int /*x*/, int /*y*/)
    {
        if (! hasCreatedCaret)
        {
            hasCreatedCaret = true;
            CreateCaret (hwnd, (HBITMAP) 1, 0, 0);
        }

        ShowCaret (hwnd);
        SetCaretPos (0, 0);
    }

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

    void performAnyPendingRepaintsNow()
    {
        MSG m;
        if (component->isVisible() && PeekMessage (&m, hwnd, WM_PAINT, WM_PAINT, PM_REMOVE))
            DispatchMessage (&m);
    }

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

        return 0;
    }

    //==============================================================================
    void setTaskBarIcon (const Image* const image)
    {
        if (image != 0)
        {
            HICON hicon = createHICONFromImage (*image, TRUE, 0, 0);

            if (taskBarIcon == 0)
            {
                taskBarIcon = new NOTIFYICONDATA();
                taskBarIcon->cbSize = sizeof (NOTIFYICONDATA);
                taskBarIcon->hWnd = (HWND) hwnd;
                taskBarIcon->uID = (int) (pointer_sized_int) hwnd;
                taskBarIcon->uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
                taskBarIcon->uCallbackMessage = WM_TRAYNOTIFY;
                taskBarIcon->hIcon = hicon;
                taskBarIcon->szTip[0] = 0;

                Shell_NotifyIcon (NIM_ADD, taskBarIcon);
            }
            else
            {
                HICON oldIcon = taskBarIcon->hIcon;

                taskBarIcon->hIcon = hicon;
                taskBarIcon->uFlags = NIF_ICON;
                Shell_NotifyIcon (NIM_MODIFY, taskBarIcon);

                DestroyIcon (oldIcon);
            }

            DestroyIcon (hicon);
        }
        else if (taskBarIcon != 0)
        {
            taskBarIcon->uFlags = 0;
            Shell_NotifyIcon (NIM_DELETE, taskBarIcon);
            DestroyIcon (taskBarIcon->hIcon);
            deleteAndZero (taskBarIcon);
        }
    }

    void setTaskBarIconToolTip (const String& toolTip) const
    {
        if (taskBarIcon != 0)
        {
            taskBarIcon->uFlags = NIF_TIP;
            toolTip.copyToBuffer (taskBarIcon->szTip, sizeof (taskBarIcon->szTip) - 1);
            Shell_NotifyIcon (NIM_MODIFY, taskBarIcon);
        }
    }

    bool isInside (HWND h) const
    {
        return GetAncestor (hwnd, GA_ROOT) == h;
    }

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

    bool dontRepaint;

private:
    HWND hwnd;
    DropShadower* shadower;
    bool fullScreen, isDragging, isMouseOver, hasCreatedCaret;
    BorderSize windowBorder;
    HICON currentWindowIcon;
    NOTIFYICONDATA* taskBarIcon;
    IDropTarget* dropTarget;

    //==============================================================================
    class TemporaryImage    : public Timer
    {
    public:
        //==============================================================================
        TemporaryImage()
            : image (0)
        {
        }

        ~TemporaryImage()
        {
            delete image;
        }

        //==============================================================================
        WindowsBitmapImage* getImage (const bool transparent, const int w, const int h) throw()
        {
            const Image::PixelFormat format = transparent ? Image::ARGB : Image::RGB;

            if (image == 0 || image->getWidth() < w || image->getHeight() < h || image->getFormat() != format)
            {
                delete image;
                image = new WindowsBitmapImage (format, (w + 31) & ~31, (h + 31) & ~31, false);
            }

            startTimer (3000);
            return image;
        }

        //==============================================================================
        void timerCallback()
        {
            stopTimer();
            deleteAndZero (image);
        }

    private:
        WindowsBitmapImage* image;

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

    TemporaryImage offscreenImageGenerator;

    //==============================================================================
    class WindowClassHolder    : public DeletedAtShutdown
    {
    public:
        WindowClassHolder()
            : windowClassName ("JUCE_")
        {
            // 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).
            windowClassName << (int) (Time::currentTimeMillis() & 0x7fffffff);

            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);
        }

        ~WindowClassHolder()
        {
            if (ComponentPeer::getNumPeers() == 0)
                UnregisterClass (windowClassName, (HINSTANCE) PlatformUtilities::getCurrentModuleInstanceHandle());

            clearSingletonInstance();
        }

        String windowClassName;

        juce_DeclareSingleton_SingleThreaded_Minimal (WindowClassHolder);
    };

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

    void createWindow()
    {
        DWORD exstyle = WS_EX_ACCEPTFILES;
        DWORD type = WS_CLIPSIBLINGS | WS_CLIPCHILDREN;

        if (hasTitleBar())
        {
            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 & windowIsResizable) != 0)
                type |= WS_THICKFRAME;
        }
        else
        {
            type |= WS_POPUP | WS_SYSMENU;

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

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

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

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

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

        hwnd = CreateWindowEx (exstyle, WindowClassHolder::getInstance()->windowClassName, L"", 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);

            if (dropTarget == 0)
                dropTarget = new JuceDropTarget (this);

            RegisterDragDrop (hwnd, dropTarget);

            updateBorderSize();

            // Calling this function here is (for some reason) necessary to make Windows
            // correctly enable the menu items that we specify in the wm_initmenu message.
            GetSystemMenu (hwnd, false);
        }
        else
        {
            jassertfalse
        }
    }

    static void* destroyWindowCallback (void* handle)
    {
        RevokeDragDrop ((HWND) handle);
        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;
    }

    inline bool hasTitleBar() const throw()         { return (styleFlags & windowHasTitleBar) != 0; }


    void setIcon (const Image& newIcon)
    {
        HICON hicon = createHICONFromImage (newIcon, TRUE, 0, 0);

        if (hicon != 0)
        {
            SendMessage (hwnd, WM_SETICON, ICON_BIG, (LPARAM) hicon);
            SendMessage (hwnd, WM_SETICON, ICON_SMALL, (LPARAM) hicon);

            if (currentWindowIcon != 0)
                DestroyIcon (currentWindowIcon);

            currentWindowIcon = hicon;
        }
    }

    //==============================================================================
    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;

        // 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;

        const bool transparent = isTransparent();

        if (transparent)
        {
            // it's not possible to have a transparent window with a title bar at the moment!
            jassert (! hasTitleBar());

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

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

            WindowsBitmapImage* const offscreenImage = offscreenImageGenerator.getImage (transparent, w, h);

            LowLevelGraphicsSoftwareRenderer context (*offscreenImage);

            RectangleList* const contextClip = context.getRawClipRegion();
            contextClip->clear();

            context.setOrigin (-x, -y);

            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)
                            {
                                contextClip->addWithoutMerging (Rectangle (cx - x, cy - y, cw, ch));
                            }
                            else
                            {
                                needToPaintAll = true;
                                break;
                            }

                            ++rects;
                        }
                    }
                }
            }

            if (needToPaintAll)
            {
                contextClip->clear();
                contextClip->addWithoutMerging (Rectangle (0, 0, w, h));
            }

            if (transparent)
            {
                RectangleList::Iterator i (*contextClip);

                while (i.next())
                {
                    const Rectangle& r = *i.getRectangle();
                    offscreenImage->clear (r.getX(), r.getY(), r.getWidth(), r.getHeight());
                }
            }

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

            updateCurrentModifiers();

            handlePaint (context);

            if (! dontRepaint)
                offscreenImage->blitToWindow (hwnd, dc, transparent, x, y, maskedRegion);
        }

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

#ifndef JUCE_GCC  //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
    }

    //==============================================================================
    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;

        const int64 mouseEventTime = getMouseEventTime();

        if (! isMouseOver)
        {
            isMouseOver = true;

            TRACKMOUSEEVENT tme;
            tme.cbSize = sizeof (tme);
            tme.dwFlags = TME_LEAVE;
            tme.hwndTrack = hwnd;
            tme.dwHoverTime = 0;

            if (! TrackMouseEvent (&tme))
            {
                jassertfalse;
            }

            updateKeyModifiers();
            handleMouseEnter (x, y, mouseEventTime);
        }
        else if (! isDragging)
        {
            if (((unsigned int) x) < (unsigned int) component->getWidth()
                 && ((unsigned int) y) < (unsigned int) component->getHeight())
            {
                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, mouseEventTime);
                    }
                }
            }
        }
        else
        {
            const uint32 now = Time::getMillisecondCounter();

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

    void doMouseDown (const int x, const int y, const WPARAM wParam)
    {
        if (GetCapture() != hwnd)
            SetCapture (hwnd);

        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 && hwnd == GetCapture())
            ReleaseCapture();

        handleMouseUp (oldModifiers, x, y, getMouseEventTime());
    }

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

            const DWORD mp = GetMessagePos();

            doMouseUp (GET_X_LPARAM (mp) - wr.left - windowBorder.getLeft(),
                       GET_Y_LPARAM (mp) - wr.top - windowBorder.getTop(),
                       (WPARAM) getMouseEventTime());
        }
    }

    void doMouseExit()
    {
        if (isMouseOver)
        {
            isMouseOver = false;
            RECT wr;
            GetWindowRect (hwnd, &wr);

            const DWORD mp = GetMessagePos();

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

    void doMouseWheel (const WPARAM wParam, const bool isVertical)
    {
        updateKeyModifiers();

        const int amount = jlimit (-1000, 1000, (int) (0.75f * (short) HIWORD (wParam)));

        handleMouseWheel (isVertical ? 0 : amount,
                          isVertical ? amount : 0,
                          getMouseEventTime());
    }

    //==============================================================================
    void sendModifierKeyChangeIfNeeded()
    {
        if (modifiersAtLastCallback != currentModifiers)
        {
            modifiersAtLastCallback = currentModifiers;
            handleModifierKeysChange();
        }
    }

    bool 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:
                sendModifierKeyChangeIfNeeded();
        }

        return handleKeyUpOrDown (false)
                || Component::getCurrentlyModalComponent() != 0;
    }

    bool doKeyDown (const WPARAM key)
    {
        updateKeyModifiers();
        bool used = false;

        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:
                sendModifierKeyChangeIfNeeded();
                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:
            case VK_F13:
            case VK_F14:
            case VK_F15:
            case VK_F16:
                used = handleKeyUpOrDown (true);
                used = handleKeyPress (extendedKeyModifier | (int) key, 0) || used;
                break;

            case VK_ADD:
            case VK_SUBTRACT:
            case VK_MULTIPLY:
            case VK_DIVIDE:
            case VK_SEPARATOR:
            case VK_DECIMAL:
                used = handleKeyUpOrDown (true);
                break;

            default:
                used = handleKeyUpOrDown (true);

                {
                    MSG msg;

                    if (! PeekMessage (&msg, hwnd, WM_CHAR, WM_DEADCHAR, PM_NOREMOVE))
                    {
                        // if there isn't a WM_CHAR or WM_DEADCHAR message pending, we need to
                        // manually generate the key-press event that matches this key-down.

                        const UINT keyChar = MapVirtualKey (key, 2);
                        used = handleKeyPress ((int) LOWORD (keyChar), 0) || used;
                    }
                }

                break;
        }

        if (Component::getCurrentlyModalComponent() != 0)
            used = true;

        return used;
    }

    bool doKeyChar (int key, const LPARAM flags)
    {
        updateKeyModifiers();

        juce_wchar textChar = (juce_wchar) key;

        const int virtualScanCode = (flags >> 16) & 0xff;

        if (key >= '0' && key <= '9')
        {
            switch (virtualScanCode)  // check for a numeric keypad scan-code
            {
            case 0x52:
            case 0x4f:
            case 0x50:
            case 0x51:
            case 0x4b:
            case 0x4c:
            case 0x4d:
            case 0x47:
            case 0x48:
            case 0x49:
                key = (key - '0') + KeyPress::numberPad0;
                break;
            default:
                break;
            }
        }
        else
        {
            // convert the scan code to an unmodified character code..
            const UINT virtualKey = MapVirtualKey (virtualScanCode, 1);
            UINT keyChar = MapVirtualKey (virtualKey, 2);

            keyChar = LOWORD (keyChar);

            if (keyChar != 0)
                key = (int) keyChar;

            // avoid sending junk text characters for some control-key combinations
            if (textChar < ' ' && (currentModifiers & (ModifierKeys::ctrlModifier | ModifierKeys::altModifier)) != 0)
                textChar = 0;
        }

        return handleKeyPress (key, textChar);
    }

    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, 0);
                return true;
            }
        }

        return false;
    }

    //==============================================================================
    class JuceDropTarget    : public IDropTarget
    {
    public:
        JuceDropTarget (Win32ComponentPeer* const owner_)
            : owner (owner_),
              refCount (1)
        {
        }

        virtual ~JuceDropTarget()
        {
            jassert (refCount == 0);
        }

        HRESULT __stdcall QueryInterface (REFIID id, void __RPC_FAR* __RPC_FAR* result)
        {
            if (id == IID_IUnknown || id == IID_IDropTarget)
            {
                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 DragEnter (IDataObject* pDataObject, DWORD /*grfKeyState*/, POINTL mousePos, DWORD* pdwEffect)
        {
            updateFileList (pDataObject);
            int x = mousePos.x, y = mousePos.y;
            owner->globalPositionToRelative (x, y);
            owner->handleFileDragMove (files, x, y);
            *pdwEffect = DROPEFFECT_COPY;
            return S_OK;
        }

        HRESULT __stdcall DragLeave()
        {
            owner->handleFileDragExit (files);
            return S_OK;
        }

        HRESULT __stdcall DragOver (DWORD /*grfKeyState*/, POINTL mousePos, DWORD* pdwEffect)
        {
            int x = mousePos.x, y = mousePos.y;
            owner->globalPositionToRelative (x, y);
            owner->handleFileDragMove (files, x, y);
            *pdwEffect = DROPEFFECT_COPY;
            return S_OK;
        }

        HRESULT __stdcall Drop (IDataObject* pDataObject, DWORD /*grfKeyState*/, POINTL mousePos, DWORD* pdwEffect)
        {
            updateFileList (pDataObject);
            int x = mousePos.x, y = mousePos.y;
            owner->globalPositionToRelative (x, y);
            owner->handleFileDragDrop (files, x, y);
            *pdwEffect = DROPEFFECT_COPY;
            return S_OK;
        }

    private:
        Win32ComponentPeer* const owner;
        int refCount;
        StringArray files;

        void updateFileList (IDataObject* const pDataObject)
        {
            files.clear();

            FORMATETC format = { CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
            STGMEDIUM medium = { TYMED_HGLOBAL, { 0 }, 0 };

            if (pDataObject->GetData (&format, &medium) == S_OK)
            {
                const SIZE_T totalLen = GlobalSize (medium.hGlobal);
                const LPDROPFILES pDropFiles = (const LPDROPFILES) GlobalLock (medium.hGlobal);
                unsigned int i = 0;

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

                    for (;;)
                    {
                        unsigned int len = 0;
                        while (i + len < totalLen && fname [i + len] != 0)
                            ++len;

                        if (len == 0)
                            break;

                        files.add (String (fname + i, len));
                        i += len + 1;
                    }
                }
                else
                {
                    const char* const fname = ((const char*) pDropFiles) + sizeof (DROPFILES);

                    for (;;)
                    {
                        unsigned int len = 0;
                        while (i + len < totalLen && fname [i + len] != 0)
                            ++len;

                        if (len == 0)
                            break;

                        files.add (String (fname + i, len));
                        i += len + 1;
                    }
                }

                GlobalUnlock (medium.hGlobal);
            }
        }

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

    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);
        }
    }

    //==============================================================================
public:
    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);
    }

private:
    LRESULT peerWindowProc (HWND h, UINT message, WPARAM wParam, LPARAM lParam)
    {
        {
            if (isValidPeer (this))
            {
                switch (message)
                {
                    case WM_NCHITTEST:
                        if (hasTitleBar())
                            break;

                        return HTCLIENT;

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

                    case WM_NCPAINT:
                        if (wParam != 1)
                            handlePaintMessage();

                        if (hasTitleBar())
                            break;

                        return 0;

                    case WM_ERASEBKGND:
                    case WM_NCCALCSIZE:
                        if (hasTitleBar())
                            break;

                        return 1;

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

                    case WM_MOUSELEAVE:
                        doMouseExit();
                        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 (hasTitleBar())
                            break;

                        return 0;

                    case 0x020A: /* WM_MOUSEWHEEL */
                        doMouseWheel (wParam, true);
                        return 0;

                    case 0x020E: /* WM_MOUSEHWHEEL */
                        doMouseWheel (wParam, false);
                        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 (component->getX() - windowBorder.getLeft(),
                                                             component->getY() - windowBorder.getTop(),
                                                             component->getWidth() + windowBorder.getLeftAndRight(),
                                                             component->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();

                        if (dontRepaint)
                            break;  // needed for non-accelerated openGL windows to draw themselves correctly..
                        else
                            return 0;

                    //==============================================================================
                    case WM_KEYDOWN:
                    case WM_SYSKEYDOWN:
                        if (doKeyDown (wParam))
                            return 0;

                        break;

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

                        break;

                    case WM_CHAR:
                        if (doKeyChar ((int) wParam, lParam))
                            return 0;

                        break;

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

                        break;

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

                    case WM_KILLFOCUS:
                        if (hasCreatedCaret)
                        {
                            hasCreatedCaret = false;
                            DestroyCaret();
                        }

                        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)
                            juce_repeatLastProcessPriority();
                        else
                            Desktop::getInstance().setKioskModeComponent (0); // turn kiosk mode off if we lose focus

                        juce_CheckCurrentlyFocusedTopLevelWindow();
                        modifiersAtLastCallback = -1;
                        return 0;

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

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

                                if (! isValidMessageListener())
                                    return 0;
                            }

                            if (LOWORD (wParam) == WA_CLICKACTIVE
                                 && component->isCurrentlyBlockedByAnotherModalComponent())
                            {
                                int mx, my;
                                component->getMouseXYRelative (mx, my);
                                Component* const underMouse = component->getComponentAt (mx, my);

                                if (underMouse != 0 && underMouse->isCurrentlyBlockedByAnotherModalComponent())
                                    Component::getCurrentlyModalComponent()->inputAttemptWhenModal();

                                return 0;
                            }

                            handleBroughtToFront();

                            if (component->isCurrentlyBlockedByAnotherModalComponent())
                                Component::getCurrentlyModalComponent()->toFront (true);

                            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)
                            wParam = TRUE; // change this and let it get passed to the DefWindowProc.

                        break;

                    case WM_MOUSEACTIVATE:
                        if (! component->getMouseClickGrabsKeyboardFocus())
                            return MA_NOACTIVATE;

                        break;

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

                        break;

                    case WM_CLOSE:
                        if (! component->isCurrentlyBlockedByAnotherModalComponent())
                            handleUserClosingWindow();

                        return 0;

                    case WM_QUIT:
                        if (JUCEApplication::getInstance() != 0)
                            JUCEApplication::getInstance()->systemRequestedQuit();
                        return 0;

                    case WM_QUERYENDSESSION:
                        if (JUCEApplication::getInstance() != 0)
                        {
                            JUCEApplication::getInstance()->systemRequestedQuit();
                            return MessageManager::getInstance()->hasStopMessageBeenSent();
                        }
                        return TRUE;

                    //==============================================================================
                    case WM_TRAYNOTIFY:
                        if (component->isCurrentlyBlockedByAnotherModalComponent())
                        {
                            if (lParam == WM_LBUTTONDOWN || lParam == WM_RBUTTONDOWN
                                 || lParam == WM_LBUTTONDBLCLK || lParam == WM_LBUTTONDBLCLK)
                            {
                                Component* const current = Component::getCurrentlyModalComponent();

                                if (current != 0)
                                    current->inputAttemptWhenModal();
                            }
                        }
                        else
                        {
                            const int oldModifiers = currentModifiers;

                            MouseEvent e (0, 0, ModifierKeys::getCurrentModifiersRealtime(), component,
                                          getMouseEventTime(), 0, 0, getMouseEventTime(), 1, false);

                            if (lParam == WM_LBUTTONDOWN || lParam == WM_LBUTTONDBLCLK)
                                e.mods = ModifierKeys (e.mods.getRawFlags() | ModifierKeys::leftButtonModifier);
                            else if (lParam == WM_RBUTTONDOWN || lParam == WM_RBUTTONDBLCLK)
                                e.mods = ModifierKeys (e.mods.getRawFlags() | ModifierKeys::rightButtonModifier);

                            if (lParam == WM_LBUTTONDOWN || lParam == WM_RBUTTONDOWN)
                            {
                                SetFocus (hwnd);
                                SetForegroundWindow (hwnd);

                                component->mouseDown (e);
                            }
                            else if (lParam == WM_LBUTTONUP || lParam == WM_RBUTTONUP)
                            {
                                e.mods = ModifierKeys (oldModifiers);
                                component->mouseUp (e);
                            }
                            else if (lParam == WM_LBUTTONDBLCLK || lParam == WM_LBUTTONDBLCLK)
                            {
                                e.mods = ModifierKeys (oldModifiers);
                                component->mouseDoubleClick (e);
                            }
                            else if (lParam == WM_MOUSEMOVE)
                            {
                                component->mouseMove (e);
                            }
                        }

                        break;

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

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

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

                    case WM_INITMENU:
                        if (! hasTitleBar())
                        {
                            if (isFullScreen())
                            {
                                EnableMenuItem ((HMENU) wParam, SC_RESTORE, MF_BYCOMMAND | MF_ENABLED);
                                EnableMenuItem ((HMENU) wParam, SC_MOVE, MF_BYCOMMAND | MF_GRAYED);
                            }
                            else if (! isMinimised())
                            {
                                EnableMenuItem ((HMENU) wParam, SC_MAXIMIZE, MF_BYCOMMAND | MF_GRAYED);
                            }
                        }
                        break;

                    case WM_SYSCOMMAND:
                        switch (wParam & 0xfff0)
                        {
                        case SC_CLOSE:
                            if (sendInputAttemptWhenModalMessage())
                                return 0;

                            if (hasTitleBar())
                            {
                                PostMessage (h, WM_CLOSE, 0, 0);
                                return 0;
                            }
                            break;

                        case SC_KEYMENU:
                            // (NB mustn't call sendInputAttemptWhenModalMessage() here because of very
                            // obscure situations that can arise if a modal loop is started from an alt-key
                            // keypress).

                            if (hasTitleBar() && h == GetCapture())
                                ReleaseCapture();

                            break;

                        case SC_MAXIMIZE:
                            if (sendInputAttemptWhenModalMessage())
                                return 0;

                            setFullScreen (true);
                            return 0;

                        case SC_MINIMIZE:
                            if (sendInputAttemptWhenModalMessage())
                                return 0;

                            if (! hasTitleBar())
                            {
                                setMinimised (true);
                                return 0;
                            }
                            break;

                        case SC_RESTORE:
                            if (sendInputAttemptWhenModalMessage())
                                return 0;

                            if (hasTitleBar())
                            {
                                if (isFullScreen())
                                {
                                    setFullScreen (false);
                                    return 0;
                                }
                            }
                            else
                            {
                                if (isMinimised())
                                    setMinimised (false);
                                else if (isFullScreen())
                                    setFullScreen (false);

                                return 0;
                            }

                            break;
                        }

                        break;

                    case WM_NCLBUTTONDOWN:
                    case WM_NCRBUTTONDOWN:
                    case WM_NCMBUTTONDOWN:
                        sendInputAttemptWhenModalMessage();
                        break;

                    //case WM_IME_STARTCOMPOSITION;
                      //  return 0;

                    case WM_GETDLGCODE:
                        return DLGC_WANTALLKEYS;

                    default:
                        break;
                }
            }
        }

        // (the message manager lock exits before calling this, to avoid deadlocks if
        // this calls into non-juce windows)
        return DefWindowProc (h, message, wParam, lParam);
    }

    bool sendInputAttemptWhenModalMessage()
    {
        if (component->isCurrentlyBlockedByAnotherModalComponent())
        {
            Component* const current = Component::getCurrentlyModalComponent();

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

            return true;
        }

        return false;
    }

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

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

juce_ImplementSingleton_SingleThreaded (Win32ComponentPeer::WindowClassHolder);

//==============================================================================
void SystemTrayIconComponent::setIconImage (const Image& newImage)
{
    Win32ComponentPeer* const wp = dynamic_cast <Win32ComponentPeer*> (getPeer());

    if (wp != 0)
        wp->setTaskBarIcon (&newImage);
}

void SystemTrayIconComponent::setIconTooltip (const String& tooltip)
{
    Win32ComponentPeer* const wp = dynamic_cast <Win32ComponentPeer*> (getPeer());

    if (wp != 0)
        wp->setTaskBarIconToolTip (tooltip);
}

//==============================================================================
void juce_setWindowStyleBit (HWND h, const int styleType, const int feature, const bool bitIsSet) throw()
{
    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() throw()
{
    HWND fg = GetForegroundWindow();

    if (fg == 0)
        return true;

    // when running as a plugin in IE8, the browser UI runs in a different process to the plugin, so
    // process ID isn't a reliable way to check if the foreground window belongs to us - instead, we
    // have to see if any of our windows are children of the foreground window
    fg = GetAncestor (fg, GA_ROOT);

    for (int i = ComponentPeer::getNumPeers(); --i >= 0;)
    {
        Win32ComponentPeer* const wp = dynamic_cast <Win32ComponentPeer*> (ComponentPeer::getPeer (i));

        if (wp != 0 && wp->isInside (fg))
            return true;
    }

    return false;
}

//==============================================================================
bool AlertWindow::showNativeDialogBox (const String& title,
                                       const String& bodyText,
                                       bool isOkCancel)
{
    return MessageBox (0, bodyText, title,
                       MB_SETFOREGROUND | (isOkCancel ? MB_OKCANCEL
                                                      : MB_OK)) == IDOK;
}


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

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

//==============================================================================
class ScreenSaverDefeater   : public Timer,
                              public DeletedAtShutdown
{
public:
    ScreenSaverDefeater() throw()
    {
        startTimer (10000);
        timerCallback();
    }

    ~ScreenSaverDefeater() {}

    void timerCallback()
    {
        if (Process::isForegroundProcess())
        {
            // simulate a shift key getting pressed..
            INPUT input[2];
            input[0].type = INPUT_KEYBOARD;
            input[0].ki.wVk = VK_SHIFT;
            input[0].ki.dwFlags = 0;
            input[0].ki.dwExtraInfo = 0;

            input[1].type = INPUT_KEYBOARD;
            input[1].ki.wVk = VK_SHIFT;
            input[1].ki.dwFlags = KEYEVENTF_KEYUP;
            input[1].ki.dwExtraInfo = 0;

            SendInput (2, input, sizeof (INPUT));
        }
    }
};

static ScreenSaverDefeater* screenSaverDefeater = 0;

void Desktop::setScreenSaverEnabled (const bool isEnabled) throw()
{
    if (isEnabled)
    {
        deleteAndZero (screenSaverDefeater);
    }
    else if (screenSaverDefeater == 0)
    {
        screenSaverDefeater = new ScreenSaverDefeater();
    }
}

bool Desktop::isScreenSaverEnabled() throw()
{
    return screenSaverDefeater == 0;
}

/* (The code below is the "correct" way to disable the screen saver, but it
    completely fails on winXP when the saver is password-protected...)

static bool juce_screenSaverEnabled = true;

void Desktop::setScreenSaverEnabled (const bool isEnabled) throw()
{
    juce_screenSaverEnabled = isEnabled;
    SetThreadExecutionState (isEnabled ? ES_CONTINUOUS
                                       : (ES_DISPLAY_REQUIRED | ES_CONTINUOUS));
}

bool Desktop::isScreenSaverEnabled() throw()
{
    return juce_screenSaverEnabled;
}
*/

//==============================================================================
void juce_setKioskComponent (Component* kioskModeComponent, bool enableOrDisable, bool /*allowMenusAndBars*/)
{
    if (enableOrDisable)
        kioskModeComponent->setBounds (Desktop::getInstance().getMainMonitorArea (false));
}

//==============================================================================
static 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) throw()
{
    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.setPosition (jmax (screen.getX(), r.left),
                            jmax (screen.getY(), r.top));

        screen.setSize (jmin (screen.getRight(), r.right) - screen.getX(),
                        jmin (screen.getBottom(), r.bottom) - screen.getY());
    }
}

//==============================================================================
static Image* createImageFromHBITMAP (HBITMAP bitmap) throw()
{
    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) throw()
{
    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;
}

static HICON createHICONFromImage (const Image& image, const BOOL isIcon, int hotspotX, int hotspotY) throw()
{
    HBITMAP mask = CreateBitmap (image.getWidth(), image.getHeight(), 1, 1, 0);

    ICONINFO info;
    info.fIcon = isIcon;
    info.xHotspot = hotspotX;
    info.yHotspot = hotspotY;
    info.hbmMask = mask;
    HICON hi = 0;

    if (SystemStats::getOperatingSystemType() >= SystemStats::WinXP)
    {
        WindowsBitmapImage bitmap (Image::ARGB, image.getWidth(), image.getHeight(), true);
        Graphics g (bitmap);
        g.drawImageAt (&image, 0, 0);

        info.hbmColor = bitmap.hBitmap;
        hi = CreateIconIndirect (&info);
    }
    else
    {
        HBITMAP colour = CreateCompatibleBitmap (GetDC (0), image.getWidth(), image.getHeight());

        HDC colDC = CreateCompatibleDC (GetDC (0));
        HDC alphaDC = CreateCompatibleDC (GetDC (0));
        SelectObject (colDC, colour);
        SelectObject (alphaDC, mask);

        for (int y = image.getHeight(); --y >= 0;)
        {
            for (int x = image.getWidth(); --x >= 0;)
            {
                const Colour c (image.getPixelAt (x, y));

                SetPixel (colDC, x, y, COLORREF (c.getRed() | (c.getGreen() << 8) | (c.getBlue() << 16)));
                SetPixel (alphaDC, x, y, COLORREF (0xffffff - (c.getAlpha() | (c.getAlpha() << 8) | (c.getAlpha() << 16))));
            }
        }

        DeleteDC (colDC);
        DeleteDC (alphaDC);

        info.hbmColor = colour;
        hi = CreateIconIndirect (&info);
        DeleteObject (colour);
    }

    DeleteObject (mask);
    return hi;
}

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;
}

//==============================================================================
void* juce_createMouseCursorFromImage (const Image& image, int hotspotX, int hotspotY) throw()
{
    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();
    }

    void* cursorH = 0;

    const SystemStats::OperatingSystemType os = SystemStats::getOperatingSystemType();

    if (os == SystemStats::WinXP)
    {
        cursorH = (void*) createHICONFromImage (*im, FALSE, hotspotX, hotspotY);
    }
    else
    {
        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;
        }

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

        juce_free (andPlane);
        juce_free (xorPlane);
    }

    delete newIm;
    return cursorH;
}

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

void* juce_createStandardMouseCursor (MouseCursor::StandardCursorType type) throw()
{
    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)
        {
            static 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;

    case MouseCursor::CopyingCursor:
        // can't seem to find one of these in the win32 list..
        break;
    }

    HCURSOR cursorH = LoadCursor (0, cursorName);

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

    return (void*) cursorH;
}

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

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

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

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

    virtual ~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
{
public:
    JuceEnumFormatEtc (const FORMATETC* const format_)
        : refCount (1),
          format (format_),
          index (0)
    {
    }

    virtual ~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** result)
    {
        if (result == 0)
            return E_POINTER;

        JuceEnumFormatEtc* const newOne = new JuceEnumFormatEtc (format);
        newOne->index = index;

        *result = newOne;
        return S_OK;
    }

    HRESULT __stdcall Next (ULONG celt, LPFORMATETC lpFormatEtc, ULONG* pceltFetched)
    {
        if (pceltFetched != 0)
            *pceltFetched = 0;
        else if (celt != 1)
            return S_FALSE;

        if (index == 0 && celt > 0 && lpFormatEtc != 0)
        {
            copyFormatEtc (lpFormatEtc [0], *format);
            ++index;

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

            return S_OK;
        }

        return S_FALSE;
    }

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

        index += celt;
        return S_OK;
    }

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

private:
    int refCount;
    const FORMATETC* const format;
    int index;

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

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

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

class JuceDataObject  : public IDataObject
{
    JuceDropSource* const dropSource;
    const FORMATETC* const format;
    const STGMEDIUM* const medium;
    int refCount;

    JuceDataObject (const JuceDataObject&);
    const JuceDataObject& operator= (const JuceDataObject&);

public:
    JuceDataObject (JuceDropSource* const dropSource_,
                    const FORMATETC* const format_,
                    const STGMEDIUM* const medium_)
        : dropSource (dropSource_),
          format (format_),
          medium (medium_),
          refCount (1)
    {
    }

    virtual ~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)
    {
        if (pFormatEtc->tymed == format->tymed
             && pFormatEtc->cfFormat == format->cfFormat
             && pFormatEtc->dwAspect == format->dwAspect)
        {
            pMedium->tymed = format->tymed;
            pMedium->pUnkForRelease = 0;

            if (format->tymed == TYMED_HGLOBAL)
            {
                const SIZE_T len = GlobalSize (medium->hGlobal);
                void* const src = GlobalLock (medium->hGlobal);
                void* const dst = GlobalAlloc (GMEM_FIXED, len);

                memcpy (dst, src, len);

                GlobalUnlock (medium->hGlobal);

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

        return DV_E_FORMATETC;
    }

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

        if (f->tymed == format->tymed
              && f->cfFormat == format->cfFormat
              && f->dwAspect == format->dwAspect)
            return S_OK;

        return 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 (format);
            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) throw()
{
    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 = true;

        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;

        GlobalUnlock (hDrop);
    }

    return hDrop;
}

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

    DWORD effect;
    const 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);

    text.copyToBuffer ((WCHAR*) d, numChars + 1);
    format.cfFormat = CF_UNICODETEXT;

    GlobalUnlock (medium.hGlobal);

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


#endif
